From 265e33241a806d3c3462f1c0701a8617190be01b Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 10 Mar 2023 14:45:49 +0100 Subject: [PATCH 001/209] version set to 3.6.0-SNAPSHOT --- application/pom.xml | 2 +- common/actor/pom.xml | 2 +- common/cache/pom.xml | 2 +- common/cluster-api/pom.xml | 2 +- common/coap-server/pom.xml | 2 +- common/dao-api/pom.xml | 2 +- common/data/pom.xml | 2 +- common/edge-api/pom.xml | 2 +- common/message/pom.xml | 2 +- common/pom.xml | 2 +- common/queue/pom.xml | 2 +- common/script/pom.xml | 2 +- common/script/remote-js-client/pom.xml | 2 +- common/script/script-api/pom.xml | 2 +- common/stats/pom.xml | 2 +- common/transport/coap/pom.xml | 2 +- common/transport/http/pom.xml | 2 +- common/transport/lwm2m/pom.xml | 2 +- common/transport/mqtt/pom.xml | 2 +- common/transport/pom.xml | 2 +- common/transport/snmp/pom.xml | 2 +- common/transport/transport-api/pom.xml | 2 +- common/util/pom.xml | 2 +- common/version-control/pom.xml | 2 +- dao/pom.xml | 2 +- msa/black-box-tests/pom.xml | 2 +- msa/js-executor/package.json | 2 +- msa/js-executor/pom.xml | 2 +- msa/pom.xml | 2 +- msa/tb-node/pom.xml | 2 +- msa/tb/pom.xml | 2 +- msa/transport/coap/pom.xml | 2 +- msa/transport/http/pom.xml | 2 +- msa/transport/lwm2m/pom.xml | 2 +- msa/transport/mqtt/pom.xml | 2 +- msa/transport/pom.xml | 2 +- msa/transport/snmp/pom.xml | 2 +- msa/vc-executor-docker/pom.xml | 2 +- msa/vc-executor/pom.xml | 2 +- msa/web-ui/package.json | 2 +- msa/web-ui/pom.xml | 2 +- netty-mqtt/pom.xml | 4 ++-- pom.xml | 2 +- rest-client/pom.xml | 2 +- rule-engine/pom.xml | 2 +- rule-engine/rule-engine-api/pom.xml | 2 +- rule-engine/rule-engine-components/pom.xml | 2 +- tools/pom.xml | 2 +- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/lwm2m/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- transport/pom.xml | 2 +- transport/snmp/pom.xml | 2 +- ui-ngx/package.json | 2 +- ui-ngx/pom.xml | 2 +- 56 files changed, 57 insertions(+), 57 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index b653465050..d2fc75b1cb 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT thingsboard application diff --git a/common/actor/pom.xml b/common/actor/pom.xml index 2bc3335ded..2de85be932 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/cache/pom.xml b/common/cache/pom.xml index 9c887cd7cd..440860fda6 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml index bd45c1c7ca..c9fa6d0894 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml index a48a0c31bc..420fc4dae6 100644 --- a/common/coap-server/pom.xml +++ b/common/coap-server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 71f872b353..029c660500 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/data/pom.xml b/common/data/pom.xml index e50ccd9dd2..f6fd744a02 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml index cf03facf55..60562efbf2 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index ac7814bc49..e64d4676ce 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index 812a05abaf..0edd153a3c 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT thingsboard common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 200867d022..edd4574bdd 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/script/pom.xml b/common/script/pom.xml index 42593f033c..26f60bd358 100644 --- a/common/script/pom.xml +++ b/common/script/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml index 8fbef37bdb..8fa852d977 100644 --- a/common/script/remote-js-client/pom.xml +++ b/common/script/remote-js-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT script org.thingsboard.common.script diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml index 24cf7d832d..c4e069f695 100644 --- a/common/script/script-api/pom.xml +++ b/common/script/script-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT script org.thingsboard.common.script diff --git a/common/stats/pom.xml b/common/stats/pom.xml index 19243d9b28..0869243e7a 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 14c105c3b7..2ac15d3e42 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index b02491768e..8cf3e0efe8 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index ddd8f227cf..4433eae2b9 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index ae0b35e44d..bb4767640c 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index 4f319abfad..6879a4924d 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index 30d14a4b28..1865f498fa 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 26dde924f7..2e2174e0a9 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/util/pom.xml b/common/util/pom.xml index a222724acd..83036d536f 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml index 381f4ed292..a8acdf2be9 100644 --- a/common/version-control/pom.xml +++ b/common/version-control/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index 4fc8a3c08d..4d4dd2a30b 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT thingsboard dao diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index ae0b12ead7..12d36272c0 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index add59eb610..dc72a2bb22 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-js-executor", "private": true, - "version": "3.5.0", + "version": "3.6.0", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 05775ef137..0bd7bf4d60 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/pom.xml b/msa/pom.xml index ad40e574ec..e80dc54269 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 8779dd6dd1..8437cc35c3 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index 4b8439e0e9..7c4af55497 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 6a4ce4dd69..386b91ea76 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 37fe6efc91..344219a660 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml index 77f5a1e2fd..8dfef617cd 100644 --- a/msa/transport/lwm2m/pom.xml +++ b/msa/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index 962025a917..e1d740c877 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index 8f6acf590e..e0bd5a9457 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index 6353419382..b6a4e15b04 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT org.thingsboard.msa.transport diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml index b076cf14cb..9b6fc493cd 100644 --- a/msa/vc-executor-docker/pom.xml +++ b/msa/vc-executor-docker/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml index 37ba0da564..9bd2befa90 100644 --- a/msa/vc-executor/pom.xml +++ b/msa/vc-executor/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index 18cc80481f..3338d2ae76 100644 --- a/msa/web-ui/package.json +++ b/msa/web-ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-web-ui", "private": true, - "version": "3.5.0", + "version": "3.6.0", "description": "ThingsBoard Web UI Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index fd73ce686c..e9500cf5e0 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index b15e8235b4..0ae03f3980 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT thingsboard netty-mqtt - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index 49f30df835..b70be187ed 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT pom Thingsboard diff --git a/rest-client/pom.xml b/rest-client/pom.xml index cb85c2134e..10a8cd311b 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT thingsboard rest-client diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index e005b5a543..fe8cb3fab5 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 5a200fc14a..47d13e5658 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index e85f0094b5..34c6e315f7 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/tools/pom.xml b/tools/pom.xml index ae6eb95a15..c0b74bb0a9 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 5cc87640d7..3b1e0a9e62 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index d685b8d1ab..3d4b806d03 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index dcdfaac0c8..efe76111f3 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 11a73d7e51..75daa483a1 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index 3e14199411..203ea6cbbb 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT thingsboard transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index 8ba98a9924..c04e7d1f1f 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT transport diff --git a/ui-ngx/package.json b/ui-ngx/package.json index a8abb6fbeb..a1564630f3 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -1,6 +1,6 @@ { "name": "thingsboard", - "version": "3.5.0", + "version": "3.6.0", "scripts": { "ng": "ng", "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open", diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index 01d0ece37d..d6dc28490f 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT thingsboard org.thingsboard From 98ea8298c3a4a73f238e783e7b2abf1e9c42aa0c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 21 Apr 2023 20:03:25 +0200 Subject: [PATCH 002/209] set 3.6 version for monitoring --- monitoring/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitoring/pom.xml b/monitoring/pom.xml index e044635e98..399e34092d 100644 --- a/monitoring/pom.xml +++ b/monitoring/pom.xml @@ -21,7 +21,7 @@ 4.0.0 org.thingsboard - 3.5.0-SNAPSHOT + 3.6.0-SNAPSHOT thingsboard From 40d6e6713141ee6273bf2f91bfce2c824b57f18a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 2 May 2023 00:46:34 +0200 Subject: [PATCH 003/209] migrate to java 17 --- .../sync/vc/VersionControlTaskRedisCache.java | 4 +- .../AutoCommitSettingsRedisCache.java | 4 +- .../RepositorySettingsRedisCache.java | 4 +- .../server/controller/AbstractWebTest.java | 14 ++++- ...alizer.java => TbJavaRedisSerializer.java} | 8 +-- .../server/cache/device/DeviceRedisCache.java | 4 +- .../UsersSessionInvalidationRedisCache.java | 4 +- common/data/pom.xml | 4 -- .../server/common/data/FSTUtils.java | 35 ------------ .../server/common/data/JavaSerDesUtil.java | 57 +++++++++++++++++++ .../bootstrap/LwM2MServerSecurityConfig.java | 4 +- .../NoSecLwM2MBootstrapServerCredential.java | 3 + .../msg/TbMsgProcessingStackItemTest.java | 6 +- ...a => JavaDataDecodingEncodingService.java} | 20 ++----- common/script/script-api/pom.xml | 4 ++ .../store/TbLwM2MDtlsSessionRedisStore.java | 8 +-- .../store/TbLwM2mRedisSecurityStore.java | 21 +++---- .../dao/asset/AssetProfileRedisCache.java | 4 +- .../server/dao/asset/AssetRedisCache.java | 4 +- .../dashboard/DashboardTitlesRedisCache.java | 4 +- .../device/DeviceCredentialsRedisCache.java | 4 +- .../dao/device/DeviceProfileRedisCache.java | 4 +- .../server/dao/edge/EdgeRedisCache.java | 4 +- .../entity/count/EntityCountRedisCache.java | 4 +- .../dao/entityview/EntityViewRedisCache.java | 4 +- .../server/dao/ota/OtaPackageRedisCache.java | 4 +- .../dao/relation/RelationRedisCache.java | 4 +- .../dao/tenant/TenantExistsRedisCache.java | 4 +- .../dao/tenant/TenantProfileRedisCache.java | 4 +- .../server/dao/tenant/TenantRedisCache.java | 4 +- .../dao/user/UserSettingsRedisCache.java | 5 +- pom.xml | 17 +++--- 32 files changed, 148 insertions(+), 130 deletions(-) rename common/cache/src/main/java/org/thingsboard/server/cache/{TbFSTRedisSerializer.java => TbJavaRedisSerializer.java} (80%) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/FSTUtils.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/JavaSerDesUtil.java rename common/queue/src/main/java/org/thingsboard/server/queue/util/{ProtoWithFSTService.java => JavaDataDecodingEncodingService.java} (61%) diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java index 5fccbb3cbb..a849577d0c 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import java.util.UUID; @@ -31,6 +31,6 @@ import java.util.UUID; public class VersionControlTaskRedisCache extends RedisTbTransactionalCache { public VersionControlTaskRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.VERSION_CONTROL_TASK_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.VERSION_CONTROL_TASK_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java index b3c47cf2a7..be9bbfa443 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; @@ -31,6 +31,6 @@ import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; public class AutoCommitSettingsRedisCache extends RedisTbTransactionalCache { public AutoCommitSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.AUTO_COMMIT_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.AUTO_COMMIT_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java index 27fba3459c..1e71a51d00 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.sync.vc.RepositorySettings; @@ -31,6 +31,6 @@ import org.thingsboard.server.common.data.sync.vc.RepositorySettings; public class RepositorySettingsRedisCache extends RedisTbTransactionalCache { public RepositorySettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.REPOSITORY_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.REPOSITORY_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index c7580867e0..d128c8ed91 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -116,6 +116,8 @@ import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest; import org.thingsboard.server.service.security.auth.rest.LoginRequest; import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.sql.SQLException; @@ -927,9 +929,15 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { protected static void setStaticFinalFieldValue(Class targetCls, String fieldName, Object value) throws Exception { Field field = targetCls.getDeclaredField(fieldName); field.setAccessible(true); - Field modifiers = Field.class.getDeclaredField("modifiers"); - modifiers.setAccessible(true); - modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL); + // Get the VarHandle for the 'modifiers' field in the Field class + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup()); + VarHandle modifiersHandle = lookup.findVarHandle(Field.class, "modifiers", int.class); + + // Remove the final modifier from the field + int currentModifiers = field.getModifiers(); + modifiersHandle.set(field, currentModifiers & ~Modifier.FINAL); + + // Set the new value field.set(null, value); } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbFSTRedisSerializer.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbJavaRedisSerializer.java similarity index 80% rename from common/cache/src/main/java/org/thingsboard/server/cache/TbFSTRedisSerializer.java rename to common/cache/src/main/java/org/thingsboard/server/cache/TbJavaRedisSerializer.java index 3d8bf66542..9a5e2fa078 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TbFSTRedisSerializer.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbJavaRedisSerializer.java @@ -16,17 +16,17 @@ package org.thingsboard.server.cache; import org.springframework.data.redis.serializer.SerializationException; -import org.thingsboard.server.common.data.FSTUtils; +import org.thingsboard.server.common.data.JavaSerDesUtil; -public class TbFSTRedisSerializer implements TbRedisSerializer { +public class TbJavaRedisSerializer implements TbRedisSerializer { @Override public byte[] serialize(V value) throws SerializationException { - return FSTUtils.encode(value); + return JavaSerDesUtil.encode(value); } @Override public V deserialize(K key, byte[] bytes) throws SerializationException { - return FSTUtils.decode(bytes); + return JavaSerDesUtil.decode(bytes); } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java index 591555a3a1..a7b7a047fb 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.Device; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.Device; public class DeviceRedisCache extends RedisTbTransactionalCache { public DeviceRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.DEVICE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.DEVICE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationRedisCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationRedisCache.java index 83360fb8af..38a9104679 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationRedisCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationRedisCache.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @@ -31,6 +31,6 @@ public class UsersSessionInvalidationRedisCache extends RedisTbTransactionalCach @Autowired public UsersSessionInvalidationRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.USERS_SESSION_INVALIDATION_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.USERS_SESSION_INVALIDATION_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/common/data/pom.xml b/common/data/pom.xml index d81203cd4d..c1fa964caa 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -108,10 +108,6 @@ io.swagger swagger-annotations - - de.ruedigermoeller - fst - com.google.protobuf protobuf-java-util diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FSTUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/FSTUtils.java deleted file mode 100644 index 83447d7a6b..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/FSTUtils.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data; - -import lombok.extern.slf4j.Slf4j; -import org.nustaq.serialization.FSTConfiguration; - -@Slf4j -public class FSTUtils { - - public static final FSTConfiguration CONFIG = FSTConfiguration.createDefaultConfiguration(); - - @SuppressWarnings("unchecked") - public static T decode(byte[] byteArray) { - return byteArray != null && byteArray.length > 0 ? (T) CONFIG.asObject(byteArray) : null; - } - - public static byte[] encode(T msq) { - return CONFIG.asByteArray(msq); - } - -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/JavaSerDesUtil.java b/common/data/src/main/java/org/thingsboard/server/common/data/JavaSerDesUtil.java new file mode 100644 index 0000000000..636fda5fc5 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/JavaSerDesUtil.java @@ -0,0 +1,57 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import lombok.extern.slf4j.Slf4j; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +@Slf4j +public class JavaSerDesUtil { + + @SuppressWarnings("unchecked") + public static T decode(byte[] byteArray) { + if (byteArray == null || byteArray.length == 0) { + return null; + } + InputStream is = new ByteArrayInputStream(byteArray); + try (ObjectInputStream ois = new ObjectInputStream(is)) { + return (T) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + log.error("Error during deserialization message, [{}]", e.getMessage()); + return null; + } + } + + public static byte[] encode(T msq) { + if (msq == null) { + return null; + } + ByteArrayOutputStream boas = new ByteArrayOutputStream(); + try (ObjectOutputStream ois = new ObjectOutputStream(boas)) { + ois.writeObject(msq); + return boas.toByteArray(); + } catch (IOException e) { + log.error("Error during serialization message, [{}]", e.getMessage()); + throw new RuntimeException(e); + } + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java index eb052da126..4405115905 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java @@ -19,9 +19,11 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import java.io.Serializable; + @ApiModel @Data -public class LwM2MServerSecurityConfig { +public class LwM2MServerSecurityConfig implements Serializable { @ApiModelProperty(position = 1, value = "Server short Id. Used as link to associate server Object Instance. This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. " + "This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'. " + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java index 6bd7b7c427..43b9a0763e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java @@ -17,8 +17,11 @@ package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; +import java.io.Serial; + public class NoSecLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { + @Serial private static final long serialVersionUID = 5540417758424747066L; @Override diff --git a/common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItemTest.java b/common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItemTest.java index 1f3fa2050a..86d061b57c 100644 --- a/common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItemTest.java +++ b/common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItemTest.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.msg; import org.junit.jupiter.api.Test; -import org.thingsboard.server.common.data.FSTUtils; +import org.thingsboard.server.common.data.JavaSerDesUtil; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -29,8 +29,8 @@ class TbMsgProcessingStackItemTest { @Test void testSerialization() { TbMsgProcessingStackItem item = new TbMsgProcessingStackItem(new RuleChainId(UUID.randomUUID()), new RuleNodeId(UUID.randomUUID())); - byte[] bytes = FSTUtils.encode(item); - TbMsgProcessingStackItem itemDecoded = FSTUtils.decode(bytes); + byte[] bytes = JavaSerDesUtil.encode(item); + TbMsgProcessingStackItem itemDecoded = JavaSerDesUtil.decode(bytes); assertThat(item).isEqualTo(itemDecoded); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/JavaDataDecodingEncodingService.java similarity index 61% rename from common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java rename to common/queue/src/main/java/org/thingsboard/server/queue/util/JavaDataDecodingEncodingService.java index b0d0b182e9..493d6f31f2 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/JavaDataDecodingEncodingService.java @@ -16,33 +16,21 @@ package org.thingsboard.server.queue.util; import lombok.extern.slf4j.Slf4j; -import org.nustaq.serialization.FSTConfiguration; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.FSTUtils; +import org.thingsboard.server.common.data.JavaSerDesUtil; import java.util.Optional; @Slf4j @Service -public class ProtoWithFSTService implements DataDecodingEncodingService { - - public static final FSTConfiguration CONFIG = FSTConfiguration.createDefaultConfiguration(); - +public class JavaDataDecodingEncodingService implements DataDecodingEncodingService { @Override public Optional decode(byte[] byteArray) { - try { - return Optional.ofNullable(FSTUtils.decode(byteArray)); - } catch (IllegalArgumentException e) { - log.error("Error during deserialization message, [{}]", e.getMessage()); - return Optional.empty(); - } + return Optional.ofNullable(JavaSerDesUtil.decode(byteArray)); } - @Override public byte[] encode(T msq) { - return FSTUtils.encode(msq); + return JavaSerDesUtil.encode(msq); } - - } diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml index 24cf7d832d..b5d652f0d9 100644 --- a/common/script/script-api/pom.xml +++ b/common/script/script-api/pom.xml @@ -52,6 +52,10 @@ org.javadelight delight-nashorn-sandbox + + org.openjdk.nashorn + nashorn-core + com.google.code.gson gson diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java index a1ffad9946..737ce96c41 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java @@ -15,25 +15,23 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import org.nustaq.serialization.FSTConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.thingsboard.server.common.data.JavaSerDesUtil; import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; public class TbLwM2MDtlsSessionRedisStore implements TbLwM2MDtlsSessionStore { private static final String SESSION_EP = "SESSION#EP#"; private final RedisConnectionFactory connectionFactory; - private final FSTConfiguration serializer; public TbLwM2MDtlsSessionRedisStore(RedisConnectionFactory redisConnectionFactory) { this.connectionFactory = redisConnectionFactory; - this.serializer = FSTConfiguration.createDefaultConfiguration(); } @Override public void put(String endpoint, TbX509DtlsSessionInfo msg) { try (var c = connectionFactory.getConnection()) { - var serializedMsg = serializer.asByteArray(msg); + var serializedMsg = JavaSerDesUtil.encode(msg); if (serializedMsg != null) { c.set(getKey(endpoint), serializedMsg); } else { @@ -47,7 +45,7 @@ public class TbLwM2MDtlsSessionRedisStore implements TbLwM2MDtlsSessionStore { try (var c = connectionFactory.getConnection()) { var data = c.get(getKey(endpoint)); if (data != null) { - return (TbX509DtlsSessionInfo) serializer.asObject(data); + return JavaSerDesUtil.decode(data); } else { return null; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java index f6539556e5..87e92c8cca 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java @@ -18,9 +18,9 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; -import org.nustaq.serialization.FSTConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.integration.redis.util.RedisLockRegistry; +import org.thingsboard.server.common.data.JavaSerDesUtil; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import java.util.concurrent.locks.Lock; @@ -31,13 +31,11 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { private static final String PSKID_SEC = "PSKID#SEC"; private final RedisConnectionFactory connectionFactory; - private final FSTConfiguration serializer; private final RedisLockRegistry redisLock; public TbLwM2mRedisSecurityStore(RedisConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; redisLock = new RedisLockRegistry(connectionFactory, "Security"); - serializer = FSTConfiguration.createDefaultConfiguration(); } @Override @@ -50,12 +48,11 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { if (data == null || data.length == 0) { return null; } else { - if (SecurityMode.NO_SEC.equals(((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityMode())) { + if (SecurityMode.NO_SEC.equals(((TbLwM2MSecurityInfo) JavaSerDesUtil.decode(data)).getSecurityMode())) { return SecurityInfo.newPreSharedKeyInfo(SecurityMode.NO_SEC.toString(), SecurityMode.NO_SEC.toString(), SecurityMode.NO_SEC.toString().getBytes()); - } - else { - return ((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityInfo(); + } else { + return ((TbLwM2MSecurityInfo) JavaSerDesUtil.decode(data)).getSecurityInfo(); } } } finally { @@ -79,7 +76,7 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { if (data == null || data.length == 0) { return null; } else { - return ((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityInfo(); + return ((TbLwM2MSecurityInfo) JavaSerDesUtil.decode(data)).getSecurityInfo(); } } } finally { @@ -92,7 +89,7 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { @Override public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { SecurityInfo info = tbSecurityInfo.getSecurityInfo(); - byte[] tbSecurityInfoSerialized = serializer.asByteArray(tbSecurityInfo); + byte[] tbSecurityInfoSerialized = JavaSerDesUtil.encode(tbSecurityInfo); Lock lock = null; try (var connection = connectionFactory.getConnection()) { lock = redisLock.obtain(tbSecurityInfo.getEndpoint()); @@ -110,7 +107,7 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { byte[] previousData = connection.getSet((SEC_EP + tbSecurityInfo.getEndpoint()).getBytes(), tbSecurityInfoSerialized); if (previousData != null && info != null) { - String previousIdentity = ((TbLwM2MSecurityInfo) serializer.asObject(previousData)).getSecurityInfo().getIdentity(); + String previousIdentity = ((TbLwM2MSecurityInfo) JavaSerDesUtil.decode(previousData)).getSecurityInfo().getIdentity(); if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) { connection.hDel(PSKID_SEC.getBytes(), previousIdentity.getBytes()); } @@ -130,7 +127,7 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { lock.lock(); byte[] data = connection.get((SEC_EP + endpoint).getBytes()); if (data != null && data.length > 0) { - return (TbLwM2MSecurityInfo) serializer.asObject(data); + return JavaSerDesUtil.decode(data); } else { return null; } @@ -149,7 +146,7 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { lock.lock(); byte[] data = connection.get((SEC_EP + endpoint).getBytes()); if (data != null && data.length > 0) { - SecurityInfo info = ((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityInfo(); + SecurityInfo info = ((TbLwM2MSecurityInfo) JavaSerDesUtil.decode(data)).getSecurityInfo(); if (info != null && info.getIdentity() != null) { connection.hDel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java index 25f919e881..1e4463cbea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.asset.AssetProfile; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.asset.AssetProfile; public class AssetProfileRedisCache extends RedisTbTransactionalCache { public AssetProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ASSET_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.ASSET_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetRedisCache.java index c0fb23c5a4..b423edae43 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetRedisCache.java @@ -23,13 +23,13 @@ import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.cache.RedisTbTransactionalCache; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("AssetCache") public class AssetRedisCache extends RedisTbTransactionalCache { public AssetRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ASSET_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.ASSET_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java index 990a175b71..ef59fb6f6d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.id.DashboardId; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.id.DashboardId; public class DashboardTitlesRedisCache extends RedisTbTransactionalCache { public DashboardTitlesRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.DASHBOARD_TITLES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.DASHBOARD_TITLES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java index 86a1086207..2c33e53c10 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java @@ -20,7 +20,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.cache.RedisTbTransactionalCache; @@ -30,6 +30,6 @@ import org.thingsboard.server.cache.RedisTbTransactionalCache; public class DeviceCredentialsRedisCache extends RedisTbTransactionalCache { public DeviceCredentialsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.DEVICE_CREDENTIALS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.DEVICE_CREDENTIALS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java index 726f19b74e..86628b4466 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java @@ -20,7 +20,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.cache.RedisTbTransactionalCache; @@ -30,6 +30,6 @@ import org.thingsboard.server.cache.RedisTbTransactionalCache; public class DeviceProfileRedisCache extends RedisTbTransactionalCache { public DeviceProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.DEVICE_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.DEVICE_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeRedisCache.java index cd788f2695..2c6f4a80ad 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeRedisCache.java @@ -23,13 +23,13 @@ import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.cache.RedisTbTransactionalCache; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("EdgeCache") public class EdgeRedisCache extends RedisTbTransactionalCache { public EdgeRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.EDGE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.EDGE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/count/EntityCountRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/entity/count/EntityCountRedisCache.java index af431f3085..791921bbba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/count/EntityCountRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/count/EntityCountRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.dao.entity.EntityCountCacheKey; @@ -30,6 +30,6 @@ import org.thingsboard.server.dao.entity.EntityCountCacheKey; public class EntityCountRedisCache extends RedisTbTransactionalCache { public EntityCountRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ENTITY_COUNT_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.ENTITY_COUNT_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java index 9343b4a205..9234ea57d5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java @@ -22,13 +22,13 @@ import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.cache.RedisTbTransactionalCache; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("EntityViewCache") public class EntityViewRedisCache extends RedisTbTransactionalCache { public EntityViewRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ENTITY_VIEW_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.ENTITY_VIEW_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageRedisCache.java index 69ff998712..11c13eabca 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageRedisCache.java @@ -20,7 +20,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.cache.RedisTbTransactionalCache; @@ -30,6 +30,6 @@ import org.thingsboard.server.cache.RedisTbTransactionalCache; public class OtaPackageRedisCache extends RedisTbTransactionalCache { public OtaPackageRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.OTA_PACKAGE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.OTA_PACKAGE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationRedisCache.java index 35b52d7fe1..0154b1d9e1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationRedisCache.java @@ -20,7 +20,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.cache.RedisTbTransactionalCache; @@ -29,6 +29,6 @@ import org.thingsboard.server.cache.RedisTbTransactionalCache; public class RelationRedisCache extends RedisTbTransactionalCache { public RelationRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.RELATIONS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.RELATIONS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java index 9cd5ba18cb..3c223cbd36 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.id.TenantId; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.id.TenantId; public class TenantExistsRedisCache extends RedisTbTransactionalCache { public TenantExistsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.TENANTS_EXIST_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.TENANTS_EXIST_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileRedisCache.java index 302006fa00..4cf47f9a62 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileRedisCache.java @@ -23,13 +23,13 @@ import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.cache.RedisTbTransactionalCache; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("TenantProfileCache") public class TenantProfileRedisCache extends RedisTbTransactionalCache { public TenantProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.TENANT_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.TENANT_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantRedisCache.java index 29ebcd9767..e9b225866b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; @@ -31,6 +31,6 @@ import org.thingsboard.server.common.data.id.TenantId; public class TenantRedisCache extends RedisTbTransactionalCache { public TenantRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.TENANTS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.TENANTS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java index de321f3eb9..384336006e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java @@ -21,9 +21,8 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; -import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; @@ -32,6 +31,6 @@ import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; public class UserSettingsRedisCache extends RedisTbTransactionalCache { public UserSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.USER_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.USER_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } diff --git a/pom.xml b/pom.xml index f78a4826bc..6e6500c742 100755 --- a/pom.xml +++ b/pom.xml @@ -79,7 +79,7 @@ 3.21.9 1.42.1 1.0.6 - 1.18.18 + 1.18.26 1.2.4 1.2.5 4.1.91.Final @@ -100,12 +100,12 @@ 5.0.2 0.2.1 + 15.4 3.2.0 4.1.1 - 2.57 2.7.7 2.0 1.11.747 @@ -607,7 +607,7 @@ maven-compiler-plugin 3.8.1 - 11 + 17 -Xlint:deprecation -Xlint:removal @@ -664,6 +664,7 @@ --illegal-access=permit -XX:+UseStringDeduplication -XX:MaxGCPauseMillis=20 + --add-opens java.base/java.lang.reflect=ALL-UNNAMED @@ -1797,11 +1798,6 @@ bucket4j-core ${bucket4j.version} - - de.ruedigermoeller - fst - ${fst.version} - org.locationtech.spatial4j spatial4j @@ -2007,6 +2003,11 @@ oshi-core ${oshi.version} + + org.openjdk.nashorn + nashorn-core + ${nashorn-core.version} + From 184900761059a96a55776ac76b5952b3e4e32dd4 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 19 May 2023 19:08:03 +0200 Subject: [PATCH 004/209] msa and packaging java 17 support --- msa/monitoring/docker/Dockerfile | 2 +- msa/tb-node/docker/Dockerfile | 4 ++-- msa/tb/docker-cassandra/Dockerfile | 2 +- msa/tb/docker-postgres/Dockerfile | 2 +- msa/transport/coap/docker/Dockerfile | 2 +- msa/transport/http/docker/Dockerfile | 2 +- msa/transport/lwm2m/docker/Dockerfile | 2 +- msa/transport/mqtt/docker/Dockerfile | 2 +- msa/transport/snmp/docker/Dockerfile | 2 +- msa/vc-executor-docker/docker/Dockerfile | 2 +- packaging/java/build.gradle | 4 ++-- packaging/java/scripts/windows/install.bat | 6 +++--- pom.xml | 4 ++-- 13 files changed, 18 insertions(+), 18 deletions(-) diff --git a/msa/monitoring/docker/Dockerfile b/msa/monitoring/docker/Dockerfile index 5da050eb91..14c6246280 100644 --- a/msa/monitoring/docker/Dockerfile +++ b/msa/monitoring/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk11:bullseye-slim +FROM thingsboard/openjdk17:bullseye-slim COPY start-tb-monitoring.sh ${pkg.name}.deb /tmp/ diff --git a/msa/tb-node/docker/Dockerfile b/msa/tb-node/docker/Dockerfile index 68e428992a..2c8af18f9a 100644 --- a/msa/tb-node/docker/Dockerfile +++ b/msa/tb-node/docker/Dockerfile @@ -14,11 +14,11 @@ # limitations under the License. # -FROM thingsboard/openjdk11:bullseye-slim +FROM thingsboard/openjdk17:bullseye-slim COPY start-tb-node.sh ${pkg.name}.deb /tmp/ -RUN echo 'networkaddress.cache.ttl=60' >> /etc/java-11-openjdk/security/java.security \ +RUN echo 'networkaddress.cache.ttl=60' >> /etc/java-17-openjdk/security/java.security \ && chmod a+x /tmp/*.sh \ && mv /tmp/start-tb-node.sh /usr/bin && \ (yes | dpkg -i /tmp/${pkg.name}.deb) && \ diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index 2f23aec399..f871d57ff8 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk11:bullseye-slim +FROM thingsboard/openjdk17:bullseye-slim ENV PG_MAJOR=12 diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile index 1c25624adf..2520bee62b 100644 --- a/msa/tb/docker-postgres/Dockerfile +++ b/msa/tb/docker-postgres/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk11:bullseye-slim +FROM thingsboard/openjdk17:bullseye-slim ENV PG_MAJOR 12 diff --git a/msa/transport/coap/docker/Dockerfile b/msa/transport/coap/docker/Dockerfile index 1786e93cb2..526a8620a8 100644 --- a/msa/transport/coap/docker/Dockerfile +++ b/msa/transport/coap/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk11:bullseye-slim +FROM thingsboard/openjdk17:bullseye-slim COPY start-tb-coap-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/transport/http/docker/Dockerfile b/msa/transport/http/docker/Dockerfile index 0ba4f713b0..399df84000 100644 --- a/msa/transport/http/docker/Dockerfile +++ b/msa/transport/http/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk11:bullseye-slim +FROM thingsboard/openjdk17:bullseye-slim COPY start-tb-http-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/transport/lwm2m/docker/Dockerfile b/msa/transport/lwm2m/docker/Dockerfile index c9270dd4f3..d17f368c8f 100644 --- a/msa/transport/lwm2m/docker/Dockerfile +++ b/msa/transport/lwm2m/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk11:bullseye-slim +FROM thingsboard/openjdk17:bullseye-slim COPY start-tb-lwm2m-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/transport/mqtt/docker/Dockerfile b/msa/transport/mqtt/docker/Dockerfile index 2037d629bb..769164b86e 100644 --- a/msa/transport/mqtt/docker/Dockerfile +++ b/msa/transport/mqtt/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk11:bullseye-slim +FROM thingsboard/openjdk17:bullseye-slim COPY start-tb-mqtt-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/transport/snmp/docker/Dockerfile b/msa/transport/snmp/docker/Dockerfile index 9edb97fbc4..2835c56bc7 100644 --- a/msa/transport/snmp/docker/Dockerfile +++ b/msa/transport/snmp/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk11:bullseye-slim +FROM thingsboard/openjdk17:bullseye-slim COPY start-tb-snmp-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/vc-executor-docker/docker/Dockerfile b/msa/vc-executor-docker/docker/Dockerfile index 653a5f2d35..70a3d3e18f 100644 --- a/msa/vc-executor-docker/docker/Dockerfile +++ b/msa/vc-executor-docker/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk11:bullseye-slim +FROM thingsboard/openjdk17:bullseye-slim COPY start-tb-vc-executor.sh ${pkg.name}.deb /tmp/ diff --git a/packaging/java/build.gradle b/packaging/java/build.gradle index d4aa399d70..86dc908426 100644 --- a/packaging/java/build.gradle +++ b/packaging/java/build.gradle @@ -92,7 +92,7 @@ buildRpm { archiveVersion = projectVersion.replace('-', '') archiveFileName = "${pkgName}.rpm" - requires("java-11") + requires("java-17") from("${buildDir}/conf") { include "${pkgName}.conf" @@ -131,7 +131,7 @@ buildDeb { archiveFileName = "${pkgName}.deb" - requires("openjdk-11-jre").or("java11-runtime").or("oracle-java11-installer").or("openjdk-11-jre-headless") + requires("openjdk-17-jre").or("java17-runtime").or("oracle-java17-installer").or("openjdk-17-jre-headless") from("${buildDir}/conf") { include "${pkgName}.conf" diff --git a/packaging/java/scripts/windows/install.bat b/packaging/java/scripts/windows/install.bat index 6f168c76d8..5f71149c83 100644 --- a/packaging/java/scripts/windows/install.bat +++ b/packaging/java/scripts/windows/install.bat @@ -11,7 +11,7 @@ if %jver% NEQ 110 GOTO JAVA_NOT_INSTALLED :JAVA_INSTALLED -@ECHO Java 11 found! +@ECHO Java 17 found! @ECHO Installing thingsboard ... SET loadDemo=false @@ -50,8 +50,8 @@ POPD GOTO END :JAVA_NOT_INSTALLED -@ECHO Java 11 is not installed. Only Java 11 is supported -@ECHO Please go to https://adoptopenjdk.net/index.html and install Java 11. Then retry installation. +@ECHO Java 17 is not installed. Only Java 17 is supported +@ECHO Please go to https://adoptopenjdk.net/index.html and install Java 17. Then retry installation. PAUSE GOTO END diff --git a/pom.xml b/pom.xml index 825138a8dd..4d456b8f4e 100755 --- a/pom.xml +++ b/pom.xml @@ -605,7 +605,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.11.0 17 @@ -650,7 +650,7 @@ org.thingsboard gradle-maven-plugin - 1.0.11 + 1.0.12 com.github.eirslett From 3caae012d65b1cbf0ec98a3baaac8de947951049 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 19 May 2023 19:39:45 +0200 Subject: [PATCH 005/209] minor improvements --- common/util/pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/util/pom.xml b/common/util/pom.xml index df09e302ba..14ae1748b2 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -92,6 +92,10 @@ com.github.oshi oshi-core + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + From 2a1259570bc06f63ec008d2a99c1f1434d78a34d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 29 May 2023 13:43:10 +0300 Subject: [PATCH 006/209] Temporary version set to 3.5.2-SNAPSHOT to merge --- application/pom.xml | 2 +- common/actor/pom.xml | 2 +- common/cache/pom.xml | 2 +- common/cluster-api/pom.xml | 2 +- common/coap-server/pom.xml | 2 +- common/dao-api/pom.xml | 2 +- common/data/pom.xml | 2 +- common/edge-api/pom.xml | 2 +- common/message/pom.xml | 2 +- common/pom.xml | 2 +- common/queue/pom.xml | 2 +- common/script/pom.xml | 2 +- common/script/remote-js-client/pom.xml | 2 +- common/script/script-api/pom.xml | 2 +- common/stats/pom.xml | 2 +- common/transport/coap/pom.xml | 2 +- common/transport/http/pom.xml | 2 +- common/transport/lwm2m/pom.xml | 2 +- common/transport/mqtt/pom.xml | 2 +- common/transport/pom.xml | 2 +- common/transport/snmp/pom.xml | 2 +- common/transport/transport-api/pom.xml | 2 +- common/util/pom.xml | 2 +- common/version-control/pom.xml | 2 +- dao/pom.xml | 2 +- monitoring/pom.xml | 2 +- msa/black-box-tests/pom.xml | 2 +- msa/js-executor/pom.xml | 2 +- msa/monitoring/pom.xml | 2 +- msa/pom.xml | 2 +- msa/tb-node/pom.xml | 2 +- msa/tb/pom.xml | 2 +- msa/transport/coap/pom.xml | 2 +- msa/transport/http/pom.xml | 2 +- msa/transport/lwm2m/pom.xml | 2 +- msa/transport/mqtt/pom.xml | 2 +- msa/transport/pom.xml | 2 +- msa/transport/snmp/pom.xml | 2 +- msa/vc-executor-docker/pom.xml | 2 +- msa/vc-executor/pom.xml | 2 +- msa/web-ui/pom.xml | 2 +- netty-mqtt/pom.xml | 4 ++-- pom.xml | 2 +- rest-client/pom.xml | 2 +- rule-engine/pom.xml | 2 +- rule-engine/rule-engine-api/pom.xml | 2 +- rule-engine/rule-engine-components/pom.xml | 2 +- tools/pom.xml | 2 +- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/lwm2m/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- transport/pom.xml | 2 +- transport/snmp/pom.xml | 2 +- ui-ngx/pom.xml | 2 +- 55 files changed, 56 insertions(+), 56 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 8c3976eb31..8e7c3cd524 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT thingsboard application diff --git a/common/actor/pom.xml b/common/actor/pom.xml index 2de85be932..b113788a11 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/cache/pom.xml b/common/cache/pom.xml index 440860fda6..0a9141846f 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml index c9fa6d0894..d6954a40f9 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml index 420fc4dae6..116a94791d 100644 --- a/common/coap-server/pom.xml +++ b/common/coap-server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 029c660500..cf825ea637 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/data/pom.xml b/common/data/pom.xml index 677c3dfd25..f4e1fb2756 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml index 60562efbf2..c99a475236 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index e64d4676ce..62e176e675 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index 0edd153a3c..51ca9546b1 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT thingsboard common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index edd4574bdd..a995d9b4a3 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/script/pom.xml b/common/script/pom.xml index 26f60bd358..7a4304b77a 100644 --- a/common/script/pom.xml +++ b/common/script/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml index 8fa852d977..53e222ad76 100644 --- a/common/script/remote-js-client/pom.xml +++ b/common/script/remote-js-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT script org.thingsboard.common.script diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml index c4e069f695..ae5f38f42f 100644 --- a/common/script/script-api/pom.xml +++ b/common/script/script-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT script org.thingsboard.common.script diff --git a/common/stats/pom.xml b/common/stats/pom.xml index 0869243e7a..10d4ea3b95 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 2ac15d3e42..3e1174362c 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index 8cf3e0efe8..09f822959f 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index 4433eae2b9..f5ee1eea52 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index bb4767640c..e11f923a0b 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index 6879a4924d..4fc43aa3ea 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index 1865f498fa..0a615cfd4f 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 2e2174e0a9..6840a24ab2 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/util/pom.xml b/common/util/pom.xml index df09e302ba..ee32f1e1e9 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml index a8acdf2be9..ae12a413c0 100644 --- a/common/version-control/pom.xml +++ b/common/version-control/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index 4d4dd2a30b..45f49790b0 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT thingsboard dao diff --git a/monitoring/pom.xml b/monitoring/pom.xml index 399e34092d..cc3a78c04d 100644 --- a/monitoring/pom.xml +++ b/monitoring/pom.xml @@ -21,7 +21,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT thingsboard diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 12d36272c0..6413973d7c 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 0bd7bf4d60..fe92a77763 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/monitoring/pom.xml b/msa/monitoring/pom.xml index 1fe43219a0..bb0539ac98 100644 --- a/msa/monitoring/pom.xml +++ b/msa/monitoring/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.5.1-SNAPSHOT + 3.5.2-SNAPSHOT msa diff --git a/msa/pom.xml b/msa/pom.xml index bf4e15d9d4..451466f98a 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 8437cc35c3..3d39ba285b 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index c85ac88274..31380a4578 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 386b91ea76..7c55ef4322 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 344219a660..2d02b2f124 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml index 8dfef617cd..7a521a6718 100644 --- a/msa/transport/lwm2m/pom.xml +++ b/msa/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index e1d740c877..4ab24589dd 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index e0bd5a9457..f12564c24f 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index b6a4e15b04..c5b5617f69 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT org.thingsboard.msa.transport diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml index 9b6fc493cd..162ae1f801 100644 --- a/msa/vc-executor-docker/pom.xml +++ b/msa/vc-executor-docker/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml index 9bd2befa90..c78b3fd129 100644 --- a/msa/vc-executor/pom.xml +++ b/msa/vc-executor/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index e9500cf5e0..58efe59856 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 0ae03f3980..d81be2fece 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT thingsboard netty-mqtt - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index 6c6608785d..98b2e1529a 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT pom Thingsboard diff --git a/rest-client/pom.xml b/rest-client/pom.xml index 638dc42009..728ffc5040 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT thingsboard rest-client diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index fe8cb3fab5..3ebb1f6c76 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 47d13e5658..f8c45d388b 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 34c6e315f7..ac493e79ce 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/tools/pom.xml b/tools/pom.xml index c0b74bb0a9..1cdd789ffb 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 3b1e0a9e62..d7572a1523 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 3d4b806d03..3a991911ce 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index efe76111f3..e8f639ba85 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 75daa483a1..85ea0c86f1 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index 203ea6cbbb..ad614a8d2c 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT thingsboard transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index c04e7d1f1f..e5ae7ce756 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT transport diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index d6dc28490f..5b5949a005 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.5.2-SNAPSHOT thingsboard org.thingsboard From 6b6e27bd60dd770ba7eeeb2d56cdf2d158e4a1a1 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 29 May 2023 14:07:02 +0300 Subject: [PATCH 007/209] Maven dependency fix --- common/util/pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/util/pom.xml b/common/util/pom.xml index df09e302ba..14ae1748b2 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -92,6 +92,10 @@ com.github.oshi oshi-core + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + From 22aaadff858f603df2feebe9154013914385ae57 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 31 May 2023 17:26:03 +0200 Subject: [PATCH 008/209] jdk 17 for windows --- packaging/java/scripts/windows/install.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/java/scripts/windows/install.bat b/packaging/java/scripts/windows/install.bat index 5f71149c83..b0f6c9b203 100644 --- a/packaging/java/scripts/windows/install.bat +++ b/packaging/java/scripts/windows/install.bat @@ -7,7 +7,7 @@ setlocal ENABLEEXTENSIONS for /f tokens^=2-5^ delims^=.-_^" %%j in ('java -fullversion 2^>^&1') do set "jver=%%j%%k" @ECHO CurrentVersion %jver% -if %jver% NEQ 110 GOTO JAVA_NOT_INSTALLED +if %jver% NEQ 170 GOTO JAVA_NOT_INSTALLED :JAVA_INSTALLED From c9966ef3ef4ce1f5be77109103de2ff9f8416d27 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 2 Jun 2023 16:27:06 +0300 Subject: [PATCH 009/209] Extracted proto files to a separate module --- .../device/DeviceActorMessageProcessor.java | 27 +-- .../queue/DefaultTbCoreConsumerService.java | 5 +- .../subscription/TbSubscriptionUtils.java | 65 +------- common/cluster-api/pom.xml | 8 +- common/pom.xml | 1 + common/proto/pom.xml | 109 ++++++++++++ .../transport/adaptor/AdaptorException.java | 0 .../transport/adaptor/JsonConverter.java | 0 .../adaptor/JsonConverterConfig.java | 0 .../transport/adaptor/ProtoConverter.java | 0 .../common/transport/util/KvProtoUtil.java | 156 ++++++++++++++++++ .../src/main/proto/jsinvoke.proto | 0 .../src/main/proto/queue.proto | 0 .../src/main/proto/transport.proto | 0 common/queue/pom.xml | 4 + common/transport/transport-api/pom.xml | 9 - pom.xml | 7 + 17 files changed, 292 insertions(+), 99 deletions(-) create mode 100644 common/proto/pom.xml rename common/{transport/transport-api => proto}/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java (100%) rename common/{transport/transport-api => proto}/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java (100%) rename common/{transport/transport-api => proto}/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java (100%) rename common/{transport/transport-api => proto}/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java (100%) create mode 100644 common/proto/src/main/java/org/thingsboard/server/common/transport/util/KvProtoUtil.java rename common/{cluster-api => proto}/src/main/proto/jsinvoke.proto (100%) rename common/{cluster-api => proto}/src/main/proto/queue.proto (100%) rename common/{transport/transport-api => proto}/src/main/proto/transport.proto (100%) diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index 934f309480..1831c6ab9c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -63,6 +63,7 @@ import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; +import org.thingsboard.server.common.transport.util.KvProtoUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; @@ -455,7 +456,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder() .setRequestId(requestId) .setSharedStateMsg(true) - .addAllSharedAttributeList(toTsKvProtos(result)) + .addAllSharedAttributeList(KvProtoUtil.attrToTsKvProtos(result)) .setIsMultipleAttributesRequest(request.getSharedAttributeNamesCount() > 1) .build(); sendToTransport(responseMsg, sessionInfo); @@ -476,8 +477,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso public void onSuccess(@Nullable List> result) { GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder() .setRequestId(requestId) - .addAllClientAttributeList(toTsKvProtos(result.get(0))) - .addAllSharedAttributeList(toTsKvProtos(result.get(1))) + .addAllClientAttributeList(KvProtoUtil.attrToTsKvProtos(result.get(0))) + .addAllSharedAttributeList(KvProtoUtil.attrToTsKvProtos(result.get(1))) .setIsMultipleAttributesRequest( request.getSharedAttributeNamesCount() + request.getClientAttributeNamesCount() > 1) .build(); @@ -547,7 +548,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) { List attributes = new ArrayList<>(msg.getValues()); if (attributes.size() > 0) { - List sharedUpdated = msg.getValues().stream().map(this::toTsKvProto) + List sharedUpdated = msg.getValues().stream().map(t -> KvProtoUtil.toTsKvProto(t.getLastUpdateTs(), t)) .collect(Collectors.toList()); if (!sharedUpdated.isEmpty()) { notification.addAllSharedUpdated(sharedUpdated); @@ -834,24 +835,6 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso }, systemContext.getDbCallbackExecutor()); } - private List toTsKvProtos(@Nullable List result) { - List clientAttributes; - if (result == null || result.isEmpty()) { - clientAttributes = Collections.emptyList(); - } else { - clientAttributes = new ArrayList<>(result.size()); - for (AttributeKvEntry attrEntry : result) { - clientAttributes.add(toTsKvProto(attrEntry)); - } - } - return clientAttributes; - } - - private TsKvProto toTsKvProto(AttributeKvEntry attrEntry) { - return TsKvProto.newBuilder().setTs(attrEntry.getLastUpdateTs()) - .setKv(toKeyValueProto(attrEntry)).build(); - } - private KeyValueProto toKeyValueProto(KvEntry kvEntry) { KeyValueProto.Builder builder = KeyValueProto.newBuilder(); builder.setKey(kvEntry.getKey()); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 2bd0043046..e11b6cdd5b 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -40,6 +40,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.stats.StatsFactory; +import org.thingsboard.server.common.transport.util.KvProtoUtil; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; @@ -522,13 +523,13 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService builder.addData(toKeyValueProto(v.getTs(), v).build())); + ts.forEach(v -> builder.addData(KvProtoUtil.toKeyValueProto(v.getTs(), v).build())); SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); msgBuilder.setTsUpdate(builder); return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); @@ -269,7 +270,7 @@ public class TbSubscriptionUtils { builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); builder.setScope(scope); - attributes.forEach(v -> builder.addData(toKeyValueProto(v.getLastUpdateTs(), v).build())); + attributes.forEach(v -> builder.addData(KvProtoUtil.toKeyValueProto(v.getLastUpdateTs(), v).build())); SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); msgBuilder.setAttrUpdate(builder); @@ -292,70 +293,10 @@ public class TbSubscriptionUtils { return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); } - - private static TsKvProto.Builder toKeyValueProto(long ts, KvEntry attr) { - KeyValueProto.Builder dataBuilder = KeyValueProto.newBuilder(); - dataBuilder.setKey(attr.getKey()); - dataBuilder.setType(KeyValueType.forNumber(attr.getDataType().ordinal())); - switch (attr.getDataType()) { - case BOOLEAN: - attr.getBooleanValue().ifPresent(dataBuilder::setBoolV); - break; - case LONG: - attr.getLongValue().ifPresent(dataBuilder::setLongV); - break; - case DOUBLE: - attr.getDoubleValue().ifPresent(dataBuilder::setDoubleV); - break; - case JSON: - attr.getJsonValue().ifPresent(dataBuilder::setJsonV); - break; - case STRING: - attr.getStrValue().ifPresent(dataBuilder::setStringV); - break; - } - return TsKvProto.newBuilder().setTs(ts).setKv(dataBuilder); - } - public static EntityId toEntityId(String entityType, long entityIdMSB, long entityIdLSB) { return EntityIdFactory.getByTypeAndUuid(entityType, new UUID(entityIdMSB, entityIdLSB)); } - public static List toTsKvEntityList(List dataList) { - List result = new ArrayList<>(dataList.size()); - dataList.forEach(proto -> result.add(new BasicTsKvEntry(proto.getTs(), getKvEntry(proto.getKv())))); - return result; - } - - public static List toAttributeKvList(List dataList) { - List result = new ArrayList<>(dataList.size()); - dataList.forEach(proto -> result.add(new BaseAttributeKvEntry(getKvEntry(proto.getKv()), proto.getTs()))); - return result; - } - - private static KvEntry getKvEntry(KeyValueProto proto) { - KvEntry entry = null; - DataType type = DataType.values()[proto.getType().getNumber()]; - switch (type) { - case BOOLEAN: - entry = new BooleanDataEntry(proto.getKey(), proto.getBoolV()); - break; - case LONG: - entry = new LongDataEntry(proto.getKey(), proto.getLongV()); - break; - case DOUBLE: - entry = new DoubleDataEntry(proto.getKey(), proto.getDoubleV()); - break; - case STRING: - entry = new StringDataEntry(proto.getKey(), proto.getStringV()); - break; - case JSON: - entry = new JsonDataEntry(proto.getKey(), proto.getJsonV()); - break; - } - return entry; - } - public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, AlarmInfo alarm) { TbAlarmUpdateProto.Builder builder = TbAlarmUpdateProto.newBuilder(); builder.setEntityType(entityId.getEntityType().name()); diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml index c9fa6d0894..93cd7c665a 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -40,6 +40,10 @@ org.thingsboard.common data + + org.thingsboard.common + proto + org.thingsboard.common message @@ -119,10 +123,6 @@ - - org.xolstice.maven.plugins - protobuf-maven-plugin - org.apache.maven.plugins maven-source-plugin diff --git a/common/pom.xml b/common/pom.xml index 0edd153a3c..0c80189acf 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -35,6 +35,7 @@ data + proto util message actor diff --git a/common/proto/pom.xml b/common/proto/pom.xml new file mode 100644 index 0000000000..ab2cdf91c9 --- /dev/null +++ b/common/proto/pom.xml @@ -0,0 +1,109 @@ + + + 4.0.0 + + org.thingsboard + 3.6.0-SNAPSHOT + common + + org.thingsboard.common + proto + jar + + Thingsboard Server Common Protobuf and gRPC structures + https://thingsboard.io + + + UTF-8 + ${basedir}/../.. + + + + + org.thingsboard.common + data + + + org.thingsboard.common + message + + + com.google.protobuf + protobuf-java + + + com.google.protobuf + protobuf-java-util + + + io.grpc + grpc-netty-shaded + provided + + + io.grpc + grpc-protobuf + provided + + + io.grpc + grpc-stub + provided + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.awaitility + awaitility + test + + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + + + + + thingsboard-repo-deploy + ThingsBoard Repo Deployment + https://repo.thingsboard.io/artifactory/libs-release-public + + + + diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java b/common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java similarity index 100% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java rename to common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java similarity index 100% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java rename to common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java b/common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java similarity index 100% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java rename to common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java b/common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java similarity index 100% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java rename to common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java diff --git a/common/proto/src/main/java/org/thingsboard/server/common/transport/util/KvProtoUtil.java b/common/proto/src/main/java/org/thingsboard/server/common/transport/util/KvProtoUtil.java new file mode 100644 index 0000000000..172d014460 --- /dev/null +++ b/common/proto/src/main/java/org/thingsboard/server/common/transport/util/KvProtoUtil.java @@ -0,0 +1,156 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.util; + +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.gen.transport.TransportProtos; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class KvProtoUtil { + + public static List attrToTsKvProtos(List result) { + List clientAttributes; + if (result == null || result.isEmpty()) { + clientAttributes = Collections.emptyList(); + } else { + clientAttributes = new ArrayList<>(result.size()); + for (AttributeKvEntry attrEntry : result) { + clientAttributes.add(toTsKvProto(attrEntry.getLastUpdateTs(), attrEntry)); + } + } + return clientAttributes; + } + + + public static List tsToTsKvProtos(List result) { + List ts; + if (result == null || result.isEmpty()) { + ts = Collections.emptyList(); + } else { + ts = new ArrayList<>(result.size()); + for (TsKvEntry attrEntry : result) { + ts.add(toTsKvProto(attrEntry.getTs(), attrEntry)); + } + } + return ts; + } + + public static TransportProtos.TsKvProto toTsKvProto(long ts, KvEntry kvEntry) { + return TransportProtos.TsKvProto.newBuilder().setTs(ts) + .setKv(KvProtoUtil.toKeyValueProto(kvEntry)).build(); + } + + public static TransportProtos.KeyValueProto toKeyValueProto(KvEntry kvEntry) { + TransportProtos.KeyValueProto.Builder builder = TransportProtos.KeyValueProto.newBuilder(); + builder.setKey(kvEntry.getKey()); + switch (kvEntry.getDataType()) { + case BOOLEAN: + builder.setType(TransportProtos.KeyValueType.BOOLEAN_V); + builder.setBoolV(kvEntry.getBooleanValue().get()); + break; + case DOUBLE: + builder.setType(TransportProtos.KeyValueType.DOUBLE_V); + builder.setDoubleV(kvEntry.getDoubleValue().get()); + break; + case LONG: + builder.setType(TransportProtos.KeyValueType.LONG_V); + builder.setLongV(kvEntry.getLongValue().get()); + break; + case STRING: + builder.setType(TransportProtos.KeyValueType.STRING_V); + builder.setStringV(kvEntry.getStrValue().get()); + break; + case JSON: + builder.setType(TransportProtos.KeyValueType.JSON_V); + builder.setJsonV(kvEntry.getJsonValue().get()); + break; + } + return builder.build(); + } + + public static TransportProtos.TsKvProto.Builder toKeyValueProto(long ts, KvEntry attr) { + TransportProtos.KeyValueProto.Builder dataBuilder = TransportProtos.KeyValueProto.newBuilder(); + dataBuilder.setKey(attr.getKey()); + dataBuilder.setType(TransportProtos.KeyValueType.forNumber(attr.getDataType().ordinal())); + switch (attr.getDataType()) { + case BOOLEAN: + attr.getBooleanValue().ifPresent(dataBuilder::setBoolV); + break; + case LONG: + attr.getLongValue().ifPresent(dataBuilder::setLongV); + break; + case DOUBLE: + attr.getDoubleValue().ifPresent(dataBuilder::setDoubleV); + break; + case JSON: + attr.getJsonValue().ifPresent(dataBuilder::setJsonV); + break; + case STRING: + attr.getStrValue().ifPresent(dataBuilder::setStringV); + break; + } + return TransportProtos.TsKvProto.newBuilder().setTs(ts).setKv(dataBuilder); + } + + public static List toTsKvEntityList(List dataList) { + List result = new ArrayList<>(dataList.size()); + dataList.forEach(proto -> result.add(new BasicTsKvEntry(proto.getTs(), getKvEntry(proto.getKv())))); + return result; + } + + public static List toAttributeKvList(List dataList) { + List result = new ArrayList<>(dataList.size()); + dataList.forEach(proto -> result.add(new BaseAttributeKvEntry(getKvEntry(proto.getKv()), proto.getTs()))); + return result; + } + + private static KvEntry getKvEntry(TransportProtos.KeyValueProto proto) { + KvEntry entry = null; + DataType type = DataType.values()[proto.getType().getNumber()]; + switch (type) { + case BOOLEAN: + entry = new BooleanDataEntry(proto.getKey(), proto.getBoolV()); + break; + case LONG: + entry = new LongDataEntry(proto.getKey(), proto.getLongV()); + break; + case DOUBLE: + entry = new DoubleDataEntry(proto.getKey(), proto.getDoubleV()); + break; + case STRING: + entry = new StringDataEntry(proto.getKey(), proto.getStringV()); + break; + case JSON: + entry = new JsonDataEntry(proto.getKey(), proto.getJsonV()); + break; + } + return entry; + } +} diff --git a/common/cluster-api/src/main/proto/jsinvoke.proto b/common/proto/src/main/proto/jsinvoke.proto similarity index 100% rename from common/cluster-api/src/main/proto/jsinvoke.proto rename to common/proto/src/main/proto/jsinvoke.proto diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto similarity index 100% rename from common/cluster-api/src/main/proto/queue.proto rename to common/proto/src/main/proto/queue.proto diff --git a/common/transport/transport-api/src/main/proto/transport.proto b/common/proto/src/main/proto/transport.proto similarity index 100% rename from common/transport/transport-api/src/main/proto/transport.proto rename to common/proto/src/main/proto/transport.proto diff --git a/common/queue/pom.xml b/common/queue/pom.xml index edd4574bdd..59e96d210d 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -36,6 +36,10 @@ + + org.thingsboard.common + proto + org.thingsboard.common data diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 2e2174e0a9..b45cd28e52 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -135,13 +135,4 @@ - - - - org.xolstice.maven.plugins - protobuf-maven-plugin - - - - diff --git a/pom.xml b/pom.xml index 9b11160fab..c778ebcaab 100755 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,8 @@ 2016 + 17 + 17 ${basedir} true none @@ -892,6 +894,11 @@ version-control ${project.version} + + org.thingsboard.common + proto + ${project.version} + org.thingsboard.common cache From 4c470583f3467fc7d4a524e95dfa33cfe99a77a4 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 2 Jun 2023 16:31:38 +0300 Subject: [PATCH 010/209] Correct package name --- .../actors/device/DeviceActorMessageProcessor.java | 2 +- .../server/controller/TelemetryController.java | 2 +- .../rpc/constructor/EntityDataMsgConstructor.java | 2 +- .../telemetry/BaseTelemetryProcessor.java | 2 +- .../queue/DefaultTbCoreConsumerService.java | 2 +- .../service/subscription/TbSubscriptionUtils.java | 14 +------------- .../importing/csv/AbstractBulkImportService.java | 2 +- .../thingsboard/server/edge/DeviceEdgeTest.java | 2 +- .../{transport => }/adaptor/AdaptorException.java | 2 +- .../{transport => }/adaptor/JsonConverter.java | 2 +- .../adaptor/JsonConverterConfig.java | 2 +- .../{transport => }/adaptor/ProtoConverter.java | 2 +- .../common/{transport => }/util/KvProtoUtil.java | 3 +-- .../transport/coap/CoapTransportResource.java | 4 ++-- .../transport/coap/adaptors/CoapAdaptorUtils.java | 2 +- .../coap/adaptors/CoapTransportAdaptor.java | 2 +- .../transport/coap/adaptors/JsonCoapAdaptor.java | 4 ++-- .../transport/coap/adaptors/ProtoCoapAdaptor.java | 6 +++--- .../callback/GetAttributesSyncSessionCallback.java | 2 +- .../callback/ToServerRpcSyncSessionCallback.java | 2 +- .../transport/coap/client/CoapClientContext.java | 2 +- .../coap/client/DefaultCoapClientContext.java | 2 +- .../coap/efento/CoapEfentoTransportResource.java | 2 +- .../coap/efento/adaptor/EfentoCoapAdaptor.java | 4 ++-- .../server/transport/http/DeviceApiController.java | 2 +- .../lwm2m/server/adaptors/LwM2MJsonAdaptor.java | 4 ++-- .../server/adaptors/LwM2MTransportAdaptor.java | 2 +- .../transport/mqtt/MqttTransportHandler.java | 2 +- .../adaptors/BackwardCompatibilityAdaptor.java | 2 +- .../transport/mqtt/adaptors/JsonMqttAdaptor.java | 4 ++-- .../mqtt/adaptors/MqttTransportAdaptor.java | 2 +- .../transport/mqtt/adaptors/ProtoMqttAdaptor.java | 6 +++--- .../session/AbstractGatewaySessionHandler.java | 6 +++--- .../mqtt/session/GatewaySessionHandler.java | 2 +- .../mqtt/session/SparkplugNodeSessionHandler.java | 6 +++--- .../mqtt/util/sparkplug/MetricDataType.java | 2 +- .../snmp/service/SnmpTransportService.java | 2 +- .../src/test/java/JsonConverterTest.java | 2 +- .../action/TbCopyAttributesToEntityViewNode.java | 2 +- .../rule/engine/profile/AlarmRuleState.java | 2 +- .../rule/engine/profile/DeviceState.java | 2 +- .../rule/engine/telemetry/TbMsgAttributesNode.java | 2 +- .../rule/engine/telemetry/TbMsgTimeseriesNode.java | 2 +- 43 files changed, 56 insertions(+), 69 deletions(-) rename common/proto/src/main/java/org/thingsboard/server/common/{transport => }/adaptor/AdaptorException.java (94%) rename common/proto/src/main/java/org/thingsboard/server/common/{transport => }/adaptor/JsonConverter.java (99%) rename common/proto/src/main/java/org/thingsboard/server/common/{transport => }/adaptor/JsonConverterConfig.java (96%) rename common/proto/src/main/java/org/thingsboard/server/common/{transport => }/adaptor/ProtoConverter.java (99%) rename common/proto/src/main/java/org/thingsboard/server/common/{transport => }/util/KvProtoUtil.java (98%) diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index 1831c6ab9c..fc8eaf9e1c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -63,7 +63,7 @@ import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; -import org.thingsboard.server.common.transport.util.KvProtoUtil; +import org.thingsboard.server.common.util.KvProtoUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 05f56e93bd..670a65f862 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -74,7 +74,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.exception.InvalidParametersException; import org.thingsboard.server.exception.UncheckedApiException; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java index 242a15ba56..0cef8f3581 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg; import org.thingsboard.server.gen.edge.v1.EntityDataProto; import org.thingsboard.server.gen.transport.TransportProtos; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index fece86b92a..ebdf1b6c57 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -55,7 +55,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index e11b6cdd5b..36799e7fac 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -40,7 +40,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.stats.StatsFactory; -import org.thingsboard.server.common.transport.util.KvProtoUtil; +import org.thingsboard.server.common.util.KvProtoUtil; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java index 7bba9a429a..2f1dca9333 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java @@ -22,20 +22,9 @@ import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; -import org.thingsboard.server.common.data.kv.BasicTsKvEntry; -import org.thingsboard.server.common.data.kv.BooleanDataEntry; -import org.thingsboard.server.common.data.kv.DataType; -import org.thingsboard.server.common.data.kv.DoubleDataEntry; -import org.thingsboard.server.common.data.kv.JsonDataEntry; -import org.thingsboard.server.common.data.kv.KvEntry; -import org.thingsboard.server.common.data.kv.LongDataEntry; -import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.common.transport.util.KvProtoUtil; +import org.thingsboard.server.common.util.KvProtoUtil; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; -import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto; @@ -52,7 +41,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesSubscrip import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationsCountSubscription; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java index 45805c5939..bc71505631 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java @@ -46,7 +46,7 @@ import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumn import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest; import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.controller.BaseController; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.action.EntityActionService; diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java index 15d5b5c809..d3a8083b4a 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java @@ -51,7 +51,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.msg.session.FeatureType; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg; diff --git a/common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/AdaptorException.java similarity index 94% rename from common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java rename to common/proto/src/main/java/org/thingsboard/server/common/adaptor/AdaptorException.java index 8b2908a4b5..1c22163c1f 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/AdaptorException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.adaptor; +package org.thingsboard.server.common.adaptor; public class AdaptorException extends Exception { diff --git a/common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java similarity index 99% rename from common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java rename to common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java index 51e74d5097..c51bc784a6 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.adaptor; +package org.thingsboard.server.common.adaptor; import com.google.gson.Gson; import com.google.gson.JsonArray; diff --git a/common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverterConfig.java similarity index 96% rename from common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java rename to common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverterConfig.java index 0a9df150e5..e160c051f5 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverterConfig.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.adaptor; +package org.thingsboard.server.common.adaptor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; diff --git a/common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/ProtoConverter.java similarity index 99% rename from common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java rename to common/proto/src/main/java/org/thingsboard/server/common/adaptor/ProtoConverter.java index 54358709f0..098108c864 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/ProtoConverter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.adaptor; +package org.thingsboard.server.common.adaptor; import com.google.gson.Gson; import com.google.gson.JsonElement; diff --git a/common/proto/src/main/java/org/thingsboard/server/common/transport/util/KvProtoUtil.java b/common/proto/src/main/java/org/thingsboard/server/common/util/KvProtoUtil.java similarity index 98% rename from common/proto/src/main/java/org/thingsboard/server/common/transport/util/KvProtoUtil.java rename to common/proto/src/main/java/org/thingsboard/server/common/util/KvProtoUtil.java index 172d014460..63ef81d35f 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/transport/util/KvProtoUtil.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/util/KvProtoUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.util; +package org.thingsboard.server.common.util; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; @@ -28,7 +28,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.gen.transport.TransportProtos; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 7dde25bfd0..96f4cfecac 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -35,8 +35,8 @@ import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.coap.callback.CoapDeviceAuthCallback; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java index 710f18a8b7..629e0380e0 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.coap.adaptors; import org.eclipse.californium.core.coap.Request; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Arrays; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java index 05a38e1ad1..e88251c93c 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java @@ -19,7 +19,7 @@ import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java index d81ff32edd..fffe1a7e1a 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java @@ -29,8 +29,8 @@ import org.eclipse.californium.core.coap.Response; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.coap.CoapTransportResource; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java index 4d3b0015ef..5287645591 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java @@ -28,9 +28,9 @@ import org.eclipse.californium.core.coap.Response; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.adaptor.ProtoConverter; +import org.thingsboard.server.common.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.ProtoConverter; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.coap.CoapTransportResource; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/GetAttributesSyncSessionCallback.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/GetAttributesSyncSessionCallback.java index 907e49f600..caacd31012 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/GetAttributesSyncSessionCallback.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/GetAttributesSyncSessionCallback.java @@ -20,7 +20,7 @@ import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.server.resources.CoapExchange; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.coap.client.TbCoapClientState; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/ToServerRpcSyncSessionCallback.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/ToServerRpcSyncSessionCallback.java index 2209ea6613..fc35da0bd6 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/ToServerRpcSyncSessionCallback.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/ToServerRpcSyncSessionCallback.java @@ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.server.resources.CoapExchange; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.coap.client.TbCoapClientState; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/CoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/CoapClientContext.java index 36ef8472de..0ca21fd8cb 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/CoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/CoapClientContext.java @@ -19,7 +19,7 @@ import org.eclipse.californium.core.observe.ObserveRelation; import org.eclipse.californium.core.server.resources.CoapExchange; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java index 3caaba0ba9..95f1698dc3 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java @@ -53,7 +53,7 @@ import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java index 93061d522a..5a59ab0f51 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/CoapEfentoTransportResource.java @@ -31,7 +31,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.EfentoCoapDeviceTypeConfiguration; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.coap.MeasurementTypeProtos; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/adaptor/EfentoCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/adaptor/EfentoCoapAdaptor.java index a6b588139a..42eebe86f1 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/adaptor/EfentoCoapAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/efento/adaptor/EfentoCoapAdaptor.java @@ -18,8 +18,8 @@ package org.thingsboard.server.transport.coap.efento.adaptor; import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource; diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index 041ae0510d..589e93d803 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -46,7 +46,7 @@ import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java index feb74b47f9..afe1d160ce 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java @@ -19,8 +19,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonSyntaxException; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java index 96e6008c63..fe24b21053 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.adaptors; import com.google.gson.JsonElement; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Collection; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index efa5aee965..8347769bd7 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -63,7 +63,7 @@ import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/BackwardCompatibilityAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/BackwardCompatibilityAdaptor.java index 40be642a1a..73bba60cff 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/BackwardCompatibilityAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/BackwardCompatibilityAdaptor.java @@ -21,7 +21,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index 513fd97cf6..a30749b17d 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -30,8 +30,8 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java index 3764c2a9a9..10599d0be7 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java @@ -24,7 +24,7 @@ import io.netty.handler.codec.mqtt.MqttMessageType; import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index 39176fa212..c1fba86776 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -28,9 +28,9 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.adaptor.ProtoConverter; +import org.thingsboard.server.common.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.ProtoConverter; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java index 95603e751c..9a7e6c5177 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java @@ -39,9 +39,9 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.adaptor.ProtoConverter; +import org.thingsboard.server.common.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.ProtoConverter; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.gen.transport.TransportApiProtos; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java index de0f2a502b..6ff505cb36 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.mqtt.session; import io.netty.buffer.ByteBuf; import io.netty.handler.codec.mqtt.MqttPublishMessage; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import java.util.UUID; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java index 48998d58cd..f3aaa2708f 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java @@ -32,9 +32,9 @@ import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.adaptor.ProtoConverter; +import org.thingsboard.server.common.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.ProtoConverter; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/MetricDataType.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/MetricDataType.java index d5e781a402..c49286a4b1 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/MetricDataType.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/sparkplug/MetricDataType.java @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.mqtt.util.sparkplug; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.mqtt.SparkplugBProto; import java.math.BigInteger; diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 6e68a0bd44..51ed8d0485 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -46,7 +46,7 @@ import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryin import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; diff --git a/common/transport/transport-api/src/test/java/JsonConverterTest.java b/common/transport/transport-api/src/test/java/JsonConverterTest.java index 39ed04a29a..1062199caf 100644 --- a/common/transport/transport-api/src/test/java/JsonConverterTest.java +++ b/common/transport/transport-api/src/test/java/JsonConverterTest.java @@ -19,7 +19,7 @@ import org.junit.Assert; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import java.util.ArrayList; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 6f7bb7cf8d..9196561ed5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -37,7 +37,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.util.CollectionsUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import javax.annotation.Nullable; import java.util.ArrayList; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java index f31896a8c9..6bb37438a1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java @@ -41,7 +41,7 @@ import org.thingsboard.server.common.data.query.KeyFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.StringFilterPredicate; import org.thingsboard.server.common.msg.tools.SchedulerUtils; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import java.time.Instant; import java.time.ZoneId; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index 31f661417b..feedfaae60 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -41,7 +41,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.rule.RuleNodeState; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.dao.sql.query.EntityKeyMapping; import java.util.ArrayList; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 9cf9bfa1bd..5baba50565 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import java.util.ArrayList; import java.util.List; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java index dcc84813cd..b94df4268c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.adaptor.JsonConverter; import java.util.ArrayList; import java.util.List; From db6f3102849a259090789ccce7c60befc033fbd8 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 5 Jun 2023 15:24:47 +0200 Subject: [PATCH 011/209] migration to spring boot 3.1 --- application/pom.xml | 11 +- .../server/actors/ActorSystemContext.java | 6 +- .../device/DeviceActorMessageProcessor.java | 2 +- .../actors/service/DefaultActorService.java | 4 +- ...tomOAuth2AuthorizationRequestResolver.java | 5 +- .../config/RateLimitProcessingFilter.java | 10 +- .../server/config/SwaggerConfiguration.java | 2 +- .../ThingsboardSecurityConfiguration.java | 82 +++--- .../thingsboard/server/config/WebConfig.java | 4 +- .../controller/AbstractRpcController.java | 2 +- .../server/controller/AuthController.java | 2 +- .../server/controller/BaseController.java | 8 +- .../server/controller/DeviceController.java | 2 +- .../server/controller/EdgeController.java | 2 +- .../controller/NotificationController.java | 2 +- .../NotificationRuleController.java | 2 +- .../NotificationTargetController.java | 2 +- .../NotificationTemplateController.java | 2 +- .../server/controller/OAuth2Controller.java | 2 +- .../server/controller/RpcV2Controller.java | 2 +- .../controller/SystemInfoController.java | 2 +- .../controller/TelemetryController.java | 6 +- .../TwoFactorAuthConfigController.java | 2 +- .../controller/TwoFactorAuthController.java | 2 +- .../server/controller/UserController.java | 2 +- .../controller/plugin/TbWebSocketHandler.java | 8 +- .../ThingsboardErrorResponseHandler.java | 16 +- .../DefaultTbApiUsageStateService.java | 4 +- .../limits/DefaultRateLimitService.java | 2 +- .../AnnotationComponentDiscoveryService.java | 2 +- .../device/ClaimDevicesServiceImpl.java | 2 +- .../edge/DefaultEdgeNotificationService.java | 4 +- .../DefaultEdgeInstallService.java | 2 +- .../edge/instructions/EdgeInstallService.java | 2 +- .../service/edge/rpc/EdgeGrpcService.java | 6 +- .../telemetry/BaseTelemetryProcessor.java | 4 +- .../DefaultTbEntityViewService.java | 2 +- .../entitiy/user/DefaultUserService.java | 2 +- .../service/entitiy/user/TbUserService.java | 2 +- .../SharedEventLoopGroupService.java | 4 +- .../DefaultSystemDataLoaderService.java | 6 +- .../TbRuleEngineQueueConfigService.java | 2 +- .../service/mail/DefaultMailService.java | 5 +- .../DefaultNotificationSchedulerService.java | 2 +- .../DefaultNotificationRuleProcessor.java | 2 +- .../cache/DefaultNotificationRulesCache.java | 2 +- .../ota/DefaultOtaPackageStateService.java | 2 +- .../queue/DefaultTbCoreConsumerService.java | 4 +- .../DefaultTbRuleEngineConsumerService.java | 5 +- .../processing/AbstractConsumerService.java | 2 +- .../rpc/DefaultTbCoreDeviceRpcService.java | 5 +- .../rpc/DefaultTbRuleEngineRpcService.java | 4 +- .../service/security/AccessValidator.java | 6 +- ...wtTokenAuthenticationProcessingFilter.java | 8 +- .../jwt/RefreshTokenProcessingFilter.java | 8 +- .../auth/jwt/SkipPathRequestMatcher.java | 2 +- .../extractor/JwtHeaderTokenExtractor.java | 2 +- .../jwt/extractor/JwtQueryTokenExtractor.java | 2 +- .../auth/jwt/extractor/TokenExtractor.java | 4 +- .../auth/oauth2/AppleOAuth2ClientMapper.java | 2 +- .../auth/oauth2/BasicOAuth2ClientMapper.java | 2 +- .../security/auth/oauth2/CookieUtils.java | 6 +- .../auth/oauth2/CustomOAuth2ClientMapper.java | 2 +- .../auth/oauth2/GithubOAuth2ClientMapper.java | 2 +- ...eOAuth2AuthorizationRequestRepository.java | 7 +- .../auth/oauth2/OAuth2ClientMapper.java | 2 +- .../Oauth2AuthenticationFailureHandler.java | 6 +- .../Oauth2AuthenticationSuccessHandler.java | 6 +- .../auth/rest/RestAuthenticationDetails.java | 2 +- .../rest/RestAuthenticationDetailsSource.java | 2 +- ...RestAwareAuthenticationFailureHandler.java | 6 +- ...RestAwareAuthenticationSuccessHandler.java | 8 +- .../auth/rest/RestLoginProcessingFilter.java | 8 +- .../rest/RestPublicLoginProcessingFilter.java | 8 +- .../system/DefaultSystemSecurityService.java | 4 +- .../system/SystemSecurityService.java | 2 +- .../server/service/sms/DefaultSmsService.java | 5 +- .../state/DefaultDeviceStateService.java | 11 +- .../service/stats/DefaultJsInvokeStats.java | 2 +- .../DefaultRuleEngineStatisticsService.java | 2 +- .../DefaultSubscriptionManagerService.java | 4 +- ...efaultTbEntityDataSubscriptionService.java | 4 +- .../DefaultTbLocalSubscriptionService.java | 4 +- .../csv/AbstractBulkImportService.java | 6 +- .../DefaultEntitiesVersionControlService.java | 4 +- .../system/DefaultSystemInfoService.java | 4 +- .../AbstractSubscriptionService.java | 6 +- .../DefaultTelemetrySubscriptionService.java | 6 +- .../transport/TbCoreTransportApiService.java | 4 +- .../service/update/DefaultUpdateService.java | 2 +- .../service/ws/DefaultWebSocketService.java | 6 +- .../thingsboard/server/utils/MiscUtils.java | 2 +- .../src/main/resources/thingsboard.yml | 2 +- .../controller/AbstractControllerTest.java | 2 +- .../server/edge/DeviceEdgeTest.java | 2 +- .../lwm2m/AbstractLwM2MIntegrationTest.java | 10 +- .../security/sql/PskLwm2mIntegrationTest.java | 2 +- .../security/sql/RpkLwM2MIntegrationTest.java | 2 +- .../sql/X509_NoTrustLwM2MIntegrationTest.java | 2 +- common/cache/pom.xml | 4 +- .../server/cache/CacheSpecsMap.java | 2 +- .../cache/RedisTbTransactionalCache.java | 3 +- common/cluster-api/pom.xml | 4 +- .../coapserver/DefaultCoapServerService.java | 4 +- common/dao-api/pom.xml | 4 +- .../cassandra/AbstractCassandraCluster.java | 2 +- .../dao/cassandra/CassandraCluster.java | 2 +- .../dao/cassandra/CassandraDriverOptions.java | 2 +- .../cassandra/CassandraInstallCluster.java | 2 +- common/data/pom.xml | 4 +- .../server/common/data/DashboardInfo.java | 2 +- .../server/common/data/DeviceProfile.java | 2 +- .../AlarmCreateOrUpdateActiveRequest.java | 4 +- .../common/data/alarm/AlarmUpdateRequest.java | 4 +- .../data/device/profile/AlarmCondition.java | 2 +- .../device/profile/AlarmConditionFilter.java | 2 +- .../common/data/device/profile/AlarmRule.java | 2 +- .../device/profile/DeviceProfileAlarm.java | 2 +- .../device/profile/DeviceProfileData.java | 2 +- .../common/data/kv/BaseAttributeKvEntry.java | 2 +- .../server/common/data/kv/BasicTsKvEntry.java | 2 +- .../notification/NotificationRequest.java | 4 +- .../NotificationRequestConfig.java | 2 +- ...faultNotificationRuleRecipientsConfig.java | 2 +- ...latedNotificationRuleRecipientsConfig.java | 2 +- .../notification/rule/NotificationRule.java | 8 +- .../NotificationRuleRecipientsConfig.java | 2 +- ...signmentNotificationRuleTriggerConfig.java | 2 +- .../AlarmNotificationRuleTriggerConfig.java | 2 +- ...ActivityNotificationRuleTriggerConfig.java | 2 +- ...iesLimitNotificationRuleTriggerConfig.java | 2 +- .../settings/NotificationSettings.java | 4 +- ...SlackNotificationDeliveryMethodConfig.java | 2 +- .../targets/NotificationTarget.java | 6 +- .../targets/platform/CustomerUsersFilter.java | 2 +- ...PlatformUsersNotificationTargetConfig.java | 4 +- .../targets/platform/UserListFilter.java | 2 +- .../targets/slack/SlackConversation.java | 4 +- .../slack/SlackNotificationTargetConfig.java | 4 +- .../DeliveryMethodNotificationTemplate.java | 2 +- ...ailDeliveryMethodNotificationTemplate.java | 2 +- .../template/NotificationTemplate.java | 6 +- .../template/NotificationTemplateConfig.java | 4 +- ...WebDeliveryMethodNotificationTemplate.java | 2 +- .../OAuth2ClientRegistrationTemplate.java | 2 +- .../data/oauth2/OAuth2MapperConfig.java | 2 +- .../data/query/FilterPredicateValue.java | 2 +- .../data/query/StringFilterPredicate.java | 2 +- .../model/mfa/PlatformTwoFaSettings.java | 8 +- .../account/BackupCodeTwoFaAccountConfig.java | 2 +- .../mfa/account/EmailTwoFaAccountConfig.java | 4 +- .../mfa/account/SmsTwoFaAccountConfig.java | 4 +- .../mfa/account/TotpTwoFaAccountConfig.java | 4 +- .../BackupCodeTwoFaProviderConfig.java | 2 +- .../provider/OtpBasedTwoFaProviderConfig.java | 2 +- .../mfa/provider/SmsTwoFaProviderConfig.java | 4 +- .../mfa/provider/TotpTwoFaProviderConfig.java | 2 +- .../server/common/data/validation/Length.java | 4 +- .../server/common/data/validation/NoXss.java | 4 +- .../servicebus/TbServiceBusQueueConfigs.java | 2 +- .../AbstractTbQueueConsumerTemplate.java | 2 +- .../common/DefaultTbQueueRequestTemplate.java | 2 +- .../DefaultTbServiceInfoProvider.java | 2 +- .../queue/discovery/HashPartitionService.java | 4 +- .../queue/discovery/ZkDiscoveryService.java | 4 +- .../environment/EnvironmentLogService.java | 2 +- .../kafka/TbKafkaConsumerStatsService.java | 4 +- .../queue/kafka/TbKafkaTopicConfigs.java | 2 +- .../provider/AwsSqsMonolithQueueFactory.java | 2 +- .../provider/AwsSqsTbCoreQueueFactory.java | 2 +- .../AwsSqsTbRuleEngineQueueFactory.java | 2 +- .../AwsSqsTbVersionControlQueueFactory.java | 2 +- .../provider/AwsSqsTransportQueueFactory.java | 2 +- .../provider/KafkaMonolithQueueFactory.java | 2 +- .../provider/KafkaTbCoreQueueFactory.java | 2 +- .../KafkaTbRuleEngineQueueFactory.java | 2 +- .../KafkaTbTransportQueueFactory.java | 2 +- .../KafkaTbVersionControlQueueFactory.java | 2 +- .../provider/PubSubMonolithQueueFactory.java | 2 +- .../provider/PubSubTbCoreQueueFactory.java | 2 +- .../PubSubTbRuleEngineQueueFactory.java | 2 +- .../PubSubTbVersionControlQueueFactory.java | 2 +- .../provider/PubSubTransportQueueFactory.java | 2 +- .../RabbitMqMonolithQueueFactory.java | 2 +- .../provider/RabbitMqTbCoreQueueFactory.java | 2 +- .../RabbitMqTbRuleEngineQueueFactory.java | 2 +- .../RabbitMqTbVersionControlQueueFactory.java | 2 +- .../RabbitMqTransportQueueFactory.java | 2 +- .../ServiceBusMonolithQueueFactory.java | 2 +- .../ServiceBusTbCoreQueueFactory.java | 2 +- .../ServiceBusTbRuleEngineQueueFactory.java | 2 +- ...erviceBusTbVersionControlQueueFactory.java | 2 +- .../ServiceBusTransportQueueFactory.java | 2 +- .../provider/TbCoreQueueProducerProvider.java | 2 +- .../TbRuleEngineProducerProvider.java | 2 +- .../TbTransportQueueProducerProvider.java | 2 +- .../TbVersionControlProducerProvider.java | 2 +- .../server/queue/pubsub/TbPubSubSettings.java | 2 +- .../pubsub/TbPubSubSubscriptionSettings.java | 2 +- .../rabbitmq/TbRabbitMqQueueArguments.java | 2 +- .../queue/rabbitmq/TbRabbitMqSettings.java | 2 +- .../scheduler/DefaultSchedulerComponent.java | 4 +- .../queue/sqs/TbAwsSqsQueueAttributes.java | 2 +- .../DefaultTbApiUsageReportClient.java | 2 +- .../service/script/RemoteJsInvokeService.java | 4 +- .../script/api/ScriptStatCallback.java | 2 +- .../script/api/js/NashornJsInvokeService.java | 4 +- .../api/tbel/DefaultTbelInvokeService.java | 4 +- .../common/stats/DefaultStatsFactory.java | 2 +- .../transport/coap/CoapTransportService.java | 4 +- .../transport/http/DeviceApiController.java | 2 +- .../LwM2MTransportBootstrapService.java | 4 +- ...LwM2MDtlsBootstrapCertificateVerifier.java | 2 +- .../TbLwM2MDtlsCertificateVerifier.java | 2 +- .../server/DefaultLwM2mTransportService.java | 2 +- .../server/LwM2mTransportServerHelper.java | 4 +- .../DefaultLwM2mDownlinkMsgHandler.java | 4 +- .../model/LwM2MModelConfigServiceImpl.java | 2 +- .../ota/DefaultLwM2MOtaUpdateService.java | 4 +- .../uplink/DefaultLwM2mUplinkMsgHandler.java | 4 +- .../transport/mqtt/MqttTransportContext.java | 2 +- .../transport/mqtt/MqttTransportService.java | 4 +- .../AbstractGatewaySessionHandler.java | 2 +- .../session/SparkplugNodeSessionHandler.java | 2 +- .../snmp/service/SnmpTransportService.java | 4 +- .../common/transport/TransportContext.java | 4 +- .../config/ssl/SslCredentialsConfig.java | 2 +- .../service/DefaultTransportService.java | 4 +- common/util/pom.xml | 4 +- .../util/AbstractListeningExecutor.java | 4 +- common/version-control/pom.xml | 4 +- .../DefaultClusterVersionControlService.java | 4 +- .../sync/vc/DefaultGitRepositoryService.java | 2 +- dao/pom.xml | 8 +- .../server/dao/asset/AssetTypeFilter.java | 2 +- .../attributes/CachedAttributesService.java | 2 +- .../audit/sink/ElasticsearchAuditLogSink.java | 2 +- .../server/dao/edge/EdgeServiceImpl.java | 2 +- .../dao/entityview/EntityViewServiceImpl.java | 2 +- .../server/dao/model/BaseSqlEntity.java | 6 +- .../model/sql/AbstractAlarmCommentEntity.java | 13 +- .../dao/model/sql/AbstractAlarmEntity.java | 17 +- .../dao/model/sql/AbstractAssetEntity.java | 12 +- .../dao/model/sql/AbstractDeviceEntity.java | 22 +- .../dao/model/sql/AbstractEdgeEntity.java | 12 +- .../model/sql/AbstractEntityViewEntity.java | 17 +- .../dao/model/sql/AbstractTenantEntity.java | 12 +- .../dao/model/sql/AbstractTsKvEntity.java | 9 +- .../model/sql/AbstractWidgetTypeEntity.java | 4 +- .../dao/model/sql/AdminSettingsEntity.java | 16 +- .../dao/model/sql/AlarmCommentEntity.java | 11 +- .../server/dao/model/sql/AlarmEntity.java | 8 +- .../server/dao/model/sql/AlarmInfoEntity.java | 6 +- .../dao/model/sql/ApiUsageStateEntity.java | 13 +- .../server/dao/model/sql/AssetEntity.java | 8 +- .../dao/model/sql/AssetProfileEntity.java | 6 +- .../model/sql/AttributeKvCompositeKey.java | 8 +- .../dao/model/sql/AttributeKvEntity.java | 8 +- .../server/dao/model/sql/AuditLogEntity.java | 18 +- .../model/sql/ComponentDescriptorEntity.java | 19 +- .../server/dao/model/sql/CustomerEntity.java | 22 +- .../server/dao/model/sql/DashboardEntity.java | 16 +- .../dao/model/sql/DashboardInfoEntity.java | 9 +- .../model/sql/DeviceCredentialsEntity.java | 10 +- .../server/dao/model/sql/DeviceEntity.java | 13 +- .../dao/model/sql/DeviceInfoEntity.java | 6 +- .../dao/model/sql/DeviceProfileEntity.java | 21 +- .../server/dao/model/sql/EdgeEntity.java | 8 +- .../server/dao/model/sql/EdgeEventEntity.java | 20 +- .../model/sql/EntityAlarmCompositeKey.java | 2 +- .../dao/model/sql/EntityAlarmEntity.java | 10 +- .../dao/model/sql/EntityViewEntity.java | 8 +- .../dao/model/sql/ErrorEventEntity.java | 6 +- .../server/dao/model/sql/EventEntity.java | 6 +- .../dao/model/sql/LifecycleEventEntity.java | 6 +- .../dao/model/sql/NotificationEntity.java | 20 +- .../model/sql/NotificationRequestEntity.java | 24 +- .../dao/model/sql/NotificationRuleEntity.java | 25 +- .../model/sql/NotificationTargetEntity.java | 14 +- .../model/sql/NotificationTemplateEntity.java | 18 +- ...Auth2ClientRegistrationTemplateEntity.java | 20 +- .../dao/model/sql/OAuth2DomainEntity.java | 10 +- .../dao/model/sql/OAuth2MobileEntity.java | 6 +- .../dao/model/sql/OAuth2ParamsEntity.java | 6 +- .../model/sql/OAuth2RegistrationEntity.java | 18 +- .../dao/model/sql/OtaPackageEntity.java | 22 +- .../dao/model/sql/OtaPackageInfoEntity.java | 22 +- .../server/dao/model/sql/QueueEntity.java | 18 +- .../dao/model/sql/RelationCompositeKey.java | 2 +- .../server/dao/model/sql/RelationEntity.java | 20 +- .../server/dao/model/sql/RpcEntity.java | 22 +- .../model/sql/RuleChainDebugEventEntity.java | 6 +- .../server/dao/model/sql/RuleChainEntity.java | 20 +- .../model/sql/RuleNodeDebugEventEntity.java | 6 +- .../server/dao/model/sql/RuleNodeEntity.java | 16 +- .../dao/model/sql/RuleNodeStateEntity.java | 9 +- .../dao/model/sql/StatisticsEventEntity.java | 6 +- .../dao/model/sql/TbResourceEntity.java | 6 +- .../dao/model/sql/TbResourceInfoEntity.java | 6 +- .../server/dao/model/sql/TenantEntity.java | 8 +- .../dao/model/sql/TenantProfileEntity.java | 20 +- .../dao/model/sql/UserAuthSettingsEntity.java | 14 +- .../dao/model/sql/UserCredentialsEntity.java | 12 +- .../server/dao/model/sql/UserEntity.java | 18 +- .../dao/model/sql/UserSettingsEntity.java | 18 +- .../model/sql/WidgetTypeDetailsEntity.java | 15 +- .../dao/model/sql/WidgetTypeEntity.java | 15 +- .../dao/model/sql/WidgetsBundleEntity.java | 6 +- .../sqlts/dictionary/TsKvDictionary.java | 16 +- .../TsKvDictionaryCompositeKey.java | 2 +- .../sqlts/latest/TsKvLatestCompositeKey.java | 2 +- .../model/sqlts/latest/TsKvLatestEntity.java | 18 +- .../ts/TimescaleTsKvCompositeKey.java | 2 +- .../timescale/ts/TimescaleTsKvEntity.java | 19 +- .../dao/model/sqlts/ts/TsKvCompositeKey.java | 2 +- .../server/dao/model/sqlts/ts/TsKvEntity.java | 7 +- .../dao/nosql/CassandraAbstractAsyncDao.java | 6 +- .../CassandraBufferedRateReadExecutor.java | 2 +- .../CassandraBufferedRateWriteExecutor.java | 2 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 2 +- .../dao/relation/BaseRelationService.java | 4 +- .../dao/service/ConstraintValidator.java | 12 +- .../server/dao/service/NoXssValidator.java | 4 +- .../dao/service/StringLengthValidator.java | 4 +- .../sql/ScheduledLogExecutorComponent.java | 4 +- .../server/dao/sql/TbSqlBlockingQueue.java | 4 +- .../server/dao/sql/alarm/AlarmRepository.java | 88 ++++--- .../dao/sql/attributes/JpaAttributeDao.java | 12 +- .../dao/sql/audit/AuditLogRepository.java | 12 +- ...ctComponentDescriptorInsertRepository.java | 6 +- .../dao/sql/device/DeviceRepository.java | 2 +- .../dao/sql/edge/JpaBaseEdgeEventDao.java | 4 +- .../dao/sql/event/EventInsertRepository.java | 2 +- .../event/EventPartitionConfiguration.java | 2 +- .../server/dao/sql/event/JpaBaseEventDao.java | 4 +- .../server/dao/sql/query/QueryContext.java | 8 +- .../relation/SqlRelationInsertRepository.java | 6 +- ...stractChunkedAggregationTimeseriesDao.java | 4 +- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 2 +- .../sqlts/BaseAbstractSqlTimeseriesDao.java | 10 +- .../dao/sqlts/SqlTimeseriesLatestDao.java | 4 +- .../latest/SearchTsKvLatestRepository.java | 4 +- .../timescale/AggregationRepository.java | 4 +- .../timescale/TimescaleTimeseriesDao.java | 4 +- .../server/dao/sqlts/ts/TsKvRepository.java | 60 ++--- .../AggregatePartitionsFunction.java | 2 +- .../CassandraBaseTimeseriesDao.java | 6 +- .../util/AbstractBufferedRateExecutor.java | 2 +- .../AbstractJsonSqlTypeDescriptor.java | 78 ------ .../mapping/JsonBinarySqlTypeDescriptor.java | 52 ---- .../dao/util/mapping/JsonBinaryType.java | 46 ---- .../dao/util/mapping/JsonConverter.java | 34 +++ .../mapping/JsonStringSqlTypeDescriptor.java | 61 ----- .../dao/util/mapping/JsonStringType.java | 48 ---- .../dao/util/mapping/JsonTypeDescriptor.java | 96 ------- .../impl/SlackNotificationChannel.java | 2 +- .../transport/TransportHealthChecker.java | 4 +- .../transport/TransportMonitoringService.java | 2 +- msa/monitoring/pom.xml | 2 +- netty-mqtt/pom.xml | 4 + .../thingsboard/mqtt/MqttClientConfig.java | 4 +- pom.xml | 114 ++++++--- rule-engine/rule-engine-api/pom.xml | 2 +- rule-engine/rule-engine-components/pom.xml | 10 +- .../TbCopyAttributesToEntityViewNode.java | 2 +- .../TbSaveToCustomCassandraTableNode.java | 2 +- .../engine/filter/TbCheckAlarmStatusNode.java | 3 +- .../TbNotificationNodeConfiguration.java | 4 +- .../TbSlackNodeConfiguration.java | 6 +- .../rule/engine/rest/TbHttpClient.java | 238 +++++++----------- .../rule/engine/rest/TbRestApiCallNode.java | 9 +- .../AttributesDeleteNodeCallback.java | 2 +- .../AttributesUpdateNodeCallback.java | 2 +- .../telemetry/TelemetryNodeCallback.java | 2 +- .../rule/engine/rest/TbHttpClientTest.java | 33 +-- .../engine/rest/TbRestApiCallNodeTest.java | 43 ++-- 376 files changed, 1261 insertions(+), 1739 deletions(-) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractJsonSqlTypeDescriptor.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonBinarySqlTypeDescriptor.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonBinaryType.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonConverter.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonStringSqlTypeDescriptor.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonStringType.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonTypeDescriptor.java diff --git a/application/pom.xml b/application/pom.xml index 8c3976eb31..1838c97c65 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -49,7 +49,6 @@ io.netty netty-transport-native-epoll - ${netty.version} linux-x86_64 @@ -212,7 +211,15 @@ com.sun.mail - javax.mail + jakarta.mail + + + jakarta.xml.bind + jakarta.xml.bind-api + + + javax.xml.bind + jaxb-api com.twilio.sdk diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index e0190bb388..437a18bbff 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -113,9 +113,9 @@ import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.transport.TbCoreToTransportService; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import java.io.IOException; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; + import java.io.PrintWriter; import java.io.StringWriter; import java.util.concurrent.ConcurrentHashMap; diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index 934f309480..e9b10287b5 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -93,7 +93,7 @@ import org.thingsboard.server.service.rpc.RemoveRpcActorMsg; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java index dd5bd4f1ed..3a78701d0b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java @@ -35,8 +35,8 @@ import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.util.AfterStartUp; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; diff --git a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java index 4d227345b1..84789fc851 100644 --- a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java +++ b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.config; +import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; @@ -42,7 +43,6 @@ import org.thingsboard.server.service.security.auth.oauth2.TbOAuth2ParameterName import org.thingsboard.server.service.security.model.token.OAuth2AppTokenFactory; import org.thingsboard.server.utils.MiscUtils; -import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -113,7 +113,6 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza return request.getParameter("appToken"); } - @SuppressWarnings("deprecation") private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction, String appPackage, String appToken) { if (registrationId == null) { return null; @@ -154,8 +153,6 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza addPkceParameters(attributes, additionalParameters); } builder.additionalParameters(additionalParameters); - } else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) { - builder = OAuth2AuthorizationRequest.implicit(); } else { throw new IllegalArgumentException("Invalid Authorization Grant Type (" + clientRegistration.getAuthorizationGrantType().getValue() + diff --git a/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java b/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java index 0593b9765a..d18e999137 100644 --- a/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java @@ -33,11 +33,11 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.service.security.model.SecurityUser; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 4415f1a0bf..c7616ed865 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -82,7 +82,7 @@ import static springfox.documentation.builders.PathSelectors.any; import static springfox.documentation.builders.PathSelectors.regex; @Slf4j -@Configuration +//@Configuration @TbCoreComponent @Profile("!test") public class SwaggerConfiguration { diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index a418dc268b..052e1bb6ba 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -26,10 +26,11 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; @@ -60,7 +61,7 @@ import java.util.List; @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled=true) +@EnableMethodSecurity @Order(SecurityProperties.BASIC_AUTH_ORDER) @TbCoreComponent public class ThingsboardSecurityConfiguration { @@ -74,11 +75,12 @@ public class ThingsboardSecurityConfiguration { public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login"; public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public"; public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token"; - protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**"}; + protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[]{"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**"}; public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**"; - @Autowired private ThingsboardErrorResponseHandler restAccessDeniedHandler; + @Autowired + private ThingsboardErrorResponseHandler restAccessDeniedHandler; @Autowired(required = false) @Qualifier("oauth2AuthenticationSuccessHandler") @@ -99,11 +101,15 @@ public class ThingsboardSecurityConfiguration { @Qualifier("defaultAuthenticationFailureHandler") private AuthenticationFailureHandler failureHandler; - @Autowired private RestAuthenticationProvider restAuthenticationProvider; - @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider; - @Autowired private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider; + @Autowired + private RestAuthenticationProvider restAuthenticationProvider; + @Autowired + private JwtAuthenticationProvider jwtAuthenticationProvider; + @Autowired + private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider; - @Autowired(required = false) OAuth2Configuration oauth2Configuration; + @Autowired(required = false) + OAuth2Configuration oauth2Configuration; @Autowired @Qualifier("jwtHeaderTokenExtractor") @@ -113,9 +119,11 @@ public class ThingsboardSecurityConfiguration { @Qualifier("jwtQueryTokenExtractor") private TokenExtractor jwtQueryTokenExtractor; - @Autowired private AuthenticationManager authenticationManager; + @Autowired + private AuthenticationManager authenticationManager; - @Autowired private RateLimitProcessingFilter rateLimitProcessingFilter; + @Autowired + private RateLimitProcessingFilter rateLimitProcessingFilter; @Bean protected RestLoginProcessingFilter buildRestLoginProcessingFilter() throws Exception { @@ -180,35 +188,28 @@ public class ThingsboardSecurityConfiguration { @Bean public WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**"); + return (web) -> web.ignoring().requestMatchers("/*.js", "/*.css", "/*.ico", "/assets/**", "/static/**"); } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.headers().cacheControl().and().frameOptions().disable() - .and() - .cors() - .and() - .csrf().disable() - .exceptionHandling() - .and() - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() - .authorizeRequests() - .antMatchers(WEBJARS_ENTRY_POINT).permitAll() // Webjars - .antMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API - .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point - .antMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll() // Public login end-point - .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point - .antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points - .and() - .authorizeRequests() - .antMatchers(WS_TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected WebSocket API End-points - .antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points - .and() - .exceptionHandling().accessDeniedHandler(restAccessDeniedHandler) - .and() + http.headers(headers -> headers + .cacheControl(config -> {}) + .frameOptions(config -> {})) + .cors(cors -> {}) + .csrf(AbstractHttpConfigurer::disable) + .exceptionHandling(config -> {}) + .sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeRequests(config -> config + .requestMatchers(WEBJARS_ENTRY_POINT).permitAll() // Webjars + .requestMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API + .requestMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point + .requestMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll() // Public login end-point + .requestMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point + .requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points + .requestMatchers(WS_TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected WebSocket API End-points + .requestMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()) // Protected API End-points + .exceptionHandling(config -> config.accessDeniedHandler(restAccessDeniedHandler)) .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) @@ -216,15 +217,14 @@ public class ThingsboardSecurityConfiguration { .addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class); if (oauth2Configuration != null) { - http.oauth2Login() - .authorizationEndpoint() - .authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository) - .authorizationRequestResolver(oAuth2AuthorizationRequestResolver) - .and() + http.oauth2Login(login -> login + .authorizationEndpoint(config -> config + .authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository) + .authorizationRequestResolver(oAuth2AuthorizationRequestResolver)) .loginPage("/oauth2Login") .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl()) .successHandler(oauth2AuthenticationSuccessHandler) - .failureHandler(oauth2AuthenticationFailureHandler); + .failureHandler(oauth2AuthenticationFailureHandler)); } return http.build(); } diff --git a/application/src/main/java/org/thingsboard/server/config/WebConfig.java b/application/src/main/java/org/thingsboard/server/config/WebConfig.java index 65396576c1..df57cf45f8 100644 --- a/application/src/main/java/org/thingsboard/server/config/WebConfig.java +++ b/application/src/main/java/org/thingsboard/server/config/WebConfig.java @@ -19,8 +19,8 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.thingsboard.server.utils.MiscUtils; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @Controller diff --git a/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java index 32797801c0..5f33eb951e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java @@ -45,7 +45,7 @@ import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.Optional; import java.util.UUID; diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index f3a3a2397c..492296995c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -62,7 +62,7 @@ import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.system.SystemSecurityService; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.net.URI; import java.net.URISyntaxException; import java.util.concurrent.ConcurrentHashMap; diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 68a987a0bc..da2740a3b7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -160,9 +160,9 @@ import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; -import javax.mail.MessagingException; -import javax.servlet.http.HttpServletResponse; -import javax.validation.ConstraintViolation; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.ConstraintViolation; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -390,7 +390,7 @@ public abstract class BaseController { } /** - * Handles validation error for controller method arguments annotated with @{@link javax.validation.Valid} + * Handles validation error for controller method arguments annotated with @{@link jakarta.validation.Valid} * */ @ExceptionHandler(MethodArgumentNotValidException.class) public void handleValidationError(MethodArgumentNotValidException validationError, HttpServletResponse response) { diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index bb34f6d5b2..9de6a66041 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -74,7 +74,7 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.UUID; diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java index 9a021643cc..0213ae5686 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java @@ -64,7 +64,7 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; import java.util.UUID; diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 0f4a288e33..5893410b2a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -66,7 +66,7 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java index ccf2124113..06eabbc215 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java @@ -43,7 +43,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.UUID; import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END; diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java index a4c829796b..6b13e9de9d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java @@ -53,7 +53,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.Arrays; import java.util.List; import java.util.UUID; diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java index ab171efacf..fef919d710 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java @@ -48,7 +48,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; import java.util.UUID; diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index ef5739a481..519629fbc3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -39,7 +39,7 @@ import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.utils.MiscUtils; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.Enumeration; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java index 464dc528a5..c611cfda54 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java @@ -49,7 +49,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.rpc.RemoveRpcActorMsg; import org.thingsboard.server.service.security.permission.Operation; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.UUID; import static org.thingsboard.server.common.data.DataConstants.RPC_DELETED; diff --git a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java index 3633cbbdeb..724cb59b73 100644 --- a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java +++ b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java @@ -41,7 +41,7 @@ import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import springfox.documentation.annotations.ApiIgnore; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 05f56e93bd..48864829ac 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -85,9 +85,9 @@ import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.telemetry.AttributeData; import org.thingsboard.server.service.telemetry.TsData; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; diff --git a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java index b07b1eb70b..a809f5280c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java @@ -39,7 +39,7 @@ import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService; import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; import org.thingsboard.server.service.security.model.SecurityUser; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; diff --git a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java index 4a5bcb3d81..ee1db6868a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java @@ -44,7 +44,7 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.system.SystemSecurityService; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.List; import java.util.Optional; diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index f79f5ee782..91434421d2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -75,7 +75,7 @@ import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.system.SystemSecurityService; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 7a396ff795..805d24e175 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -47,10 +47,10 @@ import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.WebSocketSessionType; -import javax.websocket.RemoteEndpoint; -import javax.websocket.SendHandler; -import javax.websocket.SendResult; -import javax.websocket.Session; +import jakarta.websocket.RemoteEndpoint; +import jakarta.websocket.SendHandler; +import jakarta.websocket.SendResult; +import jakarta.websocket.Session; import java.io.IOException; import java.net.URI; import java.security.InvalidParameterException; diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java index ed641c2d4f..c037710e8b 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java @@ -16,9 +16,9 @@ package org.thingsboard.server.exception; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.lang.Nullable; @@ -43,9 +43,9 @@ import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedE import org.thingsboard.server.service.security.exception.JwtExpiredTokenException; import org.thingsboard.server.service.security.exception.UserPasswordExpiredException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -138,13 +138,13 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand @Override protected ResponseEntity handleExceptionInternal( Exception ex, @Nullable Object body, - HttpHeaders headers, HttpStatus status, + HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) { - if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) { + if (HttpStatus.INTERNAL_SERVER_ERROR.equals(statusCode)) { request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST); } - ThingsboardErrorCode errorCode = statusToErrorCode(status); - return new ResponseEntity<>(ThingsboardErrorResponse.of(ex.getMessage(), errorCode, status), headers, status); + ThingsboardErrorCode errorCode = statusToErrorCode((HttpStatus) statusCode); + return new ResponseEntity<>(ThingsboardErrorResponse.of(ex.getMessage(), errorCode, (HttpStatus) statusCode), headers, statusCode); } private void handleThingsboardException(ThingsboardException thingsboardException, HttpServletResponse response) throws IOException { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 9e17641c69..f0ee13ae69 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -68,8 +68,8 @@ import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.partition.AbstractPartitionBasedService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/limits/DefaultRateLimitService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/DefaultRateLimitService.java index 9a0282c840..fac569110d 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/limits/DefaultRateLimitService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/limits/DefaultRateLimitService.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.concurrent.TimeUnit; @Service diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index 1f5e41aeed..ab43208b85 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -37,7 +37,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.dao.component.ComponentDescriptorService; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; diff --git a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java index ebc0fd0d24..a4ba2c8311 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java @@ -51,7 +51,7 @@ import org.thingsboard.server.dao.device.claim.ReclaimResult; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.Arrays; import java.util.Collections; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java index 110760acbc..7c6267c4df 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java @@ -58,8 +58,8 @@ import org.thingsboard.server.service.edge.rpc.processor.user.UserEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetBundleEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetTypeEdgeProcessor; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java index 2c4a14635d..344a4e6742 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.install.InstallScripts; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java index 2a030fc4fa..52a9a5d820 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java @@ -19,7 +19,7 @@ import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeInstallInstructions; import org.thingsboard.server.common.data.id.TenantId; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; public interface EdgeInstallService { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index bda510a762..e0f6b3f6fc 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -52,9 +52,9 @@ import org.thingsboard.server.service.edge.EdgeContextComponent; import org.thingsboard.server.service.state.DefaultDeviceStateService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.io.IOException; import java.io.InputStream; import java.util.Collections; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index fece86b92a..4ed2e8f055 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -67,8 +67,8 @@ import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; import java.util.UUID; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java index 48e6c4b634..e59340b99b 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java @@ -49,7 +49,7 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index c83c48cc8a..181e0b8b80 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -32,7 +32,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.security.system.SystemSecurityService; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import static org.thingsboard.server.controller.UserController.ACTIVATE_URL_PATTERN; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java index 3ef79371d4..b5a5b83bfd 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java @@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; public interface TbUserService { User save(TenantId tenantId, CustomerId customerId, User tbUser, boolean sendActivationMail, HttpServletRequest request, User user) throws ThingsboardException; diff --git a/application/src/main/java/org/thingsboard/server/service/executors/SharedEventLoopGroupService.java b/application/src/main/java/org/thingsboard/server/service/executors/SharedEventLoopGroupService.java index 38b563472e..245387b70e 100644 --- a/application/src/main/java/org/thingsboard/server/service/executors/SharedEventLoopGroupService.java +++ b/application/src/main/java/org/thingsboard/server/service/executors/SharedEventLoopGroupService.java @@ -20,8 +20,8 @@ import io.netty.channel.nio.NioEventLoopGroup; import lombok.Getter; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.concurrent.TimeUnit; @Component diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 497627f629..69cdebd55e 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -102,9 +102,9 @@ import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.Arrays; import java.util.Collections; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/install/TbRuleEngineQueueConfigService.java b/application/src/main/java/org/thingsboard/server/service/install/TbRuleEngineQueueConfigService.java index 455bd56309..36372650c1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TbRuleEngineQueueConfigService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TbRuleEngineQueueConfigService.java @@ -24,7 +24,7 @@ import org.springframework.context.annotation.Profile; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.List; @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index f3fd18141a..5fe876bb75 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.mail; import com.fasterxml.jackson.databind.JsonNode; import freemarker.template.Configuration; import freemarker.template.Template; +import jakarta.xml.bind.DatatypeConverter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -47,8 +48,8 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; -import javax.annotation.PostConstruct; -import javax.mail.internet.MimeMessage; +import jakarta.annotation.PostConstruct; +import jakarta.mail.internet.MimeMessage; import java.io.ByteArrayInputStream; import java.util.HashMap; import java.util.Locale; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java index 01591003ad..17dcfca40e 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java @@ -40,7 +40,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.executors.NotificationExecutorService; import org.thingsboard.server.service.partition.AbstractPartitionBasedService; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.Collections; import java.util.HashSet; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java index 3e7f67e09b..913c9c9be3 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java @@ -53,7 +53,7 @@ import org.thingsboard.server.service.notification.rule.cache.NotificationRulesC import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor; import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.io.Serializable; import java.util.Collection; import java.util.EnumMap; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/cache/DefaultNotificationRulesCache.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/cache/DefaultNotificationRulesCache.java index a02b7bf1e4..bac51d3585 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/cache/DefaultNotificationRulesCache.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/cache/DefaultNotificationRulesCache.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.dao.notification.NotificationRuleService; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.Arrays; import java.util.Collections; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java index 6ab61426a8..a8d84bf63a 100644 --- a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java @@ -51,7 +51,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 2bd0043046..f9dbf695c3 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -89,8 +89,8 @@ import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpd import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.List; import java.util.Optional; import java.util.UUID; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java index f8f6a7d25f..c47057b8dd 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java @@ -64,8 +64,8 @@ import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrateg import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; import org.thingsboard.server.service.stats.RuleEngineStatisticsService; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -107,7 +107,6 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< private final TbRuleEngineDeviceRpcService tbDeviceRpcService; private final TbServiceInfoProvider serviceInfoProvider; private final QueueService queueService; - // private final TenantId tenantId; private final ConcurrentMap>> consumers = new ConcurrentHashMap<>(); private final ConcurrentMap consumerConfigurations = new ConcurrentHashMap<>(); private final ConcurrentMap consumerStats = new ConcurrentHashMap<>(); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 2d517a2213..961d080aad 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -49,7 +49,7 @@ import org.thingsboard.server.service.queue.TbPackCallback; import org.thingsboard.server.service.queue.TbPackProcessingContext; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.util.List; import java.util.Optional; import java.util.UUID; diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java index bca7c7d81a..2665c83884 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.rpc; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -38,8 +37,8 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java index 1fef6acbc0..3edd69bf0c 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java @@ -37,8 +37,8 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbRuleEngineComponent; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index 9bb0a40a78..a3bdfc109e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -81,9 +81,9 @@ import org.thingsboard.server.service.security.permission.AccessControlService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.BiConsumer; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java index 73cfe193e1..b380363e41 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java @@ -27,10 +27,10 @@ import org.thingsboard.server.service.security.auth.JwtAuthenticationToken; import org.thingsboard.server.service.security.auth.jwt.extractor.TokenExtractor; import org.thingsboard.server.service.security.model.token.RawAccessJwtToken; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; public class JwtTokenAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java index 66c206a7ad..c6d22b1478 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java @@ -30,10 +30,10 @@ import org.thingsboard.server.service.security.auth.RefreshAuthenticationToken; import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException; import org.thingsboard.server.service.security.model.token.RawAccessJwtToken; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java index b4c4843d1b..e3eca7217d 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java @@ -20,7 +20,7 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.List; import java.util.stream.Collectors; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java index 2f01decc73..22cf552ef5 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java @@ -20,7 +20,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.config.ThingsboardSecurityConfiguration; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; @Component(value="jwtHeaderTokenExtractor") public class JwtHeaderTokenExtractor implements TokenExtractor { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java index 7a458ef052..f58224fa6c 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java @@ -20,7 +20,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.config.ThingsboardSecurityConfiguration; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; @Component(value="jwtQueryTokenExtractor") public class JwtQueryTokenExtractor implements TokenExtractor { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/TokenExtractor.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/TokenExtractor.java index b1976dce9a..8d53395c57 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/TokenExtractor.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/TokenExtractor.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.service.security.auth.jwt.extractor; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; public interface TokenExtractor { - public String extract(HttpServletRequest request); + String extract(HttpServletRequest request); } \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java index 9bbb3643e3..2875ef4c09 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java @@ -29,7 +29,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 91de70e35c..42a0e66171 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -24,7 +24,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.Map; @Service(value = "basicOAuth2ClientMapper") diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java index 429e185b3e..f2692dc296 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java @@ -17,9 +17,9 @@ package org.thingsboard.server.service.security.auth.oauth2; import org.springframework.util.SerializationUtils; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.Base64; import java.util.Optional; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index 9cac9b87a0..739bbeda79 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -30,7 +30,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; @Service(value = "customOAuth2ClientMapper") @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java index 0381e64298..326e77403c 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java @@ -30,7 +30,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Map; import java.util.Optional; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java index d63f759dd3..d52df7aec8 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java @@ -19,8 +19,8 @@ import org.springframework.security.oauth2.client.web.AuthorizationRequestReposi import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; @Component public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository { @@ -48,9 +48,8 @@ public class HttpCookieOAuth2AuthorizationRequestRepository implements Authoriza CookieUtils.addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(authorizationRequest), cookieExpireSeconds); } - @SuppressWarnings("deprecation") @Override - public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) { + public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) { return this.loadAuthorizationRequest(request); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java index 23befd67c8..5de5d216e7 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java @@ -19,7 +19,7 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.service.security.model.SecurityUser; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; public interface OAuth2ClientMapper { SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java index 4251c4bad4..5e416da302 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java @@ -27,9 +27,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.system.SystemSecurityService; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index edfc826050..edbd96ec07 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -38,9 +38,9 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.system.SystemSecurityService; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetails.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetails.java index 1eafe66c8f..774fca5a48 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetails.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetails.java @@ -19,7 +19,7 @@ import lombok.Data; import ua_parser.Client; import ua_parser.Parser; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.io.Serializable; @Data diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetailsSource.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetailsSource.java index 36ce529e24..7d96c579ac 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetailsSource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationDetailsSource.java @@ -17,7 +17,7 @@ package org.thingsboard.server.service.security.auth.rest; import org.springframework.security.authentication.AuthenticationDetailsSource; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; public class RestAuthenticationDetailsSource implements AuthenticationDetailsSource { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java index e5d20ac546..81529c62e2 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java @@ -21,9 +21,9 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand import org.springframework.stereotype.Component; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @Component(value = "defaultAuthenticationFailureHandler") diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java index 0229170781..25e1460438 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java @@ -30,10 +30,10 @@ import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManage import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import java.io.IOException; import java.util.Optional; import java.util.concurrent.TimeUnit; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java index b3ea3453b2..91ac6db711 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java @@ -31,10 +31,10 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException; import org.thingsboard.server.service.security.model.UserPrincipal; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestPublicLoginProcessingFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestPublicLoginProcessingFilter.java index 23252c27ef..741055a8a2 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestPublicLoginProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestPublicLoginProcessingFilter.java @@ -30,10 +30,10 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException; import org.thingsboard.server.service.security.model.UserPrincipal; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 726a5d98f0..222db9d7c4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -62,8 +62,8 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.utils.MiscUtils; import ua_parser.Client; -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; import java.util.Map; diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java index 64550c438e..8c23fa6755 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettin import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.service.security.model.SecurityUser; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; public interface SystemSecurityService { diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java index ff1304208d..e21a8e5a79 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java @@ -34,10 +34,9 @@ import org.thingsboard.server.common.data.sms.config.TestSmsRequest; import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; -import org.thingsboard.server.service.transport.DefaultTransportApiService; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; @Service @Slf4j diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index e33e501fbc..402be010ac 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -31,7 +31,6 @@ import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; @@ -70,20 +69,18 @@ import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.sql.query.EntityQueryRepository; -import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.util.DbTypeInfoComponent; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; -import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.partition.AbstractPartitionBasedService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; diff --git a/application/src/main/java/org/thingsboard/server/service/stats/DefaultJsInvokeStats.java b/application/src/main/java/org/thingsboard/server/service/stats/DefaultJsInvokeStats.java index bca8df431a..fcd1e5413f 100644 --- a/application/src/main/java/org/thingsboard/server/service/stats/DefaultJsInvokeStats.java +++ b/application/src/main/java/org/thingsboard/server/service/stats/DefaultJsInvokeStats.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.stats.StatsCounter; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsType; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; @Service public class DefaultJsInvokeStats implements JsInvokeStats { diff --git a/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java b/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java index 1b5bfd56d1..50e35b8b28 100644 --- a/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java +++ b/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java @@ -32,7 +32,7 @@ import org.thingsboard.server.queue.util.TbRuleEngineComponent; import org.thingsboard.server.service.queue.TbRuleEngineConsumerStats; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.Collections; import java.util.List; import java.util.concurrent.ConcurrentHashMap; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java index 60fce13560..c3c050f73b 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java @@ -66,8 +66,8 @@ import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscript import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.HashSet; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index 4b37ba26b5..a70f0cba57 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -66,8 +66,8 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index 64f1de8662..f8173d75e3 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -35,8 +35,8 @@ import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscript import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.Collections; import java.util.HashSet; import java.util.Map; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java index 45805c5939..87532fd587 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java @@ -59,9 +59,9 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.utils.CsvUtils; import org.thingsboard.server.utils.TypeCastUtil; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java index a63600619a..075632774a 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java @@ -87,8 +87,8 @@ import org.thingsboard.server.service.sync.vc.data.ReimportTask; import org.thingsboard.server.service.sync.vc.data.SimpleEntitiesExportCtx; import org.thingsboard.server.service.sync.vc.repository.TbRepositorySettingsService; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; diff --git a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java index 80d4cede03..4b4a164856 100644 --- a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java @@ -48,8 +48,8 @@ import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; -import javax.annotation.Nullable; -import javax.annotation.PreDestroy; +import jakarta.annotation.Nullable; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java index 145f43fa3f..0e791c50c2 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java @@ -32,9 +32,9 @@ import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.service.subscription.SubscriptionManagerService; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 3f5e52796a..c2888f7487 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -48,9 +48,9 @@ import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; diff --git a/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java index a2afe6b433..4276d7b465 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java @@ -34,8 +34,8 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbCoreComponent; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.concurrent.ExecutorService; /** diff --git a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java index ef066a73f6..b7cf161853 100644 --- a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java @@ -33,7 +33,7 @@ import org.thingsboard.server.queue.notification.NotificationRuleProcessor; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbCoreComponent; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java index 21d69d6b42..711a218f0e 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java @@ -81,9 +81,9 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; diff --git a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java index 2fc0fa134c..efa642e8b1 100644 --- a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java @@ -18,7 +18,7 @@ package org.thingsboard.server.utils; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.nio.charset.Charset; diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f776887287..98635273b8 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -597,7 +597,7 @@ spring: enabled: "true" jpa: properties: - javax.persistence.query.timeout: "${JAVAX_PERSISTENCE_QUERY_TIMEOUT:30000}" + jakarta.persistence.query.timeout: "${JAVAX_PERSISTENCE_QUERY_TIMEOUT:30000}" open-in-view: "false" hibernate: ddl-auto: "none" diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index 5809ab5e42..dbf7184bf6 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -21,7 +21,7 @@ import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootContextLoader; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java index 15d5b5c809..b42e6f9733 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java @@ -270,7 +270,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest { (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration(); profileConfiguration.setMaxDevices(1); tenantProfile.getProfileData().setConfiguration(profileConfiguration); - doPost("/api/tenantProfile/", tenantProfile, TenantProfile.class); + doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); loginTenantAdmin(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 64382d5d51..7d2bc51ce6 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -30,7 +30,6 @@ import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.TestPropertySource; -import org.springframework.util.SocketUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.Device; @@ -304,9 +303,12 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public void createNewClient(Security security, Configuration coapConfig, boolean isRpc, String endpoint, boolean isBootstrap, Security securityBs) throws Exception { this.clientDestroy(); lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint); - int clientPort = SocketUtils.findAvailableUdpPort(); - lwM2MTestClient.init(security, coapConfig, clientPort, isRpc, isBootstrap, this.shortServerId, this.shortServerIdBs, - securityBs, this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest); + + try (ServerSocket socket = new ServerSocket(0)) { + int clientPort = socket.getLocalPort(); + lwM2MTestClient.init(security, coapConfig, clientPort, isRpc, isBootstrap, this.shortServerId, this.shortServerIdBs, + securityBs, this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest); + } } private void clientDestroy() { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java index 79b53bf15e..f041f0be55 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCred import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import java.nio.charset.StandardCharsets; import static org.eclipse.leshan.client.object.Security.psk; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java index c22a25a504..c75ecbd820 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCred import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import java.security.PrivateKey; import java.security.cert.X509Certificate; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java index e448a41f4b..1a908bffb8 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCre import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import java.security.PrivateKey; import java.security.cert.X509Certificate; diff --git a/common/cache/pom.xml b/common/cache/pom.xml index 440860fda6..a14c84d3f4 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -57,8 +57,8 @@ caffeine - javax.annotation - javax.annotation-api + jakarta.annotation + jakarta.annotation-api org.apache.commons diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java b/common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java index bf3cc25a52..451d0e7c5d 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java @@ -22,7 +22,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.thingsboard.server.common.data.CacheConstants; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.Map; @Configuration diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java index c5af954392..0332a25452 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java @@ -27,6 +27,7 @@ import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.types.Expiration; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; +import redis.clients.jedis.Connection; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.util.JedisClusterCRC16; @@ -149,7 +150,7 @@ public abstract class RedisTbTransactionalCacheguava - javax.annotation - javax.annotation-api + jakarta.annotation + jakarta.annotation-api com.github.fge diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java index 35e77ff584..afbd148c58 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java @@ -27,8 +27,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 029c660500..6cfb7706a8 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -53,8 +53,8 @@ guava - javax.annotation - javax.annotation-api + jakarta.annotation + jakarta.annotation-api com.github.fge diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java index 293039a1d8..e20b4af7af 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java @@ -29,7 +29,7 @@ import org.thingsboard.server.dao.cassandra.guava.GuavaSession; import org.thingsboard.server.dao.cassandra.guava.GuavaSessionBuilder; import org.thingsboard.server.dao.cassandra.guava.GuavaSessionUtils; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.file.Paths; @Slf4j diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java index c2438f258a..eab9108790 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java @@ -19,7 +19,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.thingsboard.server.dao.util.NoSqlAnyDao; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; @Component("CassandraCluster") @NoSqlAnyDao diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraDriverOptions.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraDriverOptions.java index 1b6524f8a9..9474fb574e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraDriverOptions.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraDriverOptions.java @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.dao.util.NoSqlAnyDao; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraInstallCluster.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraInstallCluster.java index 13916ea48a..efa9fb7799 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraInstallCluster.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraInstallCluster.java @@ -19,7 +19,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import org.thingsboard.server.dao.util.NoSqlAnyDao; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; @Component("CassandraInstallCluster") @NoSqlAnyDao diff --git a/common/data/pom.xml b/common/data/pom.xml index 3257d4cf32..68f7eb2393 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -37,8 +37,8 @@ - javax.validation - validation-api + jakarta.validation + jakarta.validation-api org.owasp.antisamy diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java index 3c791171d5..43927acd02 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.HashSet; import java.util.Objects; import java.util.Set; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 7ff7d72023..7de86b86d3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java index 0f3dd5b636..1c71e66b87 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java @@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; @Data @Builder diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java index 23edafd6d2..e97b40e1f1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java @@ -24,8 +24,8 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; @Data @Builder diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java index b771d591b1..a8b83a1cf6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java @@ -20,7 +20,7 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.io.Serializable; import java.util.List; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java index 00562ef677..7a44d707f9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.KeyFilterPredicate; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.io.Serializable; @ApiModel diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java index 5302d42cad..0f23ae5624 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java @@ -21,7 +21,7 @@ import lombok.Data; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.io.Serializable; @ApiModel diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java index 24fff47fc2..96f365a152 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.io.Serializable; import java.util.List; import java.util.TreeMap; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java index 389b87f813..f6b2de915b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java @@ -19,7 +19,7 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.io.Serializable; import java.util.List; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java index eef43dd8b7..6e192ea473 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.kv; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.Optional; /** diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java index 3c612d3b78..a2e6bc0128 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.kv; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.Objects; import java.util.Optional; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index 61d6982264..201fef7e0a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -33,8 +33,8 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; -import javax.validation.Valid; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; import java.util.List; import java.util.UUID; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java index 8bd9214f6b..b10d772c0b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.notification; import lombok.Data; -import javax.validation.constraints.Max; +import jakarta.validation.constraints.Max; @Data public class NotificationRequestConfig { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java index 8c94ebf416..a37291f3ec 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java @@ -18,7 +18,7 @@ package org.thingsboard.server.common.data.notification.rule; import lombok.Data; import lombok.EqualsAndHashCode; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; import java.util.List; import java.util.Map; import java.util.UUID; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java index c8858321de..c4899418ec 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java @@ -18,7 +18,7 @@ package org.thingsboard.server.common.data.notification.rule; import lombok.Data; import lombok.EqualsAndHashCode; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; import java.util.List; import java.util.Map; import java.util.UUID; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java index 607ca7f639..a54a07f3ab 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java @@ -30,10 +30,10 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.Valid; -import javax.validation.constraints.AssertTrue; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serializable; @Data diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java index d31ec098ca..ed3a8c2cd6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java @@ -23,7 +23,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import lombok.Data; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import java.io.Serializable; import java.util.List; import java.util.Map; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmAssignmentNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmAssignmentNotificationRuleTriggerConfig.java index 59ad9e5316..02a3bb8e23 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmAssignmentNotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmAssignmentNotificationRuleTriggerConfig.java @@ -22,7 +22,7 @@ import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; import java.util.Set; @Data diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java index 6118e9da00..3a2eeaadc7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java @@ -22,7 +22,7 @@ import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; import java.io.Serializable; import java.util.Set; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceActivityNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceActivityNotificationRuleTriggerConfig.java index 8deeada7c0..c0ce99e76d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceActivityNotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceActivityNotificationRuleTriggerConfig.java @@ -20,7 +20,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; import java.util.Set; import java.util.UUID; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntitiesLimitNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntitiesLimitNotificationRuleTriggerConfig.java index a40ddfb081..8aeb715a11 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntitiesLimitNotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntitiesLimitNotificationRuleTriggerConfig.java @@ -21,7 +21,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.EntityType; -import javax.validation.constraints.Max; +import jakarta.validation.constraints.Max; import java.util.Set; @Data diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java index 4ed6cfc30d..86f5dee26a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java @@ -18,8 +18,8 @@ package org.thingsboard.server.common.data.notification.settings; import lombok.Data; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import java.io.Serializable; import java.util.Map; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/SlackNotificationDeliveryMethodConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/SlackNotificationDeliveryMethodConfig.java index 22a14c500c..98630482e4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/SlackNotificationDeliveryMethodConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/SlackNotificationDeliveryMethodConfig.java @@ -18,7 +18,7 @@ package org.thingsboard.server.common.data.notification.settings; import lombok.Data; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; @Data public class SlackNotificationDeliveryMethodConfig implements NotificationDeliveryMethodConfig { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java index 9a2f9a5306..d49c2e893e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java @@ -25,9 +25,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.Valid; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; @Data @EqualsAndHashCode(callSuper = true) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/CustomerUsersFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/CustomerUsersFilter.java index dd4c38b2a0..16d760c686 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/CustomerUsersFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/CustomerUsersFilter.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.notification.targets.platform; import lombok.Data; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import java.util.UUID; @Data diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/PlatformUsersNotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/PlatformUsersNotificationTargetConfig.java index d04411a194..7adb1aaebf 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/PlatformUsersNotificationTargetConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/PlatformUsersNotificationTargetConfig.java @@ -20,8 +20,8 @@ import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.NotificationTargetType; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; @Data @EqualsAndHashCode(callSuper = true) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UserListFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UserListFilter.java index d190000576..283dd78a97 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UserListFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UserListFilter.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.notification.targets.platform; import lombok.Data; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; import java.util.List; import java.util.UUID; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java index ed9dee71b6..7e9d62e067 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java @@ -24,8 +24,8 @@ import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import static org.apache.commons.lang3.StringUtils.isEmpty; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackNotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackNotificationTargetConfig.java index 5eb2b9a44f..6c55611904 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackNotificationTargetConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackNotificationTargetConfig.java @@ -20,8 +20,8 @@ import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.NotificationTargetType; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; @Data @EqualsAndHashCode(callSuper = true) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java index ecb7f2d2b6..25f3bed1b9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java @@ -25,7 +25,7 @@ import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method") diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java index fe909a104e..ee9c1432e2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; @Data @NoArgsConstructor diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java index 71da2f862b..b0937b397a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java @@ -26,9 +26,9 @@ import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.Valid; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; @Data @EqualsAndHashCode(callSuper = true) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java index 1dcb4aec0c..0797ecf164 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java @@ -18,8 +18,8 @@ package org.thingsboard.server.common.data.notification.template; import lombok.Data; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; -import javax.validation.Valid; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; import java.util.Map; @Data diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java index f26ed00568..cba325c064 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; import java.util.Optional; @Data diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java index 988af25d92..568664c01b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.validation.Length; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; @EqualsAndHashCode(callSuper = true) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java index 0a149d90e2..89c8bcaa07 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java @@ -21,7 +21,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; -import javax.validation.Valid; +import jakarta.validation.Valid; @Builder(toBuilder = true) @EqualsAndHashCode diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/FilterPredicateValue.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/FilterPredicateValue.java index 28f401e22e..8f976459e3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/FilterPredicateValue.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/FilterPredicateValue.java @@ -22,7 +22,7 @@ import lombok.Data; import lombok.Getter; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.io.Serializable; @Data diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/StringFilterPredicate.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/StringFilterPredicate.java index 125ca4665a..e233e8291b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/StringFilterPredicate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/StringFilterPredicate.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.query; import lombok.Data; -import javax.validation.Valid; +import jakarta.validation.Valid; @Data public class StringFilterPredicate implements SimpleKeyFilterPredicate { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/PlatformTwoFaSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/PlatformTwoFaSettings.java index 4f7cf4f545..7918813fa3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/PlatformTwoFaSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/PlatformTwoFaSettings.java @@ -20,10 +20,10 @@ import lombok.Data; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; -import javax.validation.Valid; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import java.util.List; import java.util.Optional; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/BackupCodeTwoFaAccountConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/BackupCodeTwoFaAccountConfig.java index 59ba4770b9..504bb12964 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/BackupCodeTwoFaAccountConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/BackupCodeTwoFaAccountConfig.java @@ -20,7 +20,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; import java.util.Set; @Data diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/EmailTwoFaAccountConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/EmailTwoFaAccountConfig.java index 270f5f7298..590f314ecf 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/EmailTwoFaAccountConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/EmailTwoFaAccountConfig.java @@ -19,8 +19,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; @Data @EqualsAndHashCode(callSuper = true) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/SmsTwoFaAccountConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/SmsTwoFaAccountConfig.java index d1b739c378..dfbaa586af 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/SmsTwoFaAccountConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/SmsTwoFaAccountConfig.java @@ -19,8 +19,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Pattern; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; @EqualsAndHashCode(callSuper = true) @Data diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TotpTwoFaAccountConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TotpTwoFaAccountConfig.java index 35c60bd540..60cd096ccb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TotpTwoFaAccountConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TotpTwoFaAccountConfig.java @@ -19,8 +19,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Pattern; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; @Data @EqualsAndHashCode(callSuper = true) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/BackupCodeTwoFaProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/BackupCodeTwoFaProviderConfig.java index 36ac3150b5..a547a00f8d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/BackupCodeTwoFaProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/BackupCodeTwoFaProviderConfig.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.security.model.mfa.provider; import lombok.Data; -import javax.validation.constraints.Min; +import jakarta.validation.constraints.Min; @Data public class BackupCodeTwoFaProviderConfig implements TwoFaProviderConfig { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/OtpBasedTwoFaProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/OtpBasedTwoFaProviderConfig.java index f304b5858e..41d767b86f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/OtpBasedTwoFaProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/OtpBasedTwoFaProviderConfig.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.security.model.mfa.provider; import lombok.Data; -import javax.validation.constraints.Min; +import jakarta.validation.constraints.Min; @Data public abstract class OtpBasedTwoFaProviderConfig implements TwoFaProviderConfig { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/SmsTwoFaProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/SmsTwoFaProviderConfig.java index e5a9fb0f67..d7adfe7020 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/SmsTwoFaProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/SmsTwoFaProviderConfig.java @@ -18,8 +18,8 @@ package org.thingsboard.server.common.data.security.model.mfa.provider; import lombok.Data; import lombok.EqualsAndHashCode; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Pattern; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; @EqualsAndHashCode(callSuper = true) @Data diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TotpTwoFaProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TotpTwoFaProviderConfig.java index 70e4abc8a9..83fdc97d23 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TotpTwoFaProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TotpTwoFaProviderConfig.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.security.model.mfa.provider; import lombok.Data; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; @Data public class TotpTwoFaProviderConfig implements TwoFaProviderConfig { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java b/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java index cb7b9b1693..a236f20037 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.common.data.validation; -import javax.validation.Constraint; -import javax.validation.Payload; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java b/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java index fb7b048f7e..6b972ba112 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.common.data.validation; -import javax.validation.Constraint; -import javax.validation.Payload; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusQueueConfigs.java b/common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusQueueConfigs.java index 5e404bc928..2fea26625d 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusQueueConfigs.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusQueueConfigs.java @@ -21,7 +21,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.queue.util.PropertyUtils; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.Map; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java index 86146dabd1..003306c83a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java @@ -21,7 +21,7 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueMsg; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java index 453183fc58..0b1988935b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java @@ -33,7 +33,7 @@ import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueRequestTemplate; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.List; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java index 3c85aef350..2f69f79c4b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java @@ -28,7 +28,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; import org.thingsboard.server.queue.util.AfterContextReady; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java index 08f2e849f8..d0b732ba71 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java @@ -33,7 +33,7 @@ import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent; import org.thingsboard.server.queue.util.AfterStartUp; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; @@ -379,7 +379,7 @@ public class HashPartitionService implements PartitionService { } } - private TenantId getIsolatedOrSystemTenantId(ServiceType serviceType, TenantId tenantId) { + protected TenantId getIsolatedOrSystemTenantId(ServiceType serviceType, TenantId tenantId) { return isIsolated(serviceType, tenantId) ? tenantId : TenantId.SYS_TENANT_ID; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index fcf80bcf3d..3a8189aef8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -40,8 +40,8 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.AfterStartUp; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.Executors; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/environment/EnvironmentLogService.java b/common/queue/src/main/java/org/thingsboard/server/queue/environment/EnvironmentLogService.java index 0a9e3a1ac0..77e52cedf6 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/environment/EnvironmentLogService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/environment/EnvironmentLogService.java @@ -20,7 +20,7 @@ import org.apache.zookeeper.Environment; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; /** * Created by igor on 11/24/16. diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java index 00f76ed50b..d15b29799a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java @@ -35,8 +35,8 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.queue.discovery.PartitionService; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.time.Duration; import java.util.ArrayList; import java.util.List; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java index 9888066ee7..b688dfb045 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java @@ -21,7 +21,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import org.thingsboard.server.queue.util.PropertyUtils; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.Map; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java index 08609b13d0..d1000c7580 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java @@ -54,7 +54,7 @@ import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate; import org.thingsboard.server.queue.sqs.TbAwsSqsQueueAttributes; import org.thingsboard.server.queue.sqs.TbAwsSqsSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java index 257ac37408..244ca4a985 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java @@ -52,7 +52,7 @@ import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate; import org.thingsboard.server.queue.sqs.TbAwsSqsQueueAttributes; import org.thingsboard.server.queue.sqs.TbAwsSqsSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java index 9b1f9dba0d..0b66e93299 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java @@ -45,7 +45,7 @@ import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate; import org.thingsboard.server.queue.sqs.TbAwsSqsQueueAttributes; import org.thingsboard.server.queue.sqs.TbAwsSqsSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbVersionControlQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbVersionControlQueueFactory.java index 24453d3c5c..89fe2b0c6e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbVersionControlQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbVersionControlQueueFactory.java @@ -30,7 +30,7 @@ import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate; import org.thingsboard.server.queue.sqs.TbAwsSqsQueueAttributes; import org.thingsboard.server.queue.sqs.TbAwsSqsSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; @Component @ConditionalOnExpression("'${queue.type:null}'=='aws-sqs' && '${service.type:null}'=='tb-vc-executor'") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java index c1ae9fe855..aee2ff39dc 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java @@ -41,7 +41,7 @@ import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate; import org.thingsboard.server.queue.sqs.TbAwsSqsQueueAttributes; import org.thingsboard.server.queue.sqs.TbAwsSqsSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; @Component @ConditionalOnExpression("'${queue.type:null}'=='aws-sqs' && (('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport')") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java index 364abcff4c..86b7da5d31 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java @@ -54,7 +54,7 @@ import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicLong; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java index 9c25a5d420..edd092f3f0 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java @@ -53,7 +53,7 @@ import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java index b8e07a45f7..a99e874310 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java @@ -49,7 +49,7 @@ import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicLong; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java index 51e3dc99c7..67e12568f9 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java @@ -42,7 +42,7 @@ import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; @Component @ConditionalOnExpression("'${queue.type:null}'=='kafka' && (('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport')") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbVersionControlQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbVersionControlQueueFactory.java index d851956420..ace91f0e74 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbVersionControlQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbVersionControlQueueFactory.java @@ -34,7 +34,7 @@ import org.thingsboard.server.queue.kafka.TbKafkaTopicConfigs; import org.thingsboard.server.queue.settings.TbQueueCoreSettings; import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; @Component @ConditionalOnExpression("'${queue.type:null}'=='kafka' && '${service.type:null}'=='tb-vc-executor'") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java index 857641098c..79d252c4e5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java @@ -54,7 +54,7 @@ import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java index f197082829..16c0cba977 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java @@ -51,7 +51,7 @@ import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java index 9766bf9915..5416a78699 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java @@ -48,7 +48,7 @@ import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbVersionControlQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbVersionControlQueueFactory.java index 57db7b884c..e0431a28aa 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbVersionControlQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbVersionControlQueueFactory.java @@ -30,7 +30,7 @@ import org.thingsboard.server.queue.pubsub.TbPubSubSubscriptionSettings; import org.thingsboard.server.queue.settings.TbQueueCoreSettings; import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; @Component @ConditionalOnExpression("'${queue.type:null}'=='pubsub' && '${service.type:null}'=='tb-vc-executor'") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTransportQueueFactory.java index d5665e2fbe..9581e3833e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTransportQueueFactory.java @@ -41,7 +41,7 @@ import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; @Component @ConditionalOnExpression("'${queue.type:null}'=='pubsub' && (('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport')") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java index 0318b5d06f..d4a3824f1d 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java @@ -54,7 +54,7 @@ import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java index 45f5a92980..362a118190 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java @@ -51,7 +51,7 @@ import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java index 37633ced95..66ef4dfc36 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java @@ -48,7 +48,7 @@ import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbVersionControlQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbVersionControlQueueFactory.java index ed1da6a302..1d2ff91bb0 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbVersionControlQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbVersionControlQueueFactory.java @@ -30,7 +30,7 @@ import org.thingsboard.server.queue.rabbitmq.TbRabbitMqSettings; import org.thingsboard.server.queue.settings.TbQueueCoreSettings; import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; @Component @ConditionalOnExpression("'${queue.type:null}'=='rabbitmq' && '${service.type:null}'=='tb-vc-executor'") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java index f11a877fc5..9bd84e63a2 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java @@ -41,7 +41,7 @@ import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; @Component @ConditionalOnExpression("'${queue.type:null}'=='rabbitmq' && (('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport')") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java index 950e49dc93..eacf4f4aa8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java @@ -53,7 +53,7 @@ import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java index b92629633d..fc31678eb5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java @@ -51,7 +51,7 @@ import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java index a1571becc7..71051e0df0 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java @@ -48,7 +48,7 @@ import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbVersionControlQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbVersionControlQueueFactory.java index 24cb4fe8bc..b327d81f6f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbVersionControlQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbVersionControlQueueFactory.java @@ -30,7 +30,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.settings.TbQueueCoreSettings; import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; @Component @ConditionalOnExpression("'${queue.type:null}'=='service-bus' && '${service.type:null}'=='tb-vc-executor'") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java index 15e661f5b0..2443bce7a3 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java @@ -41,7 +41,7 @@ import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; @Component @ConditionalOnExpression("'${queue.type:null}'=='service-bus' && (('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport')") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java index 71f79627cc..3890a5b8e7 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java @@ -27,7 +27,7 @@ import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.util.TbCoreComponent; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; @Service @TbCoreComponent diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java index 137b84a208..2769fbf4d2 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java @@ -27,7 +27,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; @Service @ConditionalOnExpression("'${service.type:null}'=='tb-rule-engine'") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java index e9177d8b1c..3c5c63d740 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java @@ -27,7 +27,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; @Service @ConditionalOnExpression("'${service.type:null}'=='tb-transport'") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlProducerProvider.java index 77a27dedd8..bb5289350c 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlProducerProvider.java @@ -27,7 +27,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; @Service @ConditionalOnExpression("'${service.type:null}'=='tb-vc-executor'") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSettings.java index fe9d85c306..7594cde20d 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSettings.java @@ -24,7 +24,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSubscriptionSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSubscriptionSettings.java index 3d7ab85ecc..0288aa021c 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSubscriptionSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSubscriptionSettings.java @@ -21,7 +21,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.queue.util.PropertyUtils; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.Map; @Component diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqQueueArguments.java b/common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqQueueArguments.java index cb96abdf3c..ac03ca7da9 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqQueueArguments.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqQueueArguments.java @@ -21,7 +21,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqSettings.java index 0273374ddc..f8e8d59877 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqSettings.java @@ -22,7 +22,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; @Slf4j @ConditionalOnExpression("'${queue.type:null}'=='rabbitmq'") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/DefaultSchedulerComponent.java b/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/DefaultSchedulerComponent.java index 808e7c2c83..eaf90ead0f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/DefaultSchedulerComponent.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/DefaultSchedulerComponent.java @@ -18,8 +18,8 @@ package org.thingsboard.server.queue.scheduler; import org.springframework.stereotype.Component; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsQueueAttributes.java b/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsQueueAttributes.java index 66110ade74..b1c033ed29 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsQueueAttributes.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsQueueAttributes.java @@ -22,7 +22,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.HashMap; import java.util.Map; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java index 8a5857e6a7..e56c9e25c5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java @@ -37,7 +37,7 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.scheduler.SchedulerComponent; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.EnumMap; import java.util.Random; import java.util.UUID; diff --git a/common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index dc4068edcc..63977d6fab 100644 --- a/common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -37,8 +37,8 @@ import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.common.TbProtoJsQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.Map; import java.util.Optional; import java.util.UUID; diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptStatCallback.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptStatCallback.java index 42131c9dc3..9b449f63e3 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptStatCallback.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptStatCallback.java @@ -19,7 +19,7 @@ import com.google.common.util.concurrent.FutureCallback; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java index 9e3ae3c032..18f9a63e76 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java @@ -31,8 +31,8 @@ import org.thingsboard.script.api.TbScriptException; import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.common.stats.TbApiUsageStateClient; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java index f98e33b615..8b0274c81d 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java @@ -43,8 +43,8 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.common.stats.TbApiUsageStateClient; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.io.Serializable; import java.nio.charset.StandardCharsets; import java.util.Calendar; diff --git a/common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java b/common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java index 6f5c7eb752..97e5d38f1e 100644 --- a/common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java +++ b/common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java @@ -24,7 +24,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.StringUtils; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.concurrent.atomic.AtomicInteger; @Service diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java index 8ad2d01e47..ff20d95d5e 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java @@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.net.UnknownHostException; @Service("CoapTransportService") diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index 041ae0510d..4cc876a122 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -64,7 +64,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMs import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.List; import java.util.UUID; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 209478f4ff..75a2f936ca 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -33,8 +33,8 @@ import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MInMemoryBoots import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.security.cert.X509Certificate; import static java.util.concurrent.TimeUnit.MILLISECONDS; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/TbLwM2MDtlsBootstrapCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/TbLwM2MDtlsBootstrapCertificateVerifier.java index 5f215e51a0..cd5f62eb28 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/TbLwM2MDtlsBootstrapCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/TbLwM2MDtlsBootstrapCertificateVerifier.java @@ -41,7 +41,7 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import javax.security.auth.x500.X500Principal; import java.net.InetSocketAddress; import java.security.PublicKey; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index a412d1c918..aa3942f508 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -47,7 +47,7 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import javax.security.auth.x500.X500Principal; import java.net.InetSocketAddress; import java.security.PublicKey; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 3eb096cd38..5fb36f6f55 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -39,7 +39,7 @@ import org.thingsboard.server.transport.lwm2m.server.store.TbSecurityStore; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.security.cert.X509Certificate; import static java.util.concurrent.TimeUnit.MILLISECONDS; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java index 8c65c04939..2f0876157d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java @@ -33,8 +33,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index de960ea436..09ecf500b6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -75,8 +75,8 @@ import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogServic import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcWriteCompositeRequest; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.Arrays; import java.util.Collection; import java.util.Date; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java index 302bd20c8b..39938d9e53 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java @@ -38,7 +38,7 @@ import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogServic import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MModelConfigStore; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.util.List; import java.util.Map; import java.util.Set; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index f59643cbeb..4e43d7b653 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -57,8 +57,8 @@ import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdate import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MClientOtaInfoStore; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.List; import java.util.Map; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java index f8ff5c70a0..4369f5f532 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java @@ -100,8 +100,8 @@ import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mSecurityStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java index 51dc2bc55b..13d9115941 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor; import org.thingsboard.server.transport.mqtt.adaptors.ProtoMqttAdaptor; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.net.InetSocketAddress; import java.util.concurrent.atomic.AtomicInteger; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java index e953f64b87..c658052467 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java @@ -31,8 +31,8 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.TbTransportService; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.net.InetSocketAddress; /** diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java index 95603e751c..03ee4fcdc9 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java @@ -56,7 +56,7 @@ import org.thingsboard.server.transport.mqtt.adaptors.ProtoMqttAdaptor; import org.thingsboard.server.transport.mqtt.util.ReturnCode; import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugConnectionState; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.Date; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java index 48998d58cd..626a658556 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/SparkplugNodeSessionHandler.java @@ -43,7 +43,7 @@ import org.thingsboard.server.transport.mqtt.MqttTransportHandler; import org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType; import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugTopic; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 6e68a0bd44..577f089f2e 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -51,8 +51,8 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.io.IOException; import java.util.Arrays; import java.util.Collections; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java index b0e7a527aa..e428b03e0c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java @@ -26,8 +26,8 @@ import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.scheduler.SchedulerComponent; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.concurrent.ExecutorService; /** diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentialsConfig.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentialsConfig.java index 7304d6a5c6..a3dd52c62d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentialsConfig.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/config/ssl/SslCredentialsConfig.java @@ -18,7 +18,7 @@ package org.thingsboard.server.common.transport.config.ssl; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; @Slf4j @Data diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index b495448d90..8c397bd2d4 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -99,8 +99,8 @@ import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbTransportComponent; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; diff --git a/common/util/pom.xml b/common/util/pom.xml index 14ae1748b2..59669b11fd 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -46,8 +46,8 @@ provided - javax.annotation - javax.annotation-api + jakarta.annotation + jakarta.annotation-api com.fasterxml.jackson.core diff --git a/common/util/src/main/java/org/thingsboard/common/util/AbstractListeningExecutor.java b/common/util/src/main/java/org/thingsboard/common/util/AbstractListeningExecutor.java index a0b88df49e..d9814336e4 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/AbstractListeningExecutor.java +++ b/common/util/src/main/java/org/thingsboard/common/util/AbstractListeningExecutor.java @@ -19,8 +19,8 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.concurrent.Callable; /** diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml index a8acdf2be9..370ecc24a9 100644 --- a/common/version-control/pom.xml +++ b/common/version-control/pom.xml @@ -62,8 +62,8 @@ provided - javax.annotation - javax.annotation-api + jakarta.annotation + jakarta.annotation-api com.google.guava diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java index 618eb11d98..2c8a902a8a 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java @@ -76,8 +76,8 @@ import org.thingsboard.server.queue.provider.TbVersionControlQueueFactory; import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbVersionControlComponent; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java index 1905c50ba4..d6722fd5ed 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java @@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; import org.thingsboard.server.service.sync.vc.GitRepository.Diff; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; diff --git a/dao/pom.xml b/dao/pom.xml index 4d4dd2a30b..36526567de 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -122,7 +122,7 @@ org.glassfish - javax.el + jakarta.el org.springframework @@ -149,6 +149,12 @@ com.datastax.oss java-driver-query-builder + + io.netty + netty-transport-native-epoll + linux-x86_64 + test + io.dropwizard.metrics metrics-jmx diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetTypeFilter.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetTypeFilter.java index ce07d27f7d..2525387357 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetTypeFilter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetTypeFilter.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.asset; import lombok.Data; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.List; /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java index b95ce39d9a..555975434a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java @@ -37,7 +37,7 @@ import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.cache.CacheExecutorService; import org.thingsboard.server.dao.service.Validator; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/sink/ElasticsearchAuditLogSink.java b/dao/src/main/java/org/thingsboard/server/dao/audit/sink/ElasticsearchAuditLogSink.java index 6ba72a377a..ea0213240d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/sink/ElasticsearchAuditLogSink.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/sink/ElasticsearchAuditLogSink.java @@ -38,7 +38,7 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.id.TenantId; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Collections; diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index 31b446fbd5..36ae061a1d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -61,7 +61,7 @@ import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.user.UserService; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java index 6433db5ae8..59caf26638 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java @@ -48,7 +48,7 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.sql.JpaExecutorService; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java index 916c459a6b..435b10779f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java @@ -23,9 +23,9 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; -import javax.persistence.Column; -import javax.persistence.Id; -import javax.persistence.MappedSuperclass; +import jakarta.persistence.Column; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; import java.util.Arrays; import java.util.Collections; import java.util.List; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java index 773156adb1..7c19e40141 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmCommentEntity.java @@ -16,10 +16,11 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.MappedSuperclass; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmCommentType; import org.thingsboard.server.common.data.id.AlarmCommentId; @@ -28,10 +29,8 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.MappedSuperclass; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COMMENT_ALARM_ID; @@ -40,7 +39,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COMMENT_TYPE @Data @EqualsAndHashCode(callSuper = true) -@TypeDef(name = "json", typeClass = JsonStringType.class) @MappedSuperclass public abstract class AbstractAlarmCommentEntity extends BaseSqlEntity implements BaseEntity { @@ -53,7 +51,7 @@ public abstract class AbstractAlarmCommentEntity extends @Column(name = ALARM_COMMENT_TYPE) private AlarmCommentType type; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ALARM_COMMENT_COMMENT) private JsonNode comment; @@ -84,6 +82,7 @@ public abstract class AbstractAlarmCommentEntity extends this.type = alarmCommentEntity.getType(); this.comment = alarmCommentEntity.getComment(); } + protected AlarmComment toAlarmComment() { AlarmComment alarmComment = new AlarmComment(new AlarmCommentId(id)); alarmComment.setCreatedTime(createdTime); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java index 164e1518a1..f11faa6210 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java @@ -16,10 +16,13 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.MappedSuperclass; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; @@ -33,12 +36,8 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.MappedSuperclass; import java.util.Arrays; import java.util.Collections; import java.util.UUID; @@ -64,7 +63,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TYPE_PROPERT @Data @EqualsAndHashCode(callSuper = true) -@TypeDef(name = "json", typeClass = JsonStringType.class) @MappedSuperclass public abstract class AbstractAlarmEntity extends BaseSqlEntity implements BaseEntity { @@ -87,7 +85,6 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity @Column(name = ALARM_SEVERITY_PROPERTY) private AlarmSeverity severity; - @Type(type="pg-uuid") @Column(name = ALARM_ASSIGNEE_ID_PROPERTY) private UUID assigneeId; @@ -112,7 +109,7 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity @Column(name = ALARM_ASSIGN_TS_PROPERTY) private Long assignTs; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.ALARM_DETAILS_PROPERTY) private JsonNode details; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java index 3b7abed70d..aa11045759 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java @@ -16,10 +16,11 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.MappedSuperclass; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetProfileId; @@ -28,10 +29,8 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.MappedSuperclass; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_PROPERTY; @@ -44,7 +43,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPER @Data @EqualsAndHashCode(callSuper = true) -@TypeDef(name = "json", typeClass = JsonStringType.class) @MappedSuperclass public abstract class AbstractAssetEntity extends BaseSqlEntity implements SearchTextEntity { @@ -66,7 +64,7 @@ public abstract class AbstractAssetEntity extends BaseSqlEntity @Column(name = SEARCH_TEXT_PROPERTY) private String searchText; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java index 403cb04aef..237fae77b3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java @@ -17,11 +17,13 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.MappedSuperclass; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; -import org.hibernate.annotations.TypeDefs; +import org.hibernate.annotations.JdbcType; +import org.hibernate.dialect.PostgreSQLJsonPGObjectJsonbType; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.data.DeviceData; @@ -33,19 +35,12 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonBinaryType; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.MappedSuperclass; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) -@TypeDefs({ - @TypeDef(name = "json", typeClass = JsonStringType.class), - @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) -}) @MappedSuperclass public abstract class AbstractDeviceEntity extends BaseSqlEntity implements SearchTextEntity { @@ -67,7 +62,7 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) private String searchText; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; @@ -80,7 +75,8 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti @Column(name = ModelConstants.DEVICE_SOFTWARE_ID_PROPERTY, columnDefinition = "uuid") private UUID softwareId; - @Type(type = "jsonb") + @Convert(converter = JsonConverter.class) + @JdbcType(PostgreSQLJsonPGObjectJsonbType.class) @Column(name = ModelConstants.DEVICE_DEVICE_DATA_PROPERTY, columnDefinition = "jsonb") private JsonNode deviceData; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEdgeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEdgeEntity.java index f5a7423df9..0e5da92ecc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEdgeEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEdgeEntity.java @@ -16,10 +16,11 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.MappedSuperclass; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; @@ -28,10 +29,8 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.MappedSuperclass; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CUSTOMER_ID_PROPERTY; @@ -46,7 +45,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPER @Data @EqualsAndHashCode(callSuper = true) -@TypeDef(name = "json", typeClass = JsonStringType.class) @MappedSuperclass public abstract class AbstractEdgeEntity extends BaseSqlEntity implements SearchTextEntity { @@ -77,7 +75,7 @@ public abstract class AbstractEdgeEntity extends BaseSqlEntity extends BaseSqlEntity implements SearchTextEntity { @@ -85,7 +82,7 @@ public abstract class AbstractEntityViewEntity extends Bas @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) private String searchText; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTenantEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTenantEntity.java index 39dca43d29..d122de2b15 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTenantEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTenantEntity.java @@ -16,25 +16,23 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.MappedSuperclass; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.MappedSuperclass; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) -@TypeDef(name = "json", typeClass = JsonStringType.class) @MappedSuperclass public abstract class AbstractTenantEntity extends BaseSqlEntity implements SearchTextEntity { @@ -71,7 +69,7 @@ public abstract class AbstractTenantEntity extends BaseSqlEnti @Column(name = ModelConstants.EMAIL_PROPERTY) private String email; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.TENANT_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java index 6d3378c0c7..0b9a3aaebe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java @@ -15,6 +15,10 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Column; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.Transient; import lombok.Data; import org.thingsboard.server.common.data.kv.AggTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; @@ -27,10 +31,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.ToData; -import javax.persistence.Column; -import javax.persistence.Id; -import javax.persistence.MappedSuperclass; -import javax.persistence.Transient; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN; @@ -59,7 +59,6 @@ public abstract class AbstractTsKvEntity implements ToData { @Column(name = KEY_COLUMN) protected int key; - @Id @Column(name = TS_COLUMN) protected Long ts; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java index 127e8509b7..626f158829 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java @@ -24,8 +24,8 @@ import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import javax.persistence.Column; -import javax.persistence.MappedSuperclass; +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; import java.util.UUID; @Data diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AdminSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AdminSettingsEntity.java index 7f61fb0fe6..52a505a035 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AdminSettingsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AdminSettingsEntity.java @@ -16,31 +16,29 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.id.AdminSettingsId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; import java.util.UUID; -import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_JSON_VALUE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_KEY_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_TABLE_NAME; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ADMIN_SETTINGS_TABLE_NAME) public final class AdminSettingsEntity extends BaseSqlEntity implements BaseEntity { @@ -50,7 +48,7 @@ public final class AdminSettingsEntity extends BaseSqlEntity impl @Column(name = ADMIN_SETTINGS_KEY_PROPERTY) private String key; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ADMIN_SETTINGS_JSON_VALUE_PROPERTY) private JsonNode jsonValue; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentEntity.java index 7baa81755f..444229672d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmCommentEntity.java @@ -15,25 +15,20 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmCommentInfo; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.Entity; -import javax.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COMMENT_TABLE_NAME; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ALARM_COMMENT_TABLE_NAME) - -public class AlarmCommentEntity extends AbstractAlarmCommentEntity { +public class AlarmCommentEntity extends AbstractAlarmCommentEntity { public AlarmCommentEntity() { super(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java index 19d1040656..beb2849a92 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java @@ -15,22 +15,18 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.Entity; -import javax.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TABLE_NAME; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ALARM_TABLE_NAME) public final class AlarmEntity extends AbstractAlarmEntity { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java index db55756060..e87e4d8257 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java @@ -21,9 +21,9 @@ import org.thingsboard.server.common.data.alarm.AlarmAssignee; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.id.UserId; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_EMAIL_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ASSIGNEE_FIRST_NAME_PROPERTY; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java index b18f414c63..298ddcac81 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java @@ -15,9 +15,13 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.id.ApiUsageStateId; @@ -26,13 +30,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; import java.util.UUID; /** @@ -41,7 +39,6 @@ import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.API_USAGE_STATE_TABLE_NAME) public class ApiUsageStateEntity extends BaseSqlEntity implements BaseEntity { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetEntity.java index c30a572068..bb551ffbda 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetEntity.java @@ -15,21 +15,17 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.Entity; -import javax.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TABLE_NAME; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ASSET_TABLE_NAME) public final class AssetEntity extends AbstractAssetEntity { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetProfileEntity.java index 1deb1e5c7b..a276047796 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetProfileEntity.java @@ -26,9 +26,9 @@ import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import java.util.UUID; @Data diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvCompositeKey.java index 745e5893f2..936a18eeeb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvCompositeKey.java @@ -15,15 +15,15 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.EntityType; -import javax.persistence.Column; -import javax.persistence.Embeddable; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; import java.io.Serializable; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java index b39384eb5a..bc97c0394f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java @@ -26,10 +26,10 @@ import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.dao.model.ToData; -import javax.persistence.Column; -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import java.io.Serializable; import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AuditLogEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AuditLogEntity.java index 86df16872c..21ef4e09a0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AuditLogEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AuditLogEntity.java @@ -16,10 +16,14 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.audit.ActionStatus; import org.thingsboard.server.common.data.audit.ActionType; @@ -32,13 +36,8 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_ACTION_DATA_PROPERTY; @@ -56,7 +55,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_USER_NAM @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.AUDIT_LOG_TABLE_NAME) public class AuditLogEntity extends BaseSqlEntity implements BaseEntity { @@ -86,7 +84,7 @@ public class AuditLogEntity extends BaseSqlEntity implements BaseEntit @Column(name = AUDIT_LOG_ACTION_TYPE_PROPERTY) private ActionType actionType; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = AUDIT_LOG_ACTION_DATA_PROPERTY) private JsonNode actionData; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ComponentDescriptorEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ComponentDescriptorEntity.java index 09cabb72f1..ca12c0a112 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ComponentDescriptorEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ComponentDescriptorEntity.java @@ -16,10 +16,14 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.ComponentDescriptorId; import org.thingsboard.server.common.data.plugin.ComponentClusteringMode; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; @@ -28,18 +32,11 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; +import org.thingsboard.server.dao.util.mapping.JsonConverter; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.COMPONENT_DESCRIPTOR_TABLE_NAME) public class ComponentDescriptorEntity extends BaseSqlEntity implements SearchTextEntity { @@ -61,7 +58,7 @@ public class ComponentDescriptorEntity extends BaseSqlEntity implements SearchTextEntity { @Column(name = ModelConstants.CUSTOMER_TENANT_ID_PROPERTY) private UUID tenantId; - + @Column(name = ModelConstants.CUSTOMER_TITLE_PROPERTY) private String title; - + @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) private String searchText; - + @Column(name = ModelConstants.COUNTRY_PROPERTY) private String country; - + @Column(name = ModelConstants.STATE_PROPERTY) private String state; @@ -73,7 +71,7 @@ public final class CustomerEntity extends BaseSqlEntity implements Sea @Column(name = ModelConstants.EMAIL_PROPERTY) private String email; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.CUSTOMER_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java index 1e99a59773..c794e1b963 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java @@ -15,14 +15,15 @@ */ package org.thingsboard.server.dao.model.sql; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.ShortCustomerInfo; @@ -32,12 +33,8 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; -import java.io.IOException; import java.util.HashSet; import java.util.UUID; @@ -45,7 +42,6 @@ import java.util.UUID; @Slf4j @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.DASHBOARD_TABLE_NAME) public final class DashboardEntity extends BaseSqlEntity implements SearchTextEntity { @@ -73,7 +69,7 @@ public final class DashboardEntity extends BaseSqlEntity implements S @Column(name = ModelConstants.DASHBOARD_MOBILE_ORDER_PROPERTY) private Integer mobileOrder; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY) private JsonNode configuration; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java index d3c8bbeb34..5dfaaa8de1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.model.sql; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import lombok.Data; import lombok.EqualsAndHashCode; @@ -30,10 +29,10 @@ import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; -import java.io.IOException; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + import java.util.HashSet; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceCredentialsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceCredentialsEntity.java index 9af4ec64e7..4ab2452168 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceCredentialsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceCredentialsEntity.java @@ -25,11 +25,11 @@ import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import java.util.UUID; @Data diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java index d476596dc1..5d2f34ee16 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java @@ -15,25 +15,16 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.TypeDef; -import org.hibernate.annotations.TypeDefs; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonBinaryType; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.Entity; -import javax.persistence.Table; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDefs({ - @TypeDef(name = "json", typeClass = JsonStringType.class), - @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) -}) @Table(name = ModelConstants.DEVICE_TABLE_NAME) public final class DeviceEntity extends AbstractDeviceEntity { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java index d8be0eb4b8..f448a79c2b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java @@ -21,9 +21,9 @@ import org.hibernate.annotations.Immutable; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.dao.model.ModelConstants; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; @Data @EqualsAndHashCode(callSuper = true) diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index 1b084c4817..cedfe5c748 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -17,10 +17,16 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; +import org.hibernate.annotations.JdbcType; +import org.hibernate.dialect.PostgreSQLJsonPGObjectJsonbType; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; @@ -35,19 +41,13 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonBinaryType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) @Table(name = ModelConstants.DEVICE_PROFILE_TABLE_NAME) public final class DeviceProfileEntity extends BaseSqlEntity implements SearchTextEntity { @@ -90,7 +90,8 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY) private String defaultQueueName; - @Type(type = "jsonb") + @Convert(converter = JsonConverter.class) + @JdbcType(PostgreSQLJsonPGObjectJsonbType.class) @Column(name = ModelConstants.DEVICE_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb") private JsonNode profileData; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEntity.java index 3c857b3246..a6467da03d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEntity.java @@ -15,21 +15,17 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.Entity; -import javax.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TABLE_NAME; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = EDGE_TABLE_NAME) public class EdgeEntity extends AbstractEdgeEntity { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEventEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEventEntity.java index 1edc47f197..6ba856df1d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEventEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEventEntity.java @@ -16,11 +16,15 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; @@ -29,20 +33,15 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_EVENT_ACTION_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_EVENT_BODY_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.EDGE_EVENT_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_EVENT_EDGE_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_EVENT_ENTITY_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_EVENT_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_EVENT_TENANT_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_EVENT_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_EVENT_UID_PROPERTY; @@ -52,7 +51,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = EDGE_EVENT_TABLE_NAME) @NoArgsConstructor public class EdgeEventEntity extends BaseSqlEntity implements BaseEntity { @@ -74,7 +72,7 @@ public class EdgeEventEntity extends BaseSqlEntity implements BaseEnt @Column(name = EDGE_EVENT_ACTION_PROPERTY) private EdgeEventActionType edgeEventAction; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = EDGE_EVENT_BODY_PROPERTY) private JsonNode entityBody; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmCompositeKey.java index ad0c3af521..83e9584385 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmCompositeKey.java @@ -20,7 +20,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.alarm.EntityAlarm; -import javax.persistence.Transient; +import jakarta.persistence.Transient; import java.io.Serializable; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java index 6304a5324d..a203195f65 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java @@ -23,11 +23,11 @@ import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.ToData; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.IdClass; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.Table; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.CREATED_TIME_PROPERTY; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java index 4bec68240d..dbd442c3d9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java @@ -15,20 +15,16 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.Entity; -import javax.persistence.Table; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.ENTITY_VIEW_TABLE_NAME) public class EntityViewEntity extends AbstractEntityViewEntity { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ErrorEventEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ErrorEventEntity.java index 2eb356ac8b..a9d460df01 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ErrorEventEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ErrorEventEntity.java @@ -22,9 +22,9 @@ import org.thingsboard.server.common.data.event.ErrorEvent; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseEntity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.ERROR_EVENT_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.EVENT_ERROR_COLUMN_NAME; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EventEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EventEntity.java index f0ac6af704..db1ec2b208 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EventEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EventEntity.java @@ -21,9 +21,9 @@ import org.thingsboard.server.common.data.event.Event; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.ModelConstants; -import javax.persistence.Column; -import javax.persistence.Id; -import javax.persistence.MappedSuperclass; +import jakarta.persistence.Column; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; import java.util.HashMap; import java.util.Map; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/LifecycleEventEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/LifecycleEventEntity.java index e20555147f..b247542228 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/LifecycleEventEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/LifecycleEventEntity.java @@ -22,9 +22,9 @@ import org.thingsboard.server.common.data.event.LifecycleEvent; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseEntity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.EVENT_ERROR_COLUMN_NAME; import static org.thingsboard.server.dao.model.ModelConstants.EVENT_SUCCESS_COLUMN_NAME; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java index 560bea2b91..eb154221d8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java @@ -16,11 +16,15 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Formula; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.UserId; @@ -30,19 +34,13 @@ import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.NOTIFICATION_TABLE_NAME) public class NotificationEntity extends BaseSqlEntity { @@ -62,11 +60,11 @@ public class NotificationEntity extends BaseSqlEntity { @Column(name = ModelConstants.NOTIFICATION_TEXT_PROPERTY, nullable = false) private String text; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.NOTIFICATION_ADDITIONAL_CONFIG_PROPERTY) private JsonNode additionalConfig; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Formula("(SELECT r.info FROM notification_request r WHERE r.id = request_id)") private JsonNode info; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java index 713aac62e2..12814e92a2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java @@ -16,10 +16,14 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.NotificationRequestId; @@ -33,19 +37,13 @@ import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.NOTIFICATION_REQUEST_TABLE_NAME) public class NotificationRequestEntity extends BaseSqlEntity { @@ -58,15 +56,15 @@ public class NotificationRequestEntity extends BaseSqlEntity { @@ -60,15 +59,15 @@ public class NotificationRuleEntity extends BaseSqlEntity { @Column(name = ModelConstants.NOTIFICATION_RULE_TRIGGER_TYPE_PROPERTY, nullable = false) private NotificationRuleTriggerType triggerType; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.NOTIFICATION_RULE_TRIGGER_CONFIG_PROPERTY, nullable = false) private JsonNode triggerConfig; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.NOTIFICATION_RULE_RECIPIENTS_CONFIG_PROPERTY, nullable = false) private JsonNode recipientsConfig; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.NOTIFICATION_RULE_ADDITIONAL_CONFIG_PROPERTY) private JsonNode additionalConfig; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java index a4748ae0c2..4da12fa404 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java @@ -16,26 +16,24 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.NOTIFICATION_TARGET_TABLE_NAME) public class NotificationTargetEntity extends BaseSqlEntity { @@ -45,7 +43,7 @@ public class NotificationTargetEntity extends BaseSqlEntity @Column(name = ModelConstants.NAME_PROPERTY, nullable = false) private String name; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.NOTIFICATION_TARGET_CONFIGURATION_PROPERTY, nullable = false) private JsonNode configuration; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java index 09b0ee7ee9..ef758918e4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java @@ -16,29 +16,27 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.NOTIFICATION_TEMPLATE_TABLE_NAME) public class NotificationTemplateEntity extends BaseSqlEntity { @@ -52,7 +50,7 @@ public class NotificationTemplateEntity extends BaseSqlEntity { @@ -89,7 +87,7 @@ public class OAuth2ClientRegistrationTemplateEntity extends BaseSqlEntity { @@ -110,7 +108,7 @@ public class OAuth2RegistrationEntity extends BaseSqlEntity @Column(name = ModelConstants.OAUTH2_MAPPER_SEND_TOKEN_PROPERTY) private Boolean sendToken; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.OAUTH2_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java index dcb3bce546..10af250d29 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java @@ -16,10 +16,15 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Lob; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.OtaPackageId; @@ -29,14 +34,8 @@ import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Lob; -import javax.persistence.Table; +import org.thingsboard.server.dao.util.mapping.JsonConverter; + import java.nio.ByteBuffer; import java.util.UUID; @@ -59,7 +58,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPER @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = OTA_PACKAGE_TABLE_NAME) public class OtaPackageEntity extends BaseSqlEntity implements SearchTextEntity { @@ -105,7 +103,7 @@ public class OtaPackageEntity extends BaseSqlEntity implements Searc @Column(name = OTA_PACKAGE_DATA_SIZE_COLUMN) private Long dataSize; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.OTA_PACKAGE_ADDITIONAL_INFO_COLUMN) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java index 5a2eb6008b..8e14cc198e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java @@ -16,10 +16,15 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -30,14 +35,8 @@ import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; -import javax.persistence.Transient; +import org.thingsboard.server.dao.util.mapping.JsonConverter; + import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_CHECKSUM_ALGORITHM_COLUMN; @@ -58,7 +57,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPER @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = OTA_PACKAGE_TABLE_NAME) public class OtaPackageInfoEntity extends BaseSqlEntity implements SearchTextEntity { @@ -100,7 +98,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity implemen @Column(name = OTA_PACKAGE_DATA_SIZE_COLUMN) private Long dataSize; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.OTA_PACKAGE_ADDITIONAL_INFO_COLUMN) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/QueueEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/QueueEntity.java index 75f3332fbe..b81ddb04e1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/QueueEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/QueueEntity.java @@ -16,10 +16,12 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.TenantId; @@ -29,17 +31,13 @@ import org.thingsboard.server.common.data.queue.SubmitStrategy; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.QUEUE_TABLE_NAME) public class QueueEntity extends BaseSqlEntity { @@ -63,15 +61,15 @@ public class QueueEntity extends BaseSqlEntity { @Column(name = ModelConstants.QUEUE_PACK_PROCESSING_TIMEOUT_PROPERTY) private long packProcessingTimeout; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.QUEUE_SUBMIT_STRATEGY_PROPERTY) private JsonNode submitStrategy; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.QUEUE_PROCESSING_STRATEGY_PROPERTY) private JsonNode processingStrategy; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.QUEUE_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RelationCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RelationCompositeKey.java index aeae9e5b86..76e953fdbf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RelationCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RelationCompositeKey.java @@ -20,7 +20,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.relation.EntityRelation; -import javax.persistence.Transient; +import jakarta.persistence.Transient; import java.io.Serializable; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RelationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RelationEntity.java index 1e502adde1..a87123a8c0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RelationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RelationEntity.java @@ -16,26 +16,25 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.Table; import lombok.Data; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.dao.model.ToData; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.IdClass; -import javax.persistence.Table; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.ADDITIONAL_INFO_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.RELATION_FROM_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.RELATION_FROM_TYPE_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TO_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TO_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TYPE_GROUP_PROPERTY; @@ -43,7 +42,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TYPE_PROP @Data @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = RELATION_TABLE_NAME) @IdClass(RelationCompositeKey.class) public final class RelationEntity implements ToData { @@ -72,7 +70,7 @@ public final class RelationEntity implements ToData { @Column(name = RELATION_TYPE_PROPERTY) private String relationType; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RpcEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RpcEntity.java index 8906619d5b..abf32bd444 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RpcEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RpcEntity.java @@ -16,10 +16,14 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.TenantId; @@ -27,13 +31,8 @@ import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.RPC_ADDITIONAL_INFO; @@ -48,7 +47,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.RPC_TENANT_ID_COLU @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = RPC_TABLE_NAME) public class RpcEntity extends BaseSqlEntity implements BaseEntity { @@ -61,11 +59,11 @@ public class RpcEntity extends BaseSqlEntity implements BaseEntity { @Column(name = RPC_EXPIRATION_TIME) private long expirationTime; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = RPC_REQUEST) private JsonNode request; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = RPC_RESPONSE) private JsonNode response; @@ -73,7 +71,7 @@ public class RpcEntity extends BaseSqlEntity implements BaseEntity { @Column(name = RPC_STATUS) private RpcStatus status; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = RPC_ADDITIONAL_INFO) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainDebugEventEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainDebugEventEntity.java index 7e0921d381..08a2fa6b72 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainDebugEventEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainDebugEventEntity.java @@ -22,9 +22,9 @@ import org.thingsboard.server.common.data.event.RuleChainDebugEvent; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseEntity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.EVENT_ERROR_COLUMN_NAME; import static org.thingsboard.server.dao.model.ModelConstants.EVENT_MESSAGE_COLUMN_NAME; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java index 82362e0259..f55e331d4f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java @@ -16,10 +16,14 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; @@ -29,19 +33,13 @@ import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.RULE_CHAIN_TABLE_NAME) public class RuleChainEntity extends BaseSqlEntity implements SearchTextEntity { @@ -67,11 +65,11 @@ public class RuleChainEntity extends BaseSqlEntity implements SearchT @Column(name = ModelConstants.DEBUG_MODE) private boolean debugMode; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.RULE_CHAIN_CONFIGURATION_PROPERTY) private JsonNode configuration; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeDebugEventEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeDebugEventEntity.java index ee4d6c3bd1..ce28037a89 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeDebugEventEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeDebugEventEntity.java @@ -23,9 +23,9 @@ import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseEntity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.EVENT_DATA_COLUMN_NAME; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java index c03920d954..26bfdd3c3d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java @@ -16,10 +16,12 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.rule.RuleNode; @@ -27,17 +29,13 @@ import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.RULE_NODE_TABLE_NAME) public class RuleNodeEntity extends BaseSqlEntity implements SearchTextEntity { @@ -53,11 +51,11 @@ public class RuleNodeEntity extends BaseSqlEntity implements SearchTex @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) private String searchText; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.RULE_NODE_CONFIGURATION_PROPERTY) private JsonNode configuration; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeStateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeStateEntity.java index 9a9efe432a..a71e57ce8b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeStateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeStateEntity.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.RuleNodeStateId; @@ -25,17 +27,12 @@ import org.thingsboard.server.common.data.rule.RuleNodeState; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.RULE_NODE_STATE_TABLE_NAME) public class RuleNodeStateEntity extends BaseSqlEntity { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/StatisticsEventEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/StatisticsEventEntity.java index 0ac017d48d..43395f2f87 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/StatisticsEventEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/StatisticsEventEntity.java @@ -22,9 +22,9 @@ import org.thingsboard.server.common.data.event.StatisticsEvent; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseEntity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.EVENT_ERRORS_OCCURRED_COLUMN_NAME; import static org.thingsboard.server.dao.model.ModelConstants.EVENT_MESSAGES_PROCESSED_COLUMN_NAME; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java index 1b30123b2e..741e21532e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java @@ -24,9 +24,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.SearchTextEntity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_DATA_COLUMN; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java index 5ebc0297af..9314c42bcc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java @@ -24,9 +24,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.SearchTextEntity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_KEY_COLUMN; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantEntity.java index 46d3f34c9e..592326c20f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantEntity.java @@ -15,20 +15,16 @@ */ package org.thingsboard.server.dao.model.sql; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.Entity; -import javax.persistence.Table; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.TENANT_TABLE_NAME) public final class TenantEntity extends AbstractTenantEntity { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java index 9672ed2bab..302a51b4c4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java @@ -17,10 +17,14 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; +import org.hibernate.annotations.JdbcType; +import org.hibernate.dialect.PostgreSQLJsonPGObjectJsonbType; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantProfileId; @@ -28,16 +32,11 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonBinaryType; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import org.thingsboard.server.dao.util.mapping.JsonConverter; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) @Table(name = ModelConstants.TENANT_PROFILE_TABLE_NAME) public final class TenantProfileEntity extends BaseSqlEntity implements SearchTextEntity { @@ -56,8 +55,9 @@ public final class TenantProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.TENANT_PROFILE_ISOLATED_TB_RULE_ENGINE) private boolean isolatedTbRuleEngine; - @Type(type = "jsonb") - @Column(name = ModelConstants.TENANT_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb") + @Convert(converter = JsonConverter.class) + @JdbcType(PostgreSQLJsonPGObjectJsonbType.class) + @Column(name = ModelConstants.TENANT_PROFILE_PROFILE_DATA_PROPERTY) private JsonNode profileData; public TenantProfileEntity() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserAuthSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserAuthSettingsEntity.java index 9d63b4d586..1ea283a18b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserAuthSettingsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserAuthSettingsEntity.java @@ -16,11 +16,13 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.UserAuthSettingsId; import org.thingsboard.server.common.data.id.UserId; @@ -29,24 +31,20 @@ import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoF import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Data @NoArgsConstructor -@TypeDef(name = "json", typeClass = JsonStringType.class) @Entity @Table(name = ModelConstants.USER_AUTH_SETTINGS_TABLE_NAME) public class UserAuthSettingsEntity extends BaseSqlEntity implements BaseEntity { @Column(name = ModelConstants.USER_AUTH_SETTINGS_USER_ID_PROPERTY, nullable = false, unique = true) private UUID userId; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.USER_AUTH_SETTINGS_TWO_FA_SETTINGS) private JsonNode twoFaSettings; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java index 7c93fd0be2..f02080bf58 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java @@ -16,9 +16,9 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Convert; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; import org.thingsboard.server.common.data.id.UserCredentialsId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.UserCredentials; @@ -26,9 +26,11 @@ import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import org.thingsboard.server.dao.util.mapping.JsonConverter; + import java.util.UUID; @Data @@ -52,7 +54,7 @@ public final class UserCredentialsEntity extends BaseSqlEntity @Column(name = ModelConstants.USER_CREDENTIALS_RESET_TOKEN_PROPERTY, unique = true) private String resetToken; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.USER_CREDENTIALS_ADDITIONAL_PROPERTY) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java index 33b19d765e..145b8508d3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java @@ -16,10 +16,14 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -28,13 +32,8 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; import java.util.UUID; /** @@ -43,7 +42,6 @@ import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.USER_PG_HIBERNATE_TABLE_NAME) public class UserEntity extends BaseSqlEntity implements SearchTextEntity { @@ -72,7 +70,7 @@ public class UserEntity extends BaseSqlEntity implements SearchTextEntity< @Column(name = ModelConstants.PHONE_PROPERTY) private String phone; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.USER_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java index a494b8096c..9e383f0359 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java @@ -16,28 +16,26 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.Table; import lombok.Data; import lombok.NoArgsConstructor; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; import org.thingsboard.server.common.data.settings.UserSettingsType; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ToData; -import org.thingsboard.server.dao.util.mapping.JsonStringType; +import org.thingsboard.server.dao.util.mapping.JsonConverter; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.IdClass; -import javax.persistence.Table; import java.util.UUID; @Data @NoArgsConstructor -@TypeDef(name = "json", typeClass = JsonStringType.class) @Entity @Table(name = ModelConstants.USER_SETTINGS_TABLE_NAME) @IdClass(UserSettingsCompositeKey.class) @@ -49,7 +47,7 @@ public class UserSettingsEntity implements ToData { @Id @Column(name = ModelConstants.USER_SETTINGS_TYPE_PROPERTY) private String type; - @Type(type = "json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.USER_SETTINGS_SETTINGS) private JsonNode settings; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeDetailsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeDetailsEntity.java index c936952b40..a304318fee 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeDetailsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeDetailsEntity.java @@ -16,23 +16,20 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.widget.BaseWidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import org.thingsboard.server.dao.util.mapping.JsonConverter; @Data @EqualsAndHashCode(callSuper = true) @Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.WIDGET_TYPE_TABLE_NAME) public class WidgetTypeDetailsEntity extends AbstractWidgetTypeEntity { @@ -42,7 +39,7 @@ public class WidgetTypeDetailsEntity extends AbstractWidgetTypeEntity { - @Type(type="json") + @Convert(converter = JsonConverter.class) @Column(name = ModelConstants.WIDGET_TYPE_DESCRIPTOR_PROPERTY) private JsonNode descriptor; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java index 0bc2f0ba36..272863134a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java @@ -25,9 +25,9 @@ import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; import java.util.UUID; @Data diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java index 36f34d4239..5f1ff6a9c1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java @@ -15,15 +15,13 @@ */ package org.thingsboard.server.dao.model.sqlts.dictionary; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.Table; import lombok.Data; import org.hibernate.annotations.Generated; -import org.hibernate.annotations.GenerationTime; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.IdClass; -import javax.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.KEY_ID_COLUMN; @@ -38,8 +36,8 @@ public final class TsKvDictionary { @Column(name = KEY_COLUMN) private String key; - @Column(name = KEY_ID_COLUMN, unique = true, columnDefinition="int") - @Generated(GenerationTime.INSERT) + @Column(name = KEY_ID_COLUMN, unique = true, columnDefinition = "int") + @Generated private int keyId; } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionaryCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionaryCompositeKey.java index 5a83260bac..49ab8b6822 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionaryCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionaryCompositeKey.java @@ -19,7 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import javax.persistence.Transient; +import jakarta.persistence.Transient; import java.io.Serializable; @Data diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestCompositeKey.java index ae1c34310c..c46d6f3d5d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestCompositeKey.java @@ -19,7 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import javax.persistence.Transient; +import jakarta.persistence.Transient; import java.io.Serializable; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestEntity.java index 58a76ddf31..7c57babdb1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestEntity.java @@ -15,19 +15,19 @@ */ package org.thingsboard.server.dao.model.sqlts.latest; +import jakarta.persistence.ColumnResult; +import jakarta.persistence.ConstructorResult; +import jakarta.persistence.Entity; +import jakarta.persistence.IdClass; +import jakarta.persistence.NamedNativeQueries; +import jakarta.persistence.NamedNativeQuery; +import jakarta.persistence.SqlResultSetMapping; +import jakarta.persistence.SqlResultSetMappings; +import jakarta.persistence.Table; import lombok.Data; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import org.thingsboard.server.dao.sqlts.latest.SearchTsKvLatestRepository; -import javax.persistence.ColumnResult; -import javax.persistence.ConstructorResult; -import javax.persistence.Entity; -import javax.persistence.IdClass; -import javax.persistence.NamedNativeQueries; -import javax.persistence.NamedNativeQuery; -import javax.persistence.SqlResultSetMapping; -import javax.persistence.SqlResultSetMappings; -import javax.persistence.Table; import java.util.UUID; @Data diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java index a7f2ed4210..23006f63e7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java @@ -15,11 +15,11 @@ */ package org.thingsboard.server.dao.model.sqlts.timescale.ts; +import jakarta.persistence.Transient; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import javax.persistence.Transient; import java.io.Serializable; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java index f50af42073..2d09caf150 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java @@ -15,21 +15,20 @@ */ package org.thingsboard.server.dao.model.sqlts.timescale.ts; +import jakarta.persistence.ColumnResult; +import jakarta.persistence.ConstructorResult; +import jakarta.persistence.Entity; +import jakarta.persistence.IdClass; +import jakarta.persistence.NamedNativeQueries; +import jakarta.persistence.NamedNativeQuery; +import jakarta.persistence.SqlResultSetMapping; +import jakarta.persistence.SqlResultSetMappings; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; -import javax.persistence.ColumnResult; -import javax.persistence.ConstructorResult; -import javax.persistence.Entity; -import javax.persistence.IdClass; -import javax.persistence.NamedNativeQueries; -import javax.persistence.NamedNativeQuery; -import javax.persistence.SqlResultSetMapping; -import javax.persistence.SqlResultSetMappings; -import javax.persistence.Table; - import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.FIND_AVG; import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.FIND_AVG_QUERY; import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.FIND_COUNT; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvCompositeKey.java index 14fe2b6eb7..7da73e5012 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvCompositeKey.java @@ -19,7 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import javax.persistence.Transient; +import jakarta.persistence.Transient; import java.io.Serializable; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java index 9f5b8d87ef..d0577b6288 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java @@ -15,14 +15,13 @@ */ package org.thingsboard.server.dao.model.sqlts.ts; +import jakarta.persistence.Entity; +import jakarta.persistence.IdClass; +import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; -import javax.persistence.Entity; -import javax.persistence.IdClass; -import javax.persistence.Table; - @EqualsAndHashCode(callSuper = true) @Data @Entity diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractAsyncDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractAsyncDao.java index 425724bf2c..87bb2f0e5c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractAsyncDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractAsyncDao.java @@ -21,9 +21,9 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java index adeee5f174..3ed763b43b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java @@ -29,7 +29,7 @@ import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; import org.thingsboard.server.dao.util.AsyncTaskContext; import org.thingsboard.server.dao.util.NoSqlAnyDao; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; /** * Created by ashvayka on 24.10.18. diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java index 55b1b5410d..e09dab1034 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java @@ -29,7 +29,7 @@ import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; import org.thingsboard.server.dao.util.AsyncTaskContext; import org.thingsboard.server.dao.util.NoSqlAnyDao; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; /** * Created by ashvayka on 24.10.18. diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 9818f4a2f0..0ba74f5811 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -43,7 +43,7 @@ import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; -import javax.transaction.Transactional; +import jakarta.transaction.Transactional; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index fb86e09ab3..6b631bf041 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -50,8 +50,8 @@ import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.sql.JpaExecutorService; import org.thingsboard.server.dao.sql.relation.JpaRelationQueryExecutorService; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java index b884de0094..b0ced3dcc3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java @@ -30,11 +30,11 @@ import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.dao.exception.DataValidationException; -import javax.validation.ConstraintViolation; -import javax.validation.Validation; -import javax.validation.Validator; -import javax.validation.constraints.AssertTrue; -import javax.validation.metadata.ConstraintDescriptor; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.metadata.ConstraintDescriptor; import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; @@ -100,7 +100,7 @@ public class ConstraintValidator { } private static ConstraintMapping getCustomConstraintMapping() { - ConstraintMapping constraintMapping = new DefaultConstraintMapping(); + ConstraintMapping constraintMapping = new DefaultConstraintMapping(null); constraintMapping.constraintDefinition(NoXss.class).validatedBy(NoXssValidator.class); constraintMapping.constraintDefinition(Length.class).validatedBy(StringLengthValidator.class); return constraintMapping; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/NoXssValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/NoXssValidator.java index 632caf10c4..8e7b2c6af0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/NoXssValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/NoXssValidator.java @@ -23,8 +23,8 @@ import org.owasp.validator.html.PolicyException; import org.owasp.validator.html.ScanException; import org.thingsboard.server.common.data.validation.NoXss; -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; import java.util.Optional; @Slf4j diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java index d4299dcc0b..935eebf19a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java @@ -20,8 +20,8 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.validation.Length; -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; @Slf4j public class StringLengthValidator implements ConstraintValidator { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/ScheduledLogExecutorComponent.java b/dao/src/main/java/org/thingsboard/server/dao/sql/ScheduledLogExecutorComponent.java index 83e1bf4afe..fa39130049 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/ScheduledLogExecutorComponent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ScheduledLogExecutorComponent.java @@ -18,8 +18,8 @@ package org.thingsboard.server.dao.sql; import org.springframework.stereotype.Component; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java index fd3822bb5f..88145a9cf1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java @@ -41,6 +41,7 @@ public class TbSqlBlockingQueue implements TbSqlQueue { private ExecutorService executor; private final MessagesStats stats; + private volatile boolean stopped = false; public TbSqlBlockingQueue(TbSqlBlockingQueueParams params, MessagesStats stats) { this.params = params; @@ -55,7 +56,7 @@ public class TbSqlBlockingQueue implements TbSqlQueue { int batchSize = params.getBatchSize(); long maxDelay = params.getMaxDelay(); List> entities = new ArrayList<>(batchSize); - while (!Thread.interrupted()) { + while (!stopped) { try { long currentTs = System.currentTimeMillis(); TbSqlQueueElement attr = queue.poll(maxDelay, TimeUnit.MILLISECONDS); @@ -109,6 +110,7 @@ public class TbSqlBlockingQueue implements TbSqlQueue { @Override public void destroy() { + stopped = true; if (executor != null) { executor.shutdownNow(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index 92537dc850..7fb6b31a3c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -52,8 +52,8 @@ public interface AlarmRepository extends JpaRepository { "AND ea.entityType = :affectedEntityType " + "AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " + "AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + @@ -69,8 +69,8 @@ public interface AlarmRepository extends JpaRepository { "AND ea.entityType = :affectedEntityType " + "AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " + "AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + @@ -97,10 +97,12 @@ public interface AlarmRepository extends JpaRepository { "AND ea.entityType = :affectedEntityType " + "AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " + "AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " + - "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + - "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 + "AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 +// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + +// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + @@ -116,10 +118,12 @@ public interface AlarmRepository extends JpaRepository { "AND ea.entityType = :affectedEntityType " + "AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " + "AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " + - "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + - "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 + "AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 +// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + +// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + @@ -144,8 +148,8 @@ public interface AlarmRepository extends JpaRepository { "WHERE a.tenantId = :tenantId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + @@ -156,8 +160,8 @@ public interface AlarmRepository extends JpaRepository { "WHERE a.tenantId = :tenantId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + @@ -178,10 +182,12 @@ public interface AlarmRepository extends JpaRepository { "WHERE a.tenantId = :tenantId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + - "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 + "AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 +// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + +// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + @@ -192,10 +198,12 @@ public interface AlarmRepository extends JpaRepository { "WHERE a.tenantId = :tenantId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + - "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 + "AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 +// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + +// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + @@ -218,8 +226,8 @@ public interface AlarmRepository extends JpaRepository { "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + @@ -231,8 +239,8 @@ public interface AlarmRepository extends JpaRepository { "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + @@ -254,10 +262,12 @@ public interface AlarmRepository extends JpaRepository { "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + - "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 + "AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 +// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + +// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + @@ -269,10 +279,12 @@ public interface AlarmRepository extends JpaRepository { "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + - "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 + "AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 +// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + +// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId)) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + @@ -297,8 +309,8 @@ public interface AlarmRepository extends JpaRepository { "AND ea.tenantId = :tenantId " + "AND ea.entityId = :affectedEntityId " + "AND ea.entityType = :affectedEntityType " + - "AND ((:clearFilterEnabled) IS FALSE OR a.cleared = :clearFilter) " + - "AND ((:ackFilterEnabled) IS FALSE OR a.acknowledged = :ackFilter) " + + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = uuid(:assigneeId))") Set findAlarmSeverities(@Param("tenantId") UUID tenantId, @Param("affectedEntityId") UUID affectedEntityId, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index 4474378f32..6d195d1cc4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -19,6 +19,8 @@ import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -39,8 +41,6 @@ import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper; import org.thingsboard.server.dao.util.SqlDao; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -132,10 +132,10 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @Override public List findAll(TenantId tenantId, EntityId entityId, String attributeType) { return DaoUtil.convertDataList(Lists.newArrayList( - attributeKvRepository.findAllByEntityTypeAndEntityIdAndAttributeType( - entityId.getEntityType(), - entityId.getId(), - attributeType))); + attributeKvRepository.findAllByEntityTypeAndEntityIdAndAttributeType( + entityId.getEntityType(), + entityId.getId(), + attributeType))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java index 3d35383eb0..68aa2a0ee9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java @@ -33,7 +33,8 @@ public interface AuditLogRepository extends JpaRepository "a.tenantId = :tenantId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " + + "AND ((:#{#actionTypes == null} = true) OR a.actionType IN (:actionTypes)) " + //HHH-15968 +// "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " + "AND (LOWER(a.entityType) LIKE LOWER(CONCAT('%', :textSearch, '%'))" + "OR LOWER(a.entityName) LIKE LOWER(CONCAT('%', :textSearch, '%'))" + "OR LOWER(a.userName) LIKE LOWER(CONCAT('%', :textSearch, '%'))" + @@ -53,7 +54,8 @@ public interface AuditLogRepository extends JpaRepository "AND a.entityType = :entityType AND a.entityId = :entityId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " + + "AND ((:#{#actionTypes == null} = true) OR a.actionType IN (:actionTypes)) " + //HHH-15968 +// "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " + "AND (LOWER(a.entityName) LIKE LOWER(CONCAT('%', :textSearch, '%'))" + "OR LOWER(a.userName) LIKE LOWER(CONCAT('%', :textSearch, '%'))" + "OR LOWER(a.actionType) LIKE LOWER(CONCAT('%', :textSearch, '%'))" + @@ -73,7 +75,8 @@ public interface AuditLogRepository extends JpaRepository "AND a.customerId = :customerId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " + + "AND ((:#{#actionTypes == null} = true) OR a.actionType IN (:actionTypes)) " + //HHH-15968 +// "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " + "AND (LOWER(a.entityType) LIKE LOWER(CONCAT('%', :textSearch, '%'))" + "OR LOWER(a.entityName) LIKE LOWER(CONCAT('%', :textSearch, '%'))" + "OR LOWER(a.userName) LIKE LOWER(CONCAT('%', :textSearch, '%'))" + @@ -93,7 +96,8 @@ public interface AuditLogRepository extends JpaRepository "AND a.userId = :userId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " + + "AND ((:#{#actionTypes == null} = true) OR a.actionType IN (:actionTypes)) " + //HHH-15968 +// "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " + "AND (LOWER(a.entityType) LIKE LOWER(CONCAT('%', :textSearch, '%'))" + "OR LOWER(a.entityName) LIKE LOWER(CONCAT('%', :textSearch, '%'))" + "OR LOWER(a.actionType) LIKE LOWER(CONCAT('%', :textSearch, '%'))" + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java index 943697149c..83c841459f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java @@ -25,9 +25,9 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.Query; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Query; @Slf4j public abstract class AbstractComponentDescriptorInsertRepository implements ComponentDescriptorInsertRepository { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 89ed713975..f1f07381d3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -122,7 +122,7 @@ public interface DeviceRepository extends JpaRepository, Exp "AND (:edgeId IS NULL OR d.id IN (SELECT re.toId FROM RelationEntity re WHERE re.toType = 'DEVICE' AND re.relationTypeGroup = 'EDGE' AND re.relationType = 'Contains' AND re.fromType = 'EDGE' AND re.fromId = uuid(:edgeId))) " + "AND ((:deviceType) IS NULL OR d.type = :deviceType) " + "AND (:deviceProfileId IS NULL OR d.deviceProfileId = uuid(:deviceProfileId)) " + - "AND ((:filterByActive) IS FALSE OR d.active = :deviceActive) " + + "AND ((:filterByActive) = FALSE OR d.active = :deviceActive) " + "AND (LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + "OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + "OR LOWER(d.type) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java index 239fe1f098..6527f022f6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java @@ -17,6 +17,8 @@ package org.thingsboard.server.dao.sql.edge; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.util.concurrent.ListenableFuture; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -41,8 +43,6 @@ import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper; import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import org.thingsboard.server.dao.util.SqlDao; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import java.util.Comparator; import java.util.Objects; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java index 8d0df9e48d..1f08da69fa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java @@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.event.RuleNodeDebugEvent; import org.thingsboard.server.common.data.event.StatisticsEvent; import org.thingsboard.server.dao.util.SqlDao; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventPartitionConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventPartitionConfiguration.java index 452e9b744f..546f813382 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventPartitionConfiguration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventPartitionConfiguration.java @@ -20,7 +20,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.event.EventType; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.concurrent.TimeUnit; @Component diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java index f35e7e608f..3ba6d21aa0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java @@ -17,6 +17,8 @@ package org.thingsboard.server.dao.sql.event; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.util.concurrent.ListenableFuture; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -43,8 +45,6 @@ import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper; import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import org.thingsboard.server.dao.util.SqlDao; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import java.util.Comparator; import java.util.List; import java.util.Map; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/QueryContext.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/QueryContext.java index 8ced23cdb4..a37432d66a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/QueryContext.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/QueryContext.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.query; import lombok.extern.slf4j.Slf4j; -import org.hibernate.type.PostgresUUIDType; +import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.CustomerId; @@ -30,7 +30,7 @@ import java.util.UUID; @Slf4j public class QueryContext implements SqlParameterSource { - private static final PostgresUUIDType UUID_TYPE = new PostgresUUIDType(); + private static final UUIDJdbcType UUID_TYPE = UUIDJdbcType.INSTANCE; private final QuerySecurityContext securityCtx; private final StringBuilder query; @@ -91,7 +91,7 @@ public class QueryContext implements SqlParameterSource { } public void addUuidParameter(String name, UUID value) { - addParameter(name, value, UUID_TYPE.sqlType(), UUID_TYPE.getName()); + addParameter(name, value, UUID_TYPE.getJdbcTypeCode(), UUID_TYPE.getFriendlyName()); } public void addStringParameter(String name, String value) { @@ -115,7 +115,7 @@ public class QueryContext implements SqlParameterSource { } public void addUuidListParameter(String name, List value) { - addParameter(name, value, UUID_TYPE.sqlType(), UUID_TYPE.getName()); + addParameter(name, value, UUID_TYPE.getJdbcTypeCode(), UUID_TYPE.getFriendlyName()); } public String getQuery() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/SqlRelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/SqlRelationInsertRepository.java index 835a23f6b5..fded581981 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/SqlRelationInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/SqlRelationInsertRepository.java @@ -23,9 +23,9 @@ import org.springframework.transaction.annotation.Transactional; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.dao.model.sql.RelationEntity; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.Query; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Query; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java index 71f9af99e5..991043db88 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java @@ -17,6 +17,8 @@ package org.thingsboard.server.dao.sqlts; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; @@ -38,8 +40,6 @@ import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository; import org.thingsboard.server.dao.sqlts.ts.TsKvRepository; import org.thingsboard.server.dao.timeseries.TimeseriesDao; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Comparator; import java.util.List; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java index ee0dbab8c2..8c82981633 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java index c4c3e021c8..52c4216c69 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java @@ -31,7 +31,7 @@ import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryComposite import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -53,11 +53,15 @@ public abstract class BaseAbstractSqlTimeseriesDao extends JpaAbstractDaoListeni if (keyId == null) { Optional tsKvDictionaryOptional; tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); - if (!tsKvDictionaryOptional.isPresent()) { + if (tsKvDictionaryOptional.isEmpty()) { tsCreationLock.lock(); try { + keyId = tsKvDictionaryMap.get(strKey); + if (keyId != null) { + return keyId; + } tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); - if (!tsKvDictionaryOptional.isPresent()) { + if (tsKvDictionaryOptional.isEmpty()) { TsKvDictionary tsKvDictionary = new TsKvDictionary(); tsKvDictionary.setKey(strKey); try { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java index b58f0dc7c7..2312fa3cb2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java @@ -49,8 +49,8 @@ import org.thingsboard.server.dao.sqlts.latest.TsKvLatestRepository; import org.thingsboard.server.dao.timeseries.TimeseriesLatestDao; import org.thingsboard.server.dao.util.SqlTsLatestAnyDao; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java index 7969e9c3c8..beaa4a5f0d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java @@ -19,8 +19,8 @@ import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; import org.thingsboard.server.dao.util.SqlTsLatestAnyDao; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import java.util.List; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java index 43ad0a18fc..23a1f4e397 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java @@ -19,8 +19,8 @@ import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity; import org.thingsboard.server.dao.util.TimescaleDBTsOrTsLatestDao; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import java.util.List; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java index 42577c2d45..128073323c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java @@ -42,8 +42,8 @@ import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository; import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java index 76b7af20a5..3a907f12bd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java @@ -30,12 +30,12 @@ import java.util.UUID; public interface TsKvRepository extends JpaRepository { /* - * Using native query to avoid adding 'nulls first' or 'nulls last' (ignoring spring.jpa.properties.hibernate.order_by.default_null_ordering) - * to the order so that index scan is done instead of full scan. - * - * Note: even when setting custom NullHandling for the Sort.Order for non-native queries, - * it will be ignored and default_null_ordering will be used - * */ + * Using native query to avoid adding 'nulls first' or 'nulls last' (ignoring spring.jpa.properties.hibernate.order_by.default_null_ordering) + * to the order so that index scan is done instead of full scan. + * + * Note: even when setting custom NullHandling for the Sort.Order for non-native queries, + * it will be ignored and default_null_ordering will be used + * */ @Query(value = "SELECT * FROM ts_kv WHERE entity_id = :entityId " + "AND key = :entityKey AND ts >= :startTs AND ts < :endTs ", nativeQuery = true) List findAllWithLimit(@Param("entityId") UUID entityId, @@ -57,41 +57,41 @@ public interface TsKvRepository extends JpaRepository= :startTs AND tskv.ts < :endTs") TsKvEntity findStringMax(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); @Query("SELECT new TsKvEntity(MAX(COALESCE(tskv.longValue, -9223372036854775807)), " + - "MAX(COALESCE(tskv.doubleValue, -1.79769E+308)), " + + "MAX(COALESCE(tskv.doubleValue, java.lang.Double.MIN_VALUE)), " + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + "'MAX', MAX(tskv.ts)) FROM TsKvEntity tskv " + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") TsKvEntity findNumericMax(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); @Query("SELECT new TsKvEntity(MIN(tskv.strValue), MAX(tskv.ts)) FROM TsKvEntity tskv " + "WHERE tskv.strValue IS NOT NULL " + "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") TsKvEntity findStringMin(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); @Query("SELECT new TsKvEntity(MIN(COALESCE(tskv.longValue, 9223372036854775807)), " + - "MIN(COALESCE(tskv.doubleValue, 1.79769E+308)), " + + "MIN(COALESCE(tskv.doubleValue, java.lang.Double.MAX_VALUE)), " + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + "'MIN', MAX(tskv.ts)) FROM TsKvEntity tskv " + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") TsKvEntity findNumericMin( - @Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); + @Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); @Query("SELECT new TsKvEntity(SUM(CASE WHEN tskv.booleanValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.strValue IS NULL THEN 0 ELSE 1 END), " + @@ -100,9 +100,9 @@ public interface TsKvRepository extends JpaRepository= :startTs AND tskv.ts < :endTs") TsKvEntity findCount(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); @Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " + "SUM(COALESCE(tskv.doubleValue, 0.0)), " + @@ -111,9 +111,9 @@ public interface TsKvRepository extends JpaRepository= :startTs AND tskv.ts < :endTs") TsKvEntity findAvg(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); @Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " + "SUM(COALESCE(tskv.doubleValue, 0.0)), " + @@ -122,8 +122,8 @@ public interface TsKvRepository extends JpaRepository= :startTs AND tskv.ts < :endTs") TsKvEntity findSum(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java index e4aca8ecd1..16655514b3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntryAggWrapper; import org.thingsboard.server.dao.nosql.TbResultSet; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.concurrent.Executor; diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 6f14b526a5..4be65b3a93 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -52,9 +52,9 @@ import org.thingsboard.server.dao.nosql.TbResultSetFuture; import org.thingsboard.server.dao.sqlts.AggregationTimeseriesDao; import org.thingsboard.server.dao.util.NoSqlTsDao; -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java index 9fbf796eb0..e70a6ba4ac 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java @@ -41,7 +41,7 @@ import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.HashMap; import java.util.Map; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractJsonSqlTypeDescriptor.java b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractJsonSqlTypeDescriptor.java deleted file mode 100644 index 9f69d65fc6..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractJsonSqlTypeDescriptor.java +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.util.mapping; - -import org.hibernate.type.descriptor.ValueExtractor; -import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.JavaTypeDescriptor; -import org.hibernate.type.descriptor.sql.BasicExtractor; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; - -import java.sql.CallableStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; - -public abstract class AbstractJsonSqlTypeDescriptor - implements SqlTypeDescriptor { - - @Override - public int getSqlType() { - return Types.VARCHAR; - } - - @Override - public boolean canBeRemapped() { - return true; - } - - @Override - public ValueExtractor getExtractor( - final JavaTypeDescriptor javaTypeDescriptor) { - return new BasicExtractor(javaTypeDescriptor, this) { - @Override - protected X doExtract( - ResultSet rs, - String name, - WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( - rs.getObject(name), options - ); - } - - @Override - protected X doExtract( - CallableStatement statement, - int index, - WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( - statement.getObject(index), options - ); - } - - @Override - protected X doExtract( - CallableStatement statement, - String name, - WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( - statement.getObject(name), options - ); - } - }; - } - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonBinarySqlTypeDescriptor.java b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonBinarySqlTypeDescriptor.java deleted file mode 100644 index e41b998148..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonBinarySqlTypeDescriptor.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.util.mapping; - -import com.fasterxml.jackson.databind.JsonNode; -import org.hibernate.type.descriptor.ValueBinder; -import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.JavaTypeDescriptor; -import org.hibernate.type.descriptor.sql.BasicBinder; - -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Types; - -public class JsonBinarySqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor { - - public static final JsonBinarySqlTypeDescriptor INSTANCE = new JsonBinarySqlTypeDescriptor(); - - @Override - public int getSqlType() { - return Types.OTHER; - } - - @Override - public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { - return new BasicBinder(javaTypeDescriptor, this) { - @Override - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { - st.setObject(index, javaTypeDescriptor.unwrap(value, JsonNode.class, options), getSqlType()); - } - - @Override - protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { - st.setObject(name, javaTypeDescriptor.unwrap(value, JsonNode.class, options), getSqlType()); - } - }; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonBinaryType.java b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonBinaryType.java deleted file mode 100644 index 7b4760a38f..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonBinaryType.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.util.mapping; - -import org.hibernate.type.AbstractSingleColumnStandardBasicType; -import org.hibernate.usertype.DynamicParameterizedType; - -import java.util.Properties; - -public class JsonBinaryType extends AbstractSingleColumnStandardBasicType implements DynamicParameterizedType { - - public JsonBinaryType() { - super( - JsonBinarySqlTypeDescriptor.INSTANCE, - new JsonTypeDescriptor() - ); - } - - public String getName() { - return "jsonb"; - } - - @Override - protected boolean registerUnderJavaType() { - return true; - } - - @Override - public void setParameterValues(Properties parameters) { - ((JsonTypeDescriptor) getJavaTypeDescriptor()) - .setParameterValues(parameters); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonConverter.java b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonConverter.java new file mode 100644 index 0000000000..78c2b8852c --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonConverter.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.util.mapping; + +import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import org.thingsboard.common.util.JacksonUtil; + +@Converter +public class JsonConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(JsonNode jsonNode) { + return JacksonUtil.toString(jsonNode); + } + + @Override + public JsonNode convertToEntityAttribute(String s) { + return JacksonUtil.toJsonNode(s); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonStringSqlTypeDescriptor.java b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonStringSqlTypeDescriptor.java deleted file mode 100644 index 314eb2751f..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonStringSqlTypeDescriptor.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.util.mapping; - -import org.hibernate.type.descriptor.ValueBinder; -import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.JavaTypeDescriptor; -import org.hibernate.type.descriptor.sql.BasicBinder; - -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.SQLException; - -public class JsonStringSqlTypeDescriptor - extends AbstractJsonSqlTypeDescriptor { - - public static final JsonStringSqlTypeDescriptor INSTANCE = - new JsonStringSqlTypeDescriptor(); - - @Override - public ValueBinder getBinder( - final JavaTypeDescriptor javaTypeDescriptor) { - return new BasicBinder(javaTypeDescriptor, this) { - @Override - protected void doBind( - PreparedStatement st, - X value, - int index, - WrapperOptions options) throws SQLException { - st.setString(index, - javaTypeDescriptor.unwrap(value, String.class, options) - ); - } - - @Override - protected void doBind( - CallableStatement st, - X value, - String name, - WrapperOptions options) - throws SQLException { - st.setString(name, - javaTypeDescriptor.unwrap(value, String.class, options - )); - } - }; - } -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonStringType.java b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonStringType.java deleted file mode 100644 index 42627ef27f..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonStringType.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.util.mapping; - -import org.hibernate.type.AbstractSingleColumnStandardBasicType; -import org.hibernate.usertype.DynamicParameterizedType; - -import java.util.Properties; - -public class JsonStringType - extends AbstractSingleColumnStandardBasicType - implements DynamicParameterizedType { - - public JsonStringType() { - super( - JsonStringSqlTypeDescriptor.INSTANCE, - new JsonTypeDescriptor() - ); - } - - public String getName() { - return "json"; - } - - @Override - protected boolean registerUnderJavaType() { - return true; - } - - @Override - public void setParameterValues(Properties parameters) { - ((JsonTypeDescriptor) getJavaTypeDescriptor()) - .setParameterValues(parameters); - } -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonTypeDescriptor.java b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonTypeDescriptor.java deleted file mode 100644 index 0654cab84f..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JsonTypeDescriptor.java +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.util.mapping; - -import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.AbstractTypeDescriptor; -import org.hibernate.type.descriptor.java.MutableMutabilityPlan; -import org.hibernate.usertype.DynamicParameterizedType; -import org.thingsboard.common.util.JacksonUtil; - -import java.util.Properties; - -/** - * Created by Valerii Sosliuk on 5/12/2017. - */ -public class JsonTypeDescriptor - extends AbstractTypeDescriptor - implements DynamicParameterizedType { - - private Class jsonObjectClass; - - @Override - public void setParameterValues(Properties parameters) { - jsonObjectClass = ( (ParameterType) parameters.get( PARAMETER_TYPE ) ) - .getReturnedClass(); - - } - - public JsonTypeDescriptor() { - super( Object.class, new MutableMutabilityPlan() { - @Override - protected Object deepCopyNotNull(Object value) { - return JacksonUtil.clone(value); - } - }); - } - - @Override - public boolean areEqual(Object one, Object another) { - if ( one == another ) { - return true; - } - if ( one == null || another == null ) { - return false; - } - return JacksonUtil.toJsonNode(JacksonUtil.toString(one)).equals( - JacksonUtil.toJsonNode(JacksonUtil.toString(another))); - } - - @Override - public String toString(Object value) { - return JacksonUtil.toString(value); - } - - @Override - public Object fromString(String string) { - return JacksonUtil.fromString(string, jsonObjectClass); - } - - @SuppressWarnings({ "unchecked" }) - @Override - public X unwrap(Object value, Class type, WrapperOptions options) { - if ( value == null ) { - return null; - } - if ( String.class.isAssignableFrom( type ) ) { - return (X) toString(value); - } - if ( Object.class.isAssignableFrom( type ) ) { - return (X) JacksonUtil.toJsonNode(toString(value)); - } - throw unknownUnwrap( type ); - } - - @Override - public Object wrap(X value, WrapperOptions options) { - if ( value == null ) { - return null; - } - return fromString(value.toString()); - } - -} \ No newline at end of file diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/impl/SlackNotificationChannel.java b/monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/impl/SlackNotificationChannel.java index 092ea0b8c6..28b5133402 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/impl/SlackNotificationChannel.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/impl/SlackNotificationChannel.java @@ -24,7 +24,7 @@ import org.springframework.web.client.RestTemplate; import org.thingsboard.monitoring.data.notification.Notification; import org.thingsboard.monitoring.notification.channels.NotificationChannel; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.time.Duration; import java.util.Map; diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/transport/TransportHealthChecker.java b/monitoring/src/main/java/org/thingsboard/monitoring/transport/TransportHealthChecker.java index 0c742e7709..bc6bfb2680 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/transport/TransportHealthChecker.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/transport/TransportHealthChecker.java @@ -31,8 +31,8 @@ import org.thingsboard.monitoring.data.TransportInfo; import org.thingsboard.monitoring.service.MonitoringReporter; import org.thingsboard.monitoring.util.TbStopWatch; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.UUID; @Slf4j diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/transport/TransportMonitoringService.java b/monitoring/src/main/java/org/thingsboard/monitoring/transport/TransportMonitoringService.java index d94cbc9624..18a8013e2b 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/transport/TransportMonitoringService.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/transport/TransportMonitoringService.java @@ -54,7 +54,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.LinkedList; import java.util.List; import java.util.UUID; diff --git a/msa/monitoring/pom.xml b/msa/monitoring/pom.xml index 1fe43219a0..ba96ada39c 100644 --- a/msa/monitoring/pom.xml +++ b/msa/monitoring/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.5.1-SNAPSHOT + 3.6.0-SNAPSHOT msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 0ae03f3980..3e09ac6b98 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -69,6 +69,10 @@ ch.qos.logback logback-classic + + jakarta.annotation + jakarta.annotation-api + org.springframework.boot spring-boot-starter-test diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java index 1e20a842c3..f697f86e8e 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java @@ -20,8 +20,8 @@ import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.mqtt.MqttVersion; import io.netty.handler.ssl.SslContext; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import java.util.Random; @SuppressWarnings({"WeakerAccess", "unused"}) diff --git a/pom.xml b/pom.xml index 4d456b8f4e..fb667a9526 100755 --- a/pom.xml +++ b/pom.xml @@ -36,20 +36,21 @@ ${project.name} /var/log/${pkg.name} /usr/share/${pkg.name} - 1.3.2 - 2.3.2 - 2.3.2 - 2.7.10 - 2.7.10 - 5.3.26 - 5.5.17 - 5.7.7 - 2.7.10 - 3.8.0 - 0.7.0 - 1.7.32 - 2.17.1 - 1.2.10 + 2.1.1 + 4.0.0 + 2.3.1 + 4.0.2 + 3.1.0 + 3.1.0 + 6.0.9 + 6.1.0 + 6.1.0 + 3.1.0 + 4.3.2 + 0.9.1 + 2.0.7 + 2.19.0 + 1.4.7 0.10 4.15.0 4.0.5 @@ -73,8 +74,8 @@ 2.0.0-M5 2.9.0 2.3.30 - 1.6.2 - 4.2.0 + 2.0.1 + 5.5.0 3.8.1 3.21.9 1.42.1 @@ -82,8 +83,9 @@ 1.18.26 1.2.4 1.2.5 - 4.1.91.Final - 2.0.51.Final + 4.1.93.Final + 2.0.61.Final + 1.1.7 1.7.0 4.8.0 3.0.0-M9 @@ -111,25 +113,25 @@ 1.11.747 1.105.0 2.1.0 - 3.2.0 + 3.6.7 1.5.0 1.5.4 1.9.4 3.2.2 - 1.9.0 + 1.11.0 1.0.4TB 3.4.0 - 8.17.0 - 6.0.20.Final - 3.0.0 - 2.0.1.Final - 1.7.2 + 9.6.1 + 8.0.0.Final + 4.0.2 + 3.0.2 + 1.7.3 2.8.5 4.1.0 2.7.2 1.5.2 - 5.8.2 + 5.9.3 2.6.0 5.13.1 1.3.0 @@ -612,6 +614,7 @@ -Xlint:deprecation -Xlint:removal -Xlint:unchecked + -parameters @@ -663,7 +666,7 @@ ${surefire.version} - --illegal-access=permit -XX:+UseStringDeduplication -XX:MaxGCPauseMillis=20 + -XX:+UseStringDeduplication -XX:MaxGCPauseMillis=20 --add-opens java.base/java.lang.reflect=ALL-UNNAMED @@ -1073,15 +1076,20 @@ test - javax.annotation - javax.annotation-api - ${javax-annotation.version} + jakarta.annotation + jakarta.annotation-api + ${jakarta-annotation.version} jakarta.xml.bind jakarta.xml.bind-api ${jakarta.xml.bind-api.version} + + javax.xml.bind + jaxb-api + ${javax.xml.bind-api.version} + org.glassfish.jaxb jaxb-runtime @@ -1149,6 +1157,16 @@ spring-data-commons ${spring-data.version} + + org.springframework.boot + spring-boot-starter-webflux + ${spring-boot.version} + + + io.projectreactor.netty + reactor-netty-http + ${reactor-netty.version} + org.apache.kafka kafka-clients @@ -1218,7 +1236,7 @@ com.sun.mail - javax.mail + jakarta.mail ${mail.version} @@ -1324,12 +1342,32 @@ netty-transport ${netty.version} + + io.netty + netty-transport-classes-epoll + ${netty.version} + + + io.netty + netty-transport-classes-kqueue + ${netty.version} + + + io.netty + netty-transport-native-epoll + ${netty.version} + io.netty netty-transport-native-epoll ${netty.version} linux-x86_64 + + io.netty + netty-transport-native-kqueue + ${netty.version} + io.netty netty-transport-native-kqueue @@ -1901,6 +1939,10 @@ io.jsonwebtoken jjwt-impl + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + @@ -1910,13 +1952,13 @@ org.glassfish - javax.el - ${javax.el.version} + jakarta.el + ${jakarta.el.version} - javax.validation - validation-api - ${javax.validation-api.version} + jakarta.validation + jakarta.validation-api + ${jakarta.validation-api.version} org.owasp.antisamy diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 47d13e5658..eb57b59c3f 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -95,7 +95,7 @@ com.sun.mail - javax.mail + jakarta.mail provided diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 34c6e315f7..c449b590fe 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -84,6 +84,14 @@ spring-web provided + + org.springframework.boot + spring-boot-starter-webflux + + + io.projectreactor.netty + reactor-netty-http + org.apache.kafka kafka-clients @@ -118,7 +126,7 @@ com.sun.mail - javax.mail + jakarta.mail provided diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 6f7bb7cf8d..cbb15f5854 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -39,7 +39,7 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Set; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java index 3377ac9ded..9d458fdc1c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java @@ -43,7 +43,7 @@ import org.thingsboard.server.dao.cassandra.guava.GuavaSession; import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.nosql.TbResultSetFuture; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java index dcaf6698cd..add560431f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java @@ -32,8 +32,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import javax.annotation.Nullable; -import java.io.IOException; +import jakarta.annotation.Nullable; @Slf4j @RuleNode( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java index 9508a6a848..27be78a683 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java @@ -19,8 +19,8 @@ import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.server.common.data.id.NotificationTemplateId; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.UUID; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java index 6e15bd834f..29b2c6029c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java @@ -20,9 +20,9 @@ import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; import org.thingsboard.server.common.data.notification.targets.slack.SlackConversationType; -import javax.validation.Valid; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; @Data public class TbSlackNodeConfiguration implements NodeConfiguration { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 6f9541f5b1..28deb5b4da 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -15,30 +15,19 @@ */ package org.thingsboard.rule.engine.rest; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.handler.ssl.SslContext; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.conn.ssl.DefaultHostnameVerifier; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; -import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; -import org.apache.http.impl.nio.client.HttpAsyncClients; -import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory; -import org.springframework.http.client.Netty4ClientHttpRequestFactory; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureCallback; -import org.springframework.web.client.AsyncRestTemplate; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.client.RestClientResponseException; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec; import org.springframework.web.util.UriComponentsBuilder; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; @@ -51,24 +40,20 @@ import org.thingsboard.rule.engine.credentials.CredentialsType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import reactor.netty.http.client.HttpClient; +import reactor.netty.transport.ProxyProvider; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; -import java.net.Authenticator; -import java.net.PasswordAuthentication; import java.net.URI; import java.nio.charset.StandardCharsets; -import java.security.NoSuchAlgorithmException; -import java.util.Deque; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; @Data @Slf4j -@SuppressWarnings("deprecation") public class TbHttpClient { private static final String STATUS = "status"; @@ -80,86 +65,58 @@ public class TbHttpClient { private final TbRestApiCallNodeConfiguration config; - private EventLoopGroup eventLoopGroup; - private AsyncRestTemplate httpClient; - private Deque>> pendingFutures; + private WebClient webClient; + private Semaphore semaphore; - TbHttpClient(TbRestApiCallNodeConfiguration config, EventLoopGroup eventLoopGroupShared) throws TbNodeException { + TbHttpClient(TbRestApiCallNodeConfiguration config) throws TbNodeException { try { this.config = config; if (config.getMaxParallelRequestsCount() > 0) { - pendingFutures = new ConcurrentLinkedDeque<>(); + semaphore = new Semaphore(config.getMaxParallelRequestsCount()); } + HttpClient httpClient = HttpClient.create(); + if (config.isEnableProxy()) { checkProxyHost(config.getProxyHost()); checkProxyPort(config.getProxyPort()); - String proxyUser; - String proxyPassword; - - CloseableHttpAsyncClient asyncClient; - HttpComponentsAsyncClientHttpRequestFactory requestFactory = new HttpComponentsAsyncClientHttpRequestFactory(); - if (config.isUseSystemProxyProperties()) { checkSystemProxyProperties(); - - asyncClient = HttpAsyncClients.createSystem(); - - proxyUser = System.getProperty("tb.proxy.user"); - proxyPassword = System.getProperty("tb.proxy.password"); - - if (useAuth(proxyUser, proxyPassword)) { - Authenticator.setDefault(new Authenticator() { - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(proxyUser, proxyPassword.toCharArray()); - } - }); - } + //TODO: maybe we should replace it and manually get all system props + httpClient = httpClient.proxyWithSystemProperties(); } else { - HttpAsyncClientBuilder httpAsyncClientBuilder = HttpAsyncClientBuilder.create() - .setSSLHostnameVerifier(new DefaultHostnameVerifier()) - .setSSLContext(SSLContext.getDefault()) - .setProxy(new HttpHost(config.getProxyHost(), config.getProxyPort(), config.getProxyScheme())); - - proxyUser = config.getProxyUser(); - proxyPassword = config.getProxyPassword(); - - if (useAuth(proxyUser, proxyPassword)) { - CredentialsProvider credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials( - new AuthScope(config.getProxyHost(), config.getProxyPort()), - new UsernamePasswordCredentials(proxyUser, proxyPassword) - ); - httpAsyncClientBuilder.setDefaultCredentialsProvider(credsProvider); - } - asyncClient = httpAsyncClientBuilder.build(); + checkProxyHost(config.getProxyHost()); + checkProxyPort(config.getProxyPort()); + + String proxyUser = config.getProxyUser(); + String proxyPassword = config.getProxyPassword(); + + httpClient = httpClient.proxy(options -> + options.type(ProxyProvider.Proxy.HTTP) + .host(config.getProxyHost()) + .port(config.getProxyPort()) + .username(proxyUser) + .password(u -> proxyPassword)); } - - requestFactory.setAsyncClient(asyncClient); - requestFactory.setReadTimeout(config.getReadTimeoutMs()); - httpClient = new AsyncRestTemplate(requestFactory); - } else if (config.isUseSimpleClientHttpFactory()) { + } else if (!config.isUseSimpleClientHttpFactory()) { if (CredentialsType.CERT_PEM == config.getCredentials().getType()) { throw new TbNodeException("Simple HTTP Factory does not support CERT PEM credentials!"); } - httpClient = new AsyncRestTemplate(); + + httpClient = HttpClient.create(); + } else { - Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(getSharedOrCreateEventLoopGroup(eventLoopGroupShared)); - nettyFactory.setSslContext(config.getCredentials().initSslContext()); - nettyFactory.setReadTimeout(config.getReadTimeoutMs()); - httpClient = new AsyncRestTemplate(nettyFactory); + SslContext sslContext = config.getCredentials().initSslContext(); + httpClient = HttpClient.create().secure(t -> t.sslContext(sslContext)); } - } catch (SSLException | NoSuchAlgorithmException e) { - throw new TbNodeException(e); - } - } - EventLoopGroup getSharedOrCreateEventLoopGroup(EventLoopGroup eventLoopGroupShared) { - if (eventLoopGroupShared != null) { - return eventLoopGroupShared; + this.webClient = WebClient.builder() + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .build(); + } catch (SSLException e) { + throw new TbNodeException(e); } - return this.eventLoopGroup = new NioEventLoopGroup(); } private void checkSystemProxyProperties() throws TbNodeException { @@ -176,48 +133,52 @@ public class TbHttpClient { return !StringUtils.isEmpty(proxyUser) && !StringUtils.isEmpty(proxyPassword); } - void destroy() { - if (this.eventLoopGroup != null) { - this.eventLoopGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS); - } - } - public void processMessage(TbContext ctx, TbMsg msg) { - String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg); - HttpHeaders headers = prepareHeaders(msg); - HttpMethod method = HttpMethod.valueOf(config.getRequestMethod()); - HttpEntity entity; - if(HttpMethod.GET.equals(method) || HttpMethod.HEAD.equals(method) || - HttpMethod.OPTIONS.equals(method) || HttpMethod.TRACE.equals(method) || - config.isIgnoreRequestBody()) { - entity = new HttpEntity<>(headers); - } else { - entity = new HttpEntity<>(getData(msg), headers); - } - - URI uri = buildEncodedUri(endpointUrl); - ListenableFuture> future = httpClient.exchange( - uri, method, entity, String.class); - future.addCallback(new ListenableFutureCallback>() { - @Override - public void onFailure(Throwable throwable) { - TbMsg next = processException(ctx, msg, throwable); - ctx.tellFailure(next, throwable); + try { + if (semaphore != null) { + semaphore.tryAcquire(config.getReadTimeoutMs(), TimeUnit.MILLISECONDS); } - @Override - public void onSuccess(ResponseEntity responseEntity) { - if (responseEntity.getStatusCode().is2xxSuccessful()) { - TbMsg next = processResponse(ctx, msg, responseEntity); - ctx.tellSuccess(next); - } else { - TbMsg next = processFailureResponse(ctx, msg, responseEntity); - ctx.tellNext(next, TbRelationTypes.FAILURE); - } + String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg); + HttpMethod method = HttpMethod.valueOf(config.getRequestMethod()); + URI uri = buildEncodedUri(endpointUrl); + + RequestBodySpec request = webClient + .method(method) + .uri(uri) + .headers(headers -> prepareHeaders(headers, msg)); + + if (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method) || + HttpMethod.PATCH.equals(method) || HttpMethod.DELETE.equals(method) || + config.isIgnoreRequestBody()) { + request.body(BodyInserters.fromValue(getData(msg))); } - }); - if (pendingFutures != null) { - processParallelRequests(future); + + request + .retrieve() + .toEntity(String.class) + .subscribe(responseEntity -> { + if (semaphore != null) { + semaphore.release(); + } + + if (responseEntity.getStatusCode().is2xxSuccessful()) { + TbMsg next = processResponse(ctx, msg, responseEntity); + ctx.tellSuccess(next); + } else { + TbMsg next = processFailureResponse(ctx, msg, responseEntity); + ctx.tellNext(next, TbRelationTypes.FAILURE); + } + }, throwable -> { + if (semaphore != null) { + semaphore.release(); // Make sure to release in case of error + } + + TbMsg next = processException(ctx, msg, throwable); + ctx.tellFailure(next, throwable); + }); + } catch (InterruptedException e) { + log.warn("Timeout during waiting for reply!", e); } } @@ -248,7 +209,7 @@ public class TbHttpClient { if (config.isTrimDoubleQuotes()) { final String dataBefore = data; - data = data.replaceAll("^\"|\"$", "");; + data = data.replaceAll("^\"|\"$", ""); log.trace("Trimming double quotes. Before trim: [{}], after trim: [{}]", dataBefore, data); } @@ -257,9 +218,10 @@ public class TbHttpClient { private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { TbMsgMetaData metaData = origMsg.getMetaData(); - metaData.putValue(STATUS, response.getStatusCode().name()); + HttpStatus httpStatus = (HttpStatus) response.getStatusCode(); + metaData.putValue(STATUS, httpStatus.name()); metaData.putValue(STATUS_CODE, response.getStatusCode().value() + ""); - metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); + metaData.putValue(STATUS_REASON, httpStatus.getReasonPhrase()); headersToMetaData(response.getHeaders(), metaData::putValue); String body = response.getBody() == null ? "{}" : response.getBody(); return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, body); @@ -281,10 +243,11 @@ public class TbHttpClient { } private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { + HttpStatus httpStatus = (HttpStatus) response.getStatusCode(); TbMsgMetaData metaData = origMsg.getMetaData(); - metaData.putValue(STATUS, response.getStatusCode().name()); + metaData.putValue(STATUS, httpStatus.name()); metaData.putValue(STATUS_CODE, response.getStatusCode().value() + ""); - metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); + metaData.putValue(STATUS_REASON, httpStatus.getReasonPhrase()); metaData.putValue(ERROR_BODY, response.getBody()); headersToMetaData(response.getHeaders(), metaData::putValue); return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); @@ -302,8 +265,7 @@ public class TbHttpClient { return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); } - private HttpHeaders prepareHeaders(TbMsg msg) { - HttpHeaders headers = new HttpHeaders(); + private void prepareHeaders(HttpHeaders headers, TbMsg msg) { config.getHeaders().forEach((k, v) -> headers.add(TbNodeUtils.processPattern(k, msg), TbNodeUtils.processPattern(v, msg))); ClientCredentials credentials = config.getCredentials(); if (CredentialsType.BASIC == credentials.getType()) { @@ -312,26 +274,6 @@ public class TbHttpClient { String encodedAuthString = new String(Base64.encodeBase64(authString.getBytes(StandardCharsets.UTF_8))); headers.add("Authorization", "Basic " + encodedAuthString); } - return headers; - } - - private void processParallelRequests(ListenableFuture> future) { - pendingFutures.add(future); - if (pendingFutures.size() > config.getMaxParallelRequestsCount()) { - for (int i = 0; i < config.getMaxParallelRequestsCount(); i++) { - try { - ListenableFuture> pendingFuture = pendingFutures.removeFirst(); - try { - pendingFuture.get(config.getReadTimeoutMs(), TimeUnit.MILLISECONDS); - } catch (Exception e) { - log.warn("Timeout during waiting for reply!", e); - pendingFuture.cancel(true); - } - } catch (Exception e) { - log.warn("Failure during waiting for reply!", e); - } - } - } } private static void checkProxyHost(String proxyHost) throws TbNodeException { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java index d2356c69f7..8197315e6c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java @@ -51,7 +51,7 @@ public class TbRestApiCallNode implements TbNode { @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { TbRestApiCallNodeConfiguration config = TbNodeUtils.convert(configuration, TbRestApiCallNodeConfiguration.class); - httpClient = new TbHttpClient(config, ctx.getSharedEventLoop()); + httpClient = new TbHttpClient(config); useRedisQueueForMsgPersistence = config.isUseRedisQueueForMsgPersistence(); if (useRedisQueueForMsgPersistence) { log.warn("[{}][{}] Usage of Redis Template is deprecated starting 2.5 and will have no affect", ctx.getTenantId(), ctx.getSelfId()); @@ -63,11 +63,4 @@ public class TbRestApiCallNode implements TbNode { httpClient.processMessage(ctx, msg); } - @Override - public void destroy() { - if (this.httpClient != null) { - this.httpClient.destroy(); - } - } - } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesDeleteNodeCallback.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesDeleteNodeCallback.java index 200ab76263..a1c6b11659 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesDeleteNodeCallback.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesDeleteNodeCallback.java @@ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.server.common.msg.TbMsg; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.List; @Slf4j diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesUpdateNodeCallback.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesUpdateNodeCallback.java index 3696812d87..6e94b85be5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesUpdateNodeCallback.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesUpdateNodeCallback.java @@ -19,7 +19,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.msg.TbMsg; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import java.util.List; public class AttributesUpdateNodeCallback extends TelemetryNodeCallback { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TelemetryNodeCallback.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TelemetryNodeCallback.java index ce3e9d2455..9958ee106e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TelemetryNodeCallback.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TelemetryNodeCallback.java @@ -20,7 +20,7 @@ import lombok.Data; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.server.common.msg.TbMsg; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; /** * Created by ashvayka on 02.04.18. diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java index 14ab6cfbbf..46e31d0f69 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java @@ -16,11 +16,8 @@ package org.thingsboard.rule.engine.rest; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; import org.assertj.core.api.Assertions; import org.awaitility.Awaitility; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -28,7 +25,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.mockserver.integration.ClientAndServer; import org.springframework.util.LinkedMultiValueMap; -import org.springframework.web.client.AsyncRestTemplate; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; @@ -41,9 +37,6 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.willCallRealMethod; @@ -57,32 +50,11 @@ import static org.mockserver.model.HttpResponse.response; public class TbHttpClientTest { - EventLoopGroup eventLoop; TbHttpClient client; @Before public void setUp() throws Exception { client = mock(TbHttpClient.class); - willCallRealMethod().given(client).getSharedOrCreateEventLoopGroup(any()); - } - - @After - public void tearDown() throws Exception { - if (eventLoop != null) { - eventLoop.shutdownGracefully(); - } - } - - @Test - public void givenSharedEventLoop_whenGetEventLoop_ThenReturnShared() { - eventLoop = mock(EventLoopGroup.class); - assertThat(client.getSharedOrCreateEventLoopGroup(eventLoop), is(eventLoop)); - } - - @Test - public void givenNull_whenGetEventLoop_ThenReturnShared() { - eventLoop = client.getSharedOrCreateEventLoopGroup(null); - assertThat(eventLoop, instanceOf(NioEventLoopGroup.class)); } @Test @@ -139,10 +111,7 @@ public class TbHttpClientTest { config.setRestEndpointUrlPattern(endpointUrl); config.setUseSimpleClientHttpFactory(true); - var asyncRestTemplate = new AsyncRestTemplate(); - - var httpClient = new TbHttpClient(config, eventLoop); - httpClient.setHttpClient(asyncRestTemplate); + var httpClient = new TbHttpClient(config); var msg = TbMsg.newMsg("GET", new DeviceId(EntityId.NULL_UUID), TbMsgMetaData.EMPTY, "{}"); var successMsg = TbMsg.newMsg( diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java index 9185a31118..2b6698d415 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java @@ -16,7 +16,6 @@ package org.thingsboard.rule.engine.rest; import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; @@ -49,14 +48,13 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class TbRestApiCallNodeTest { - + private TbRestApiCallNode restNode; @Mock @@ -68,17 +66,17 @@ public class TbRestApiCallNodeTest { private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - private HttpServer server; + private HttpServer server; public void setupServer(String pattern, HttpRequestHandler handler) throws IOException { - SocketConfig config = SocketConfig.custom().setSoReuseAddress(true).setTcpNoDelay(true).build(); - server = ServerBootstrap.bootstrap() + SocketConfig config = SocketConfig.custom().setSoReuseAddress(true).setTcpNoDelay(true).build(); + server = ServerBootstrap.bootstrap() .setSocketConfig(config) - .registerHandler(pattern, handler) - .create(); + .registerHandler(pattern, handler) + .create(); server.start(); } - + private void initWithConfig(TbRestApiCallNodeConfiguration config) { try { TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); @@ -93,19 +91,18 @@ public class TbRestApiCallNodeTest { public void teardown() { server.stop(); } - + @Test public void deleteRequestWithoutBody() throws IOException, InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final String path = "/path/to/delete"; - setupServer("*", new HttpRequestHandler() { - - @Override - public void handle(HttpRequest request, HttpResponse response, HttpContext context) - throws HttpException, IOException { + setupServer("*", new HttpRequestHandler() { + + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) + throws HttpException, IOException { try { assertEquals("Request path matches", request.getRequestLine().getUri(), path); - assertFalse("Content-Type not included", request.containsHeader("Content-Type")); assertTrue("Custom header included", request.containsHeader("Foo")); assertEquals("Custom header value", "Bar", request.getFirstHeader("Foo").getValue()); response.setStatusCode(200); @@ -121,13 +118,13 @@ public class TbRestApiCallNodeTest { } } }).start(); - } catch ( Exception e ) { + } catch (Exception e) { System.out.println("Exception handling request: " + e.toString()); e.printStackTrace(); latch.countDown(); } } - }); + }); TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); config.setRequestMethod("DELETE"); @@ -136,7 +133,7 @@ public class TbRestApiCallNodeTest { config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path)); initWithConfig(config); - TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); restNode.onMsg(ctx, msg); assertTrue("Server handled request", latch.await(10, TimeUnit.SECONDS)); @@ -167,7 +164,7 @@ public class TbRestApiCallNodeTest { try { assertEquals("Request path matches", path, request.getRequestLine().getUri()); assertTrue("Content-Type included", request.containsHeader("Content-Type")); - assertEquals("Content-Type value", "text/plain;charset=ISO-8859-1", + assertEquals("Content-Type value", "text/plain;charset=UTF-8", request.getFirstHeader("Content-Type").getValue()); assertTrue("Content-Length included", request.containsHeader("Content-Length")); assertEquals("Content-Length value", "2", @@ -187,7 +184,7 @@ public class TbRestApiCallNodeTest { } } }).start(); - } catch ( Exception e ) { + } catch (Exception e) { System.out.println("Exception handling request: " + e.toString()); e.printStackTrace(); latch.countDown(); @@ -202,7 +199,7 @@ public class TbRestApiCallNodeTest { config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path)); initWithConfig(config); - TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); restNode.onMsg(ctx, msg); assertTrue("Server handled request", latch.await(10, TimeUnit.SECONDS)); @@ -213,7 +210,7 @@ public class TbRestApiCallNodeTest { ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - + assertEquals("USER", typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertNotSame(metaData, metadataCaptor.getValue()); From ae2fd08c36d4d335da2969b105b0a442b87e8be8 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 13 Mar 2023 22:32:44 +0100 Subject: [PATCH 012/209] refactored HttpClient proxy --- .../rule/engine/rest/TbHttpClient.java | 122 +++++++++++++++--- 1 file changed, 102 insertions(+), 20 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 28deb5b4da..7a5ca407af 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.rest; import io.netty.handler.ssl.SslContext; +import io.netty.handler.timeout.ReadTimeoutHandler; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; @@ -48,6 +49,7 @@ import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; @@ -63,6 +65,19 @@ public class TbHttpClient { private static final String ERROR_BODY = "error_body"; private static final String ERROR_SYSTEM_PROPERTIES = "Didn't set any system proxy properties. Should be added next system proxy properties: \"http.proxyHost\" and \"http.proxyPort\" or \"https.proxyHost\" and \"https.proxyPort\" or \"socksProxyHost\" and \"socksProxyPort\""; + private static final String HTTP_PROXY_HOST = "http.proxyHost"; + private static final String HTTP_PROXY_PORT = "http.proxyPort"; + private static final String HTTPS_PROXY_HOST = "https.proxyHost"; + private static final String HTTPS_PROXY_PORT = "https.proxyPort"; + + private static final String SOCKS_PROXY_HOST = "socksProxyHost"; + private static final String SOCKS_PROXY_PORT = "socksProxyPort"; + private static final String SOCKS_VERSION = "socksProxyVersion"; + private static final String SOCKS_VERSION_5 = "5"; + private static final String SOCKS_VERSION_4 = "4"; + public static final String PROXY_USER = "tb.proxy.user"; + public static final String PROXY_PASSWORD = "tb.proxy.password"; + private final TbRestApiCallNodeConfiguration config; private WebClient webClient; @@ -75,29 +90,29 @@ public class TbHttpClient { semaphore = new Semaphore(config.getMaxParallelRequestsCount()); } - HttpClient httpClient = HttpClient.create(); + HttpClient httpClient = HttpClient.create() + .doOnConnected(c -> + c.addHandlerLast(new ReadTimeoutHandler(config.getReadTimeoutMs(), TimeUnit.MILLISECONDS))); if (config.isEnableProxy()) { - checkProxyHost(config.getProxyHost()); - checkProxyPort(config.getProxyPort()); - if (config.isUseSystemProxyProperties()) { checkSystemProxyProperties(); - //TODO: maybe we should replace it and manually get all system props - httpClient = httpClient.proxyWithSystemProperties(); + httpClient = httpClient.proxy(this::createSystemProxyProvider); } else { checkProxyHost(config.getProxyHost()); checkProxyPort(config.getProxyPort()); - String proxyUser = config.getProxyUser(); String proxyPassword = config.getProxyPassword(); - httpClient = httpClient.proxy(options -> - options.type(ProxyProvider.Proxy.HTTP) - .host(config.getProxyHost()) - .port(config.getProxyPort()) - .username(proxyUser) - .password(u -> proxyPassword)); + httpClient = httpClient.proxy(options -> { + var o = options.type(ProxyProvider.Proxy.HTTP) + .host(config.getProxyHost()) + .port(config.getProxyPort()); + + if (useAuth(proxyUser, proxyPassword)) { + o.username(proxyUser).password(u -> proxyPassword); + } + }); } } else if (!config.isUseSimpleClientHttpFactory()) { if (CredentialsType.CERT_PEM == config.getCredentials().getType()) { @@ -135,8 +150,9 @@ public class TbHttpClient { public void processMessage(TbContext ctx, TbMsg msg) { try { - if (semaphore != null) { - semaphore.tryAcquire(config.getReadTimeoutMs(), TimeUnit.MILLISECONDS); + if (semaphore != null && !semaphore.tryAcquire(config.getReadTimeoutMs(), TimeUnit.MILLISECONDS)) { + ctx.tellFailure(msg, new RuntimeException("Timeout during waiting for reply!")); + return; } String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg); @@ -171,7 +187,7 @@ public class TbHttpClient { } }, throwable -> { if (semaphore != null) { - semaphore.release(); // Make sure to release in case of error + semaphore.release(); } TbMsg next = processException(ctx, msg, throwable); @@ -276,15 +292,81 @@ public class TbHttpClient { } } - private static void checkProxyHost(String proxyHost) throws TbNodeException { + private static void checkProxyHost(String proxyHost) { if (StringUtils.isEmpty(proxyHost)) { - throw new TbNodeException("Proxy host can't be empty"); + throw new IllegalArgumentException("Proxy host can't be empty"); } } - private static void checkProxyPort(int proxyPort) throws TbNodeException { + private static void checkProxyPort(int proxyPort) { if (proxyPort < 0 || proxyPort > 65535) { - throw new TbNodeException("Proxy port out of range:" + proxyPort); + throw new IllegalArgumentException("Proxy port out of range:" + proxyPort); + } + } + + private void createSystemProxyProvider(ProxyProvider.TypeSpec option) { + Properties properties = System.getProperties(); + if (properties.containsKey(HTTP_PROXY_HOST) || properties.containsKey(HTTPS_PROXY_HOST)) { + createHttpProxyFrom(option, properties); + } + if (properties.containsKey(SOCKS_PROXY_HOST)) { + createSocksProxyFrom(option, properties); + } + } + + private void createHttpProxyFrom(ProxyProvider.TypeSpec option, Properties properties) { + String hostProperty; + String portProperty; + if (properties.containsKey(HTTPS_PROXY_HOST)) { + hostProperty = HTTPS_PROXY_HOST; + portProperty = HTTPS_PROXY_PORT; + } else { + hostProperty = HTTP_PROXY_HOST; + portProperty = HTTP_PROXY_PORT; + } + + String hostname = properties.getProperty(hostProperty); + int port = Integer.parseInt(properties.getProperty(portProperty)); + + checkProxyHost(config.getProxyHost()); + checkProxyPort(config.getProxyPort()); + + var proxy = option + .type(ProxyProvider.Proxy.HTTP) + .host(hostname) + .port(port); + + var proxyUser = properties.getProperty(PROXY_USER); + var proxyPassword = properties.getProperty(PROXY_PASSWORD); + + if (useAuth(proxyUser, proxyPassword)) { + proxy.username(proxyUser).password(u -> proxyPassword); + } + } + + private void createSocksProxyFrom(ProxyProvider.TypeSpec option, Properties properties) { + String hostname = properties.getProperty(SOCKS_PROXY_HOST); + String version = properties.getProperty(SOCKS_VERSION, SOCKS_VERSION_5); + if (!SOCKS_VERSION_5.equals(version) && !SOCKS_VERSION_4.equals(version)) { + throw new IllegalArgumentException(String.format("Wrong socks version %s! Supported only socks versions 4 and 5.", version)); + } + + ProxyProvider.Proxy type = SOCKS_VERSION_5.equals(version) ? ProxyProvider.Proxy.SOCKS5 : ProxyProvider.Proxy.SOCKS4; + int port = Integer.parseInt(properties.getProperty(SOCKS_PROXY_PORT)); + + checkProxyHost(config.getProxyHost()); + checkProxyPort(config.getProxyPort()); + + ProxyProvider.Builder proxy = option + .type(type) + .host(hostname) + .port(port); + + var proxyUser = properties.getProperty(PROXY_USER); + var proxyPassword = properties.getProperty(PROXY_PASSWORD); + + if (useAuth(proxyUser, proxyPassword)) { + proxy.username(proxyUser).password(u -> proxyPassword); } } From ce87bc5e13bf883d5f9e2001244000736bd51249 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 12 Jun 2023 13:25:37 +0200 Subject: [PATCH 013/209] to springdoc migration --- application/pom.xml | 4 +- .../server/ThingsboardInstallApplication.java | 3 +- .../server/config/SwaggerConfiguration.java | 506 +++++++----------- .../config/annotations/ApiOperation.java | 45 ++ .../controller/AbstractRpcController.java | 2 +- .../server/controller/AdminController.java | 24 +- .../controller/AlarmCommentController.java | 31 +- .../server/controller/AlarmController.java | 159 +++--- .../server/controller/AssetController.java | 147 ++--- .../controller/AssetProfileController.java | 57 +- .../server/controller/AuditLogController.java | 89 +-- .../server/controller/AuthController.java | 20 +- .../server/controller/BaseController.java | 6 +- .../ComponentDescriptorController.java | 15 +- .../controller/ControllerConstants.java | 35 +- .../server/controller/CustomerController.java | 29 +- .../controller/DashboardController.java | 145 +++-- .../server/controller/DeviceController.java | 155 +++--- .../controller/DeviceProfileController.java | 69 ++- .../server/controller/EdgeController.java | 139 ++--- .../controller/EdgeEventController.java | 27 +- .../EntitiesVersionControlController.java | 70 +-- .../controller/EntityQueryController.java | 18 +- .../controller/EntityRelationController.java | 98 ++-- .../controller/EntityViewController.java | 94 ++-- .../server/controller/EventController.java | 89 +-- .../server/controller/Lwm2mController.java | 11 +- .../controller/NotificationController.java | 30 +- .../NotificationRuleController.java | 16 +- .../NotificationTargetController.java | 22 +- .../NotificationTemplateController.java | 20 +- .../OAuth2ConfigTemplateController.java | 6 +- .../server/controller/OAuth2Controller.java | 16 +- .../controller/OtaPackageController.java | 69 ++- .../server/controller/QueueController.java | 30 +- .../server/controller/RpcV1Controller.java | 12 +- .../server/controller/RpcV2Controller.java | 56 +- .../controller/RuleChainController.java | 76 +-- .../controller/SystemInfoController.java | 6 +- .../controller/TbResourceController.java | 59 +- .../controller/TelemetryController.java | 253 +++++---- .../server/controller/TenantController.java | 36 +- .../controller/TenantProfileController.java | 38 +- .../TwoFactorAuthConfigController.java | 11 +- .../controller/TwoFactorAuthController.java | 4 +- .../controller/UiSettingsController.java | 2 +- .../server/controller/UserController.java | 97 ++-- .../controller/WidgetTypeController.java | 28 +- .../controller/WidgetsBundleController.java | 23 +- .../controller/plugin/TbWebSocketHandler.java | 8 +- ...ThingsboardCredentialsExpiredResponse.java | 7 +- .../exception/ThingsboardErrorResponse.java | 17 +- .../security/auth/rest/LoginRequest.java | 9 +- .../security/auth/rest/LoginResponse.java | 9 +- .../security/model/ActivateUserRequest.java | 9 +- .../security/model/ChangePasswordRequest.java | 9 +- .../model/ResetPasswordEmailRequest.java | 7 +- .../security/model/ResetPasswordRequest.java | 9 +- .../service/telemetry/AttributeData.java | 11 +- .../server/service/telemetry/TsData.java | 9 +- ...ngfoxHandlerProviderBeanPostProcessor.java | 59 -- .../src/main/resources/thingsboard.yml | 8 +- common/data/pom.xml | 4 +- .../server/common/data/AdminSettings.java | 15 +- .../server/common/data/Customer.java | 31 +- .../server/common/data/Dashboard.java | 6 +- .../server/common/data/DashboardInfo.java | 23 +- .../server/common/data/Device.java | 29 +- .../server/common/data/DeviceInfo.java | 13 +- .../server/common/data/DeviceProfile.java | 41 +- .../server/common/data/DeviceProfileInfo.java | 12 +- .../server/common/data/EntityInfo.java | 9 +- .../server/common/data/EntityView.java | 24 +- .../server/common/data/EntityViewInfo.java | 6 +- .../server/common/data/EventInfo.java | 17 +- .../server/common/data/ExportableEntity.java | 4 +- .../server/common/data/HomeDashboard.java | 7 +- .../server/common/data/HomeDashboardInfo.java | 9 +- .../server/common/data/OtaPackage.java | 7 +- .../server/common/data/OtaPackageInfo.java | 37 +- .../SaveDeviceWithCredentialsRequest.java | 9 +- .../data/SaveOtaPackageInfoRequest.java | 7 +- .../server/common/data/ShortCustomerInfo.java | 11 +- .../server/common/data/SystemInfo.java | 6 +- .../server/common/data/SystemInfoData.java | 18 +- .../server/common/data/TbResource.java | 6 +- .../server/common/data/TbResourceInfo.java | 19 +- .../server/common/data/Tenant.java | 35 +- .../server/common/data/TenantInfo.java | 7 +- .../server/common/data/TenantProfile.java | 19 +- .../server/common/data/UpdateMessage.java | 17 +- .../thingsboard/server/common/data/User.java | 27 +- .../server/common/data/UserEmailInfo.java | 13 +- .../server/common/data/alarm/Alarm.java | 49 +- .../common/data/alarm/AlarmComment.java | 19 +- .../common/data/alarm/AlarmCommentInfo.java | 11 +- .../AlarmCreateOrUpdateActiveRequest.java | 20 +- .../server/common/data/alarm/AlarmInfo.java | 11 +- .../data/alarm/AlarmPropagationInfo.java | 10 +- .../common/data/alarm/AlarmUpdateRequest.java | 16 +- .../server/common/data/asset/Asset.java | 23 +- .../server/common/data/asset/AssetInfo.java | 11 +- .../common/data/asset/AssetProfile.java | 27 +- .../common/data/asset/AssetProfileInfo.java | 8 +- .../common/data/asset/AssetSearchQuery.java | 8 +- .../server/common/data/audit/AuditLog.java | 29 +- .../common/data/device/DeviceSearchQuery.java | 11 +- .../data/DefaultDeviceConfiguration.java | 4 +- .../data/device/data/DeviceConfiguration.java | 4 +- .../common/data/device/data/DeviceData.java | 9 +- .../data/DeviceTransportConfiguration.java | 4 +- .../data/device/profile/AlarmCondition.java | 9 +- .../device/profile/AlarmConditionFilter.java | 13 +- .../profile/AlarmConditionFilterKey.java | 9 +- .../common/data/device/profile/AlarmRule.java | 13 +- .../device/profile/DeviceProfileAlarm.java | 21 +- .../device/profile/DeviceProfileData.java | 13 +- .../bootstrap/LwM2MServerSecurityConfig.java | 43 +- .../LwM2MServerSecurityConfigDefault.java | 9 +- .../server/common/data/edge/Edge.java | 25 +- .../data/edge/EdgeInstallInstructions.java | 7 +- .../common/data/edge/EdgeSearchQuery.java | 8 +- .../entityview/EntityViewSearchQuery.java | 11 +- .../common/data/event/DebugEventFilter.java | 11 +- .../common/data/event/ErrorEventFilter.java | 11 +- .../server/common/data/event/EventFilter.java | 7 +- .../data/event/LifeCycleEventFilter.java | 13 +- .../data/event/RuleChainDebugEventFilter.java | 7 +- .../data/event/RuleNodeDebugEventFilter.java | 21 +- .../data/event/StatisticsEventFilter.java | 15 +- .../server/common/data/id/AlarmCommentId.java | 4 +- .../server/common/data/id/AlarmId.java | 7 +- .../common/data/id/ApiUsageStateId.java | 7 +- .../server/common/data/id/AssetId.java | 7 +- .../server/common/data/id/AssetProfileId.java | 4 +- .../server/common/data/id/CustomerId.java | 7 +- .../server/common/data/id/DashboardId.java | 7 +- .../server/common/data/id/DeviceId.java | 7 +- .../common/data/id/DeviceProfileId.java | 4 +- .../server/common/data/id/EdgeId.java | 4 +- .../server/common/data/id/EntityId.java | 9 +- .../server/common/data/id/EntityViewId.java | 4 +- .../server/common/data/id/OtaPackageId.java | 4 +- .../server/common/data/id/RpcId.java | 4 +- .../server/common/data/id/RuleChainId.java | 4 +- .../server/common/data/id/RuleNodeId.java | 4 +- .../server/common/data/id/TbResourceId.java | 4 +- .../server/common/data/id/TenantId.java | 4 +- .../common/data/id/TenantProfileId.java | 4 +- .../server/common/data/id/UUIDBased.java | 7 +- .../server/common/data/id/UserId.java | 4 +- .../server/common/data/id/WidgetTypeId.java | 4 +- .../common/data/id/WidgetsBundleId.java | 4 +- .../common/data/lwm2m/LwM2mInstance.java | 9 +- .../server/common/data/lwm2m/LwM2mObject.java | 17 +- .../data/lwm2m/LwM2mResourceObserve.java | 17 +- .../data/oauth2/OAuth2BasicMapperConfig.java | 21 +- .../common/data/oauth2/OAuth2ClientInfo.java | 11 +- .../OAuth2ClientRegistrationTemplate.java | 31 +- .../common/data/oauth2/OAuth2DomainInfo.java | 9 +- .../server/common/data/oauth2/OAuth2Info.java | 9 +- .../data/oauth2/OAuth2MapperConfig.java | 12 +- .../common/data/oauth2/OAuth2MobileInfo.java | 9 +- .../common/data/oauth2/OAuth2ParamsInfo.java | 11 +- .../data/oauth2/OAuth2RegistrationInfo.java | 33 +- .../data/objects/AttributesEntityView.java | 11 +- .../data/objects/TelemetryEntityView.java | 9 +- .../server/common/data/page/PageData.java | 13 +- .../data/plugin/ComponentDescriptor.java | 23 +- .../common/data/query/EntityCountQuery.java | 4 +- .../server/common/data/query/EntityKey.java | 4 +- .../server/common/data/query/KeyFilter.java | 4 +- .../common/data/relation/EntityRelation.java | 15 +- .../data/relation/EntityRelationInfo.java | 6 +- .../data/relation/EntityRelationsQuery.java | 9 +- .../relation/RelationEntityTypeFilter.java | 9 +- .../relation/RelationsSearchParameters.java | 17 +- .../server/common/data/rpc/Rpc.java | 23 +- .../rule/DefaultRuleChainCreateRequest.java | 7 +- .../common/data/rule/NodeConnectionInfo.java | 11 +- .../server/common/data/rule/RuleChain.java | 23 +- .../data/rule/RuleChainConnectionInfo.java | 13 +- .../common/data/rule/RuleChainData.java | 9 +- .../common/data/rule/RuleChainMetaData.java | 15 +- .../data/rule/RuleChainOutputLabelsUsage.java | 15 +- .../server/common/data/rule/RuleNode.java | 23 +- .../data/security/DeviceCredentials.java | 17 +- .../common/data/security/model/JwtPair.java | 9 +- .../data/security/model/JwtSettings.java | 13 +- .../data/security/model/SecuritySettings.java | 11 +- .../security/model/UserPasswordPolicy.java | 21 +- .../settings/AbstractUserDashboardInfo.java | 9 +- .../settings/LastVisitedDashboardInfo.java | 9 +- .../data/settings/StarredDashboardInfo.java | 7 +- .../data/settings/UserDashboardsInfo.java | 9 +- .../common/data/settings/UserSettings.java | 11 +- .../AwsSnsSmsProviderConfiguration.java | 11 +- .../config/SmppSmsProviderConfiguration.java | 34 +- .../data/sms/config/TestSmsRequest.java | 11 +- .../TwilioSmsProviderConfiguration.java | 11 +- .../tenant/profile/TenantProfileData.java | 9 +- .../common/data/widget/BaseWidgetType.java | 14 +- .../server/common/data/widget/WidgetType.java | 4 +- .../common/data/widget/WidgetTypeDetails.java | 6 +- .../common/data/widget/WidgetTypeInfo.java | 8 +- .../common/data/widget/WidgetsBundle.java | 21 +- .../transport/http/DeviceApiController.java | 133 ++--- pom.xml | 30 +- 208 files changed, 2462 insertions(+), 2712 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/config/annotations/ApiOperation.java delete mode 100644 application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java diff --git a/application/pom.xml b/application/pom.xml index 1838c97c65..f473986211 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -262,8 +262,8 @@ opensmpp-core - org.thingsboard - springfox-boot-starter + org.springdoc + springdoc-openapi-starter-webmvc-ui com.sun.winsw diff --git a/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java b/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java index b780f0e8cd..5b80e21fe6 100644 --- a/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java +++ b/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java @@ -33,8 +33,7 @@ import java.util.Arrays; "org.thingsboard.server.dao", "org.thingsboard.server.common.stats", "org.thingsboard.server.common.transport.config.ssl", - "org.thingsboard.server.cache", - "org.thingsboard.server.springfox" + "org.thingsboard.server.cache" }) public class ThingsboardInstallApplication { diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index c7616ed865..50104e866d 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -15,84 +15,58 @@ */ package org.thingsboard.server.config; -import com.fasterxml.classmate.TypeResolver; +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.examples.Example; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.Content; +import io.swagger.v3.oas.models.media.MediaType; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.RequestBody; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.responses.ApiResponses; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.tags.Tag; import lombok.extern.slf4j.Slf4j; +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springdoc.core.models.GroupedOpenApi; +import org.springdoc.core.properties.SwaggerUiConfigProperties; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; -import org.springframework.core.annotation.Order; -import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; -import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.exception.ThingsboardCredentialsExpiredResponse; import org.thingsboard.server.exception.ThingsboardErrorResponse; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.rest.LoginRequest; import org.thingsboard.server.service.security.auth.rest.LoginResponse; -import springfox.documentation.builders.ApiInfoBuilder; -import springfox.documentation.builders.ExampleBuilder; -import springfox.documentation.builders.OperationBuilder; -import springfox.documentation.builders.RepresentationBuilder; -import springfox.documentation.builders.RequestParameterBuilder; -import springfox.documentation.builders.ResponseBuilder; -import springfox.documentation.schema.Example; -import springfox.documentation.service.ApiDescription; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.service.ApiListing; -import springfox.documentation.service.AuthorizationScope; -import springfox.documentation.service.Contact; -import springfox.documentation.service.HttpLoginPasswordScheme; -import springfox.documentation.service.ParameterType; -import springfox.documentation.service.Response; -import springfox.documentation.service.SecurityReference; -import springfox.documentation.service.SecurityScheme; -import springfox.documentation.service.Tag; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spi.service.ApiListingBuilderPlugin; -import springfox.documentation.spi.service.ApiListingScannerPlugin; -import springfox.documentation.spi.service.contexts.ApiListingContext; -import springfox.documentation.spi.service.contexts.DocumentationContext; -import springfox.documentation.spi.service.contexts.OperationContext; -import springfox.documentation.spi.service.contexts.SecurityContext; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.spring.web.readers.operation.CachingOperationNameGenerator; -import springfox.documentation.swagger.common.SwaggerPluginSupport; -import springfox.documentation.swagger.web.DocExpansion; -import springfox.documentation.swagger.web.ModelRendering; -import springfox.documentation.swagger.web.OperationsSorter; -import springfox.documentation.swagger.web.UiConfiguration; -import springfox.documentation.swagger.web.UiConfigurationBuilder; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; + import java.util.List; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Predicate; +import java.util.Map; -import static com.google.common.collect.Lists.newArrayList; -import static java.util.function.Predicate.not; -import static springfox.documentation.builders.PathSelectors.any; -import static springfox.documentation.builders.PathSelectors.regex; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @Slf4j -//@Configuration +@Configuration @TbCoreComponent @Profile("!test") public class SwaggerConfiguration { - @Value("${swagger.enabled:true}") - private boolean enabled; - @Value("${swagger.api_path_regex}") - private String apiPathRegex; - @Value("${swagger.security_path_regex}") - private String securityPathRegex; + public static final String LOGIN_ENDPOINT = "/api/auth/login"; + + @Value("${swagger.api_path:/api/**}") + private String apiPath; @Value("${swagger.non_security_path_regex}") private String nonSecurityPathRegex; @Value("${swagger.title}") @@ -115,288 +89,206 @@ public class SwaggerConfiguration { private String appVersion; @Bean - public Docket thingsboardApi() { - TypeResolver typeResolver = new TypeResolver(); - return new Docket(DocumentationType.OAS_30) - .enable(enabled) - .groupName("thingsboard") - .apiInfo(apiInfo()) - .additionalModels( - typeResolver.resolve(ThingsboardErrorResponse.class), - typeResolver.resolve(ThingsboardCredentialsExpiredResponse.class), - typeResolver.resolve(LoginRequest.class), - typeResolver.resolve(LoginResponse.class) - ) - .select() - .paths(apiPaths()) - .paths(any()) - .build() - .globalResponses(HttpMethod.GET, - defaultErrorResponses(false) - ) - .globalResponses(HttpMethod.POST, - defaultErrorResponses(true) - ) - .globalResponses(HttpMethod.DELETE, - defaultErrorResponses(false) - ) - .securitySchemes(newArrayList(httpLogin())) - .securityContexts(newArrayList(securityContext())) - .ignoredParameterTypes(AuthenticationPrincipal.class) - .enableUrlTemplating(true); - } + public OpenAPI tbOpenAPI() { + Contact contact = new Contact() + .name(contactName) + .url(contactUrl) + .email(contactEmail); - @Bean - @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER) - ApiListingScannerPlugin loginEndpointListingScanner(final CachingOperationNameGenerator operationNames) { - return new ApiListingScannerPlugin() { - @Override - public List apply(DocumentationContext context) { - return List.of(loginEndpointApiDescription(operationNames)); - } - - @Override - public boolean supports(DocumentationType delimiter) { - return DocumentationType.SWAGGER_2.equals(delimiter) || DocumentationType.OAS_30.equals(delimiter); - } - }; - } + License license = new License() + .name(licenseTitle) + .url(licenseUrl); - @Bean - @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER) - ApiListingBuilderPlugin loginEndpointListingBuilder() { - return new ApiListingBuilderPlugin() { - @Override - public void apply(ApiListingContext apiListingContext) { - if (apiListingContext.getResourceGroup().getGroupName().equals("default")) { - ApiListing apiListing = apiListingContext.apiListingBuilder().build(); - if (apiListing.getResourcePath().equals("/api/auth/login")) { - apiListingContext.apiListingBuilder().tags(Set.of(new Tag("login-endpoint", "Login Endpoint"))); - apiListingContext.apiListingBuilder().description("Login Endpoint"); - } - } - } + String apiVersion = version; + if (StringUtils.isEmpty(apiVersion)) { + apiVersion = appVersion; + } - @Override - public boolean supports(DocumentationType delimiter) { - return DocumentationType.SWAGGER_2.equals(delimiter) || DocumentationType.OAS_30.equals(delimiter); - } - }; + Info info = new Info() + .title(title) + .description(description) + .contact(contact) + .license(license) + .version(apiVersion); + + SecurityScheme securityScheme = new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("basic") + .bearerFormat("JWT") + .in(SecurityScheme.In.HEADER) + .description("Enter Username / Password"); + + var openApi = new OpenAPI() + .components(new Components().addSecuritySchemes("HTTP login form", securityScheme)) + .info(info); + addLoginOperation(openApi); + return openApi; } - @Bean - UiConfiguration uiConfig() { - return UiConfigurationBuilder.builder() - .deepLinking(true) - .displayOperationId(false) - .defaultModelsExpandDepth(1) - .defaultModelExpandDepth(1) - .defaultModelRendering(ModelRendering.EXAMPLE) - .displayRequestDuration(false) - .docExpansion(DocExpansion.NONE) - .filter(false) - .maxDisplayedTags(null) - .operationsSorter(OperationsSorter.ALPHA) - .showExtensions(false) - .showCommonExtensions(false) - .supportedSubmitMethods(UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS) - .validatorUrl(null) - .persistAuthorization(true) - .syntaxHighlightActivate(true) - .syntaxHighlightTheme("agate") - .build(); + public void addLoginOperation(OpenAPI openAPI) { + openAPI.getComponents() + .addSchemas("LoginRequest", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(LoginRequest.class)).schema) + .addSchemas("LoginResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(LoginResponse.class)).schema) + .addSchemas("ThingsboardErrorResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ThingsboardErrorResponse.class)).schema) + .addSchemas("ThingsboardCredentialsExpiredResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ThingsboardCredentialsExpiredResponse.class)).schema); + + var operation = new Operation(); + operation.summary("Login method to get user JWT token data"); + operation.description("Login method used to authenticate user and get JWT token data.\n\nValue of the response **token** " + + "field can be used as **X-Authorization** header value:\n\n`X-Authorization: Bearer $JWT_TOKEN_VALUE`."); + var requestBody = new RequestBody().content(new Content().addMediaType(APPLICATION_JSON_VALUE, + new MediaType().schema(new Schema().$ref("#/components/schemas/LoginRequest")))); + operation.requestBody(requestBody); + operation.responses(getResponses()); + operation.addTagsItem("login-endpoint"); + var pathItem = new PathItem().post(operation); + openAPI.path(LOGIN_ENDPOINT, pathItem); } - private SecurityScheme httpLogin() { - return HttpLoginPasswordScheme - .X_AUTHORIZATION_BUILDER - .loginEndpoint("/api/auth/login") - .name("HTTP login form") - .description("Enter Username / Password") - .build(); - } + private ApiResponses getResponses() { + ApiResponses apiResponses = new ApiResponses(); + + apiResponses.addApiResponse("200", new ApiResponse().description("OK") + .content(new Content().addMediaType(APPLICATION_JSON_VALUE, + new MediaType().schema(new Schema().$ref("#/components/schemas/LoginResponse"))))); + + ApiResponse unauthorizedResponse = new ApiResponse().description("Unauthorized"); + Content content = new Content(); + MediaType mediaType = new MediaType().schema(new Schema().$ref("#/components/schemas/ThingsboardErrorResponse")); + + Map examples = Map.of( + "bad-credentials", errorExample("Bad credentials", + ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)), + "token-expired", errorExample("JWT token expired", + ThingsboardErrorResponse.of("Token has expired", ThingsboardErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED)), + "account-disabled", errorExample("Disabled account", + ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)), + "account-locked", errorExample("Locked account", + ThingsboardErrorResponse.of("User account is locked due to security policy", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)), + "authentication-failed", errorExample("General authentication error", + ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)) + ); - private SecurityContext securityContext() { - return SecurityContext.builder() - .securityReferences(defaultAuth()) - .operationSelector(securityPathOperationSelector()) - .build(); + mediaType.setExamples(examples); + content.addMediaType(APPLICATION_JSON_VALUE, mediaType); + unauthorizedResponse.setContent(content); + apiResponses.addApiResponse("401", unauthorizedResponse); + + ApiResponse expiredCredentialsResponse = new ApiResponse().description("Unauthorized (**Expired credentials**)"); + Content expiredContent = new Content(); + MediaType expiredMediaType = new MediaType().schema(new Schema().$ref("#/components/schemas/ThingsboardCredentialsExpiredResponse")); + expiredMediaType.addExamples("credentials-expired", errorExample("Expired credentials", + ThingsboardCredentialsExpiredResponse.of("User password expired!", StringUtils.randomAlphanumeric(30)))); + expiredContent.addMediaType(APPLICATION_JSON_VALUE, expiredMediaType); + expiredCredentialsResponse.setContent(expiredContent); + apiResponses.addApiResponse("401 ", expiredCredentialsResponse); + + return apiResponses; } - private Predicate apiPaths() { - return regex(apiPathRegex); + private Example errorExample(String summary, ThingsboardErrorResponse example) { + return new Example() + .summary(summary) + .value(example); } - private Predicate securityPathOperationSelector() { - return new SecurityPathOperationSelector(securityPathRegex, nonSecurityPathRegex); + @Bean + public GroupedOpenApi thingsboardApi() { + return GroupedOpenApi.builder() + .group("thingsboard") + .pathsToMatch(apiPath) + .addOpenApiCustomizer(customOpenApiCustomizer()) + .build(); } - List defaultAuth() { - AuthorizationScope[] authorizationScopes = new AuthorizationScope[3]; - authorizationScopes[0] = new AuthorizationScope(Authority.SYS_ADMIN.name(), "System administrator"); - authorizationScopes[1] = new AuthorizationScope(Authority.TENANT_ADMIN.name(), "Tenant administrator"); - authorizationScopes[2] = new AuthorizationScope(Authority.CUSTOMER_USER.name(), "Customer"); - return newArrayList( - new SecurityReference("HTTP login form", authorizationScopes)); + @Bean + @Primary + public SwaggerUiConfigProperties swaggerUiConfig(SwaggerUiConfigProperties uiProperties) { + uiProperties.setDeepLinking(true); + uiProperties.setDisplayOperationId(false); + uiProperties.setDefaultModelsExpandDepth(1); + uiProperties.setDefaultModelExpandDepth(1); + uiProperties.setDefaultModelRendering("example"); + uiProperties.setDisplayRequestDuration(false); + uiProperties.setDocExpansion("none"); + uiProperties.setFilter("false"); + uiProperties.setMaxDisplayedTags(null); + uiProperties.setOperationsSorter("alpha"); + uiProperties.setTagsSorter("alpha"); + uiProperties.setShowExtensions(false); + uiProperties.setShowCommonExtensions(false); + uiProperties.setSupportedSubmitMethods(List.of("get", "put", "post", "delete", "options", "head", "patch", "trace")); + uiProperties.setValidatorUrl(null); + uiProperties.setPersistAuthorization(true); + + var syntaxHighLight = new SwaggerUiConfigProperties.SyntaxHighlight(); + syntaxHighLight.setActivated(true); + syntaxHighLight.setTheme("agate"); + + uiProperties.setSyntaxHighlight(syntaxHighLight); + return uiProperties; } - private ApiInfo apiInfo() { - String apiVersion = version; - if (StringUtils.isEmpty(apiVersion)) { - apiVersion = appVersion; - } - return new ApiInfoBuilder() - .title(title) - .description(description) - .contact(new Contact(contactName, contactUrl, contactEmail)) - .license(licenseTitle) - .licenseUrl(licenseUrl) - .version(apiVersion) - .build(); - } + public OpenApiCustomizer customOpenApiCustomizer() { + SecurityRequirement loginForm = new SecurityRequirement().addList("HTTP login form"); - private ApiDescription loginEndpointApiDescription(final CachingOperationNameGenerator operationNames) { - return new ApiDescription(null, "/api/auth/login", "Login method to get user JWT token data", "Login endpoint", Collections.singletonList( - new OperationBuilder(operationNames) - .summary("Login method to get user JWT token data") - .tags(Set.of("login-endpoint")) - .authorizations(new ArrayList<>()) - .position(0) - .codegenMethodNameStem("loginPost") - .method(HttpMethod.POST) - .notes("Login method used to authenticate user and get JWT token data.\n\nValue of the response **token** " + - "field can be used as **X-Authorization** header value:\n\n`X-Authorization: Bearer $JWT_TOKEN_VALUE`.") - .requestParameters( - List.of( - new RequestParameterBuilder() - .in(ParameterType.BODY) - .required(true) - .description("Login request") - .content(c -> - c.requestBody(true) - .representation(MediaType.APPLICATION_JSON) - .apply(classRepresentation(LoginRequest.class, false)) - ) - .build() - ) - ) - .responses(loginResponses()) - .build() - ), false); - } + return openAPI -> openAPI.getPaths().entrySet().stream().peek(entry -> { + if (!(entry.getKey().matches(nonSecurityPathRegex) || entry.getKey().equals(LOGIN_ENDPOINT))) { + entry.getValue() + .readOperationsMap() + .values() + .forEach(operation -> operation.addSecurityItem(loginForm)); + } - private Collection loginResponses() { - List responses = new ArrayList<>(); - responses.add( - new ResponseBuilder() - .code("200") - .description("OK") - .representation(MediaType.APPLICATION_JSON) - .apply(classRepresentation(LoginResponse.class, true)). - build() - ); - responses.addAll(loginErrorResponses()); - return responses; - } + entry.getValue().readOperationsMap().forEach(((httpMethod, operation) -> { + operation.setResponses(getResponses(operation.getResponses(), httpMethod.equals(PathItem.HttpMethod.POST))); + })); - /** Helper methods **/ - - private List defaultErrorResponses(boolean isPost) { - return List.of( - errorResponse("400", "Bad Request", - ThingsboardErrorResponse.of(isPost ? "Invalid request body" : "Invalid UUID string: 123", ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST)), - errorResponse("401", "Unauthorized", - ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)), - errorResponse("403", "Forbidden", - ThingsboardErrorResponse.of("You don't have permission to perform this operation!", - ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN)), - errorResponse("404", "Not Found", - ThingsboardErrorResponse.of("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND, HttpStatus.NOT_FOUND)), - errorResponse("429", "Too Many Requests", - ThingsboardErrorResponse.of("Too many requests for current tenant!", - ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS)) - ); + }).map(entry -> { + String tagItem = entry.getValue().readOperationsMap().values().stream().findAny().get().getTags().get(0); + return tagFromTagItem(tagItem); + }).forEach(openAPI::addTagsItem); } - private List loginErrorResponses() { - return List.of( - errorResponse("401", "Unauthorized", - List.of( - errorExample("bad-credentials", "Bad credentials", - ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)), - errorExample("token-expired", "JWT token expired", - ThingsboardErrorResponse.of("Token has expired", ThingsboardErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED)), - errorExample("account-disabled", "Disabled account", - ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)), - errorExample("account-locked", "Locked account", - ThingsboardErrorResponse.of("User account is locked due to security policy", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)), - errorExample("authentication-failed", "General authentication error", - ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)) - ) - ), - errorResponse("401 ", "Unauthorized (**Expired credentials**)", - List.of( - errorExample("credentials-expired", "Expired credentials", - ThingsboardCredentialsExpiredResponse.of("User password expired!", StringUtils.randomAlphanumeric(30))) - ), ThingsboardCredentialsExpiredResponse.class - ) - ); - } + private Tag tagFromTagItem(String tagItem) { + String[] words = tagItem.split("-"); + StringBuilder sb = new StringBuilder(); - private Response errorResponse(String code, String description, ThingsboardErrorResponse example) { - return errorResponse(code, description, List.of(errorExample("error-code-" + code, description, example))); - } + for (String word : words) { + sb.append(word.substring(0, 1).toUpperCase()); + sb.append(word.substring(1).toLowerCase()); + sb.append(" "); + } - private Response errorResponse(String code, String description, List examples) { - return errorResponse(code, description, examples, ThingsboardErrorResponse.class); + return new Tag().name(tagItem).description(sb.toString().trim()); } - private Response errorResponse(String code, String description, List examples, - Class errorResponseClass) { - return new ResponseBuilder() - .code(code) - .description(description) - .examples(examples) - .representation(MediaType.APPLICATION_JSON) - .apply(classRepresentation(errorResponseClass, true)) - .build(); - } + private ApiResponses getResponses(ApiResponses apiResponses, boolean isPost) { + if (apiResponses == null) { + apiResponses = new ApiResponses(); + } - private Example errorExample(String id, String summary, ThingsboardErrorResponse example) { - return new ExampleBuilder() - .mediaType(MediaType.APPLICATION_JSON_VALUE) - .summary(summary) - .id(id) - .value(example).build(); - } + apiResponses.addApiResponse("400", new ApiResponse().description("Bad Request") + .content(getErrorContent(ThingsboardErrorResponse.of(isPost ? "Invalid request body" : "Invalid UUID string: 123", ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST)))); - private Consumer classRepresentation(Class clazz, boolean isResponse) { - return r -> r.model( - m -> - m.referenceModel(ref -> - ref.key(k -> - k.qualifiedModelName(q -> - q.namespace(clazz.getPackageName()) - .name(clazz.getSimpleName())).isResponse(isResponse))) - ); - } + apiResponses.addApiResponse("401", new ApiResponse().description("Unauthorized") + .content(getErrorContent(ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)))); - private static class SecurityPathOperationSelector implements Predicate { + apiResponses.addApiResponse("403", new ApiResponse().description("Forbidden") + .content(getErrorContent(ThingsboardErrorResponse.of("You don't have permission to perform this operation!", ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN)))); - private final Predicate securityPathSelector; + apiResponses.addApiResponse("404", new ApiResponse().description("Not Found") + .content(getErrorContent(ThingsboardErrorResponse.of("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND, HttpStatus.NOT_FOUND)))); - SecurityPathOperationSelector(String securityPathRegex, String nonSecurityPathRegex) { - this.securityPathSelector = regex(securityPathRegex).and( - not( - regex(nonSecurityPathRegex) - )); - } + apiResponses.addApiResponse("429", new ApiResponse().description("Too Many Requests") + .content(getErrorContent(ThingsboardErrorResponse.of("Too many requests for current tenant!", ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS)))); - @Override - public boolean test(OperationContext operationContext) { - return this.securityPathSelector.test(operationContext.requestMappingPattern()); - } + return apiResponses; } + private Content getErrorContent(ThingsboardErrorResponse errorResponse) { + return new Content().addMediaType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE, + new MediaType().schema(new Schema().example(errorResponse))); + } } diff --git a/application/src/main/java/org/thingsboard/server/config/annotations/ApiOperation.java b/application/src/main/java/org/thingsboard/server/config/annotations/ApiOperation.java new file mode 100644 index 0000000000..d8710149a6 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/config/annotations/ApiOperation.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.config.annotations; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.springframework.core.annotation.AliasFor; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Operation +public @interface ApiOperation { + + @AliasFor(annotation = Operation.class, attribute = "summary") + String value(); + + @AliasFor(annotation = Operation.class, attribute = "description") + String notes() default ""; + + boolean hidden() default false; + + RequestBody requestBody() default @RequestBody; + + ApiResponse[] responses() default {}; + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java index 5f33eb951e..faef8edd4a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java @@ -17,6 +17,7 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.FutureCallback; +import jakarta.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -45,7 +46,6 @@ import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; -import jakarta.annotation.Nullable; import java.util.Optional; import java.util.UUID; diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index 5cf97a7cda..12c5abf47d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -19,8 +19,9 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; @@ -53,6 +54,7 @@ import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.common.data.sync.vc.RepositorySettingsInfo; import org.thingsboard.server.common.data.sync.vc.VcUtils; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -96,7 +98,7 @@ public class AdminController extends BaseController { @RequestMapping(value = "/settings/{key}", method = RequestMethod.GET) @ResponseBody public AdminSettings getAdminSettings( - @ApiParam(value = "A string value of the key (e.g. 'general' or 'mail').") + @Parameter(description = "A string value of the key (e.g. 'general' or 'mail').") @PathVariable("key") String key) throws ThingsboardException { accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key), "No Administration settings found for key: " + key); @@ -114,7 +116,7 @@ public class AdminController extends BaseController { @RequestMapping(value = "/settings", method = RequestMethod.POST) @ResponseBody public AdminSettings saveAdminSettings( - @ApiParam(value = "A JSON value representing the Administration Settings.") + @Parameter(description = "A JSON value representing the Administration Settings.") @RequestBody AdminSettings adminSettings) throws ThingsboardException { accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); adminSettings.setTenantId(getTenantId()); @@ -144,7 +146,7 @@ public class AdminController extends BaseController { @RequestMapping(value = "/securitySettings", method = RequestMethod.POST) @ResponseBody public SecuritySettings saveSecuritySettings( - @ApiParam(value = "A JSON value representing the Security Settings.") + @Parameter(description = "A JSON value representing the Security Settings.") @RequestBody SecuritySettings securitySettings) throws ThingsboardException { accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); securitySettings = checkNotNull(systemSecurityService.saveSecuritySettings(TenantId.SYS_TENANT_ID, securitySettings)); @@ -153,7 +155,7 @@ public class AdminController extends BaseController { @ApiOperation(value = "Get the JWT Settings object (getJwtSettings)", notes = "Get the JWT Settings object that contains JWT token policy, etc. " + SYSTEM_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/jwtSettings", method = RequestMethod.GET) @ResponseBody @@ -164,12 +166,12 @@ public class AdminController extends BaseController { @ApiOperation(value = "Update JWT Settings (saveJwtSettings)", notes = "Updates the JWT Settings object that contains JWT token policy, etc. The tokenSigningKey field is a Base64 encoded string." + SYSTEM_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/jwtSettings", method = RequestMethod.POST) @ResponseBody public JwtPair saveJwtSettings( - @ApiParam(value = "A JSON value representing the JWT Settings.") + @Parameter(description = "A JSON value representing the JWT Settings.") @RequestBody JwtSettings jwtSettings) throws ThingsboardException { SecurityUser securityUser = getCurrentUser(); accessControlService.checkPermission(securityUser, Resource.ADMIN_SETTINGS, Operation.WRITE); @@ -183,7 +185,7 @@ public class AdminController extends BaseController { @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/settings/testMail", method = RequestMethod.POST) public void sendTestMail( - @ApiParam(value = "A JSON value representing the Mail Settings.") + @Parameter(description = "A JSON value representing the Mail Settings.") @RequestBody AdminSettings adminSettings) throws ThingsboardException { accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); adminSettings = checkNotNull(adminSettings); @@ -203,7 +205,7 @@ public class AdminController extends BaseController { @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/settings/testSms", method = RequestMethod.POST) public void sendTestSms( - @ApiParam(value = "A JSON value representing the Test SMS request.") + @Parameter(description = "A JSON value representing the Test SMS request.") @RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException { SecurityUser user = getCurrentUser(); accessControlService.checkPermission(user, Resource.ADMIN_SETTINGS, Operation.READ); @@ -286,7 +288,7 @@ public class AdminController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/repositorySettings/checkAccess", method = RequestMethod.POST) public DeferredResult checkRepositoryAccess( - @ApiParam(value = "A JSON value representing the Repository Settings.") + @Parameter(description = "A JSON value representing the Repository Settings.") @RequestBody RepositorySettings settings) throws Exception { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); settings = checkNotNull(settings); diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java index 92b2cb923f..529b1fe48a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; @@ -35,17 +37,16 @@ import org.thingsboard.server.common.data.id.AlarmCommentId; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService; import org.thingsboard.server.service.security.permission.Operation; import static org.thingsboard.server.controller.ControllerConstants.ALARM_COMMENT_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.ALARM_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; @@ -69,12 +70,12 @@ public class AlarmCommentController extends BaseController { "\n\n To create new Alarm comment entity it is enough to specify 'comment' json element with 'text' node, for example: {\"comment\": { \"text\": \"my comment\"}}. " + "\n\n If comment type is not specified the default value 'OTHER' will be saved. If 'alarmId' or 'userId' specified in body it will be ignored." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH - , produces = MediaType.APPLICATION_JSON_VALUE) + , responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.POST) @ResponseBody - public AlarmComment saveAlarmComment(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) - @PathVariable(ALARM_ID) String strAlarmId, @ApiParam(value = "A JSON value representing the comment.") @RequestBody AlarmComment alarmComment) throws ThingsboardException { + public AlarmComment saveAlarmComment(@Parameter(description = ALARM_ID_PARAM_DESCRIPTION) + @PathVariable(ALARM_ID) String strAlarmId, @Parameter(description = "A JSON value representing the comment.") @RequestBody AlarmComment alarmComment) throws ThingsboardException { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); @@ -83,11 +84,11 @@ public class AlarmCommentController extends BaseController { } @ApiOperation(value = "Delete Alarm comment (deleteAlarmComment)", - notes = "Deletes the Alarm comment. Referencing non-existing Alarm comment Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + notes = "Deletes the Alarm comment. Referencing non-existing Alarm comment Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}/comment/{commentId}", method = RequestMethod.DELETE) @ResponseBody - public void deleteAlarmComment(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId, @ApiParam(value = ALARM_COMMENT_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_COMMENT_ID) String strCommentId) throws ThingsboardException { + public void deleteAlarmComment(@Parameter(description = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId, @Parameter(description = ALARM_COMMENT_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_COMMENT_ID) String strCommentId) throws ThingsboardException { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); @@ -99,20 +100,20 @@ public class AlarmCommentController extends BaseController { @ApiOperation(value = "Get Alarm comments (getAlarmComments)", notes = "Returns a page of alarm comments for specified alarm. " + - PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.GET) @ResponseBody public PageData getAlarmComments( - @ApiParam(value = ALARM_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = ALARM_ID_PARAM_DESCRIPTION, required = true) @PathVariable(ALARM_ID) String strAlarmId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "id"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder ) throws Exception { checkParameter(ALARM_ID, strAlarmId); diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 44263d19bd..18ad89f1a0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -45,6 +47,7 @@ import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.alarm.TbAlarmService; import org.thingsboard.server.service.security.permission.Operation; @@ -59,7 +62,6 @@ import java.util.concurrent.ExecutionException; import static org.thingsboard.server.controller.ControllerConstants.ALARM_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ALARM_INFO_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.ALARM_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.ASSIGNEE_ID; import static org.thingsboard.server.controller.ControllerConstants.ASSIGN_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID; @@ -69,7 +71,6 @@ import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_ import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; @@ -104,11 +105,11 @@ public class AlarmController extends BaseController { "filled in the AlarmInfo object field: 'originatorName' or will returns as null."; @ApiOperation(value = "Get Alarm (getAlarmById)", - notes = "Fetch the Alarm object based on the provided Alarm Id. " + ALARM_SECURITY_CHECK, produces = MediaType.APPLICATION_JSON_VALUE) + notes = "Fetch the Alarm object based on the provided Alarm Id. " + ALARM_SECURITY_CHECK, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.GET) @ResponseBody - public Alarm getAlarmById(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + public Alarm getAlarmById(@Parameter(description = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); @@ -117,11 +118,11 @@ public class AlarmController extends BaseController { @ApiOperation(value = "Get Alarm Info (getAlarmInfoById)", notes = "Fetch the Alarm Info object based on the provided Alarm Id. " + - ALARM_SECURITY_CHECK + ALARM_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + ALARM_SECURITY_CHECK + ALARM_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/info/{alarmId}", method = RequestMethod.GET) @ResponseBody - public AlarmInfo getAlarmInfoById(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + public AlarmInfo getAlarmInfoById(@Parameter(description = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); @@ -139,11 +140,11 @@ public class AlarmController extends BaseController { "If the user clears the alarm (see 'Clear Alarm(clearAlarm)'), than new alarm with the same type and same device may be created. " + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Alarm entity. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH - , produces = MediaType.APPLICATION_JSON_VALUE) + , responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm", method = RequestMethod.POST) @ResponseBody - public Alarm saveAlarm(@ApiParam(value = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException { + public Alarm saveAlarm(@Parameter(description = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException { alarm.setTenantId(getTenantId()); checkNotNull(alarm.getOriginator()); checkEntity(alarm.getId(), alarm, Resource.ALARM); @@ -155,11 +156,11 @@ public class AlarmController extends BaseController { } @ApiOperation(value = "Delete Alarm (deleteAlarm)", - notes = "Deletes the Alarm. Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + notes = "Deletes the Alarm. Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.DELETE) @ResponseBody - public Boolean deleteAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { + public Boolean deleteAlarm(@Parameter(description = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); Alarm alarm = checkAlarmId(alarmId, Operation.DELETE); @@ -169,11 +170,11 @@ public class AlarmController extends BaseController { @ApiOperation(value = "Acknowledge Alarm (ackAlarm)", notes = "Acknowledge the Alarm. " + "Once acknowledged, the 'ack_ts' field will be set to current timestamp and special rule chain event 'ALARM_ACK' will be generated. " + - "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public AlarmInfo ackAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception { + public AlarmInfo ackAlarm(@Parameter(description = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); @@ -184,11 +185,11 @@ public class AlarmController extends BaseController { @ApiOperation(value = "Clear Alarm (clearAlarm)", notes = "Clear the Alarm. " + "Once cleared, the 'clear_ts' field will be set to current timestamp and special rule chain event 'ALARM_CLEAR' will be generated. " + - "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public AlarmInfo clearAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception { + public AlarmInfo clearAlarm(@Parameter(description = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); @@ -200,13 +201,13 @@ public class AlarmController extends BaseController { notes = "Assign the Alarm. " + "Once assigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_ASSIGNED' " + "(or ALARM_REASSIGNED in case of assigning already assigned alarm) will be generated. " + - "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}/assign/{assigneeId}", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public Alarm assignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + public Alarm assignAlarm(@Parameter(description = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId, - @ApiParam(value = ASSIGN_ID_PARAM_DESCRIPTION) + @Parameter(description = ASSIGN_ID_PARAM_DESCRIPTION) @PathVariable(ASSIGNEE_ID) String strAssigneeId ) throws Exception { checkParameter(ALARM_ID, strAlarmId); @@ -221,11 +222,11 @@ public class AlarmController extends BaseController { @ApiOperation(value = "Unassign Alarm (unassignAlarm)", notes = "Unassign the Alarm. " + "Once unassigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_UNASSIGNED' will be generated. " + - "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}/assign", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public Alarm unassignAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + public Alarm unassignAlarm(@Parameter(description = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId ) throws Exception { checkParameter(ALARM_ID, strAlarmId); @@ -236,36 +237,36 @@ public class AlarmController extends BaseController { @ApiOperation(value = "Get Alarms (getAlarms)", notes = "Returns a page of alarms for the selected entity. Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. " + - PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET) @ResponseBody public PageData getAlarms( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable(ENTITY_TYPE) String strEntityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_ID) String strEntityId, - @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES) + @Parameter(description = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)) @RequestParam(required = false) String searchStatus, - @ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES) + @Parameter(description = ALARM_QUERY_STATUS_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES)) @RequestParam(required = false) String status, - @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION) + @Parameter(description = ALARM_QUERY_ASSIGNEE_DESCRIPTION) @RequestParam(required = false) String assigneeId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ALARM_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "startTs", "endTs", "type", "ackTs", "clearTs", "severity", "status"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = ALARM_QUERY_START_TIME_DESCRIPTION) + @Parameter(description = ALARM_QUERY_START_TIME_DESCRIPTION) @RequestParam(required = false) Long startTime, - @ApiParam(value = ALARM_QUERY_END_TIME_DESCRIPTION) + @Parameter(description = ALARM_QUERY_END_TIME_DESCRIPTION) @RequestParam(required = false) Long endTime, - @ApiParam(value = ALARM_QUERY_FETCH_ORIGINATOR_DESCRIPTION) + @Parameter(description = ALARM_QUERY_FETCH_ORIGINATOR_DESCRIPTION) @RequestParam(required = false) Boolean fetchOriginator ) throws ThingsboardException, ExecutionException, InterruptedException { checkParameter("EntityId", strEntityId); @@ -292,32 +293,32 @@ public class AlarmController extends BaseController { "If the user has the authority of 'Tenant Administrator', the server returns alarms that belongs to the tenant of current user. " + "If the user has the authority of 'Customer User', the server returns alarms that belongs to the customer of current user. " + "Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. " + - PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarms", method = RequestMethod.GET) @ResponseBody public PageData getAllAlarms( - @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES) + @Parameter(description = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)) @RequestParam(required = false) String searchStatus, - @ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES) + @Parameter(description = ALARM_QUERY_STATUS_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES)) @RequestParam(required = false) String status, - @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION) + @Parameter(description = ALARM_QUERY_ASSIGNEE_DESCRIPTION) @RequestParam(required = false) String assigneeId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ALARM_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "startTs", "endTs", "type", "ackTs", "clearTs", "severity", "status"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = ALARM_QUERY_START_TIME_DESCRIPTION) + @Parameter(description = ALARM_QUERY_START_TIME_DESCRIPTION) @RequestParam(required = false) Long startTime, - @ApiParam(value = ALARM_QUERY_END_TIME_DESCRIPTION) + @Parameter(description = ALARM_QUERY_END_TIME_DESCRIPTION) @RequestParam(required = false) Long endTime, - @ApiParam(value = ALARM_QUERY_FETCH_ORIGINATOR_DESCRIPTION) + @Parameter(description = ALARM_QUERY_FETCH_ORIGINATOR_DESCRIPTION) @RequestParam(required = false) Boolean fetchOriginator ) throws ThingsboardException, ExecutionException, InterruptedException { AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus); @@ -341,36 +342,36 @@ public class AlarmController extends BaseController { @ApiOperation(value = "Get Alarms (getAlarmsV2)", notes = "Returns a page of alarms for the selected entity. " + - PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/v2/alarm/{entityType}/{entityId}", method = RequestMethod.GET) @ResponseBody public PageData getAlarmsV2( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable(ENTITY_TYPE) String strEntityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_ID) String strEntityId, - @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES) + @Parameter(description = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)) @RequestParam(required = false) String[] statusList, - @ApiParam(value = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, allowableValues = ALARM_QUERY_SEVERITY_ALLOWABLE_VALUES) + @Parameter(description = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEVERITY_ALLOWABLE_VALUES)) @RequestParam(required = false) String[] severityList, - @ApiParam(value = ALARM_QUERY_TYPE_ARRAY_DESCRIPTION) + @Parameter(description = ALARM_QUERY_TYPE_ARRAY_DESCRIPTION) @RequestParam(required = false) String[] typeList, - @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION) + @Parameter(description = ALARM_QUERY_ASSIGNEE_DESCRIPTION) @RequestParam(required = false) String assigneeId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ALARM_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "startTs", "endTs", "type", "ackTs", "clearTs", "severity", "status"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = ALARM_QUERY_START_TIME_DESCRIPTION) + @Parameter(description = ALARM_QUERY_START_TIME_DESCRIPTION) @RequestParam(required = false) Long startTime, - @ApiParam(value = ALARM_QUERY_END_TIME_DESCRIPTION) + @Parameter(description = ALARM_QUERY_END_TIME_DESCRIPTION) @RequestParam(required = false) Long endTime ) throws ThingsboardException, ExecutionException, InterruptedException { checkParameter("EntityId", strEntityId); @@ -407,32 +408,32 @@ public class AlarmController extends BaseController { notes = "Returns a page of alarms that belongs to the current user owner. " + "If the user has the authority of 'Tenant Administrator', the server returns alarms that belongs to the tenant of current user. " + "If the user has the authority of 'Customer User', the server returns alarms that belongs to the customer of current user. " + - PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/v2/alarms", method = RequestMethod.GET) @ResponseBody public PageData getAllAlarmsV2( - @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES) + @Parameter(description = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)) @RequestParam(required = false) String[] statusList, - @ApiParam(value = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, allowableValues = ALARM_QUERY_SEVERITY_ALLOWABLE_VALUES) + @Parameter(description = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEVERITY_ALLOWABLE_VALUES)) @RequestParam(required = false) String[] severityList, - @ApiParam(value = ALARM_QUERY_TYPE_ARRAY_DESCRIPTION) + @Parameter(description = ALARM_QUERY_TYPE_ARRAY_DESCRIPTION) @RequestParam(required = false) String[] typeList, - @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION) + @Parameter(description = ALARM_QUERY_ASSIGNEE_DESCRIPTION) @RequestParam(required = false) String assigneeId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ALARM_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "startTs", "endTs", "type", "ackTs", "clearTs", "severity", "status"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = ALARM_QUERY_START_TIME_DESCRIPTION) + @Parameter(description = ALARM_QUERY_START_TIME_DESCRIPTION) @RequestParam(required = false) Long startTime, - @ApiParam(value = ALARM_QUERY_END_TIME_DESCRIPTION) + @Parameter(description = ALARM_QUERY_END_TIME_DESCRIPTION) @RequestParam(required = false) Long endTime ) throws ThingsboardException, ExecutionException, InterruptedException { List alarmStatusList = new ArrayList<>(); @@ -468,20 +469,20 @@ public class AlarmController extends BaseController { @ApiOperation(value = "Get Highest Alarm Severity (getHighestAlarmSeverity)", notes = "Search the alarms by originator ('entityType' and entityId') and optional 'status' or 'searchStatus' filters and returns the highest AlarmSeverity(CRITICAL, MAJOR, MINOR, WARNING or INDETERMINATE). " + "Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH - , produces = MediaType.APPLICATION_JSON_VALUE) + , responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET) @ResponseBody public AlarmSeverity getHighestAlarmSeverity( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable(ENTITY_TYPE) String strEntityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_ID) String strEntityId, - @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES) + @Parameter(description = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)) @RequestParam(required = false) String searchStatus, - @ApiParam(value = ALARM_QUERY_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES) + @Parameter(description = ALARM_QUERY_STATUS_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES)) @RequestParam(required = false) String status, - @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION) + @Parameter(description = ALARM_QUERY_ASSIGNEE_DESCRIPTION) @RequestParam(required = false) String assigneeId ) throws ThingsboardException { checkParameter("EntityId", strEntityId); diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java index 510fefe305..79d8d88352 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java @@ -16,8 +16,10 @@ package org.thingsboard.server.controller; import com.google.common.util.concurrent.ListenableFuture; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -49,6 +51,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest; import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -67,7 +70,6 @@ import static org.thingsboard.server.controller.ControllerConstants.ASSET_ID_PAR import static org.thingsboard.server.controller.ControllerConstants.ASSET_INFO_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ASSET_NAME_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.ASSET_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.ASSET_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ASSET_TYPE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; @@ -79,7 +81,6 @@ import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIG import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; @@ -102,11 +103,11 @@ public class AssetController extends BaseController { notes = "Fetch the Asset object based on the provided Asset Id. " + "If the user has the authority of 'Tenant Administrator', the server checks that the asset is owned by the same tenant. " + "If the user has the authority of 'Customer User', the server checks that the asset is assigned to the same customer." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH - , produces = MediaType.APPLICATION_JSON_VALUE) + , responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.GET) @ResponseBody - public Asset getAssetById(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) + public Asset getAssetById(@Parameter(description = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter(ASSET_ID, strAssetId); AssetId assetId = new AssetId(toUUID(strAssetId)); @@ -117,11 +118,11 @@ public class AssetController extends BaseController { notes = "Fetch the Asset Info object based on the provided Asset Id. " + "If the user has the authority of 'Tenant Administrator', the server checks that the asset is owned by the same tenant. " + "If the user has the authority of 'Customer User', the server checks that the asset is assigned to the same customer. " - + ASSET_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + + ASSET_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/asset/info/{assetId}", method = RequestMethod.GET) @ResponseBody - public AssetInfo getAssetInfoById(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) + public AssetInfo getAssetInfoById(@Parameter(description = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter(ASSET_ID, strAssetId); AssetId assetId = new AssetId(toUUID(strAssetId)); @@ -134,11 +135,11 @@ public class AssetController extends BaseController { "Specify existing Asset id to update the asset. " + "Referencing non-existing Asset Id will cause 'Not Found' error. " + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Asset entity. " - + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/asset", method = RequestMethod.POST) @ResponseBody - public Asset saveAsset(@ApiParam(value = "A JSON value representing the asset.") @RequestBody Asset asset) throws Exception { + public Asset saveAsset(@Parameter(description = "A JSON value representing the asset.") @RequestBody Asset asset) throws Exception { asset.setTenantId(getTenantId()); checkEntity(asset.getId(), asset, Resource.ASSET); return tbAssetService.save(asset, getCurrentUser()); @@ -149,7 +150,7 @@ public class AssetController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public void deleteAsset(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws Exception { + public void deleteAsset(@Parameter(description = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws Exception { checkParameter(ASSET_ID, strAssetId); AssetId assetId = new AssetId(toUUID(strAssetId)); Asset asset = checkAssetId(assetId, Operation.DELETE); @@ -157,12 +158,12 @@ public class AssetController extends BaseController { } @ApiOperation(value = "Assign asset to customer (assignAssetToCustomer)", - notes = "Creates assignment of the asset to customer. Customer will be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + notes = "Creates assignment of the asset to customer. Customer will be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/{customerId}/asset/{assetId}", method = RequestMethod.POST) @ResponseBody - public Asset assignAssetToCustomer(@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable("customerId") String strCustomerId, - @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { + public Asset assignAssetToCustomer(@Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable("customerId") String strCustomerId, + @Parameter(description = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter("customerId", strCustomerId); checkParameter(ASSET_ID, strAssetId); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); @@ -173,11 +174,11 @@ public class AssetController extends BaseController { } @ApiOperation(value = "Unassign asset from customer (unassignAssetFromCustomer)", - notes = "Clears assignment of the asset to customer. Customer will not be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + notes = "Clears assignment of the asset to customer. Customer will not be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/asset/{assetId}", method = RequestMethod.DELETE) @ResponseBody - public Asset unassignAssetFromCustomer(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { + public Asset unassignAssetFromCustomer(@Parameter(description = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter(ASSET_ID, strAssetId); AssetId assetId = new AssetId(toUUID(strAssetId)); Asset asset = checkAssetId(assetId, Operation.UNASSIGN_FROM_CUSTOMER); @@ -191,11 +192,11 @@ public class AssetController extends BaseController { @ApiOperation(value = "Make asset publicly available (assignAssetToPublicCustomer)", notes = "Asset will be available for non-authorized (not logged-in) users. " + "This is useful to create dashboards that you plan to share/embed on a publicly available website. " + - "However, users that are logged-in and belong to different tenant will not be able to access the asset." + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + "However, users that are logged-in and belong to different tenant will not be able to access the asset." + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/public/asset/{assetId}", method = RequestMethod.POST) @ResponseBody - public Asset assignAssetToPublicCustomer(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { + public Asset assignAssetToPublicCustomer(@Parameter(description = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter(ASSET_ID, strAssetId); AssetId assetId = new AssetId(toUUID(strAssetId)); checkAssetId(assetId, Operation.ASSIGN_TO_CUSTOMER); @@ -204,22 +205,22 @@ public class AssetController extends BaseController { @ApiOperation(value = "Get Tenant Assets (getTenantAssets)", notes = "Returns a page of assets owned by tenant. " + - PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantAssets( - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @Parameter(description = PAGE_SIZE_DESCRIPTION) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @Parameter(description = PAGE_NUMBER_DESCRIPTION) @RequestParam int page, - @ApiParam(value = ASSET_TYPE_DESCRIPTION) + @Parameter(description = ASSET_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ASSET_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); @@ -232,24 +233,24 @@ public class AssetController extends BaseController { @ApiOperation(value = "Get Tenant Asset Infos (getTenantAssetInfos)", notes = "Returns a page of assets info objects owned by tenant. " + - PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantAssetInfos( - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @Parameter(description = PAGE_SIZE_DESCRIPTION) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @Parameter(description = PAGE_NUMBER_DESCRIPTION) @RequestParam int page, - @ApiParam(value = ASSET_TYPE_DESCRIPTION) + @Parameter(description = ASSET_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = ASSET_PROFILE_ID_PARAM_DESCRIPTION) @RequestParam(required = false) String assetProfileId, - @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ASSET_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); @@ -265,12 +266,12 @@ public class AssetController extends BaseController { @ApiOperation(value = "Get Tenant Asset (getTenantAsset)", notes = "Requested asset must be owned by tenant that the user belongs to. " + - "Asset name is an unique property of asset. So it can be used to identify the asset." + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + "Asset name is an unique property of asset. So it can be used to identify the asset." + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET) @ResponseBody public Asset getTenantAsset( - @ApiParam(value = ASSET_NAME_DESCRIPTION) + @Parameter(description = ASSET_NAME_DESCRIPTION) @RequestParam String assetName) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); return checkNotNull(assetService.findAssetByTenantIdAndName(tenantId, assetName)); @@ -278,24 +279,24 @@ public class AssetController extends BaseController { @ApiOperation(value = "Get Customer Assets (getCustomerAssets)", notes = "Returns a page of assets objects assigned to customer. " + - PAGE_DATA_PARAMETERS, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/customer/{customerId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomerAssets( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable("customerId") String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @Parameter(description = PAGE_SIZE_DESCRIPTION) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @Parameter(description = PAGE_NUMBER_DESCRIPTION) @RequestParam int page, - @ApiParam(value = ASSET_TYPE_DESCRIPTION) + @Parameter(description = ASSET_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ASSET_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); TenantId tenantId = getCurrentUser().getTenantId(); @@ -311,26 +312,26 @@ public class AssetController extends BaseController { @ApiOperation(value = "Get Customer Asset Infos (getCustomerAssetInfos)", notes = "Returns a page of assets info objects assigned to customer. " + - PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/customer/{customerId}/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomerAssetInfos( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable("customerId") String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @Parameter(description = PAGE_SIZE_DESCRIPTION) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @Parameter(description = PAGE_NUMBER_DESCRIPTION) @RequestParam int page, - @ApiParam(value = ASSET_TYPE_DESCRIPTION) + @Parameter(description = ASSET_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = ASSET_PROFILE_ID_PARAM_DESCRIPTION) @RequestParam(required = false) String assetProfileId, - @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ASSET_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); TenantId tenantId = getCurrentUser().getTenantId(); @@ -348,12 +349,12 @@ public class AssetController extends BaseController { } @ApiOperation(value = "Get Assets By Ids (getAssetsByIds)", - notes = "Requested assets must be owned by tenant or assigned to customer which user is performing the request. ", produces = MediaType.APPLICATION_JSON_VALUE) + notes = "Requested assets must be owned by tenant or assigned to customer which user is performing the request. ", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET) @ResponseBody public List getAssetsByIds( - @ApiParam(value = "A list of assets ids, separated by comma ','") + @Parameter(description = "A list of assets ids, separated by comma ','") @RequestParam("assetIds") String[] strAssetIds) throws ThingsboardException, ExecutionException, InterruptedException { checkArrayParameter("assetIds", strAssetIds); SecurityUser user = getCurrentUser(); @@ -375,7 +376,7 @@ public class AssetController extends BaseController { @ApiOperation(value = "Find related assets (findByQuery)", notes = "Returns all assets that are related to the specific entity. " + "The entity id, relation type, asset types, depth of the search, and other query parameters defined using complex 'AssetSearchQuery' object. " + - "See 'Model' tab of the Parameters for more info.", produces = MediaType.APPLICATION_JSON_VALUE) + "See 'Model' tab of the Parameters for more info.", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/assets", method = RequestMethod.POST) @ResponseBody @@ -397,7 +398,7 @@ public class AssetController extends BaseController { } @ApiOperation(value = "Get Asset Types (getAssetTypes)", - notes = "Returns a set of unique asset types based on assets that are either owned by the tenant or assigned to the customer which user is performing the request.", produces = MediaType.APPLICATION_JSON_VALUE) + notes = "Returns a set of unique asset types based on assets that are either owned by the tenant or assigned to the customer which user is performing the request.", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/asset/types", method = RequestMethod.GET) @ResponseBody @@ -414,12 +415,12 @@ public class AssetController extends BaseController { "Second, remote edge service will receive a copy of assignment asset " + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + "Third, once asset will be delivered to edge service, it's going to be available for usage on remote edge instance.", - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST) @ResponseBody - public Asset assignAssetToEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId, - @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { + public Asset assignAssetToEdge(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId, + @Parameter(description = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); checkParameter(ASSET_ID, strAssetId); @@ -438,12 +439,12 @@ public class AssetController extends BaseController { "Second, remote edge service will receive an 'unassign' command to remove asset " + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + "Third, once 'unassign' command will be delivered to edge service, it's going to remove asset locally.", - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.DELETE) @ResponseBody - public Asset unassignAssetFromEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId, - @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { + public Asset unassignAssetFromEdge(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId, + @Parameter(description = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); checkParameter(ASSET_ID, strAssetId); EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); @@ -457,28 +458,28 @@ public class AssetController extends BaseController { @ApiOperation(value = "Get assets assigned to edge (getEdgeAssets)", notes = "Returns a page of assets assigned to edge. " + - PAGE_DATA_PARAMETERS, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/edge/{edgeId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getEdgeAssets( - @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) + @Parameter(description = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @Parameter(description = PAGE_SIZE_DESCRIPTION) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @Parameter(description = PAGE_NUMBER_DESCRIPTION) @RequestParam int page, - @ApiParam(value = ASSET_TYPE_DESCRIPTION) + @Parameter(description = ASSET_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ASSET_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = "Timestamp. Assets with creation time before it won't be queried") + @Parameter(description = "Timestamp. Assets with creation time before it won't be queried") @RequestParam(required = false) Long startTime, - @ApiParam(value = "Timestamp. Assets with creation time after it won't be queried") + @Parameter(description = "Timestamp. Assets with creation time after it won't be queried") @RequestParam(required = false) Long endTime) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); TenantId tenantId = getCurrentUser().getTenantId(); @@ -507,7 +508,7 @@ public class AssetController extends BaseController { } @ApiOperation(value = "Import the bulk of assets (processAssetsBulkImport)", - notes = "There's an ability to import the bulk of assets using the only .csv file.", produces = MediaType.APPLICATION_JSON_VALUE) + notes = "There's an ability to import the bulk of assets using the only .csv file.", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PostMapping("/asset/bulk_import") public BulkImportResult processAssetsBulkImport(@RequestBody BulkImportRequest request) throws Exception { diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java b/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java index 47a7eede4b..0d6f652241 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java @@ -15,11 +15,14 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -35,6 +38,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.asset.profile.TbAssetProfileService; import org.thingsboard.server.service.security.permission.Operation; @@ -43,13 +47,11 @@ import org.thingsboard.server.service.security.permission.Resource; import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_ID; import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_INFO_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; @@ -68,12 +70,12 @@ public class AssetProfileController extends BaseController { @ApiOperation(value = "Get Asset Profile (getAssetProfileById)", notes = "Fetch the Asset Profile object based on the provided Asset Profile Id. " + "The server checks that the asset profile is owned by the same tenant. " + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/assetProfile/{assetProfileId}", method = RequestMethod.GET) @ResponseBody public AssetProfile getAssetProfileById( - @ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = ASSET_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_PROFILE_ID) String strAssetProfileId) throws ThingsboardException { checkParameter(ASSET_PROFILE_ID, strAssetProfileId); AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId)); @@ -83,12 +85,12 @@ public class AssetProfileController extends BaseController { @ApiOperation(value = "Get Asset Profile Info (getAssetProfileInfoById)", notes = "Fetch the Asset Profile Info object based on the provided Asset Profile Id. " + ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/assetProfileInfo/{assetProfileId}", method = RequestMethod.GET) @ResponseBody public AssetProfileInfo getAssetProfileInfoById( - @ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = ASSET_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_PROFILE_ID) String strAssetProfileId) throws ThingsboardException { checkParameter(ASSET_PROFILE_ID, strAssetProfileId); AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId)); @@ -98,7 +100,7 @@ public class AssetProfileController extends BaseController { @ApiOperation(value = "Get Default Asset Profile (getDefaultAssetProfileInfo)", notes = "Fetch the Default Asset Profile Info object. " + ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/assetProfileInfo/default", method = RequestMethod.GET) @ResponseBody @@ -114,13 +116,12 @@ public class AssetProfileController extends BaseController { "Asset profile name is unique in the scope of tenant. Only one 'default' asset profile may exist in scope of tenant. " + "Remove 'id', 'tenantId' from the request body example (below) to create new Asset Profile entity. " + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json", - consumes = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/assetProfile", method = RequestMethod.POST) @ResponseBody public AssetProfile saveAssetProfile( - @ApiParam(value = "A JSON value representing the asset profile.") + @Parameter(description = "A JSON value representing the asset profile.") @RequestBody AssetProfile assetProfile) throws Exception { assetProfile.setTenantId(getTenantId()); checkEntity(assetProfile.getId(), assetProfile, Resource.ASSET_PROFILE); @@ -130,12 +131,12 @@ public class AssetProfileController extends BaseController { @ApiOperation(value = "Delete asset profile (deleteAssetProfile)", notes = "Deletes the asset profile. Referencing non-existing asset profile Id will cause an error. " + "Can't delete the asset profile if it is referenced by existing assets." + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/assetProfile/{assetProfileId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteAssetProfile( - @ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = ASSET_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_PROFILE_ID) String strAssetProfileId) throws ThingsboardException { checkParameter(ASSET_PROFILE_ID, strAssetProfileId); AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId)); @@ -145,12 +146,12 @@ public class AssetProfileController extends BaseController { @ApiOperation(value = "Make Asset Profile Default (setDefaultAssetProfile)", notes = "Marks asset profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/assetProfile/{assetProfileId}/default", method = RequestMethod.POST) @ResponseBody public AssetProfile setDefaultAssetProfile( - @ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = ASSET_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_PROFILE_ID) String strAssetProfileId) throws ThingsboardException { checkParameter(ASSET_PROFILE_ID, strAssetProfileId); AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId)); @@ -162,20 +163,20 @@ public class AssetProfileController extends BaseController { @ApiOperation(value = "Get Asset Profiles (getAssetProfiles)", notes = "Returns a page of asset profile objects owned by tenant. " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/assetProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getAssetProfiles( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ASSET_PROFILE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ASSET_PROFILE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "description", "isDefault"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(assetProfileService.findAssetProfiles(getTenantId(), pageLink)); @@ -184,20 +185,20 @@ public class AssetProfileController extends BaseController { @ApiOperation(value = "Get Asset Profile infos (getAssetProfileInfos)", notes = "Returns a page of asset profile info objects owned by tenant. " + PAGE_DATA_PARAMETERS + ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/assetProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getAssetProfileInfos( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ASSET_PROFILE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ASSET_PROFILE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "description", "isDefault"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(assetProfileService.findAssetProfileInfos(getTenantId(), pageLink)); diff --git a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java index 387f0d014b..71113d41fa 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; @@ -35,6 +37,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import java.util.Arrays; @@ -42,7 +45,6 @@ import java.util.List; import java.util.UUID; import java.util.stream.Collectors; -import static org.thingsboard.server.controller.ControllerConstants.AUDIT_LOG_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.AUDIT_LOG_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; @@ -50,7 +52,6 @@ import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_ import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.USER_ID_PARAM_DESCRIPTION; @@ -74,28 +75,28 @@ public class AuditLogController extends BaseController { notes = "Returns a page of audit logs related to the targeted customer entities (devices, assets, etc.), " + "and users actions (login, logout, etc.) that belong to this customer. " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getAuditLogsByCustomerId( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable("customerId") String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @Parameter(description = PAGE_SIZE_DESCRIPTION) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @Parameter(description = PAGE_NUMBER_DESCRIPTION) @RequestParam int page, - @ApiParam(value = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = AUDIT_LOG_SORT_PROPERTY_DESCRIPTION, allowableValues = AUDIT_LOG_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = AUDIT_LOG_SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "entityType", "entityName", "userName", "actionType", "actionStatus"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = AUDIT_LOG_QUERY_START_TIME_DESCRIPTION) + @Parameter(description = AUDIT_LOG_QUERY_START_TIME_DESCRIPTION) @RequestParam(required = false) Long startTime, - @ApiParam(value = AUDIT_LOG_QUERY_END_TIME_DESCRIPTION) + @Parameter(description = AUDIT_LOG_QUERY_END_TIME_DESCRIPTION) @RequestParam(required = false) Long endTime, - @ApiParam(value = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) + @Parameter(description = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { checkParameter("CustomerId", strCustomerId); TenantId tenantId = getCurrentUser().getTenantId(); @@ -108,28 +109,28 @@ public class AuditLogController extends BaseController { notes = "Returns a page of audit logs related to the actions of targeted user. " + "For example, RPC call to a particular device, or alarm acknowledgment for a specific device, etc. " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/audit/logs/user/{userId}", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getAuditLogsByUserId( - @ApiParam(value = USER_ID_PARAM_DESCRIPTION) + @Parameter(description = USER_ID_PARAM_DESCRIPTION) @PathVariable("userId") String strUserId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @Parameter(description = PAGE_SIZE_DESCRIPTION) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @Parameter(description = PAGE_NUMBER_DESCRIPTION) @RequestParam int page, - @ApiParam(value = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = AUDIT_LOG_SORT_PROPERTY_DESCRIPTION, allowableValues = AUDIT_LOG_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = AUDIT_LOG_SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "entityType", "entityName", "userName", "actionType", "actionStatus"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = AUDIT_LOG_QUERY_START_TIME_DESCRIPTION) + @Parameter(description = AUDIT_LOG_QUERY_START_TIME_DESCRIPTION) @RequestParam(required = false) Long startTime, - @ApiParam(value = AUDIT_LOG_QUERY_END_TIME_DESCRIPTION) + @Parameter(description = AUDIT_LOG_QUERY_END_TIME_DESCRIPTION) @RequestParam(required = false) Long endTime, - @ApiParam(value = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) + @Parameter(description = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { checkParameter("UserId", strUserId); TenantId tenantId = getCurrentUser().getTenantId(); @@ -143,30 +144,30 @@ public class AuditLogController extends BaseController { "Basically, this API call is used to get the full lifecycle of some specific entity. " + "For example to see when a device was created, updated, assigned to some customer, or even deleted from the system. " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getAuditLogsByEntityId( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String strEntityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String strEntityId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = AUDIT_LOG_SORT_PROPERTY_DESCRIPTION, allowableValues = AUDIT_LOG_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = AUDIT_LOG_SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "entityType", "entityName", "userName", "actionType", "actionStatus"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = AUDIT_LOG_QUERY_START_TIME_DESCRIPTION) + @Parameter(description = AUDIT_LOG_QUERY_START_TIME_DESCRIPTION) @RequestParam(required = false) Long startTime, - @ApiParam(value = AUDIT_LOG_QUERY_END_TIME_DESCRIPTION) + @Parameter(description = AUDIT_LOG_QUERY_END_TIME_DESCRIPTION) @RequestParam(required = false) Long endTime, - @ApiParam(value = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) + @Parameter(description = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); @@ -179,26 +180,26 @@ public class AuditLogController extends BaseController { @ApiOperation(value = "Get all audit logs (getAuditLogs)", notes = "Returns a page of audit logs related to all entities in the scope of the current user's Tenant. " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/audit/logs", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getAuditLogs( - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @Parameter(description = PAGE_SIZE_DESCRIPTION) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @Parameter(description = PAGE_NUMBER_DESCRIPTION) @RequestParam int page, - @ApiParam(value = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = AUDIT_LOG_SORT_PROPERTY_DESCRIPTION, allowableValues = AUDIT_LOG_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = AUDIT_LOG_SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "entityType", "entityName", "userName", "actionType", "actionStatus"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = AUDIT_LOG_QUERY_START_TIME_DESCRIPTION) + @Parameter(description = AUDIT_LOG_QUERY_START_TIME_DESCRIPTION) @RequestParam(required = false) Long startTime, - @ApiParam(value = AUDIT_LOG_QUERY_END_TIME_DESCRIPTION) + @Parameter(description = AUDIT_LOG_QUERY_END_TIME_DESCRIPTION) @RequestParam(required = false) Long endTime, - @ApiParam(value = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) + @Parameter(description = AUDIT_LOG_QUERY_ACTION_TYPES_DESCRIPTION) @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); List actionTypes = parseActionTypesStr(actionTypesStr); diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 5357c0e2a4..86941e2b07 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -16,8 +16,8 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.node.ObjectNode; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -48,9 +48,10 @@ import org.thingsboard.server.common.data.security.event.UserSessionInvalidation import org.thingsboard.server.common.data.security.model.JwtPair; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; -import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.util.limits.LimitedApi; import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.model.ActivateUserRequest; import org.thingsboard.server.service.security.model.ChangePasswordRequest; @@ -61,7 +62,6 @@ import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.system.SystemSecurityService; -import jakarta.servlet.http.HttpServletRequest; import java.net.URI; import java.net.URISyntaxException; @@ -107,7 +107,7 @@ public class AuthController extends BaseController { @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) public ObjectNode changePassword( - @ApiParam(value = "Change Password Request") + @Parameter(description = "Change Password Request") @RequestBody ChangePasswordRequest changePasswordRequest) throws ThingsboardException { String currentPassword = changePasswordRequest.getCurrentPassword(); String newPassword = changePasswordRequest.getNewPassword(); @@ -148,7 +148,7 @@ public class AuthController extends BaseController { "If token is not valid, returns '409 Conflict'.") @RequestMapping(value = "/noauth/activate", params = {"activateToken"}, method = RequestMethod.GET) public ResponseEntity checkActivateToken( - @ApiParam(value = "The activate token string.") + @Parameter(description = "The activate token string.") @RequestParam(value = "activateToken") String activateToken) { HttpHeaders headers = new HttpHeaders(); HttpStatus responseStatus; @@ -175,7 +175,7 @@ public class AuthController extends BaseController { @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) public void requestResetPasswordByEmail( - @ApiParam(value = "The JSON object representing the reset password email request.") + @Parameter(description = "The JSON object representing the reset password email request.") @RequestBody ResetPasswordEmailRequest resetPasswordByEmailRequest, HttpServletRequest request) throws ThingsboardException { try { @@ -198,7 +198,7 @@ public class AuthController extends BaseController { "If token is not valid, returns '409 Conflict'.") @RequestMapping(value = "/noauth/resetPassword", params = {"resetToken"}, method = RequestMethod.GET) public ResponseEntity checkResetToken( - @ApiParam(value = "The reset token string.") + @Parameter(description = "The reset token string.") @RequestParam(value = "resetToken") String resetToken) { HttpHeaders headers = new HttpHeaders(); HttpStatus responseStatus; @@ -234,7 +234,7 @@ public class AuthController extends BaseController { @ResponseStatus(value = HttpStatus.OK) @ResponseBody public JwtPair activateUser( - @ApiParam(value = "Activate user request.") + @Parameter(description = "Activate user request.") @RequestBody ActivateUserRequest activateRequest, @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail, HttpServletRequest request) throws ThingsboardException { @@ -272,7 +272,7 @@ public class AuthController extends BaseController { @ResponseStatus(value = HttpStatus.OK) @ResponseBody public JwtPair resetPassword( - @ApiParam(value = "Reset password request.") + @Parameter(description = "Reset password request.") @RequestBody ResetPasswordRequest resetPasswordRequest, HttpServletRequest request) throws ThingsboardException { String resetToken = resetPasswordRequest.getResetToken(); diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index da2740a3b7..13118645a8 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -20,6 +20,9 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.ConstraintViolation; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -160,9 +163,6 @@ import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; -import jakarta.mail.MessagingException; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.ConstraintViolation; import java.util.List; import java.util.Objects; import java.util.Optional; diff --git a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java index 33a4edfbe0..b0f1786437 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java +++ b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import java.util.HashSet; @@ -54,7 +55,7 @@ public class ComponentDescriptorController extends BaseController { @RequestMapping(value = "/component/{componentDescriptorClazz:.+}", method = RequestMethod.GET) @ResponseBody public ComponentDescriptor getComponentDescriptorByClazz( - @ApiParam(value = "Component Descriptor class name", required = true) + @Parameter(description = "Component Descriptor class name", required = true) @PathVariable("componentDescriptorClazz") String strComponentDescriptorClazz) throws ThingsboardException { checkParameter("strComponentDescriptorClazz", strComponentDescriptorClazz); return checkComponentDescriptorByClazz(strComponentDescriptorClazz); @@ -67,9 +68,9 @@ public class ComponentDescriptorController extends BaseController { @RequestMapping(value = "/components/{componentType}", method = RequestMethod.GET) @ResponseBody public List getComponentDescriptorsByType( - @ApiParam(value = "Type of the Rule Node", allowableValues = "ENRICHMENT,FILTER,TRANSFORMATION,ACTION,EXTERNAL", required = true) + @Parameter(description = "Type of the Rule Node", schema = @Schema(allowableValues = "ENRICHMENT,FILTER,TRANSFORMATION,ACTION,EXTERNAL", required = true)) @PathVariable("componentType") String strComponentType, - @ApiParam(value = "Type of the Rule Chain", allowableValues = "CORE,EDGE") + @Parameter(description = "Type of the Rule Chain", schema = @Schema(allowableValues = "CORE,EDGE")) @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException { checkParameter("componentType", strComponentType); return checkComponentDescriptorsByType(ComponentType.valueOf(strComponentType), getRuleChainType(strRuleChainType)); @@ -82,9 +83,9 @@ public class ComponentDescriptorController extends BaseController { @RequestMapping(value = "/components", params = {"componentTypes"}, method = RequestMethod.GET) @ResponseBody public List getComponentDescriptorsByTypes( - @ApiParam(value = "List of types of the Rule Nodes, (ENRICHMENT, FILTER, TRANSFORMATION, ACTION or EXTERNAL)", required = true) + @Parameter(description = "List of types of the Rule Nodes, (ENRICHMENT, FILTER, TRANSFORMATION, ACTION or EXTERNAL)", required = true) @RequestParam("componentTypes") String[] strComponentTypes, - @ApiParam(value = "Type of the Rule Chain", allowableValues = "CORE,EDGE") + @Parameter(description = "Type of the Rule Chain", schema = @Schema(allowableValues = "CORE,EDGE")) @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException { checkArrayParameter("componentTypes", strComponentTypes); Set componentTypes = new HashSet<>(); diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index f425bde86c..8f95dbff35 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -89,33 +89,8 @@ public class ControllerConstants { protected static final String EVENT_TEXT_SEARCH_DESCRIPTION = "The value is not used in searching."; protected static final String AUDIT_LOG_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on one of the next properties: entityType, entityName, userName, actionType, actionStatus."; protected static final String SORT_PROPERTY_DESCRIPTION = "Property of entity to sort by"; - protected static final String DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title"; - protected static final String CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, email, country, city"; - protected static final String RPC_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, expirationTime, request, response"; - protected static final String DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, deviceProfileName, label, customerTitle"; - protected static final String ENTITY_VIEW_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type"; - protected static final String ENTITY_VIEW_INFO_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, customerTitle"; - protected static final String USER_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, firstName, lastName, email"; - protected static final String TENANT_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, email, country, state, city, address, address2, zip, phone, email"; - protected static final String TENANT_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, description, isDefault"; - protected static final String TENANT_PROFILE_INFO_SORT_PROPERTY_ALLOWABLE_VALUES = "id, name"; - protected static final String TENANT_INFO_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, tenantProfileName, title, email, country, state, city, address, address2, zip, phone, email"; - protected static final String DEVICE_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, transportType, description, isDefault"; - - protected static final String ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, description, isDefault"; - protected static final String ASSET_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle"; - protected static final String ALARM_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, startTs, endTs, type, ackTs, clearTs, severity, status"; - protected static final String ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, id"; - protected static final String EVENT_SORT_PROPERTY_ALLOWABLE_VALUES = "ts, id"; - protected static final String EDGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle"; - protected static final String RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, root"; - protected static final String WIDGET_BUNDLE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, tenantId"; - protected static final String AUDIT_LOG_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, entityType, entityName, userName, actionType, actionStatus"; + protected static final String SORT_ORDER_DESCRIPTION = "Sort order. ASC (ASCENDING) or DESC (DESCENDING)"; - protected static final String SORT_ORDER_ALLOWABLE_VALUES = "ASC, DESC"; - protected static final String RPC_STATUS_ALLOWABLE_VALUES = "QUEUED, SENT, DELIVERED, SUCCESSFUL, TIMEOUT, EXPIRED, FAILED"; - protected static final String RULE_CHAIN_TYPES_ALLOWABLE_VALUES = "CORE, EDGE"; - protected static final String TRANSPORT_TYPE_ALLOWABLE_VALUES = "DEFAULT, MQTT, COAP, LWM2M, SNMP"; protected static final String DEVICE_INFO_DESCRIPTION = "Device Info is an extension of the default Device object that contains information about the assigned customer name and device profile name. "; protected static final String ASSET_INFO_DESCRIPTION = "Asset Info is an extension of the default Asset object that contains information about the assigned customer name. "; protected static final String ALARM_INFO_DESCRIPTION = "Alarm Info is an extension of the default Alarm object that also contains name of the alarm originator."; @@ -125,23 +100,17 @@ public class ControllerConstants { protected static final String ASSET_PROFILE_INFO_DESCRIPTION = "Asset Profile Info is a lightweight object that includes main information about Asset Profile. "; protected static final String QUEUE_SERVICE_TYPE_DESCRIPTION = "Service type (implemented only for the TB-RULE-ENGINE)"; - protected static final String QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES = "TB-RULE-ENGINE, TB-CORE, TB-TRANSPORT, JS-EXECUTOR"; protected static final String QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the queue name."; - protected static final String QUEUE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, topic"; protected static final String QUEUE_ID_PARAM_DESCRIPTION = "A string value representing the queue id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String QUEUE_NAME_PARAM_DESCRIPTION = "A string value representing the queue id. For example, 'Main'"; protected static final String OTA_PACKAGE_INFO_DESCRIPTION = "OTA Package Info is a lightweight object that includes main information about the OTA Package excluding the heavyweight data. "; protected static final String OTA_PACKAGE_DESCRIPTION = "OTA Package is a heavyweight object that includes main information about the OTA Package and also data. "; - protected static final String OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES = "MD5, SHA256, SHA384, SHA512, CRC32, MURMUR3_32, MURMUR3_128"; protected static final String OTA_PACKAGE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the ota package title."; - protected static final String OTA_PACKAGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, type, title, version, tag, url, fileName, dataSize, checksum"; protected static final String RESOURCE_INFO_DESCRIPTION = "Resource Info is a lightweight object that includes main information about the Resource excluding the heavyweight data. "; protected static final String RESOURCE_DESCRIPTION = "Resource is a heavyweight object that includes main information about the Resource and also data. "; protected static final String RESOURCE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the resource title."; - protected static final String RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, resourceType, tenantId"; protected static final String LWM2M_OBJECT_DESCRIPTION = "LwM2M Object is a object that includes information about the LwM2M model which can be used in transport configuration for the LwM2M device profile. "; - protected static final String LWM2M_OBJECT_SORT_PROPERTY_ALLOWABLE_VALUES = "id, name"; protected static final String DEVICE_NAME_DESCRIPTION = "A string value representing the Device name."; protected static final String ASSET_NAME_DESCRIPTION = "A string value representing the Asset name."; @@ -1439,8 +1408,6 @@ public class ControllerConstants { protected static final String ATTRIBUTES_SCOPE_DESCRIPTION = "A string value representing the attributes scope. For example, 'SERVER_SCOPE'."; protected static final String ATTRIBUTES_KEYS_DESCRIPTION = "A string value representing the comma-separated list of attributes keys. For example, 'active,inactivityAlarmTime'."; - protected static final String ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES = "SERVER_SCOPE, SHARED_SCOPE"; - protected static final String ATTRIBUTES_SCOPE_ALLOWED_VALUES = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES + ", CLIENT_SCOPE"; protected static final String ATTRIBUTES_JSON_REQUEST_DESCRIPTION = "A string value representing the json object. For example, '{\"key\":\"value\"}'. See API call description for more details."; protected static final String TELEMETRY_KEYS_BASE_DESCRIPTION = "A string value representing the comma-separated list of telemetry keys."; diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java index dd904add2e..7203f71392 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java @@ -17,8 +17,8 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.customer.TbCustomerService; import org.thingsboard.server.service.security.permission.Operation; @@ -44,13 +45,11 @@ import org.thingsboard.server.service.security.permission.Resource; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.HOME_DASHBOARD; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; @@ -76,7 +75,7 @@ public class CustomerController extends BaseController { @RequestMapping(value = "/customer/{customerId}", method = RequestMethod.GET) @ResponseBody public Customer getCustomerById( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); @@ -95,7 +94,7 @@ public class CustomerController extends BaseController { @RequestMapping(value = "/customer/{customerId}/shortInfo", method = RequestMethod.GET) @ResponseBody public JsonNode getShortCustomerInfoById( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); @@ -113,7 +112,7 @@ public class CustomerController extends BaseController { @RequestMapping(value = "/customer/{customerId}/title", method = RequestMethod.GET, produces = "application/text") @ResponseBody public String getCustomerTitleById( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); @@ -131,7 +130,7 @@ public class CustomerController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer", method = RequestMethod.POST) @ResponseBody - public Customer saveCustomer(@ApiParam(value = "A JSON value representing the customer.") @RequestBody Customer customer) throws Exception { + public Customer saveCustomer(@Parameter(description = "A JSON value representing the customer.") @RequestBody Customer customer) throws Exception { customer.setTenantId(getTenantId()); checkEntity(customer.getId(), customer, Resource.CUSTOMER); return tbCustomerService.save(customer, getCurrentUser()); @@ -144,7 +143,7 @@ public class CustomerController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/{customerId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public void deleteCustomer(@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + public void deleteCustomer(@Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); @@ -159,15 +158,15 @@ public class CustomerController extends BaseController { @RequestMapping(value = "/customers", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomers( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = CUSTOMER_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = CUSTOMER_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "title", "email", "country, city"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); TenantId tenantId = getCurrentUser().getTenantId(); @@ -180,7 +179,7 @@ public class CustomerController extends BaseController { @RequestMapping(value = "/tenant/customers", params = {"customerTitle"}, method = RequestMethod.GET) @ResponseBody public Customer getTenantCustomer( - @ApiParam(value = "A string value representing the Customer title.") + @Parameter(description = "A string value representing the Customer title.") @RequestParam String customerTitle) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); return checkNotNull(customerService.findCustomerByTenantIdAndTitle(tenantId, customerTitle), "Customer with title [" + customerTitle + "] is not found"); diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index afc737ab88..c6678deffe 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -17,11 +17,11 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.Example; -import io.swagger.annotations.ExampleProperty; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; @@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.dashboard.TbDashboardService; import org.thingsboard.server.service.security.model.SecurityUser; @@ -66,7 +67,6 @@ import java.util.stream.Collectors; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION; @@ -77,7 +77,6 @@ import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIG import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; @@ -111,7 +110,7 @@ public class DashboardController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET) @ResponseBody - @ApiResponse(code = 200, message = "OK", examples = @Example(value = @ExampleProperty(value = "1636023857137", mediaType = "application/json"))) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = "1636023857137"))) public long getServerTime() throws ThingsboardException { return System.currentTimeMillis(); } @@ -124,20 +123,20 @@ public class DashboardController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/dashboard/maxDatapointsLimit", method = RequestMethod.GET) @ResponseBody - @ApiResponse(code = 200, message = "OK", examples = @Example(value = @ExampleProperty(value = "5000", mediaType = "application/json"))) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = "5000"))) public long getMaxDatapointsLimit() throws ThingsboardException { return maxDatapointsLimit; } @ApiOperation(value = "Get Dashboard Info (getDashboardInfoById)", notes = "Get the information about the dashboard based on 'dashboardId' parameter. " + DASHBOARD_INFO_DEFINITION, - produces = MediaType.APPLICATION_JSON_VALUE + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) ) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET) @ResponseBody public DashboardInfo getDashboardInfoById( - @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { checkParameter(DASHBOARD_ID, strDashboardId); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); @@ -146,13 +145,13 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Get Dashboard (getDashboardById)", notes = "Get the dashboard based on 'dashboardId' parameter. " + DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) ) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET) @ResponseBody public Dashboard getDashboardById( - @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { checkParameter(DASHBOARD_ID, strDashboardId); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); @@ -166,13 +165,12 @@ public class DashboardController extends BaseController { "Referencing non-existing dashboard Id will cause 'Not Found' error. " + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Dashboard entity. " + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE, - consumes = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/dashboard", method = RequestMethod.POST) @ResponseBody public Dashboard saveDashboard( - @ApiParam(value = "A JSON value representing the dashboard.") + @Parameter(description = "A JSON value representing the dashboard.") @RequestBody Dashboard dashboard) throws Exception { dashboard.setTenantId(getTenantId()); checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD); @@ -185,7 +183,7 @@ public class DashboardController extends BaseController { @RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteDashboard( - @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { checkParameter(DASHBOARD_ID, strDashboardId); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); @@ -196,14 +194,14 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Assign the Dashboard (assignDashboardToCustomer)", notes = "Assign the Dashboard to specified Customer or do nothing if the Dashboard is already assigned to that Customer. " + "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST) @ResponseBody public Dashboard assignDashboardToCustomer( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable(CUSTOMER_ID) String strCustomerId, - @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); checkParameter(DASHBOARD_ID, strDashboardId); @@ -219,14 +217,14 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Unassign the Dashboard (unassignDashboardFromCustomer)", notes = "Unassign the Dashboard from specified Customer or do nothing if the Dashboard is already assigned to that Customer. " + "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE) @ResponseBody public Dashboard unassignDashboardFromCustomer( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable(CUSTOMER_ID) String strCustomerId, - @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { checkParameter("customerId", strCustomerId); checkParameter(DASHBOARD_ID, strDashboardId); @@ -240,16 +238,15 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Update the Dashboard Customers (updateDashboardCustomers)", notes = "Updates the list of Customers that this Dashboard is assigned to. Removes previous assignments to customers that are not in the provided list. " + "Returns the Dashboard object. " + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE, - consumes = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/dashboard/{dashboardId}/customers", method = RequestMethod.POST) @ResponseBody public Dashboard updateDashboardCustomers( - @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DASHBOARD_ID) String strDashboardId, - @ApiParam(value = "JSON array with the list of customer ids, or empty to remove all customers") + @Parameter(description = "JSON array with the list of customer ids, or empty to remove all customers") @RequestBody(required = false) String[] strCustomerIds) throws ThingsboardException { checkParameter(DASHBOARD_ID, strDashboardId); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); @@ -261,15 +258,14 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Adds the Dashboard Customers (addDashboardCustomers)", notes = "Adds the list of Customers to the existing list of assignments for the Dashboard. Keeps previous assignments to customers that are not in the provided list. " + "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE, - consumes = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/dashboard/{dashboardId}/customers/add", method = RequestMethod.POST) @ResponseBody public Dashboard addDashboardCustomers( - @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DASHBOARD_ID) String strDashboardId, - @ApiParam(value = "JSON array with the list of customer ids") + @Parameter(description = "JSON array with the list of customer ids") @RequestBody String[] strCustomerIds) throws ThingsboardException { checkParameter(DASHBOARD_ID, strDashboardId); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); @@ -281,15 +277,14 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Remove the Dashboard Customers (removeDashboardCustomers)", notes = "Removes the list of Customers from the existing list of assignments for the Dashboard. Keeps other assignments to customers that are not in the provided list. " + "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE, - consumes = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/dashboard/{dashboardId}/customers/remove", method = RequestMethod.POST) @ResponseBody public Dashboard removeDashboardCustomers( - @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DASHBOARD_ID) String strDashboardId, - @ApiParam(value = "JSON array with the list of customer ids") + @Parameter(description = "JSON array with the list of customer ids") @RequestBody String[] strCustomerIds) throws ThingsboardException { checkParameter(DASHBOARD_ID, strDashboardId); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); @@ -305,12 +300,12 @@ public class DashboardController extends BaseController { "Use [assign Asset to Public Customer](#!/asset-controller/assignAssetToPublicCustomerUsingPOST) and " + "[assign Device to Public Customer](#!/device-controller/assignDeviceToPublicCustomerUsingPOST) for this purpose. " + "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST) @ResponseBody public Dashboard assignDashboardToPublicCustomer( - @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { checkParameter(DASHBOARD_ID, strDashboardId); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); @@ -321,12 +316,12 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Unassign the Dashboard from Public Customer (unassignDashboardFromPublicCustomer)", notes = "Unassigns the dashboard from a special, auto-generated 'Public' Customer. Once unassigned, unauthenticated users may no longer browse the dashboard. " + "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE) @ResponseBody public Dashboard unassignDashboardFromPublicCustomer( - @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { checkParameter(DASHBOARD_ID, strDashboardId); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); @@ -337,22 +332,22 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Get Tenant Dashboards by System Administrator (getTenantDashboards)", notes = "Returns a page of dashboard info objects owned by tenant. " + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + SYSTEM_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantDashboards( - @ApiParam(value = TENANT_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = TENANT_ID_PARAM_DESCRIPTION, required = true) @PathVariable(TENANT_ID) String strTenantId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = DASHBOARD_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "title"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); checkTenantId(tenantId, Operation.READ); @@ -363,22 +358,22 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Get Tenant Dashboards (getTenantDashboards)", notes = "Returns a page of dashboard info objects owned by the tenant of a current user. " + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantDashboards( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = HIDDEN_FOR_MOBILE) + @Parameter(description = HIDDEN_FOR_MOBILE) @RequestParam(required = false) Boolean mobile, - @ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = DASHBOARD_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "title"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); @@ -392,24 +387,24 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Get Customer Dashboards (getCustomerDashboards)", notes = "Returns a page of dashboard info objects owned by the specified customer. " + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/customer/{customerId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomerDashboards( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) @PathVariable(CUSTOMER_ID) String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = HIDDEN_FOR_MOBILE) + @Parameter(description = HIDDEN_FOR_MOBILE) @RequestParam(required = false) Boolean mobile, - @ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = DASHBOARD_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "title"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); TenantId tenantId = getCurrentUser().getTenantId(); @@ -428,7 +423,7 @@ public class DashboardController extends BaseController { "If 'homeDashboardId' parameter is not set on the User level and the User has authority 'CUSTOMER_USER', check the same parameter for the corresponding Customer. " + "If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. " + DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/dashboard/home", method = RequestMethod.GET) @ResponseBody @@ -461,7 +456,7 @@ public class DashboardController extends BaseController { "If 'homeDashboardId' parameter is not set on the User level and the User has authority 'CUSTOMER_USER', check the same parameter for the corresponding Customer. " + "If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/dashboard/home/info", method = RequestMethod.GET) @ResponseBody @@ -492,7 +487,7 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Get Tenant Home Dashboard Info (getTenantHomeDashboardInfo)", notes = "Returns the home dashboard info object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the corresponding tenant. " + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET) @ResponseBody @@ -514,12 +509,12 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Update Tenant Home Dashboard Info (getTenantHomeDashboardInfo)", notes = "Update the home dashboard assignment for the current tenant. " + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) public void setTenantHomeDashboardInfo( - @ApiParam(value = "A JSON object that represents home dashboard id and other parameters", required = true) + @Parameter(description = "A JSON object that represents home dashboard id and other parameters", required = true) @RequestBody HomeDashboardInfo homeDashboardInfo) throws ThingsboardException { if (homeDashboardInfo.getDashboardId() != null) { @@ -582,7 +577,7 @@ public class DashboardController extends BaseController { EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + "Third, once dashboard will be delivered to edge service, it's going to be available for usage on remote edge instance." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.POST) @ResponseBody @@ -606,7 +601,7 @@ public class DashboardController extends BaseController { EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + "Third, once 'unassign' command will be delivered to edge service, it's going to remove dashboard locally." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.DELETE) @ResponseBody @@ -627,22 +622,22 @@ public class DashboardController extends BaseController { @ApiOperation(value = "Get Edge Dashboards (getEdgeDashboards)", notes = "Returns a page of dashboard info objects assigned to the specified edge. " + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/edge/{edgeId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getEdgeDashboards( - @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = DASHBOARD_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "title"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("edgeId", strEdgeId); TenantId tenantId = getCurrentUser().getTenantId(); diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 9de6a66041..603409bba0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -19,8 +19,11 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.annotation.Nullable; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -62,6 +65,7 @@ import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest; import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.device.claim.ReclaimResult; @@ -74,7 +78,6 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -90,7 +93,6 @@ import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PA import static org.thingsboard.server.controller.ControllerConstants.DEVICE_INFO_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_NAME_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_TYPE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_DESCRIPTION_MARKDOWN; @@ -102,7 +104,6 @@ import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIG import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; @@ -133,7 +134,7 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET) @ResponseBody - public Device getDeviceById(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + public Device getDeviceById(@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); @@ -148,7 +149,7 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device/info/{deviceId}", method = RequestMethod.GET) @ResponseBody - public DeviceInfo getDeviceInfoById(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + public DeviceInfo getDeviceInfoById(@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); @@ -167,8 +168,8 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device", method = RequestMethod.POST) @ResponseBody - public Device saveDevice(@ApiParam(value = "A JSON value representing the device.") @RequestBody Device device, - @ApiParam(value = "Optional value of the device credentials to be used during device creation. " + + public Device saveDevice(@Parameter(description = "A JSON value representing the device.") @RequestBody Device device, + @Parameter(description = "Optional value of the device credentials to be used during device creation. " + "If omitted, access token will be auto-generated.") @RequestParam(name = "accessToken", required = false) String accessToken) throws Exception { device.setTenantId(getCurrentUser().getTenantId()); Device oldDevice = null; @@ -190,7 +191,7 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device-with-credentials", method = RequestMethod.POST) @ResponseBody - public Device saveDeviceWithCredentials(@ApiParam(value = "The JSON object with device and credentials. See method description above for example.") + public Device saveDeviceWithCredentials(@Parameter(description = "The JSON object with device and credentials. See method description above for example.") @RequestBody SaveDeviceWithCredentialsRequest deviceAndCredentials) throws ThingsboardException { Device device = checkNotNull(deviceAndCredentials.getDevice()); DeviceCredentials credentials = checkNotNull(deviceAndCredentials.getCredentials()); @@ -204,7 +205,7 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public void deleteDevice(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + public void deleteDevice(@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws Exception { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); @@ -217,9 +218,9 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/{customerId}/device/{deviceId}", method = RequestMethod.POST) @ResponseBody - public Device assignDeviceToCustomer(@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + public Device assignDeviceToCustomer(@Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable("customerId") String strCustomerId, - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter("customerId", strCustomerId); checkParameter(DEVICE_ID, strDeviceId); @@ -235,7 +236,7 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/device/{deviceId}", method = RequestMethod.DELETE) @ResponseBody - public Device unassignDeviceFromCustomer(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + public Device unassignDeviceFromCustomer(@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); @@ -256,7 +257,7 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/public/device/{deviceId}", method = RequestMethod.POST) @ResponseBody - public Device assignDeviceToPublicCustomer(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + public Device assignDeviceToPublicCustomer(@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); @@ -269,7 +270,7 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device/{deviceId}/credentials", method = RequestMethod.GET) @ResponseBody - public DeviceCredentials getDeviceCredentialsByDeviceId(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + public DeviceCredentials getDeviceCredentialsByDeviceId(@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); @@ -285,7 +286,7 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/device/credentials", method = RequestMethod.POST) @ResponseBody public DeviceCredentials updateDeviceCredentials( - @ApiParam(value = "A JSON value representing the device credentials.") + @Parameter(description = "A JSON value representing the device credentials.") @RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { checkNotNull(deviceCredentials); Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS); @@ -299,17 +300,17 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/tenant/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantDevices( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = DEVICE_TYPE_DESCRIPTION) + @Parameter(description = DEVICE_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = DEVICE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "deviceProfileName", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); @@ -327,21 +328,21 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/tenant/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantDeviceInfos( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = DEVICE_TYPE_DESCRIPTION) + @Parameter(description = DEVICE_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @RequestParam(required = false) String deviceProfileId, - @ApiParam(value = DEVICE_ACTIVE_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_ACTIVE_PARAM_DESCRIPTION) @RequestParam(required = false) Boolean active, - @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = DEVICE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "deviceProfileName", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder ) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); @@ -364,7 +365,7 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/tenant/devices", params = {"deviceName"}, method = RequestMethod.GET) @ResponseBody public Device getTenantDevice( - @ApiParam(value = DEVICE_NAME_DESCRIPTION) + @Parameter(description = DEVICE_NAME_DESCRIPTION) @RequestParam String deviceName) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); return checkNotNull(deviceService.findDeviceByTenantIdAndName(tenantId, deviceName)); @@ -377,19 +378,19 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/customer/{customerId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomerDevices( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) @PathVariable(CUSTOMER_ID) String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = DEVICE_TYPE_DESCRIPTION) + @Parameter(description = DEVICE_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = DEVICE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "deviceProfileName", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); TenantId tenantId = getCurrentUser().getTenantId(); @@ -410,23 +411,23 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/customer/{customerId}/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomerDeviceInfos( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) @PathVariable("customerId") String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = DEVICE_TYPE_DESCRIPTION) + @Parameter(description = DEVICE_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @RequestParam(required = false) String deviceProfileId, - @ApiParam(value = DEVICE_ACTIVE_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_ACTIVE_PARAM_DESCRIPTION) @RequestParam(required = false) Boolean active, - @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = DEVICE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "deviceProfileName", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); TenantId tenantId = getCurrentUser().getTenantId(); @@ -451,7 +452,7 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET) @ResponseBody public List getDevicesByIds( - @ApiParam(value = "A list of devices ids, separated by comma ','") + @Parameter(description = "A list of devices ids, separated by comma ','") @RequestParam("deviceIds") String[] strDeviceIds) throws ThingsboardException, ExecutionException, InterruptedException { checkArrayParameter("deviceIds", strDeviceIds); SecurityUser user = getCurrentUser(); @@ -478,7 +479,7 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/devices", method = RequestMethod.POST) @ResponseBody public List findByQuery( - @ApiParam(value = "The device search query JSON") + @Parameter(description = "The device search query JSON") @RequestBody DeviceSearchQuery query) throws ThingsboardException, ExecutionException, InterruptedException { checkNotNull(query); checkNotNull(query.getParameters()); @@ -519,9 +520,9 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAuthority('CUSTOMER_USER')") @RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.POST) @ResponseBody - public DeferredResult claimDevice(@ApiParam(value = "Unique name of the device which is going to be claimed") + public DeferredResult claimDevice(@Parameter(description = "Unique name of the device which is going to be claimed") @PathVariable(DEVICE_NAME) String deviceName, - @ApiParam(value = "Claiming request which can optionally contain secret key") + @Parameter(description = "Claiming request which can optionally contain secret key") @RequestBody(required = false) ClaimRequest claimRequest) throws ThingsboardException { checkParameter(DEVICE_NAME, deviceName); final DeferredResult deferredResult = new DeferredResult<>(); @@ -568,7 +569,7 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public DeferredResult reClaimDevice(@ApiParam(value = "Unique name of the device which is going to be reclaimed") + public DeferredResult reClaimDevice(@Parameter(description = "Unique name of the device which is going to be reclaimed") @PathVariable(DEVICE_NAME) String deviceName) throws ThingsboardException { checkParameter(DEVICE_NAME, deviceName); final DeferredResult deferredResult = new DeferredResult<>(); @@ -608,9 +609,9 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/{tenantId}/device/{deviceId}", method = RequestMethod.POST) @ResponseBody - public Device assignDeviceToTenant(@ApiParam(value = TENANT_ID_PARAM_DESCRIPTION) + public Device assignDeviceToTenant(@Parameter(description = TENANT_ID_PARAM_DESCRIPTION) @PathVariable(TENANT_ID) String strTenantId, - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(TENANT_ID, strTenantId); checkParameter(DEVICE_ID, strDeviceId); @@ -631,13 +632,13 @@ public class DeviceController extends BaseController { "Second, remote edge service will receive a copy of assignment device " + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + "Third, once device will be delivered to edge service, it's going to be available for usage on remote edge instance." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST) @ResponseBody - public Device assignDeviceToEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) + public Device assignDeviceToEdge(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId, - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); checkParameter(DEVICE_ID, strDeviceId); @@ -656,13 +657,13 @@ public class DeviceController extends BaseController { "Second, remote edge service will receive an 'unassign' command to remove device " + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + "Third, once 'unassign' command will be delivered to edge service, it's going to remove device locally." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.DELETE) @ResponseBody - public Device unassignDeviceFromEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) + public Device unassignDeviceFromEdge(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId, - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); checkParameter(DEVICE_ID, strDeviceId); @@ -681,27 +682,27 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/edge/{edgeId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getEdgeDevices( - @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = DEVICE_TYPE_DESCRIPTION) + @Parameter(description = DEVICE_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @RequestParam(required = false) String deviceProfileId, - @ApiParam(value = DEVICE_ACTIVE_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_ACTIVE_PARAM_DESCRIPTION) @RequestParam(required = false) Boolean active, - @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = DEVICE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "deviceProfileName", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = "Timestamp. Devices with creation time before it won't be queried") + @Parameter(description = "Timestamp. Devices with creation time before it won't be queried") @RequestParam(required = false) Long startTime, - @ApiParam(value = "Timestamp. Devices with creation time after it won't be queried") + @Parameter(description = "Timestamp. Devices with creation time after it won't be queried") @RequestParam(required = false) Long endTime) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); TenantId tenantId = getCurrentUser().getTenantId(); @@ -728,11 +729,11 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/devices/count/{otaPackageType}/{deviceProfileId}", method = RequestMethod.GET) @ResponseBody - public Long countByDeviceProfileAndEmptyOtaPackage - (@ApiParam(value = "OTA package type", allowableValues = "FIRMWARE, SOFTWARE") - @PathVariable("otaPackageType") String otaPackageType, - @ApiParam(value = "Device Profile Id. I.g. '784f394c-42b6-435a-983c-b7beff2784f9'") - @PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException { + public Long countByDeviceProfileAndEmptyOtaPackage( + @Parameter(description = "OTA package type", schema = @Schema(allowableValues = {"FIRMWARE", "SOFTWARE"})) + @PathVariable("otaPackageType") String otaPackageType, + @Parameter(description = "Device Profile Id. I.g. '784f394c-42b6-435a-983c-b7beff2784f9'") + @PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException { checkParameter("OtaPackageType", otaPackageType); checkParameter("DeviceProfileId", deviceProfileId); return deviceService.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage( diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index c888d67252..093f44dcfd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -15,12 +15,15 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -37,6 +40,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.device.profile.TbDeviceProfileService; @@ -50,18 +54,15 @@ import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFI import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_ID; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_INFO_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; -import static org.thingsboard.server.controller.ControllerConstants.TRANSPORT_TYPE_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; @RestController @@ -79,12 +80,12 @@ public class DeviceProfileController extends BaseController { @ApiOperation(value = "Get Device Profile (getDeviceProfileById)", notes = "Fetch the Device Profile object based on the provided Device Profile Id. " + "The server checks that the device profile is owned by the same tenant. " + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.GET) @ResponseBody public DeviceProfile getDeviceProfileById( - @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); @@ -94,12 +95,12 @@ public class DeviceProfileController extends BaseController { @ApiOperation(value = "Get Device Profile Info (getDeviceProfileInfoById)", notes = "Fetch the Device Profile Info object based on the provided Device Profile Id. " + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/deviceProfileInfo/{deviceProfileId}", method = RequestMethod.GET) @ResponseBody public DeviceProfileInfo getDeviceProfileInfoById( - @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); @@ -109,7 +110,7 @@ public class DeviceProfileController extends BaseController { @ApiOperation(value = "Get Default Device Profile (getDefaultDeviceProfileInfo)", notes = "Fetch the Default Device Profile Info object. " + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/deviceProfileInfo/default", method = RequestMethod.GET) @ResponseBody @@ -123,12 +124,12 @@ public class DeviceProfileController extends BaseController { "The call is used for auto-complete in the UI forms. " + "The implementation limits the number of devices that participate in search to 100 as a trade of between accurate results and time-consuming queries. " + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/deviceProfile/devices/keys/timeseries", method = RequestMethod.GET) @ResponseBody public List getTimeseriesKeys( - @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @RequestParam(name = DEVICE_PROFILE_ID, required = false) String deviceProfileIdStr) throws ThingsboardException { DeviceProfileId deviceProfileId; if (StringUtils.isNotEmpty(deviceProfileIdStr)) { @@ -147,12 +148,12 @@ public class DeviceProfileController extends BaseController { "The call is used for auto-complete in the UI forms. " + "The implementation limits the number of devices that participate in search to 100 as a trade of between accurate results and time-consuming queries. " + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/deviceProfile/devices/keys/attributes", method = RequestMethod.GET) @ResponseBody public List getAttributesKeys( - @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @RequestParam(name = DEVICE_PROFILE_ID, required = false) String deviceProfileIdStr) throws ThingsboardException { DeviceProfileId deviceProfileId; if (StringUtils.isNotEmpty(deviceProfileIdStr)) { @@ -173,13 +174,12 @@ public class DeviceProfileController extends BaseController { "Device profile name is unique in the scope of tenant. Only one 'default' device profile may exist in scope of tenant." + DEVICE_PROFILE_DATA + "Remove 'id', 'tenantId' from the request body example (below) to create new Device Profile entity. " + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json", - consumes = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/deviceProfile", method = RequestMethod.POST) @ResponseBody public DeviceProfile saveDeviceProfile( - @ApiParam(value = "A JSON value representing the device profile.") + @Parameter(description = "A JSON value representing the device profile.") @RequestBody DeviceProfile deviceProfile) throws Exception { deviceProfile.setTenantId(getTenantId()); checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE); @@ -188,13 +188,12 @@ public class DeviceProfileController extends BaseController { @ApiOperation(value = "Delete device profile (deleteDeviceProfile)", notes = "Deletes the device profile. Referencing non-existing device profile Id will cause an error. " + - "Can't delete the device profile if it is referenced by existing devices." + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + "Can't delete the device profile if it is referenced by existing devices." + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteDeviceProfile( - @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); @@ -204,12 +203,12 @@ public class DeviceProfileController extends BaseController { @ApiOperation(value = "Make Device Profile Default (setDefaultDeviceProfile)", notes = "Marks device profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/deviceProfile/{deviceProfileId}/default", method = RequestMethod.POST) @ResponseBody public DeviceProfile setDefaultDeviceProfile( - @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); @@ -221,20 +220,20 @@ public class DeviceProfileController extends BaseController { @ApiOperation(value = "Get Device Profiles (getDeviceProfiles)", notes = "Returns a page of devices profile objects owned by tenant. " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/deviceProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getDeviceProfiles( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "transportType", "description", "isDefault"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(deviceProfileService.findDeviceProfiles(getTenantId(), pageLink)); @@ -243,22 +242,22 @@ public class DeviceProfileController extends BaseController { @ApiOperation(value = "Get Device Profiles for transport type (getDeviceProfileInfos)", notes = "Returns a page of devices profile info objects owned by tenant. " + PAGE_DATA_PARAMETERS + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/deviceProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getDeviceProfileInfos( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "transportType", "description", "isDefault"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = "Type of the transport", allowableValues = TRANSPORT_TYPE_ALLOWABLE_VALUES) + @Parameter(description = "Type of the transport", schema = @Schema(allowableValues = {"DEFAULT", "MQTT", "COAP", "LWM2M", "SNMP"})) @RequestParam(required = false) String transportType) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink, transportType)); diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java index 0213ae5686..153b760531 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java @@ -16,8 +16,11 @@ package org.thingsboard.server.controller; import com.google.common.util.concurrent.ListenableFuture; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -54,6 +57,7 @@ import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportReques import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; import org.thingsboard.server.common.msg.edge.FromEdgeSyncResponse; import org.thingsboard.server.common.msg.edge.ToEdgeSyncRequest; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; @@ -64,7 +68,6 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import jakarta.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -74,14 +77,12 @@ import java.util.stream.Collectors; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_INFO_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.EDGE_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.EDGE_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_TYPE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; @@ -112,11 +113,11 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Get Edge (getEdgeById)", notes = "Get the Edge object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.GET) @ResponseBody - public Edge getEdgeById(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + public Edge getEdgeById(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); @@ -125,11 +126,11 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Get Edge Info (getEdgeInfoById)", notes = "Get the Edge Info object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/edge/info/{edgeId}", method = RequestMethod.GET) @ResponseBody - public EdgeInfo getEdgeInfoById(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + public EdgeInfo getEdgeInfoById(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); @@ -144,11 +145,11 @@ public class EdgeController extends BaseController { "\n\nEdge name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the edge names and non-unique 'label' field for user-friendly visualization purposes." + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Edge entity. " + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge", method = RequestMethod.POST) @ResponseBody - public Edge saveEdge(@ApiParam(value = "A JSON value representing the edge.", required = true) + public Edge saveEdge(@Parameter(description = "A JSON value representing the edge.", required = true) @RequestBody Edge edge) throws Exception { TenantId tenantId = getTenantId(); edge.setTenantId(tenantId); @@ -174,7 +175,7 @@ public class EdgeController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public void deleteEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + public void deleteEdge(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); @@ -184,19 +185,19 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Get Tenant Edges (getEdges)", notes = "Returns a page of edges owned by tenant. " + - PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edges", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public PageData getEdges(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + public PageData getEdges(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = EDGE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = EDGE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); TenantId tenantId = getCurrentUser().getTenantId(); @@ -205,13 +206,13 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Assign edge to customer (assignEdgeToCustomer)", notes = "Creates assignment of the edge to customer. Customer will be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/{customerId}/edge/{edgeId}", method = RequestMethod.POST) @ResponseBody - public Edge assignEdgeToCustomer(@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) + public Edge assignEdgeToCustomer(@Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) @PathVariable("customerId") String strCustomerId, - @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { checkParameter("customerId", strCustomerId); checkParameter(EDGE_ID, strEdgeId); @@ -224,11 +225,11 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Unassign edge from customer (unassignEdgeFromCustomer)", notes = "Clears assignment of the edge to customer. Customer will not be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/edge/{edgeId}", method = RequestMethod.DELETE) @ResponseBody - public Edge unassignEdgeFromCustomer(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + public Edge unassignEdgeFromCustomer(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); @@ -245,11 +246,11 @@ public class EdgeController extends BaseController { notes = "Edge will be available for non-authorized (not logged-in) users. " + "This is useful to create dashboards that you plan to share/embed on a publicly available website. " + "However, users that are logged-in and belong to different tenant will not be able to access the edge." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer/public/edge/{edgeId}", method = RequestMethod.POST) @ResponseBody - public Edge assignEdgeToPublicCustomer(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + public Edge assignEdgeToPublicCustomer(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); @@ -259,22 +260,22 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Get Tenant Edges (getTenantEdges)", notes = "Returns a page of edges owned by tenant. " + - PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/edges", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantEdges( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = EDGE_TYPE_DESCRIPTION) + @Parameter(description = EDGE_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = EDGE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = EDGE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); @@ -288,22 +289,22 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Get Tenant Edge Infos (getTenantEdgeInfos)", notes = "Returns a page of edges info objects owned by tenant. " + PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantEdgeInfos( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = EDGE_TYPE_DESCRIPTION) + @Parameter(description = EDGE_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = EDGE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = EDGE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); @@ -317,11 +318,11 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Get Tenant Edge (getTenantEdge)", notes = "Requested edge must be owned by tenant or customer that the user belongs to. " + "Edge name is an unique property of edge. So it can be used to identify the edge." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/edges", params = {"edgeName"}, method = RequestMethod.GET) @ResponseBody - public Edge getTenantEdge(@ApiParam(value = "Unique name of the edge", required = true) + public Edge getTenantEdge(@Parameter(description = "Unique name of the edge", required = true) @RequestParam String edgeName) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); return checkNotNull(edgeService.findEdgeByTenantIdAndName(tenantId, edgeName)); @@ -330,13 +331,13 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Set root rule chain for provided edge (setEdgeRootRuleChain)", notes = "Change root rule chain of the edge to the new provided rule chain. \n" + "This operation will send a notification to update root rule chain on remote edge service." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/{ruleChainId}/root", method = RequestMethod.POST) @ResponseBody - public Edge setEdgeRootRuleChain(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + public Edge setEdgeRootRuleChain(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId, - @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION, required = true) @PathVariable("ruleChainId") String strRuleChainId) throws Exception { checkParameter(EDGE_ID, strEdgeId); checkParameter("ruleChainId", strRuleChainId); @@ -350,24 +351,24 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Get Customer Edges (getCustomerEdges)", notes = "Returns a page of edges objects assigned to customer. " + - PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/customer/{customerId}/edges", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomerEdges( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable("customerId") String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = EDGE_TYPE_DESCRIPTION) + @Parameter(description = EDGE_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = EDGE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = EDGE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); SecurityUser user = getCurrentUser(); @@ -386,24 +387,24 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Get Customer Edge Infos (getCustomerEdgeInfos)", notes = "Returns a page of edges info objects assigned to customer. " + - PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/customer/{customerId}/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomerEdgeInfos( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable("customerId") String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = EDGE_TYPE_DESCRIPTION) + @Parameter(description = EDGE_TYPE_DESCRIPTION) @RequestParam(required = false) String type, - @ApiParam(value = EDGE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = EDGE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); SecurityUser user = getCurrentUser(); @@ -422,12 +423,12 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Get Edges By Ids (getEdgesByIds)", notes = "Requested edges must be owned by tenant or assigned to customer which user is performing the request." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/edges", params = {"edgeIds"}, method = RequestMethod.GET) @ResponseBody public List getEdgesByIds( - @ApiParam(value = "A list of edges ids, separated by comma ','", required = true) + @Parameter(description = "A list of edges ids, separated by comma ','", required = true) @RequestParam("edgeIds") String[] strEdgeIds) throws ThingsboardException, ExecutionException, InterruptedException { checkArrayParameter("edgeIds", strEdgeIds); SecurityUser user = getCurrentUser(); @@ -451,7 +452,7 @@ public class EdgeController extends BaseController { notes = "Returns all edges that are related to the specific entity. " + "The entity id, relation type, edge types, depth of the search, and other query parameters defined using complex 'EdgeSearchQuery' object. " + "See 'Model' tab of the Parameters for more info." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/edges", method = RequestMethod.POST) @ResponseBody @@ -477,7 +478,7 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Get Edge Types (getEdgeTypes)", notes = "Returns a set of unique edge types based on edges that are either owned by the tenant or assigned to the customer which user is performing the request." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/edge/types", method = RequestMethod.GET) @ResponseBody @@ -493,7 +494,7 @@ public class EdgeController extends BaseController { "All entities that are assigned to particular edge are going to be send to remote edge service." + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/sync/{edgeId}", method = RequestMethod.POST) - public DeferredResult syncEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + public DeferredResult syncEdge(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("edgeId") String strEdgeId) throws ThingsboardException { checkParameter("edgeId", strEdgeId); final DeferredResult response = new DeferredResult<>(); @@ -523,7 +524,7 @@ public class EdgeController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/missingToRelatedRuleChains/{edgeId}", method = RequestMethod.GET) @ResponseBody - public String findMissingToRelatedRuleChains(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + public String findMissingToRelatedRuleChains(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("edgeId") String strEdgeId) throws ThingsboardException { EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); edgeId = checkNotNull(edgeId); @@ -534,7 +535,7 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Import the bulk of edges (processEdgesBulkImport)", notes = "There's an ability to import the bulk of edges using the only .csv file." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PostMapping("/edge/bulk_import") public BulkImportResult processEdgesBulkImport(@RequestBody BulkImportRequest request) throws Exception { @@ -549,12 +550,12 @@ public class EdgeController extends BaseController { @ApiOperation(value = "Get Edge Docker Install Instructions (getEdgeDockerInstallInstructions)", notes = "Get a docker install instructions for provided edge id." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/instructions/{edgeId}", method = RequestMethod.GET) @ResponseBody public EdgeInstallInstructions getEdgeDockerInstallInstructions( - @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("edgeId") String strEdgeId, HttpServletRequest request) throws ThingsboardException { EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java index fc85f439e0..fc95c02217 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; @@ -33,16 +35,15 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.EDGE_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; @@ -59,26 +60,26 @@ public class EdgeEventController extends BaseController { @ApiOperation(value = "Get Edge Events (getEdgeEvents)", notes = "Returns a page of edge events for the requested edge. " + - PAGE_DATA_PARAMETERS, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/events", method = RequestMethod.GET) @ResponseBody public PageData getEdgeEvents( - @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = "The case insensitive 'substring' filter based on the edge event type name.") + @Parameter(description = "The case insensitive 'substring' filter based on the edge event type name.") @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "label", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = "Timestamp. Edge events with creation time before it won't be queried") + @Parameter(description = "Timestamp. Edge events with creation time before it won't be queried") @RequestParam(required = false) Long startTime, - @ApiParam(value = "Timestamp. Edge events with creation time after it won't be queried") + @Parameter(description = "Timestamp. Edge events with creation time after it won't be queried") @RequestParam(required = false) Long endTime) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); TenantId tenantId = getCurrentUser().getTenantId(); diff --git a/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java index b5c8e18ed2..b501771a3e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java @@ -18,8 +18,8 @@ package org.thingsboard.server.controller; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; @@ -46,6 +46,7 @@ import org.thingsboard.server.common.data.sync.vc.VersionLoadResult; import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest; import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadRequest; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; @@ -67,7 +68,6 @@ import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; @@ -189,7 +189,7 @@ public class EntitiesVersionControlController extends BaseController { MARKDOWN_CODE_BLOCK_END + TENANT_AUTHORITY_PARAGRAPH) @GetMapping(value = "/version/{requestId}/status") - public VersionCreationResult getVersionCreateRequestStatus(@ApiParam(value = VC_REQUEST_ID_PARAM_DESCRIPTION, required = true) + public VersionCreationResult getVersionCreateRequestStatus(@Parameter(description = VC_REQUEST_ID_PARAM_DESCRIPTION, required = true) @PathVariable UUID requestId) throws Exception { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE); return versionControlService.getVersionCreateStatus(getCurrentUser(), requestId); @@ -232,21 +232,21 @@ public class EntitiesVersionControlController extends BaseController { MARKDOWN_CODE_BLOCK_END + TENANT_AUTHORITY_PARAGRAPH) @GetMapping(value = "/version/{entityType}/{externalEntityUuid}", params = {"branch", "pageSize", "page"}) - public DeferredResult> listEntityVersions(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + public DeferredResult> listEntityVersions(@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @PathVariable EntityType entityType, - @ApiParam(value = "A string value representing external entity id. This is `externalId` property of an entity, or otherwise if not set - simply id of this entity.") + @Parameter(description = "A string value representing external entity id. This is `externalId` property of an entity, or otherwise if not set - simply id of this entity.") @PathVariable UUID externalEntityUuid, - @ApiParam(value = BRANCH_PARAM_DESCRIPTION) + @Parameter(description = BRANCH_PARAM_DESCRIPTION) @RequestParam String branch, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp") + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = "timestamp")) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws Exception { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); EntityId externalEntityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid); @@ -261,19 +261,19 @@ public class EntitiesVersionControlController extends BaseController { "The response structure is the same as for `listEntityVersions` API method." + TENANT_AUTHORITY_PARAGRAPH) @GetMapping(value = "/version/{entityType}", params = {"branch", "pageSize", "page"}) - public DeferredResult> listEntityTypeVersions(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + public DeferredResult> listEntityTypeVersions(@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @PathVariable EntityType entityType, - @ApiParam(value = BRANCH_PARAM_DESCRIPTION, required = true) + @Parameter(description = BRANCH_PARAM_DESCRIPTION, required = true) @RequestParam String branch, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp") + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = "timestamp")) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws Exception { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); @@ -286,17 +286,17 @@ public class EntitiesVersionControlController extends BaseController { "The response format is the same as for `listEntityVersions` API method." + TENANT_AUTHORITY_PARAGRAPH) @GetMapping(value = "/version", params = {"branch", "pageSize", "page"}) - public DeferredResult> listVersions(@ApiParam(value = BRANCH_PARAM_DESCRIPTION, required = true) + public DeferredResult> listVersions(@Parameter(description = BRANCH_PARAM_DESCRIPTION, required = true) @RequestParam String branch, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp") + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = "timestamp")) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws Exception { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); @@ -310,9 +310,9 @@ public class EntitiesVersionControlController extends BaseController { "Entities order will be the same as in the repository." + TENANT_AUTHORITY_PARAGRAPH) @GetMapping(value = "/entity/{entityType}/{versionId}") - public DeferredResult> listEntitiesAtVersion(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + public DeferredResult> listEntitiesAtVersion(@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @PathVariable EntityType entityType, - @ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = VERSION_ID_PARAM_DESCRIPTION, required = true) @PathVariable String versionId) throws Exception { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); return wrapFuture(versionControlService.listEntitiesAtVersion(getTenantId(), versionId, entityType)); @@ -324,7 +324,7 @@ public class EntitiesVersionControlController extends BaseController { "Returned entities order will be the same as in the repository." + TENANT_AUTHORITY_PARAGRAPH) @GetMapping(value = "/entity/{versionId}") - public DeferredResult> listAllEntitiesAtVersion(@ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true) + public DeferredResult> listAllEntitiesAtVersion(@Parameter(description = VERSION_ID_PARAM_DESCRIPTION, required = true) @PathVariable String versionId) throws Exception { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); return wrapFuture(versionControlService.listAllEntitiesAtVersion(getTenantId(), versionId)); @@ -337,11 +337,11 @@ public class EntitiesVersionControlController extends BaseController { "`hasCredentials` (whether stored device data has credentials)." + TENANT_AUTHORITY_PARAGRAPH) @GetMapping("/info/{versionId}/{entityType}/{externalEntityUuid}") - public DeferredResult getEntityDataInfo(@ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true) + public DeferredResult getEntityDataInfo(@Parameter(description = VERSION_ID_PARAM_DESCRIPTION, required = true) @PathVariable String versionId, - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @PathVariable EntityType entityType, - @ApiParam(value = "A string value representing external entity id", required = true) + @Parameter(description = "A string value representing external entity id", required = true) @PathVariable UUID externalEntityUuid) throws Exception { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid); @@ -353,11 +353,11 @@ public class EntitiesVersionControlController extends BaseController { "Entity data structure is the same as stored in a repository. " + TENANT_AUTHORITY_PARAGRAPH) @GetMapping(value = "/diff/{entityType}/{internalEntityUuid}", params = {"versionId"}) - public DeferredResult compareEntityDataToVersion(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + public DeferredResult compareEntityDataToVersion(@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @PathVariable EntityType entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable UUID internalEntityUuid, - @ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = VERSION_ID_PARAM_DESCRIPTION, required = true) @RequestParam String versionId) throws Exception { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, internalEntityUuid); @@ -466,7 +466,7 @@ public class EntitiesVersionControlController extends BaseController { TENANT_AUTHORITY_PARAGRAPH ) @GetMapping(value = "/entity/{requestId}/status") - public VersionLoadResult getVersionLoadRequestStatus(@ApiParam(value = VC_REQUEST_ID_PARAM_DESCRIPTION, required = true) + public VersionLoadResult getVersionLoadRequestStatus(@Parameter(description = VC_REQUEST_ID_PARAM_DESCRIPTION, required = true) @PathVariable UUID requestId) throws Exception { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE); return versionControlService.getVersionLoadStatus(getCurrentUser(), requestId); diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java index fbb8901ca9..539d6a81f4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -38,6 +37,7 @@ import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.query.EntityQueryService; import org.thingsboard.server.service.security.permission.Operation; @@ -61,7 +61,7 @@ public class EntityQueryController extends BaseController { @RequestMapping(value = "/entitiesQuery/count", method = RequestMethod.POST) @ResponseBody public long countEntitiesByQuery( - @ApiParam(value = "A JSON value representing the entity count query. See API call notes above for more details.") + @Parameter(description = "A JSON value representing the entity count query. See API call notes above for more details.") @RequestBody EntityCountQuery query) throws ThingsboardException { checkNotNull(query); return this.entityQueryService.countEntitiesByQuery(getCurrentUser(), query); @@ -72,7 +72,7 @@ public class EntityQueryController extends BaseController { @RequestMapping(value = "/entitiesQuery/find", method = RequestMethod.POST) @ResponseBody public PageData findEntityDataByQuery( - @ApiParam(value = "A JSON value representing the entity data query. See API call notes above for more details.") + @Parameter(description = "A JSON value representing the entity data query. See API call notes above for more details.") @RequestBody EntityDataQuery query) throws ThingsboardException { checkNotNull(query); return this.entityQueryService.findEntityDataByQuery(getCurrentUser(), query); @@ -83,7 +83,7 @@ public class EntityQueryController extends BaseController { @RequestMapping(value = "/alarmsQuery/find", method = RequestMethod.POST) @ResponseBody public PageData findAlarmDataByQuery( - @ApiParam(value = "A JSON value representing the alarm data query. See API call notes above for more details.") + @Parameter(description = "A JSON value representing the alarm data query. See API call notes above for more details.") @RequestBody AlarmDataQuery query) throws ThingsboardException { checkNotNull(query); checkNotNull(query.getPageLink()); @@ -98,7 +98,7 @@ public class EntityQueryController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarmsQuery/count", method = RequestMethod.POST) @ResponseBody - public long countAlarmsByQuery(@ApiParam(value = "A JSON value representing the alarm count query.") + public long countAlarmsByQuery(@Parameter(description = "A JSON value representing the alarm count query.") @RequestBody AlarmCountQuery query) throws ThingsboardException { checkNotNull(query); UserId assigneeId = query.getAssigneeId(); @@ -114,11 +114,11 @@ public class EntityQueryController extends BaseController { @RequestMapping(value = "/entitiesQuery/find/keys", method = RequestMethod.POST) @ResponseBody public DeferredResult findEntityTimeseriesAndAttributesKeysByQuery( - @ApiParam(value = "A JSON value representing the entity data query. See API call notes above for more details.") + @Parameter(description = "A JSON value representing the entity data query. See API call notes above for more details.") @RequestBody EntityDataQuery query, - @ApiParam(value = "Include all unique time-series keys to the result.") + @Parameter(description = "Include all unique time-series keys to the result.") @RequestParam("timeseries") boolean isTimeseries, - @ApiParam(value = "Include all unique attribute keys to the result.") + @Parameter(description = "Include all unique attribute keys to the result.") @RequestParam("attributes") boolean isAttributes) throws ThingsboardException { TenantId tenantId = getTenantId(); checkNotNull(query); diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index 08448f1d28..cb17661e4a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -15,8 +15,9 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -35,6 +36,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationInfo; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.entity.relation.TbEntityRelationService; import org.thingsboard.server.service.security.model.SecurityUser; @@ -79,7 +81,7 @@ public class EntityRelationController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relation", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public void saveRelation(@ApiParam(value = "A JSON value representing the relation.", required = true) + public void saveRelation(@Parameter(description = "A JSON value representing the relation.", required = true) @RequestBody EntityRelation relation) throws ThingsboardException { checkNotNull(relation); checkCanCreateRelation(relation.getFrom()); @@ -96,12 +98,12 @@ public class EntityRelationController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relation", method = RequestMethod.DELETE, params = {FROM_ID, FROM_TYPE, RELATION_TYPE, TO_ID, TO_TYPE}) @ResponseStatus(value = HttpStatus.OK) - public void deleteRelation(@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_ID) String strFromId, - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_TYPE) String strFromType, - @ApiParam(value = RELATION_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(RELATION_TYPE) String strRelationType, - @ApiParam(value = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TO_ID) String strToId, - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(TO_TYPE) String strToType) throws ThingsboardException { + public void deleteRelation(@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_ID) String strFromId, + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_TYPE) String strFromType, + @Parameter(description = RELATION_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(RELATION_TYPE) String strRelationType, + @Parameter(description = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TO_ID) String strToId, + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(TO_TYPE) String strToType) throws ThingsboardException { checkParameter(FROM_ID, strFromId); checkParameter(FROM_TYPE, strFromType); checkParameter(RELATION_TYPE, strRelationType); @@ -123,8 +125,8 @@ public class EntityRelationController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.DELETE, params = {"entityId", "entityType"}) @ResponseStatus(value = HttpStatus.OK) - public void deleteRelations(@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam("entityId") String strId, - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam("entityType") String strType) throws ThingsboardException { + public void deleteRelations(@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam("entityId") String strId, + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam("entityType") String strType) throws ThingsboardException { checkParameter("entityId", strId); checkParameter("entityType", strType); EntityId entityId = EntityIdFactory.getByTypeAndId(strType, strId); @@ -134,16 +136,16 @@ public class EntityRelationController extends BaseController { @ApiOperation(value = "Get Relation (getRelation)", notes = "Returns relation object between two specified entities if present. Otherwise throws exception. " + SECURITY_CHECKS_ENTITIES_DESCRIPTION, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relation", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE, RELATION_TYPE, TO_ID, TO_TYPE}) @ResponseBody - public EntityRelation getRelation(@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_ID) String strFromId, - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_TYPE) String strFromType, - @ApiParam(value = RELATION_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(RELATION_TYPE) String strRelationType, - @ApiParam(value = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TO_ID) String strToId, - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(TO_TYPE) String strToType) throws ThingsboardException { + public EntityRelation getRelation(@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_ID) String strFromId, + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_TYPE) String strFromType, + @Parameter(description = RELATION_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(RELATION_TYPE) String strRelationType, + @Parameter(description = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TO_ID) String strToId, + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(TO_TYPE) String strToType) throws ThingsboardException { checkParameter(FROM_ID, strFromId); checkParameter(FROM_TYPE, strFromType); checkParameter(RELATION_TYPE, strRelationType); @@ -160,13 +162,13 @@ public class EntityRelationController extends BaseController { @ApiOperation(value = "Get List of Relations (findByFrom)", notes = "Returns list of relation objects for the specified entity by the 'from' direction. " + SECURITY_CHECKS_ENTITY_DESCRIPTION, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE}) @ResponseBody - public List findByFrom(@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_ID) String strFromId, - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_TYPE) String strFromType, - @ApiParam(value = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) + public List findByFrom(@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_ID) String strFromId, + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_TYPE) String strFromType, + @Parameter(description = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException { checkParameter(FROM_ID, strFromId); checkParameter(FROM_TYPE, strFromType); @@ -179,13 +181,13 @@ public class EntityRelationController extends BaseController { @ApiOperation(value = "Get List of Relation Infos (findInfoByFrom)", notes = "Returns list of relation info objects for the specified entity by the 'from' direction. " + SECURITY_CHECKS_ENTITY_DESCRIPTION + " " + RELATION_INFO_DESCRIPTION, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE}) @ResponseBody - public List findInfoByFrom(@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_ID) String strFromId, - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_TYPE) String strFromType, - @ApiParam(value = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) + public List findInfoByFrom(@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_ID) String strFromId, + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_TYPE) String strFromType, + @Parameter(description = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException, ExecutionException, InterruptedException { checkParameter(FROM_ID, strFromId); checkParameter(FROM_TYPE, strFromType); @@ -198,14 +200,14 @@ public class EntityRelationController extends BaseController { @ApiOperation(value = "Get List of Relations (findByFrom)", notes = "Returns list of relation objects for the specified entity by the 'from' direction and relation type. " + SECURITY_CHECKS_ENTITY_DESCRIPTION, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE, RELATION_TYPE}) @ResponseBody - public List findByFrom(@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_ID) String strFromId, - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_TYPE) String strFromType, - @ApiParam(value = RELATION_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(RELATION_TYPE) String strRelationType, - @ApiParam(value = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) + public List findByFrom(@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_ID) String strFromId, + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(FROM_TYPE) String strFromType, + @Parameter(description = RELATION_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(RELATION_TYPE) String strRelationType, + @Parameter(description = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException { checkParameter(FROM_ID, strFromId); checkParameter(FROM_TYPE, strFromType); @@ -219,13 +221,13 @@ public class EntityRelationController extends BaseController { @ApiOperation(value = "Get List of Relations (findByTo)", notes = "Returns list of relation objects for the specified entity by the 'to' direction. " + SECURITY_CHECKS_ENTITY_DESCRIPTION, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {TO_ID, TO_TYPE}) @ResponseBody - public List findByTo(@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TO_ID) String strToId, - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(TO_TYPE) String strToType, - @ApiParam(value = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) + public List findByTo(@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TO_ID) String strToId, + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(TO_TYPE) String strToType, + @Parameter(description = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException { checkParameter(TO_ID, strToId); checkParameter(TO_TYPE, strToType); @@ -238,13 +240,13 @@ public class EntityRelationController extends BaseController { @ApiOperation(value = "Get List of Relation Infos (findInfoByTo)", notes = "Returns list of relation info objects for the specified entity by the 'to' direction. " + SECURITY_CHECKS_ENTITY_DESCRIPTION + " " + RELATION_INFO_DESCRIPTION, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {TO_ID, TO_TYPE}) @ResponseBody - public List findInfoByTo(@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TO_ID) String strToId, - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(TO_TYPE) String strToType, - @ApiParam(value = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) + public List findInfoByTo(@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TO_ID) String strToId, + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(TO_TYPE) String strToType, + @Parameter(description = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException, ExecutionException, InterruptedException { checkParameter(TO_ID, strToId); checkParameter(TO_TYPE, strToType); @@ -257,14 +259,14 @@ public class EntityRelationController extends BaseController { @ApiOperation(value = "Get List of Relations (findByTo)", notes = "Returns list of relation objects for the specified entity by the 'to' direction and relation type. " + SECURITY_CHECKS_ENTITY_DESCRIPTION, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {TO_ID, TO_TYPE, RELATION_TYPE}) @ResponseBody - public List findByTo(@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TO_ID) String strToId, - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(TO_TYPE) String strToType, - @ApiParam(value = RELATION_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(RELATION_TYPE) String strRelationType, - @ApiParam(value = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) + public List findByTo(@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TO_ID) String strToId, + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(TO_TYPE) String strToType, + @Parameter(description = RELATION_TYPE_PARAM_DESCRIPTION, required = true) @RequestParam(RELATION_TYPE) String strRelationType, + @Parameter(description = RELATION_TYPE_GROUP_PARAM_DESCRIPTION) @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException { checkParameter(TO_ID, strToId); checkParameter(TO_TYPE, strToType); @@ -278,11 +280,11 @@ public class EntityRelationController extends BaseController { @ApiOperation(value = "Find related entities (findByQuery)", notes = "Returns all entities that are related to the specific entity. " + "The entity id, relation type, entity types, depth of the search, and other query parameters defined using complex 'EntityRelationsQuery' object. " + - "See 'Model' tab of the Parameters for more info.", produces = MediaType.APPLICATION_JSON_VALUE) + "See 'Model' tab of the Parameters for more info.", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.POST) @ResponseBody - public List findByQuery(@ApiParam(value = "A JSON value representing the entity relations query object.", required = true) + public List findByQuery(@Parameter(description = "A JSON value representing the entity relations query object.", required = true) @RequestBody EntityRelationsQuery query) throws ThingsboardException, ExecutionException, InterruptedException { checkNotNull(query); checkNotNull(query.getParameters()); @@ -294,11 +296,11 @@ public class EntityRelationController extends BaseController { @ApiOperation(value = "Find related entity infos (findInfoByQuery)", notes = "Returns all entity infos that are related to the specific entity. " + "The entity id, relation type, entity types, depth of the search, and other query parameters defined using complex 'EntityRelationsQuery' object. " + - "See 'Model' tab of the Parameters for more info. " + RELATION_INFO_DESCRIPTION, produces = MediaType.APPLICATION_JSON_VALUE) + "See 'Model' tab of the Parameters for more info. " + RELATION_INFO_DESCRIPTION, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations/info", method = RequestMethod.POST) @ResponseBody - public List findInfoByQuery(@ApiParam(value = "A JSON value representing the entity relations query object.", required = true) + public List findInfoByQuery(@Parameter(description = "A JSON value representing the entity relations query object.", required = true) @RequestBody EntityRelationsQuery query) throws ThingsboardException, ExecutionException, InterruptedException { checkNotNull(query); checkNotNull(query.getParameters()); diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java index b2904a0989..bff71bb43b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java @@ -16,8 +16,10 @@ package org.thingsboard.server.controller; import com.google.common.util.concurrent.ListenableFuture; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -45,6 +47,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -66,15 +69,12 @@ import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIG import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VIEW_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VIEW_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VIEW_INFO_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VIEW_INFO_SORT_PROPERTY_ALLOWABLE_VALUES; -import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VIEW_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VIEW_TYPE; import static org.thingsboard.server.controller.ControllerConstants.MODEL_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; @@ -98,12 +98,12 @@ public class EntityViewController extends BaseController { @ApiOperation(value = "Get entity view (getEntityViewById)", notes = "Fetch the EntityView object based on the provided entity view id. " + ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.GET) @ResponseBody public EntityView getEntityViewById( - @ApiParam(value = ENTITY_VIEW_ID_PARAM_DESCRIPTION) + @Parameter(description = ENTITY_VIEW_ID_PARAM_DESCRIPTION) @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { checkParameter(ENTITY_VIEW_ID, strEntityViewId); return checkEntityViewId(new EntityViewId(toUUID(strEntityViewId)), Operation.READ); @@ -112,12 +112,12 @@ public class EntityViewController extends BaseController { @ApiOperation(value = "Get Entity View info (getEntityViewInfoById)", notes = "Fetch the Entity View info object based on the provided Entity View Id. " + ENTITY_VIEW_INFO_DESCRIPTION + MODEL_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/entityView/info/{entityViewId}", method = RequestMethod.GET) @ResponseBody public EntityViewInfo getEntityViewInfoById( - @ApiParam(value = ENTITY_VIEW_ID_PARAM_DESCRIPTION) + @Parameter(description = ENTITY_VIEW_ID_PARAM_DESCRIPTION) @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { checkParameter(ENTITY_VIEW_ID, strEntityViewId); EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); @@ -128,12 +128,12 @@ public class EntityViewController extends BaseController { notes = ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Entity View entity." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/entityView", method = RequestMethod.POST) @ResponseBody public EntityView saveEntityView( - @ApiParam(value = "A JSON object representing the entity view.") + @Parameter(description = "A JSON object representing the entity view.") @RequestBody EntityView entityView) throws Exception { entityView.setTenantId(getCurrentUser().getTenantId()); EntityView existingEntityView = null; @@ -153,7 +153,7 @@ public class EntityViewController extends BaseController { @RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteEntityView( - @ApiParam(value = ENTITY_VIEW_ID_PARAM_DESCRIPTION) + @Parameter(description = ENTITY_VIEW_ID_PARAM_DESCRIPTION) @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { checkParameter(ENTITY_VIEW_ID, strEntityViewId); EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); @@ -163,12 +163,12 @@ public class EntityViewController extends BaseController { @ApiOperation(value = "Get Entity View by name (getTenantEntityView)", notes = "Fetch the Entity View object based on the tenant id and entity view name. " + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/entityViews", params = {"entityViewName"}, method = RequestMethod.GET) @ResponseBody public EntityView getTenantEntityView( - @ApiParam(value = "Entity View name") + @Parameter(description = "Entity View name") @RequestParam String entityViewName) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); return checkNotNull(entityViewService.findEntityViewByTenantIdAndName(tenantId, entityViewName)); @@ -180,9 +180,9 @@ public class EntityViewController extends BaseController { @RequestMapping(value = "/customer/{customerId}/entityView/{entityViewId}", method = RequestMethod.POST) @ResponseBody public EntityView assignEntityViewToCustomer( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable(CUSTOMER_ID) String strCustomerId, - @ApiParam(value = ENTITY_VIEW_ID_PARAM_DESCRIPTION) + @Parameter(description = ENTITY_VIEW_ID_PARAM_DESCRIPTION) @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); checkParameter(ENTITY_VIEW_ID, strEntityViewId); @@ -202,7 +202,7 @@ public class EntityViewController extends BaseController { @RequestMapping(value = "/customer/entityView/{entityViewId}", method = RequestMethod.DELETE) @ResponseBody public EntityView unassignEntityViewFromCustomer( - @ApiParam(value = ENTITY_VIEW_ID_PARAM_DESCRIPTION) + @Parameter(description = ENTITY_VIEW_ID_PARAM_DESCRIPTION) @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { checkParameter(ENTITY_VIEW_ID, strEntityViewId); EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); @@ -223,19 +223,19 @@ public class EntityViewController extends BaseController { @RequestMapping(value = "/customer/{customerId}/entityViews", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomerEntityViews( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) @PathVariable(CUSTOMER_ID) String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ENTITY_VIEW_TYPE) + @Parameter(description = ENTITY_VIEW_TYPE) @RequestParam(required = false) String type, - @ApiParam(value = ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ENTITY_VIEW_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name, type"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter(CUSTOMER_ID, strCustomerId); TenantId tenantId = getCurrentUser().getTenantId(); @@ -256,19 +256,19 @@ public class EntityViewController extends BaseController { @RequestMapping(value = "/customer/{customerId}/entityViewInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomerEntityViewInfos( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) @PathVariable(CUSTOMER_ID) String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ENTITY_VIEW_TYPE) + @Parameter(description = ENTITY_VIEW_TYPE) @RequestParam(required = false) String type, - @ApiParam(value = ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ENTITY_VIEW_INFO_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); TenantId tenantId = getCurrentUser().getTenantId(); @@ -289,17 +289,17 @@ public class EntityViewController extends BaseController { @RequestMapping(value = "/tenant/entityViews", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantEntityViews( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ENTITY_VIEW_TYPE) + @Parameter(description = ENTITY_VIEW_TYPE) @RequestParam(required = false) String type, - @ApiParam(value = ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ENTITY_VIEW_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name, type"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); @@ -318,17 +318,17 @@ public class EntityViewController extends BaseController { @RequestMapping(value = "/tenant/entityViewInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantEntityViewInfos( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = ENTITY_VIEW_TYPE) + @Parameter(description = ENTITY_VIEW_TYPE) @RequestParam(required = false) String type, - @ApiParam(value = ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ENTITY_VIEW_INFO_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type", "customerTitle"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); @@ -347,7 +347,7 @@ public class EntityViewController extends BaseController { @RequestMapping(value = "/entityViews", method = RequestMethod.POST) @ResponseBody public List findByQuery( - @ApiParam(value = "The entity view search query JSON") + @Parameter(description = "The entity view search query JSON") @RequestBody EntityViewSearchQuery query) throws ThingsboardException, ExecutionException, InterruptedException { checkNotNull(query); checkNotNull(query.getParameters()); @@ -386,7 +386,7 @@ public class EntityViewController extends BaseController { @RequestMapping(value = "/customer/public/entityView/{entityViewId}", method = RequestMethod.POST) @ResponseBody public EntityView assignEntityViewToPublicCustomer( - @ApiParam(value = ENTITY_VIEW_ID_PARAM_DESCRIPTION) + @Parameter(description = ENTITY_VIEW_ID_PARAM_DESCRIPTION) @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { checkParameter(ENTITY_VIEW_ID, strEntityViewId); EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); @@ -400,7 +400,7 @@ public class EntityViewController extends BaseController { "Second, remote edge service will receive a copy of assignment entity view " + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + "Third, once entity view will be delivered to edge service, it's going to be available for usage on remote edge instance.", - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/entityView/{entityViewId}", method = RequestMethod.POST) @ResponseBody @@ -425,7 +425,7 @@ public class EntityViewController extends BaseController { "Second, remote edge service will receive an 'unassign' command to remove entity view " + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + "Third, once 'unassign' command will be delivered to edge service, it's going to remove entity view locally.", - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/entityView/{entityViewId}", method = RequestMethod.DELETE) @ResponseBody diff --git a/application/src/main/java/org/thingsboard/server/controller/EventController.java b/application/src/main/java/org/thingsboard/server/controller/EventController.java index ec75f981a7..09ef2e9d05 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EventController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EventController.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -39,6 +41,7 @@ import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; @@ -52,7 +55,6 @@ import static org.thingsboard.server.controller.ControllerConstants.EVENT_DEBUG_ import static org.thingsboard.server.controller.ControllerConstants.EVENT_END_TIME_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EVENT_ERROR_FILTER_OBJ; import static org.thingsboard.server.controller.ControllerConstants.EVENT_LC_EVENT_FILTER_OBJ; -import static org.thingsboard.server.controller.ControllerConstants.EVENT_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.EVENT_START_TIME_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EVENT_STATS_FILTER_OBJ; import static org.thingsboard.server.controller.ControllerConstants.EVENT_TEXT_SEARCH_DESCRIPTION; @@ -60,7 +62,6 @@ import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_ID; @@ -107,32 +108,32 @@ public class EventController extends BaseController { @ApiOperation(value = "Get Events by type (getEvents)", notes = "Returns a page of events for specified entity by specifying event type. " + - PAGE_DATA_PARAMETERS, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/events/{entityType}/{entityId}/{eventType}", method = RequestMethod.GET) @ResponseBody public PageData getEvents( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_TYPE) String strEntityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_ID) String strEntityId, - @ApiParam(value = "A string value representing event type", example = "STATS", required = true) + @Parameter(description = "A string value representing event type", example = "STATS", required = true) @PathVariable("eventType") String eventType, - @ApiParam(value = TENANT_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = TENANT_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TENANT_ID) String strTenantId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = EVENT_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = EVENT_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EVENT_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"ts", "id"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = EVENT_START_TIME_DESCRIPTION) + @Parameter(description = EVENT_START_TIME_DESCRIPTION) @RequestParam(required = false) Long startTime, - @ApiParam(value = EVENT_END_TIME_DESCRIPTION) + @Parameter(description = EVENT_END_TIME_DESCRIPTION) @RequestParam(required = false) Long endTime) throws ThingsboardException { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); @@ -149,30 +150,30 @@ public class EventController extends BaseController { "The call was deprecated to improve the performance of the system. " + "Current implementation will return 'Lifecycle' events only. " + "Use 'Get events by type' or 'Get events by filter' instead. " + - PAGE_DATA_PARAMETERS, produces = MediaType.APPLICATION_JSON_VALUE) + PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.GET) @ResponseBody public PageData getEvents( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_TYPE) String strEntityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_ID) String strEntityId, - @ApiParam(value = TENANT_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = TENANT_ID_PARAM_DESCRIPTION, required = true) @RequestParam("tenantId") String strTenantId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = EVENT_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = EVENT_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EVENT_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"ts", "id"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = EVENT_START_TIME_DESCRIPTION) + @Parameter(description = EVENT_START_TIME_DESCRIPTION) @RequestParam(required = false) Long startTime, - @ApiParam(value = EVENT_END_TIME_DESCRIPTION) + @Parameter(description = EVENT_END_TIME_DESCRIPTION) @RequestParam(required = false) Long endTime) throws ThingsboardException { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); @@ -190,32 +191,32 @@ public class EventController extends BaseController { notes = "Returns a page of events for the chosen entity by specifying the event filter. " + PAGE_DATA_PARAMETERS + NEW_LINE + EVENT_FILTER_DEFINITION, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.POST) @ResponseBody public PageData getEvents( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_TYPE) String strEntityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_ID) String strEntityId, - @ApiParam(value = TENANT_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = TENANT_ID_PARAM_DESCRIPTION, required = true) @RequestParam(TENANT_ID) String strTenantId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = "A JSON value representing the event filter.", required = true) + @Parameter(description = "A JSON value representing the event filter.", required = true) @RequestBody EventFilter eventFilter, - @ApiParam(value = EVENT_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = EVENT_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EVENT_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"ts", "id"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder, - @ApiParam(value = EVENT_START_TIME_DESCRIPTION) + @Parameter(description = EVENT_START_TIME_DESCRIPTION) @RequestParam(required = false) Long startTime, - @ApiParam(value = EVENT_END_TIME_DESCRIPTION) + @Parameter(description = EVENT_END_TIME_DESCRIPTION) @RequestParam(required = false) Long endTime) throws ThingsboardException { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); @@ -232,15 +233,15 @@ public class EventController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/events/{entityType}/{entityId}/clear", method = RequestMethod.POST) @ResponseStatus(HttpStatus.OK) - public void clearEvents(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + public void clearEvents(@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_TYPE) String strEntityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_ID) String strEntityId, - @ApiParam(value = EVENT_START_TIME_DESCRIPTION) + @Parameter(description = EVENT_START_TIME_DESCRIPTION) @RequestParam(required = false) Long startTime, - @ApiParam(value = EVENT_END_TIME_DESCRIPTION) + @Parameter(description = EVENT_END_TIME_DESCRIPTION) @RequestParam(required = false) Long endTime, - @ApiParam(value = EVENT_FILTER_DEFINITION) + @Parameter(description = EVENT_FILTER_DEFINITION) @RequestBody EventFilter eventFilter) throws ThingsboardException { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); diff --git a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java index 8949cd95dd..433eea639e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java +++ b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java @@ -15,11 +15,13 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -33,6 +35,7 @@ import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.service.lwm2m.LwM2MService; import java.util.Map; @@ -57,12 +60,12 @@ public class Lwm2mController extends BaseController { @ApiOperation(value = "Get Lwm2m Bootstrap SecurityInfo (getLwm2mBootstrapSecurityInfo)", notes = "Get the Lwm2m Bootstrap SecurityInfo object (of the current server) based on the provided isBootstrapServer parameter. If isBootstrapServer == true, get the parameters of the current Bootstrap Server. If isBootstrapServer == false, get the parameters of the current Lwm2m Server. Used for client settings when starting the client in Bootstrap mode. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{isBootstrapServer}", method = RequestMethod.GET) @ResponseBody public LwM2MServerSecurityConfigDefault getLwm2mBootstrapSecurityInfo( - @ApiParam(value = IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION) + @Parameter(description = IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION) @PathVariable(IS_BOOTSTRAP_SERVER) boolean bootstrapServer) throws ThingsboardException { return lwM2MService.getServerSecurityInfo(bootstrapServer); } diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 5893410b2a..e2a1f1a417 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; @@ -55,6 +55,7 @@ import org.thingsboard.server.common.data.notification.template.NotificationTemp import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationSettingsService; @@ -66,7 +67,6 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import jakarta.validation.Valid; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; @@ -158,17 +158,17 @@ public class NotificationController extends BaseController { "}\n```") @GetMapping("/notifications") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - public PageData getNotifications(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + public PageData getNotifications(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = "Case-insensitive 'substring' filter based on notification subject or text") + @Parameter(description = "Case-insensitive 'substring' filter based on notification subject or text") @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION) + @Parameter(description = SORT_PROPERTY_DESCRIPTION) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION) + @Parameter(description = SORT_ORDER_DESCRIPTION) @RequestParam(required = false) String sortOrder, - @ApiParam(value = "To search for unread notifications only") + @Parameter(description = "To search for unread notifications only") @RequestParam(defaultValue = "false") boolean unreadOnly, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { // no permissions @@ -266,7 +266,7 @@ public class NotificationController extends BaseController { @PostMapping("/notification/request/preview") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationRequestPreview getNotificationRequestPreview(@RequestBody @Valid NotificationRequest request, - @ApiParam(value = "Amount of the recipients to show in preview") + @Parameter(description = "Amount of the recipients to show in preview") @RequestParam(defaultValue = "20") int recipientsPreviewSize, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { NotificationTemplate template; @@ -361,15 +361,15 @@ public class NotificationController extends BaseController { SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @GetMapping("/notification/requests") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public PageData getNotificationRequests(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + public PageData getNotificationRequests(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = "Case-insensitive 'substring' filed based on the used template name") + @Parameter(description = "Case-insensitive 'substring' filed based on the used template name") @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION) + @Parameter(description = SORT_PROPERTY_DESCRIPTION) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION) + @Parameter(description = SORT_ORDER_DESCRIPTION) @RequestParam(required = false) String sortOrder, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { // generic permission diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java index 06eabbc215..457fac9304 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; @@ -38,12 +38,12 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; -import jakarta.validation.Valid; import java.util.UUID; import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END; @@ -150,15 +150,15 @@ public class NotificationRuleController extends BaseController { SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @GetMapping("/rules") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public PageData getNotificationRules(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + public PageData getNotificationRules(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = "Case-insensitive 'substring' filter based on rule's name") + @Parameter(description = "Case-insensitive 'substring' filter based on rule's name") @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION) + @Parameter(description = SORT_PROPERTY_DESCRIPTION) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION) + @Parameter(description = SORT_ORDER_DESCRIPTION) @RequestParam(required = false) String sortOrder, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { // generic permission diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java index 6b13e9de9d..1dd16ea462 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; @@ -48,12 +48,12 @@ import org.thingsboard.server.common.data.notification.targets.platform.UserList import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; -import jakarta.validation.Valid; import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -134,9 +134,9 @@ public class NotificationTargetController extends BaseController { @PostMapping("/target/recipients") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public PageData getRecipientsForNotificationTargetConfig(@RequestBody NotificationTarget notificationTarget, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { // generic permission @@ -156,7 +156,7 @@ public class NotificationTargetController extends BaseController { SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @GetMapping(value = "/targets", params = {"ids"}) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public List getNotificationTargetsByIds(@ApiParam(value = "Comma-separated list of uuids representing targets ids", required = true) + public List getNotificationTargetsByIds(@Parameter(description = "Comma-separated list of uuids representing targets ids", required = true) @RequestParam("ids") UUID[] ids, @AuthenticationPrincipal SecurityUser user) { // generic permission @@ -170,15 +170,15 @@ public class NotificationTargetController extends BaseController { SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @GetMapping("/targets") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public PageData getNotificationTargets(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + public PageData getNotificationTargets(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = "Case-insensitive 'substring' filed based on the target's name") + @Parameter(description = "Case-insensitive 'substring' filed based on the target's name") @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION) + @Parameter(description = SORT_PROPERTY_DESCRIPTION) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION) + @Parameter(description = SORT_ORDER_DESCRIPTION) @RequestParam(required = false) String sortOrder, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { // generic permission diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java index fef919d710..a28e218809 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.security.access.prepost.PreAuthorize; @@ -42,13 +42,13 @@ import org.thingsboard.server.common.data.notification.targets.slack.SlackConver import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; -import jakarta.validation.Valid; import java.util.List; import java.util.UUID; @@ -137,17 +137,17 @@ public class NotificationTemplateController extends BaseController { SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @GetMapping("/templates") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public PageData getNotificationTemplates(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + public PageData getNotificationTemplates(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = "Case-insensitive 'substring' filter based on template's name and notification type") + @Parameter(description = "Case-insensitive 'substring' filter based on template's name and notification type") @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION) + @Parameter(description = SORT_PROPERTY_DESCRIPTION) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION) + @Parameter(description = SORT_ORDER_DESCRIPTION) @RequestParam(required = false) String sortOrder, - @ApiParam(value = "Comma-separated list of notification types to filter the templates") + @Parameter(description = "Comma-separated list of notification types to filter the templates") @RequestParam(required = false) NotificationType[] notificationTypes, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { // generic permission @@ -177,7 +177,7 @@ public class NotificationTemplateController extends BaseController { @GetMapping("/slack/conversations") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public List listSlackConversations(@RequestParam SlackConversationType type, - @ApiParam(value = "Slack bot token. If absent - system Slack settings will be used") + @Parameter(description = "Slack bot token. If absent - system Slack settings will be used") @RequestParam(required = false) String token, @AuthenticationPrincipal SecurityUser user) { // generic permission diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java index f547a5f0ad..63258c5196 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; @@ -30,6 +29,7 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -63,7 +63,7 @@ public class OAuth2ConfigTemplateController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @RequestMapping(value = "/{clientRegistrationTemplateId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public void deleteClientRegistrationTemplate(@ApiParam(value = "String representation of client registration template id to delete", example = "139b1f81-2f5d-11ec-9dbe-9b627e1a88f4") + public void deleteClientRegistrationTemplate(@Parameter(description = "String representation of client registration template id to delete", example = "139b1f81-2f5d-11ec-9dbe-9b627e1a88f4") @PathVariable(CLIENT_REGISTRATION_TEMPLATE_ID) String strClientRegistrationTemplateId) throws ThingsboardException { checkParameter(CLIENT_REGISTRATION_TEMPLATE_ID, strClientRegistrationTemplateId); accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.DELETE); diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 519629fbc3..1688f08b52 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -15,8 +15,9 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -33,13 +34,13 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2Info; import org.thingsboard.server.common.data.oauth2.PlatformType; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.utils.MiscUtils; -import jakarta.servlet.http.HttpServletRequest; import java.util.Enumeration; import java.util.List; @@ -61,13 +62,13 @@ public class OAuth2Controller extends BaseController { @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @ResponseBody public List getOAuth2Clients(HttpServletRequest request, - @ApiParam(value = "Mobile application package name, to find OAuth2 clients " + + @Parameter(description = "Mobile application package name, to find OAuth2 clients " + "where there is configured mobile application with such package name") @RequestParam(required = false) String pkgName, - @ApiParam(value = "Platform type to search OAuth2 clients for which " + + @Parameter(description = "Platform type to search OAuth2 clients for which " + "the usage with this platform type is allowed in the settings. " + "If platform type is not one of allowable values - it will just be ignored", - allowableValues = "WEB, ANDROID, IOS") + schema = @Schema(allowableValues = "WEB, ANDROID, IOS")) @RequestParam(required = false) String platform) throws ThingsboardException { if (log.isDebugEnabled()) { log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort()); @@ -81,7 +82,8 @@ public class OAuth2Controller extends BaseController { if (StringUtils.isNotEmpty(platform)) { try { platformType = PlatformType.valueOf(platform); - } catch (Exception e) {} + } catch (Exception e) { + } } return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request), pkgName, platformType); } diff --git a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java index 9b923e01dc..2ee07ce196 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java +++ b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ByteArrayResource; @@ -42,6 +44,7 @@ import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.ota.TbOtaPackageService; import org.thingsboard.server.service.security.permission.Operation; @@ -52,16 +55,13 @@ import java.io.IOException; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.OTA_PACKAGE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.OTA_PACKAGE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.OTA_PACKAGE_INFO_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.OTA_PACKAGE_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.OTA_PACKAGE_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; @@ -84,7 +84,7 @@ public class OtaPackageController extends BaseController { @PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')") @RequestMapping(value = "/otaPackage/{otaPackageId}/download", method = RequestMethod.GET) @ResponseBody - public ResponseEntity downloadOtaPackage(@ApiParam(value = OTA_PACKAGE_ID_PARAM_DESCRIPTION) + public ResponseEntity downloadOtaPackage(@Parameter(description = OTA_PACKAGE_ID_PARAM_DESCRIPTION) @PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException { checkParameter(OTA_PACKAGE_ID, strOtaPackageId); OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); @@ -106,11 +106,11 @@ public class OtaPackageController extends BaseController { @ApiOperation(value = "Get OTA Package Info (getOtaPackageInfoById)", notes = "Fetch the OTA Package Info object based on the provided OTA Package Id. " + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/otaPackage/info/{otaPackageId}", method = RequestMethod.GET) @ResponseBody - public OtaPackageInfo getOtaPackageInfoById(@ApiParam(value = OTA_PACKAGE_ID_PARAM_DESCRIPTION) + public OtaPackageInfo getOtaPackageInfoById(@Parameter(description = OTA_PACKAGE_ID_PARAM_DESCRIPTION) @PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException { checkParameter(OTA_PACKAGE_ID, strOtaPackageId); OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); @@ -120,11 +120,11 @@ public class OtaPackageController extends BaseController { @ApiOperation(value = "Get OTA Package (getOtaPackageById)", notes = "Fetch the OTA Package object based on the provided OTA Package Id. " + "The server checks that the OTA Package is owned by the same tenant. " + OTA_PACKAGE_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, - produces = APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.GET) @ResponseBody - public OtaPackage getOtaPackageById(@ApiParam(value = OTA_PACKAGE_ID_PARAM_DESCRIPTION) + public OtaPackage getOtaPackageById(@Parameter(description = OTA_PACKAGE_ID_PARAM_DESCRIPTION) @PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException { checkParameter(OTA_PACKAGE_ID, strOtaPackageId); OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); @@ -137,12 +137,11 @@ public class OtaPackageController extends BaseController { "Specify existing OTA Package id to update the OTA Package Info. " + "Referencing non-existing OTA Package Id will cause 'Not Found' error. " + "\n\nOTA Package combination of the title with the version is unique in the scope of tenant. " + TENANT_AUTHORITY_PARAGRAPH, - produces = APPLICATION_JSON_VALUE, - consumes = APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/otaPackage", method = RequestMethod.POST) @ResponseBody - public OtaPackageInfo saveOtaPackageInfo(@ApiParam(value = "A JSON value representing the OTA Package.") + public OtaPackageInfo saveOtaPackageInfo(@Parameter(description = "A JSON value representing the OTA Package.") @RequestBody SaveOtaPackageInfoRequest otaPackageInfo) throws ThingsboardException { otaPackageInfo.setTenantId(getTenantId()); checkEntity(otaPackageInfo.getId(), otaPackageInfo, Resource.OTA_PACKAGE); @@ -152,18 +151,18 @@ public class OtaPackageController extends BaseController { @ApiOperation(value = "Save OTA Package data (saveOtaPackageData)", notes = "Update the OTA Package. Adds the date to the existing OTA Package Info" + TENANT_AUTHORITY_PARAGRAPH, - produces = APPLICATION_JSON_VALUE, - consumes = MULTIPART_FORM_DATA_VALUE) + responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)), + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = MULTIPART_FORM_DATA_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST, consumes = MULTIPART_FORM_DATA_VALUE) @ResponseBody - public OtaPackageInfo saveOtaPackageData(@ApiParam(value = OTA_PACKAGE_ID_PARAM_DESCRIPTION) + public OtaPackageInfo saveOtaPackageData(@Parameter(description = OTA_PACKAGE_ID_PARAM_DESCRIPTION) @PathVariable(OTA_PACKAGE_ID) String strOtaPackageId, - @ApiParam(value = "OTA Package checksum. For example, '0xd87f7e0c'") + @Parameter(description = "OTA Package checksum. For example, '0xd87f7e0c'") @RequestParam(required = false) String checksum, - @ApiParam(value = "OTA Package checksum algorithm.", allowableValues = OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES) + @Parameter(description = "OTA Package checksum algorithm.", schema = @Schema(allowableValues = {"MD5", "SHA256", "SHA384", "SHA512", "CRC32", "MURMUR3_32", "MURMUR3_128"})) @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr, - @ApiParam(value = "OTA Package data.") + @Parameter(description = "OTA Package data.") @RequestPart MultipartFile file) throws ThingsboardException, IOException { checkParameter(OTA_PACKAGE_ID, strOtaPackageId); checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr); @@ -178,19 +177,19 @@ public class OtaPackageController extends BaseController { @ApiOperation(value = "Get OTA Package Infos (getOtaPackages)", notes = "Returns a page of OTA Package Info objects owned by tenant. " + PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/otaPackages", method = RequestMethod.GET) @ResponseBody - public PageData getOtaPackages(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + public PageData getOtaPackages(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = OTA_PACKAGE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = OTA_PACKAGE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = OTA_PACKAGE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "type", "title", "version", "tag", "url", "fileName", "dataSize", "checksum"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantId(getTenantId(), pageLink)); @@ -199,23 +198,23 @@ public class OtaPackageController extends BaseController { @ApiOperation(value = "Get OTA Package Infos (getOtaPackages)", notes = "Returns a page of OTA Package Info objects owned by tenant. " + PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET) @ResponseBody - public PageData getOtaPackages(@ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) + public PageData getOtaPackages(@Parameter(description = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable("deviceProfileId") String strDeviceProfileId, - @ApiParam(value = "OTA Package type.", allowableValues = "FIRMWARE, SOFTWARE") + @Parameter(description = "OTA Package type.", schema = @Schema(allowableValues = "FIRMWARE, SOFTWARE")) @PathVariable("type") String strType, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = OTA_PACKAGE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = OTA_PACKAGE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = OTA_PACKAGE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "type", "title", "version", "tag", "url", "fileName", "dataSize", "checksum"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("deviceProfileId", strDeviceProfileId); checkParameter("type", strType); @@ -227,11 +226,11 @@ public class OtaPackageController extends BaseController { @ApiOperation(value = "Delete OTA Package (deleteOtaPackage)", notes = "Deletes the OTA Package. Referencing non-existing OTA Package Id will cause an error. " + "Can't delete the OTA Package if it is referenced by existing devices or device profile." + TENANT_AUTHORITY_PARAGRAPH, - produces = APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.DELETE) @ResponseBody - public void deleteOtaPackage(@ApiParam(value = OTA_PACKAGE_ID_PARAM_DESCRIPTION) + public void deleteOtaPackage(@Parameter(description = OTA_PACKAGE_ID_PARAM_DESCRIPTION) @PathVariable("otaPackageId") String strOtaPackageId) throws ThingsboardException { checkParameter(OTA_PACKAGE_ID, strOtaPackageId); OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); diff --git a/application/src/main/java/org/thingsboard/server/controller/QueueController.java b/application/src/main/java/org/thingsboard/server/controller/QueueController.java index 0e79ae7955..774c991f94 100644 --- a/application/src/main/java/org/thingsboard/server/controller/QueueController.java +++ b/application/src/main/java/org/thingsboard/server/controller/QueueController.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.queue.TbQueueService; import org.thingsboard.server.service.security.permission.Operation; @@ -45,10 +46,7 @@ import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DE import static org.thingsboard.server.controller.ControllerConstants.QUEUE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.QUEUE_NAME_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.QUEUE_SERVICE_TYPE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.QUEUE_SORT_PROPERTY_ALLOWABLE_VALUES; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; @@ -69,17 +67,17 @@ public class QueueController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/queues", params = {"serviceType", "pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public PageData getTenantQueuesByServiceType(@ApiParam(value = QUEUE_SERVICE_TYPE_DESCRIPTION, allowableValues = QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES, required = true) + public PageData getTenantQueuesByServiceType(@Parameter(description = QUEUE_SERVICE_TYPE_DESCRIPTION, schema = @Schema(allowableValues = {"TB-RULE-ENGINE", "TB-CORE", "TB-TRANSPORT", "JS-EXECUTOR"}, required = true)) @RequestParam String serviceType, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = QUEUE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "topic"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("serviceType", serviceType); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); @@ -97,7 +95,7 @@ public class QueueController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/queues/{queueId}", method = RequestMethod.GET) @ResponseBody - public Queue getQueueById(@ApiParam(value = QUEUE_ID_PARAM_DESCRIPTION) + public Queue getQueueById(@Parameter(description = QUEUE_ID_PARAM_DESCRIPTION) @PathVariable("queueId") String queueIdStr) throws ThingsboardException { checkParameter("queueId", queueIdStr); QueueId queueId = new QueueId(UUID.fromString(queueIdStr)); @@ -110,7 +108,7 @@ public class QueueController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/queues/name/{queueName}", method = RequestMethod.GET) @ResponseBody - public Queue getQueueByName(@ApiParam(value = QUEUE_NAME_PARAM_DESCRIPTION) + public Queue getQueueByName(@Parameter(description = QUEUE_NAME_PARAM_DESCRIPTION) @PathVariable("queueName") String queueName) throws ThingsboardException { checkParameter("queueName", queueName); return checkNotNull(queueService.findQueueByTenantIdAndName(getTenantId(), queueName)); @@ -127,9 +125,9 @@ public class QueueController extends BaseController { @RequestMapping(value = "/queues", params = {"serviceType"}, method = RequestMethod.POST) @ResponseBody - public Queue saveQueue(@ApiParam(value = "A JSON value representing the queue.") + public Queue saveQueue(@Parameter(description = "A JSON value representing the queue.") @RequestBody Queue queue, - @ApiParam(value = QUEUE_SERVICE_TYPE_DESCRIPTION, allowableValues = QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES, required = true) + @Parameter(description = QUEUE_SERVICE_TYPE_DESCRIPTION, schema = @Schema(allowableValues = {"TB-RULE-ENGINE", "TB-CORE", "TB-TRANSPORT", "JS-EXECUTOR"}, required = true)) @RequestParam String serviceType) throws ThingsboardException { checkParameter("serviceType", serviceType); queue.setTenantId(getCurrentUser().getTenantId()); @@ -152,7 +150,7 @@ public class QueueController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @RequestMapping(value = "/queues/{queueId}", method = RequestMethod.DELETE) @ResponseBody - public void deleteQueue(@ApiParam(value = QUEUE_ID_PARAM_DESCRIPTION) + public void deleteQueue(@Parameter(description = QUEUE_ID_PARAM_DESCRIPTION) @PathVariable("queueId") String queueIdStr) throws ThingsboardException { checkParameter("queueId", queueIdStr); QueueId queueId = new QueueId(toUUID(queueIdStr)); diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java index 509e91e546..fd207dcc34 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -30,6 +29,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import java.util.UUID; @@ -48,9 +48,9 @@ public class RpcV1Controller extends AbstractRpcController { @RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST) @ResponseBody public DeferredResult handleOneWayDeviceRPCRequest( - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable("deviceId") String deviceIdStr, - @ApiParam(value = "A JSON value representing the RPC request.") + @Parameter(description = "A JSON value representing the RPC request.") @RequestBody String requestBody) throws ThingsboardException { return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.REQUEST_TIMEOUT, HttpStatus.CONFLICT); } @@ -60,9 +60,9 @@ public class RpcV1Controller extends AbstractRpcController { @RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST) @ResponseBody public DeferredResult handleTwoWayDeviceRPCRequest( - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable("deviceId") String deviceIdStr, - @ApiParam(value = "A JSON value representing the RPC request.") + @Parameter(description = "A JSON value representing the RPC request.") @RequestBody String requestBody) throws ThingsboardException { return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.REQUEST_TIMEOUT, HttpStatus.CONFLICT); } diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java index c611cfda54..eb7481c6e2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java @@ -16,10 +16,11 @@ package org.thingsboard.server.controller; import com.google.common.util.concurrent.FutureCallback; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -44,12 +45,12 @@ import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.exception.ToErrorResponseEntity; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.rpc.RemoveRpcActorMsg; import org.thingsboard.server.service.security.permission.Operation; -import jakarta.annotation.Nullable; import java.util.UUID; import static org.thingsboard.server.common.data.DataConstants.RPC_DELETED; @@ -61,10 +62,7 @@ import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_ import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.RPC_ID; import static org.thingsboard.server.controller.ControllerConstants.RPC_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.RPC_SORT_PROPERTY_ALLOWABLE_VALUES; -import static org.thingsboard.server.controller.ControllerConstants.RPC_STATUS_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.RPC_TEXT_SEARCH_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; @@ -117,36 +115,36 @@ public class RpcV2Controller extends AbstractRpcController { @ApiOperation(value = "Send one-way RPC request", notes = ONE_WAY_RPC_REQUEST_DESCRIPTION) @ApiResponses(value = { - @ApiResponse(code = 200, message = "Persistent RPC request was saved to the database or lightweight RPC request was sent to the device."), - @ApiResponse(code = 400, message = "Invalid structure of the request."), - @ApiResponse(code = 401, message = "User is not authorized to send the RPC request. Most likely, User belongs to different Customer or Tenant."), - @ApiResponse(code = 504, message = "Timeout to process the RPC call. Most likely, device is offline."), + @ApiResponse(responseCode = "200", description = "Persistent RPC request was saved to the database or lightweight RPC request was sent to the device."), + @ApiResponse(responseCode = "400", description = "Invalid structure of the request."), + @ApiResponse(responseCode = "401", description = "User is not authorized to send the RPC request. Most likely, User belongs to different Customer or Tenant."), + @ApiResponse(responseCode = "504", description = "Timeout to process the RPC call. Most likely, device is offline."), }) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST) @ResponseBody public DeferredResult handleOneWayDeviceRPCRequest( - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable("deviceId") String deviceIdStr, - @ApiParam(value = "A JSON value representing the RPC request.") + @Parameter(description = "A JSON value representing the RPC request.") @RequestBody String requestBody) throws ThingsboardException { return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.GATEWAY_TIMEOUT, HttpStatus.GATEWAY_TIMEOUT); } @ApiOperation(value = "Send two-way RPC request", notes = TWO_WAY_RPC_REQUEST_DESCRIPTION) @ApiResponses(value = { - @ApiResponse(code = 200, message = "Persistent RPC request was saved to the database or lightweight RPC response received."), - @ApiResponse(code = 400, message = "Invalid structure of the request."), - @ApiResponse(code = 401, message = "User is not authorized to send the RPC request. Most likely, User belongs to different Customer or Tenant."), - @ApiResponse(code = 504, message = "Timeout to process the RPC call. Most likely, device is offline."), + @ApiResponse(responseCode = "200", description = "Persistent RPC request was saved to the database or lightweight RPC response received."), + @ApiResponse(responseCode = "400", description = "Invalid structure of the request."), + @ApiResponse(responseCode = "401", description = "User is not authorized to send the RPC request. Most likely, User belongs to different Customer or Tenant."), + @ApiResponse(responseCode = "504", description = "Timeout to process the RPC call. Most likely, device is offline."), }) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST) @ResponseBody public DeferredResult handleTwoWayDeviceRPCRequest( - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String deviceIdStr, - @ApiParam(value = "A JSON value representing the RPC request.") + @Parameter(description = "A JSON value representing the RPC request.") @RequestBody String requestBody) throws ThingsboardException { return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.GATEWAY_TIMEOUT, HttpStatus.GATEWAY_TIMEOUT); } @@ -156,7 +154,7 @@ public class RpcV2Controller extends AbstractRpcController { @RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.GET) @ResponseBody public Rpc getPersistedRpc( - @ApiParam(value = RPC_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = RPC_ID_PARAM_DESCRIPTION, required = true) @PathVariable(RPC_ID) String strRpc) throws ThingsboardException { checkParameter("RpcId", strRpc); RpcId rpcId = new RpcId(UUID.fromString(strRpc)); @@ -168,19 +166,19 @@ public class RpcV2Controller extends AbstractRpcController { @RequestMapping(value = "/persistent/device/{deviceId}", method = RequestMethod.GET) @ResponseBody public DeferredResult getPersistedRpcByDevice( - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String strDeviceId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = "Status of the RPC", allowableValues = RPC_STATUS_ALLOWABLE_VALUES) + @Parameter(description = "Status of the RPC", schema = @Schema(allowableValues = {"QUEUED", "SENT", "DELIVERED", "SUCCESSFUL", "TIMEOUT", "EXPIRED", "FAILED"})) @RequestParam(required = false) RpcStatus rpcStatus, - @ApiParam(value = RPC_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = RPC_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = RPC_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "expirationTime", "request", "response"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("DeviceId", strDeviceId); if (rpcStatus != null && rpcStatus.equals(RpcStatus.DELETED)) { @@ -223,7 +221,7 @@ public class RpcV2Controller extends AbstractRpcController { @RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.DELETE) @ResponseBody public void deleteRpc( - @ApiParam(value = RPC_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = RPC_ID_PARAM_DESCRIPTION, required = true) @PathVariable(RPC_ID) String strRpc) throws ThingsboardException { checkParameter("RpcId", strRpc); RpcId rpcId = new RpcId(UUID.fromString(strRpc)); diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index 50abb3de1d..64606968b5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -20,8 +20,10 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -66,6 +68,7 @@ import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.rule.TbRuleChainService; @@ -93,12 +96,9 @@ import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PA import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_TEXT_SEARCH_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_TYPES_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_TYPE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.RULE_NODE_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; @@ -163,7 +163,7 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.GET) @ResponseBody public RuleChain getRuleChainById( - @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) + @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); @@ -177,7 +177,7 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChain/{ruleChainId}/output/labels", method = RequestMethod.GET) @ResponseBody public Set getRuleChainOutputLabels( - @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) + @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); @@ -192,7 +192,7 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChain/{ruleChainId}/output/labels/usage", method = RequestMethod.GET) @ResponseBody public List getRuleChainOutputLabelsUsage( - @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) + @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); @@ -206,7 +206,7 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChain/{ruleChainId}/metadata", method = RequestMethod.GET) @ResponseBody public RuleChainMetaData getRuleChainMetaData( - @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) + @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); @@ -226,7 +226,7 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChain", method = RequestMethod.POST) @ResponseBody public RuleChain saveRuleChain( - @ApiParam(value = "A JSON value representing the rule chain.") + @Parameter(description = "A JSON value representing the rule chain.") @RequestBody RuleChain ruleChain) throws Exception { ruleChain.setTenantId(getCurrentUser().getTenantId()); checkEntity(ruleChain.getId(), ruleChain, Resource.RULE_CHAIN); @@ -240,7 +240,7 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChain/device/default", method = RequestMethod.POST) @ResponseBody public RuleChain saveRuleChain( - @ApiParam(value = "A JSON value representing the request.") + @Parameter(description = "A JSON value representing the request.") @RequestBody DefaultRuleChainCreateRequest request) throws Exception { checkNotNull(request); checkParameter(request.getName(), "name"); @@ -253,7 +253,7 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChain/{ruleChainId}/root", method = RequestMethod.POST) @ResponseBody public RuleChain setRootRuleChain( - @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) + @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); @@ -267,9 +267,9 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChain/metadata", method = RequestMethod.POST) @ResponseBody public RuleChainMetaData saveRuleChainMetaData( - @ApiParam(value = "A JSON value representing the rule chain metadata.") + @Parameter(description = "A JSON value representing the rule chain metadata.") @RequestBody RuleChainMetaData ruleChainMetaData, - @ApiParam(value = "Update related rule nodes.") + @Parameter(description = "Update related rule nodes.") @RequestParam(value = "updateRelated", required = false, defaultValue = "true") boolean updateRelated ) throws Exception { TenantId tenantId = getTenantId(); @@ -291,17 +291,17 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getRuleChains( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = RULE_CHAIN_TYPE_DESCRIPTION, allowableValues = RULE_CHAIN_TYPES_ALLOWABLE_VALUES) + @Parameter(description = RULE_CHAIN_TYPE_DESCRIPTION, schema = @Schema(allowableValues = {"CORE", "EDGE"})) @RequestParam(value = "type", required = false) String typeStr, - @ApiParam(value = RULE_CHAIN_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = RULE_CHAIN_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "root"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); @@ -319,7 +319,7 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteRuleChain( - @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) + @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); @@ -334,7 +334,7 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleNode/{ruleNodeId}/debugIn", method = RequestMethod.GET) @ResponseBody public JsonNode getLatestRuleNodeDebugInput( - @ApiParam(value = RULE_NODE_ID_PARAM_DESCRIPTION) + @Parameter(description = RULE_NODE_ID_PARAM_DESCRIPTION) @PathVariable(RULE_NODE_ID) String strRuleNodeId) throws ThingsboardException { checkParameter(RULE_NODE_ID, strRuleNodeId); RuleNodeId ruleNodeId = new RuleNodeId(toUUID(strRuleNodeId)); @@ -369,9 +369,9 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST) @ResponseBody public JsonNode testScript( - @ApiParam(value = "Script language: JS or TBEL") + @Parameter(description = "Script language: JS or TBEL") @RequestParam(required = false) ScriptLanguage scriptLang, - @ApiParam(value = "Test JS request. See API call description above.") + @Parameter(description = "Test JS request. See API call description above.") @RequestBody JsonNode inputParams) throws ThingsboardException, JsonProcessingException { String script = inputParams.get("script").asText(); String scriptType = inputParams.get("scriptType").asText(); @@ -443,7 +443,7 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChains/export", params = {"limit"}, method = RequestMethod.GET) @ResponseBody public RuleChainData exportRuleChains( - @ApiParam(value = "A limit of rule chains to export.", required = true) + @Parameter(description = "A limit of rule chains to export.", required = true) @RequestParam("limit") int limit) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); PageLink pageLink = new PageLink(limit); @@ -455,9 +455,9 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChains/import", method = RequestMethod.POST) @ResponseBody public List importRuleChains( - @ApiParam(value = "A JSON value representing the rule chains.") + @Parameter(description = "A JSON value representing the rule chains.") @RequestBody RuleChainData ruleChainData, - @ApiParam(value = "Enables overwrite for existing rule chains with the same name.") + @Parameter(description = "Enables overwrite for existing rule chains with the same name.") @RequestParam(required = false, defaultValue = "false") boolean overwrite) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); List importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, overwrite); @@ -507,7 +507,7 @@ public class RuleChainController extends BaseController { EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + "Third, once rule chain will be delivered to edge service, it's going to start processing messages locally. " + "\n\nOnly rule chain with type 'EDGE' can be assigned to edge." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.POST) @ResponseBody @@ -530,7 +530,7 @@ public class RuleChainController extends BaseController { "Second, remote edge service will receive an 'unassign' command to remove rule chain " + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + "Third, once 'unassign' command will be delivered to edge service, it's going to remove rule chain locally." + TENANT_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.DELETE) @ResponseBody @@ -552,17 +552,17 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getEdgeRuleChains( - @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = RULE_CHAIN_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = RULE_CHAIN_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "root"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); TenantId tenantId = getCurrentUser().getTenantId(); @@ -578,7 +578,7 @@ public class RuleChainController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/ruleChain/{ruleChainId}/edgeTemplateRoot", method = RequestMethod.POST) @ResponseBody - public RuleChain setEdgeTemplateRootRuleChain(@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) + public RuleChain setEdgeTemplateRootRuleChain(@Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); @@ -592,7 +592,7 @@ public class RuleChainController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/ruleChain/{ruleChainId}/autoAssignToEdge", method = RequestMethod.POST) @ResponseBody - public RuleChain setAutoAssignToEdgeRuleChain(@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) + public RuleChain setAutoAssignToEdgeRuleChain(@Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); @@ -606,7 +606,7 @@ public class RuleChainController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/ruleChain/{ruleChainId}/autoAssignToEdge", method = RequestMethod.DELETE) @ResponseBody - public RuleChain unsetAutoAssignToEdgeRuleChain(@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) + public RuleChain unsetAutoAssignToEdgeRuleChain(@Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); diff --git a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java index 724cb59b73..07d8fa04d3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java +++ b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java @@ -17,6 +17,8 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import io.swagger.v3.oas.annotations.Hidden; +import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -39,14 +41,12 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; -import springfox.documentation.annotations.ApiIgnore; -import jakarta.annotation.PostConstruct; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; -@ApiIgnore +@Hidden @RestController @TbCoreComponent @RequestMapping("/api") diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index d94dc31fa1..387d31d40d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ByteArrayResource; @@ -39,6 +41,7 @@ import org.thingsboard.server.common.data.lwm2m.LwM2mObject; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.resource.TbResourceService; import org.thingsboard.server.service.security.permission.Operation; @@ -48,16 +51,13 @@ import java.util.Base64; import java.util.List; import static org.thingsboard.server.controller.ControllerConstants.LWM2M_OBJECT_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.LWM2M_OBJECT_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_INFO_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_TEXT_SEARCH_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; @@ -79,7 +79,7 @@ public class TbResourceController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET) @ResponseBody - public ResponseEntity downloadResource(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) + public ResponseEntity downloadResource(@Parameter(description = RESOURCE_ID_PARAM_DESCRIPTION) @PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException { checkParameter(RESOURCE_ID, strResourceId); TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); @@ -97,11 +97,11 @@ public class TbResourceController extends BaseController { @ApiOperation(value = "Get Resource Info (getResourceInfoById)", notes = "Fetch the Resource Info object based on the provided Resource Id. " + RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/resource/info/{resourceId}", method = RequestMethod.GET) @ResponseBody - public TbResourceInfo getResourceInfoById(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) + public TbResourceInfo getResourceInfoById(@Parameter(description = RESOURCE_ID_PARAM_DESCRIPTION) @PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException { checkParameter(RESOURCE_ID, strResourceId); TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); @@ -111,11 +111,11 @@ public class TbResourceController extends BaseController { @ApiOperation(value = "Get Resource (getResourceById)", notes = "Fetch the Resource object based on the provided Resource Id. " + RESOURCE_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.GET) @ResponseBody - public TbResource getResourceById(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) + public TbResource getResourceById(@Parameter(description = RESOURCE_ID_PARAM_DESCRIPTION) @PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException { checkParameter(RESOURCE_ID, strResourceId); TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); @@ -130,12 +130,11 @@ public class TbResourceController extends BaseController { "\n\nResource combination of the title with the key is unique in the scope of tenant. " + "Remove 'id', 'tenantId' from the request body example (below) to create new Resource entity." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json", - consumes = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/resource", method = RequestMethod.POST) @ResponseBody - public TbResource saveResource(@ApiParam(value = "A JSON value representing the Resource.") + public TbResource saveResource(@Parameter(description = "A JSON value representing the Resource.") @RequestBody TbResource resource) throws Exception { resource.setTenantId(getTenantId()); checkEntity(resource.getId(), resource, Resource.TB_RESOURCE); @@ -145,19 +144,19 @@ public class TbResourceController extends BaseController { @ApiOperation(value = "Get Resource Infos (getResources)", notes = "Returns a page of Resource Info objects owned by tenant or sysadmin. " + PAGE_DATA_PARAMETERS + RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/resource", method = RequestMethod.GET) @ResponseBody - public PageData getResources(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + public PageData getResources(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = RESOURCE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = RESOURCE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "title", "resourceType", "tenantId"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { @@ -170,19 +169,19 @@ public class TbResourceController extends BaseController { @ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjectsPage)", notes = "Returns a page of LwM2M objects parsed from Resources with type 'LWM2M_MODEL' owned by tenant or sysadmin. " + PAGE_DATA_PARAMETERS + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/resource/lwm2m/page", method = RequestMethod.GET) @ResponseBody - public List getLwm2mListObjectsPage(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + public List getLwm2mListObjectsPage(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = RESOURCE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = RESOURCE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = LWM2M_OBJECT_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"id", "name"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = new PageLink(pageSize, page, textSearch); return checkNotNull(resourceService.findLwM2mObjectPage(getTenantId(), sortProperty, sortOrder, pageLink)); @@ -191,15 +190,15 @@ public class TbResourceController extends BaseController { @ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjects)", notes = "Returns a page of LwM2M objects parsed from Resources with type 'LWM2M_MODEL' owned by tenant or sysadmin. " + "You can specify parameters to filter the results. " + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/resource/lwm2m", method = RequestMethod.GET) @ResponseBody - public List getLwm2mListObjects(@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES, required = true) + public List getLwm2mListObjects(@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}, required = true)) @RequestParam String sortOrder, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = LWM2M_OBJECT_SORT_PROPERTY_ALLOWABLE_VALUES, required = true) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"id", "name"}, required = true)) @RequestParam String sortProperty, - @ApiParam(value = "LwM2M Object ids.", required = true) + @Parameter(description = "LwM2M Object ids.", required = true) @RequestParam(required = false) String[] objectIds) throws ThingsboardException { return checkNotNull(resourceService.findLwM2mObject(getTenantId(), sortOrder, sortProperty, objectIds)); } @@ -209,7 +208,7 @@ public class TbResourceController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.DELETE) @ResponseBody - public void deleteResource(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) + public void deleteResource(@Parameter(description = RESOURCE_ID_PARAM_DESCRIPTION) @PathVariable("resourceId") String strResourceId) throws ThingsboardException { checkParameter(RESOURCE_ID, strResourceId); TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 262cd38629..1e72d70cbb 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -24,10 +24,14 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.annotation.Nullable; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -46,6 +50,7 @@ import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; @@ -74,7 +79,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.common.adaptor.JsonConverter; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.exception.InvalidParametersException; import org.thingsboard.server.exception.UncheckedApiException; @@ -85,9 +90,6 @@ import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.telemetry.AttributeData; import org.thingsboard.server.service.telemetry.TsData; -import jakarta.annotation.Nullable; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; @@ -100,8 +102,6 @@ import java.util.stream.Collectors; import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_JSON_REQUEST_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_KEYS_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES; -import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_SCOPE_ALLOWED_VALUES; import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_SCOPE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTE_DATA_EXAMPLE; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID; @@ -126,7 +126,6 @@ import static org.thingsboard.server.controller.ControllerConstants.SAVE_ENTITY_ import static org.thingsboard.server.controller.ControllerConstants.SAVE_ENTITY_TIMESERIES_STATUS_OK; import static org.thingsboard.server.controller.ControllerConstants.SAVE_ENTITY_TIMESERIES_STATUS_UNAUTHORIZED; import static org.thingsboard.server.controller.ControllerConstants.SAVE_TIMESERIES_REQUEST_PAYLOAD; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.STRICT_DATA_TYPES_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TELEMETRY_JSON_REQUEST_DESCRIPTION; @@ -176,13 +175,13 @@ public class TelemetryController extends BaseController { "\n * CLIENT_SCOPE - supported for devices;" + "\n * SHARED_SCOPE - supported for devices. " + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/keys/attributes", method = RequestMethod.GET) @ResponseBody public DeferredResult getAttributeKeys( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, this::getAttributeKeysCallback); } @@ -192,16 +191,16 @@ public class TelemetryController extends BaseController { "\n * CLIENT_SCOPE - supported for devices;" + "\n * SHARED_SCOPE - supported for devices. " + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/keys/attributes/{scope}", method = RequestMethod.GET) @ResponseBody public DeferredResult getAttributeKeysByScope( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope) throws ThingsboardException { + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope") String scope) throws ThingsboardException { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, - (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope)); + (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope)); } @ApiOperation(value = "Get attributes (getAttributes)", @@ -211,17 +210,17 @@ public class TelemetryController extends BaseController { + ATTRIBUTE_DATA_EXAMPLE + MARKDOWN_CODE_BLOCK_END + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/values/attributes", method = RequestMethod.GET) @ResponseBody public DeferredResult getAttributes( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { - SecurityUser user = getCurrentUser(); + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, + @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { + SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, - (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr)); + (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr)); } @@ -234,32 +233,32 @@ public class TelemetryController extends BaseController { + ATTRIBUTE_DATA_EXAMPLE + MARKDOWN_CODE_BLOCK_END + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/values/attributes/{scope}", method = RequestMethod.GET) @ResponseBody public DeferredResult getAttributesByScope( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, - @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, required = true)) @PathVariable("scope") String scope, + @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, - (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr)); + (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr)); } @ApiOperation(value = "Get time-series keys (getTimeseriesKeys)", notes = "Returns a set of unique time-series key names for the selected entity. " + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/keys/timeseries", method = RequestMethod.GET) @ResponseBody public DeferredResult getTimeseriesKeys( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, - (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor())); + (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor())); } @ApiOperation(value = "Get latest time-series value (getLatestTimeseries)", @@ -274,15 +273,15 @@ public class TelemetryController extends BaseController { + LATEST_TS_STRICT_DATA_EXAMPLE + MARKDOWN_CODE_BLOCK_END + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET) @ResponseBody public DeferredResult getLatestTimeseries( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr, - @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION) + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, + @Parameter(description = TELEMETRY_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr, + @Parameter(description = STRICT_DATA_TYPES_DESCRIPTION) @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, @@ -298,30 +297,30 @@ public class TelemetryController extends BaseController { + TS_STRICT_DATA_EXAMPLE + MARKDOWN_CODE_BLOCK_END + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"}) @ResponseBody public DeferredResult getTimeseries( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @ApiParam(value = TELEMETRY_KEYS_BASE_DESCRIPTION, required = true) @RequestParam(name = "keys") String keys, - @ApiParam(value = "A long value representing the start timestamp of the time range in milliseconds, UTC.") + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, + @Parameter(description = TELEMETRY_KEYS_BASE_DESCRIPTION, required = true) @RequestParam(name = "keys") String keys, + @Parameter(description = "A long value representing the start timestamp of the time range in milliseconds, UTC.") @RequestParam(name = "startTs") Long startTs, - @ApiParam(value = "A long value representing the end timestamp of the time range in milliseconds, UTC.") + @Parameter(description = "A long value representing the end timestamp of the time range in milliseconds, UTC.") @RequestParam(name = "endTs") Long endTs, - @ApiParam(value = "A long value representing the aggregation interval range in milliseconds.") + @Parameter(description = "A long value representing the aggregation interval range in milliseconds.") @RequestParam(name = "interval", defaultValue = "0") Long interval, - @ApiParam(value = "An integer value that represents a max number of timeseries data points to fetch." + - " This parameter is used only in the case if 'agg' parameter is set to 'NONE'.", defaultValue = "100") + @Parameter(description = "An integer value that represents a max number of timeseries data points to fetch." + + " This parameter is used only in the case if 'agg' parameter is set to 'NONE'.", schema = @Schema(defaultValue = "100")) @RequestParam(name = "limit", defaultValue = "100") Integer limit, - @ApiParam(value = "A string value representing the aggregation function. " + + @Parameter(description = "A string value representing the aggregation function. " + "If the interval is not specified, 'agg' parameter will use 'NONE' value.", - allowableValues = "MIN, MAX, AVG, SUM, COUNT, NONE") + schema = @Schema(allowableValues = "MIN, MAX, AVG, SUM, COUNT, NONE")) @RequestParam(name = "agg", defaultValue = "NONE") String aggStr, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(name = "orderBy", defaultValue = "DESC") String orderBy, - @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION) + @Parameter(description = STRICT_DATA_TYPES_DESCRIPTION) @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, (result, tenantId, entityId) -> { @@ -338,23 +337,23 @@ public class TelemetryController extends BaseController { notes = "Creates or updates the device attributes based on device id and specified attribute scope. " + SAVE_ATTRIBUTES_REQUEST_PAYLOAD + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @ApiResponses(value = { - @ApiResponse(code = 200, message = SAVE_ATTIRIBUTES_STATUS_OK + + @ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK + "Platform creates an audit log event about device attributes updates with action type 'ATTRIBUTES_UPDATED', " + "and also sends event msg to the rule engine with msg type 'ATTRIBUTES_UPDATED'."), - @ApiResponse(code = 400, message = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST), - @ApiResponse(code = 401, message = "User is not authorized to save device attributes for selected device. Most likely, User belongs to different Customer or Tenant."), - @ApiResponse(code = 500, message = "The exception was thrown during processing the request. " + + @ApiResponse(responseCode = "400", description = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST), + @ApiResponse(responseCode = "401", description = "User is not authorized to save device attributes for selected device. Most likely, User belongs to different Customer or Tenant."), + @ApiResponse(responseCode = "500", description = "The exception was thrown during processing the request. " + "Platform creates an audit log event about device attributes updates with action type 'ATTRIBUTES_UPDATED' that includes an error stacktrace."), }) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.POST) @ResponseBody public DeferredResult saveDeviceAttributes( - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr, - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, - @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { + @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, required = true)) @PathVariable("scope") String scope, + @Parameter(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); return saveAttributes(getTenantId(), entityId, scope, request); } @@ -364,21 +363,21 @@ public class TelemetryController extends BaseController { ENTITY_SAVE_ATTRIBUTE_SCOPES + SAVE_ATTRIBUTES_REQUEST_PAYLOAD + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @ApiResponses(value = { - @ApiResponse(code = 200, message = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK), - @ApiResponse(code = 400, message = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST), - @ApiResponse(code = 401, message = SAVE_ENTITY_ATTRIBUTES_STATUS_UNAUTHORIZED), - @ApiResponse(code = 500, message = SAVE_ENTITY_ATTRIBUTES_STATUS_INTERNAL_SERVER_ERROR), + @ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK), + @ApiResponse(responseCode = "400", description = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST), + @ApiResponse(responseCode = "401", description = SAVE_ENTITY_ATTRIBUTES_STATUS_UNAUTHORIZED), + @ApiResponse(responseCode = "500", description = SAVE_ENTITY_ATTRIBUTES_STATUS_INTERNAL_SERVER_ERROR), }) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.POST) @ResponseBody public DeferredResult saveEntityAttributesV1( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope, - @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"})) @PathVariable("scope")String scope, + @Parameter(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody JsonNode request) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return saveAttributes(getTenantId(), entityId, scope, request); } @@ -388,21 +387,21 @@ public class TelemetryController extends BaseController { ENTITY_SAVE_ATTRIBUTE_SCOPES + SAVE_ATTRIBUTES_REQUEST_PAYLOAD + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @ApiResponses(value = { - @ApiResponse(code = 200, message = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK), - @ApiResponse(code = 400, message = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST), - @ApiResponse(code = 401, message = SAVE_ENTITY_ATTRIBUTES_STATUS_UNAUTHORIZED), - @ApiResponse(code = 500, message = SAVE_ENTITY_ATTRIBUTES_STATUS_INTERNAL_SERVER_ERROR), + @ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK), + @ApiResponse(responseCode = "400", description = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST), + @ApiResponse(responseCode = "401", description = SAVE_ENTITY_ATTRIBUTES_STATUS_UNAUTHORIZED), + @ApiResponse(responseCode = "500", description = SAVE_ENTITY_ATTRIBUTES_STATUS_INTERNAL_SERVER_ERROR), }) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/attributes/{scope}", method = RequestMethod.POST) @ResponseBody public DeferredResult saveEntityAttributesV2( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, - @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, required = true)) @PathVariable("scope")String scope, + @Parameter(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody JsonNode request) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return saveAttributes(getTenantId(), entityId, scope, request); } @@ -413,21 +412,21 @@ public class TelemetryController extends BaseController { SAVE_TIMESERIES_REQUEST_PAYLOAD + "\n\n The scope parameter is not used in the API call implementation but should be specified whatever value because it is used as a path variable. " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @ApiResponses(value = { - @ApiResponse(code = 200, message = SAVE_ENTITY_TIMESERIES_STATUS_OK), - @ApiResponse(code = 400, message = INVALID_STRUCTURE_OF_THE_REQUEST), - @ApiResponse(code = 401, message = SAVE_ENTITY_TIMESERIES_STATUS_UNAUTHORIZED), - @ApiResponse(code = 500, message = SAVE_ENTITY_TIMESERIES_STATUS_INTERNAL_SERVER_ERROR), + @ApiResponse(responseCode = "200", description = SAVE_ENTITY_TIMESERIES_STATUS_OK), + @ApiResponse(responseCode = "400", description = INVALID_STRUCTURE_OF_THE_REQUEST), + @ApiResponse(responseCode = "401", description = SAVE_ENTITY_TIMESERIES_STATUS_UNAUTHORIZED), + @ApiResponse(responseCode = "500", description = SAVE_ENTITY_TIMESERIES_STATUS_INTERNAL_SERVER_ERROR), }) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/timeseries/{scope}", method = RequestMethod.POST) @ResponseBody public DeferredResult saveEntityTelemetry( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope, - @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException { + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, + @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope, + @Parameter(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return saveTelemetry(getTenantId(), entityId, requestBody, 0L); } @@ -438,22 +437,22 @@ public class TelemetryController extends BaseController { "\n\n The scope parameter is not used in the API call implementation but should be specified whatever value because it is used as a path variable. " + "\n\nThe ttl parameter takes affect only in case of Cassandra DB." + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @ApiResponses(value = { - @ApiResponse(code = 200, message = SAVE_ENTITY_TIMESERIES_STATUS_OK), - @ApiResponse(code = 400, message = INVALID_STRUCTURE_OF_THE_REQUEST), - @ApiResponse(code = 401, message = SAVE_ENTITY_TIMESERIES_STATUS_UNAUTHORIZED), - @ApiResponse(code = 500, message = SAVE_ENTITY_TIMESERIES_STATUS_INTERNAL_SERVER_ERROR), + @ApiResponse(responseCode = "200", description = SAVE_ENTITY_TIMESERIES_STATUS_OK), + @ApiResponse(responseCode = "400", description = INVALID_STRUCTURE_OF_THE_REQUEST), + @ApiResponse(responseCode = "401", description = SAVE_ENTITY_TIMESERIES_STATUS_UNAUTHORIZED), + @ApiResponse(responseCode = "500", description = SAVE_ENTITY_TIMESERIES_STATUS_INTERNAL_SERVER_ERROR), }) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/timeseries/{scope}/{ttl}", method = RequestMethod.POST) @ResponseBody public DeferredResult saveEntityTelemetryWithTTL( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope, - @ApiParam(value = "A long value representing TTL (Time to Live) parameter.", required = true) @PathVariable("ttl") Long ttl, - @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException { + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, + @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope, + @Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true)@PathVariable("ttl")Long ttl, + @Parameter(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return saveTelemetry(getTenantId(), entityId, requestBody, ttl); } @@ -464,29 +463,29 @@ public class TelemetryController extends BaseController { " Use 'startTs' and 'endTs' to specify time-range instead. " + " Use 'rewriteLatestIfDeleted' to rewrite latest value (stored in separate table for performance) after deletion of the time range. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @ApiResponses(value = { - @ApiResponse(code = 200, message = "Timeseries for the selected keys in the request was removed. " + + @ApiResponse(responseCode = "200", description = "Timeseries for the selected keys in the request was removed. " + "Platform creates an audit log event about entity timeseries removal with action type 'TIMESERIES_DELETED'."), - @ApiResponse(code = 400, message = "Platform returns a bad request in case if keys list is empty or start and end timestamp values is empty when deleteAllDataForKeys is set to false."), - @ApiResponse(code = 401, message = "User is not authorized to delete entity timeseries for selected entity. Most likely, User belongs to different Customer or Tenant."), - @ApiResponse(code = 500, message = "The exception was thrown during processing the request. " + + @ApiResponse(responseCode = "400", description = "Platform returns a bad request in case if keys list is empty or start and end timestamp values is empty when deleteAllDataForKeys is set to false."), + @ApiResponse(responseCode = "401", description = "User is not authorized to delete entity timeseries for selected entity. Most likely, User belongs to different Customer or Tenant."), + @ApiResponse(responseCode = "500", description = "The exception was thrown during processing the request. " + "Platform creates an audit log event about entity timeseries removal with action type 'TIMESERIES_DELETED' that includes an error stacktrace."), }) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/timeseries/delete", method = RequestMethod.DELETE) @ResponseBody public DeferredResult deleteEntityTimeseries( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr, - @ApiParam(value = "A boolean value to specify if should be deleted all data for selected keys or only data that are in the selected time range.") + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, + @Parameter(description = TELEMETRY_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr, + @Parameter(description = "A boolean value to specify if should be deleted all data for selected keys or only data that are in the selected time range.") @RequestParam(name = "deleteAllDataForKeys", defaultValue = "false") boolean deleteAllDataForKeys, - @ApiParam(value = "A long value representing the start timestamp of removal time range in milliseconds.") + @Parameter(description = "A long value representing the start timestamp of removal time range in milliseconds.") @RequestParam(name = "startTs", required = false) Long startTs, - @ApiParam(value = "A long value representing the end timestamp of removal time range in milliseconds.") + @Parameter(description = "A long value representing the end timestamp of removal time range in milliseconds.") @RequestParam(name = "endTs", required = false) Long endTs, - @ApiParam(value = "If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.") + @Parameter(description = "If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.") @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted); @@ -538,22 +537,22 @@ public class TelemetryController extends BaseController { @ApiOperation(value = "Delete device attributes (deleteDeviceAttributes)", notes = "Delete device attributes using provided Device Id, scope and a list of keys. " + "Referencing a non-existing Device Id will cause an error" + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @ApiResponses(value = { - @ApiResponse(code = 200, message = "Device attributes was removed for the selected keys in the request. " + + @ApiResponse(responseCode = "200", description = "Device attributes was removed for the selected keys in the request. " + "Platform creates an audit log event about device attributes removal with action type 'ATTRIBUTES_DELETED'."), - @ApiResponse(code = 400, message = "Platform returns a bad request in case if keys or scope are not specified."), - @ApiResponse(code = 401, message = "User is not authorized to delete device attributes for selected entity. Most likely, User belongs to different Customer or Tenant."), - @ApiResponse(code = 500, message = "The exception was thrown during processing the request. " + + @ApiResponse(responseCode = "400", description = "Platform returns a bad request in case if keys or scope are not specified."), + @ApiResponse(responseCode = "401", description = "User is not authorized to delete device attributes for selected entity. Most likely, User belongs to different Customer or Tenant."), + @ApiResponse(responseCode = "500", description = "The exception was thrown during processing the request. " + "Platform creates an audit log event about device attributes removal with action type 'ATTRIBUTES_DELETED' that includes an error stacktrace."), }) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.DELETE) @ResponseBody public DeferredResult deleteDeviceAttributes( - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr, - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, - @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException { + @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, required = true)) @PathVariable("scope")String scope, + @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); return deleteAttributes(entityId, scope, keysStr); } @@ -561,23 +560,23 @@ public class TelemetryController extends BaseController { @ApiOperation(value = "Delete entity attributes (deleteEntityAttributes)", notes = "Delete entity attributes using provided Entity Id, scope and a list of keys. " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @ApiResponses(value = { - @ApiResponse(code = 200, message = "Entity attributes was removed for the selected keys in the request. " + + @ApiResponse(responseCode = "200", description = "Entity attributes was removed for the selected keys in the request. " + "Platform creates an audit log event about entity attributes removal with action type 'ATTRIBUTES_DELETED'."), - @ApiResponse(code = 400, message = "Platform returns a bad request in case if keys or scope are not specified."), - @ApiResponse(code = 401, message = "User is not authorized to delete entity attributes for selected entity. Most likely, User belongs to different Customer or Tenant."), - @ApiResponse(code = 500, message = "The exception was thrown during processing the request. " + + @ApiResponse(responseCode = "400", description = "Platform returns a bad request in case if keys or scope are not specified."), + @ApiResponse(responseCode = "401", description = "User is not authorized to delete entity attributes for selected entity. Most likely, User belongs to different Customer or Tenant."), + @ApiResponse(responseCode = "500", description = "The exception was thrown during processing the request. " + "Platform creates an audit log event about entity attributes removal with action type 'ATTRIBUTES_DELETED' that includes an error stacktrace."), }) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.DELETE) @ResponseBody public DeferredResult deleteEntityAttributes( - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope, - @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException { + @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, + @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope")String scope, + @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return deleteAttributes(entityId, scope, keysStr); } diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index 6574969e15..6fb58207bf 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -16,8 +16,8 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.node.ObjectNode; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.tenant.TbTenantService; @@ -46,15 +47,12 @@ import static org.thingsboard.server.controller.ControllerConstants.HOME_DASHBOA import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.TENANT_ID; import static org.thingsboard.server.controller.ControllerConstants.TENANT_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.TENANT_INFO_SORT_PROPERTY_ALLOWABLE_VALUES; -import static org.thingsboard.server.controller.ControllerConstants.TENANT_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.TENANT_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; @@ -76,7 +74,7 @@ public class TenantController extends BaseController { @RequestMapping(value = "/tenant/{tenantId}", method = RequestMethod.GET) @ResponseBody public Tenant getTenantById( - @ApiParam(value = TENANT_ID_PARAM_DESCRIPTION) + @Parameter(description = TENANT_ID_PARAM_DESCRIPTION) @PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException { checkParameter(TENANT_ID, strTenantId); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); @@ -94,7 +92,7 @@ public class TenantController extends BaseController { @RequestMapping(value = "/tenant/info/{tenantId}", method = RequestMethod.GET) @ResponseBody public TenantInfo getTenantInfoById( - @ApiParam(value = TENANT_ID_PARAM_DESCRIPTION) + @Parameter(description = TENANT_ID_PARAM_DESCRIPTION) @PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException { checkParameter(TENANT_ID, strTenantId); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); @@ -112,7 +110,7 @@ public class TenantController extends BaseController { @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/tenant", method = RequestMethod.POST) @ResponseBody - public Tenant saveTenant(@ApiParam(value = "A JSON value representing the tenant.") + public Tenant saveTenant(@Parameter(description = "A JSON value representing the tenant.") @RequestBody Tenant tenant) throws Exception { checkEntity(tenant.getId(), tenant, Resource.TENANT); return tbTenantService.save(tenant); @@ -123,7 +121,7 @@ public class TenantController extends BaseController { @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/tenant/{tenantId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public void deleteTenant(@ApiParam(value = TENANT_ID_PARAM_DESCRIPTION) + public void deleteTenant(@Parameter(description = TENANT_ID_PARAM_DESCRIPTION) @PathVariable(TENANT_ID) String strTenantId) throws Exception { checkParameter(TENANT_ID, strTenantId); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); @@ -136,15 +134,15 @@ public class TenantController extends BaseController { @RequestMapping(value = "/tenants", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenants( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = TENANT_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = TENANT_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = TENANT_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "title", "email", "country", "state", "city", "address", "address2", "zip", "phone", "email"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(tenantService.findTenants(pageLink)); @@ -156,15 +154,15 @@ public class TenantController extends BaseController { @RequestMapping(value = "/tenantInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantInfos( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = TENANT_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = TENANT_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = TENANT_INFO_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "tenantProfileName", "title", "email", "country", "state", "city", "address", "address2", "zip", "phone", "email"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder ) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java index a7fe973ec0..8f667ec184 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileService; import org.thingsboard.server.service.security.permission.Operation; @@ -50,13 +51,10 @@ import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_COD import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.TENANT_PROFILE_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.TENANT_PROFILE_INFO_SORT_PROPERTY_ALLOWABLE_VALUES; -import static org.thingsboard.server.controller.ControllerConstants.TENANT_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.TENANT_PROFILE_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; @@ -77,7 +75,7 @@ public class TenantProfileController extends BaseController { @RequestMapping(value = "/tenantProfile/{tenantProfileId}", method = RequestMethod.GET) @ResponseBody public TenantProfile getTenantProfileById( - @ApiParam(value = TENANT_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = TENANT_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { checkParameter("tenantProfileId", strTenantProfileId); TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); @@ -90,7 +88,7 @@ public class TenantProfileController extends BaseController { @RequestMapping(value = "/tenantProfileInfo/{tenantProfileId}", method = RequestMethod.GET) @ResponseBody public EntityInfo getTenantProfileInfoById( - @ApiParam(value = TENANT_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = TENANT_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { checkParameter("tenantProfileId", strTenantProfileId); TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); @@ -162,7 +160,7 @@ public class TenantProfileController extends BaseController { @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/tenantProfile", method = RequestMethod.POST) @ResponseBody - public TenantProfile saveTenantProfile(@ApiParam(value = "A JSON value representing the tenant profile.") + public TenantProfile saveTenantProfile(@Parameter(description = "A JSON value representing the tenant profile.") @RequestBody TenantProfile tenantProfile) throws ThingsboardException { TenantProfile oldProfile; if (tenantProfile.getId() == null) { @@ -180,7 +178,7 @@ public class TenantProfileController extends BaseController { @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/tenantProfile/{tenantProfileId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public void deleteTenantProfile(@ApiParam(value = TENANT_PROFILE_ID_PARAM_DESCRIPTION) + public void deleteTenantProfile(@Parameter(description = TENANT_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { checkParameter("tenantProfileId", strTenantProfileId); TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); @@ -194,7 +192,7 @@ public class TenantProfileController extends BaseController { @RequestMapping(value = "/tenantProfile/{tenantProfileId}/default", method = RequestMethod.POST) @ResponseBody public TenantProfile setDefaultTenantProfile( - @ApiParam(value = TENANT_PROFILE_ID_PARAM_DESCRIPTION) + @Parameter(description = TENANT_PROFILE_ID_PARAM_DESCRIPTION) @PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { checkParameter("tenantProfileId", strTenantProfileId); TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); @@ -208,15 +206,15 @@ public class TenantProfileController extends BaseController { @RequestMapping(value = "/tenantProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantProfiles( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = TENANT_PROFILE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = TENANT_PROFILE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = TENANT_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "description", "isDefault"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(tenantProfileService.findTenantProfiles(getTenantId(), pageLink)); @@ -228,15 +226,15 @@ public class TenantProfileController extends BaseController { @RequestMapping(value = "/tenantProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantProfileInfos( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = TENANT_PROFILE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = TENANT_PROFILE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = TENANT_PROFILE_INFO_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"id", "name"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(tenantProfileService.findTenantProfileInfos(getTenantId(), pageLink)); diff --git a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java index a809f5280c..f674ed0ca8 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java @@ -15,8 +15,9 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; @@ -34,12 +35,12 @@ import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoF import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService; import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; import org.thingsboard.server.service.security.model.SecurityUser; -import jakarta.validation.Valid; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -99,7 +100,7 @@ public class TwoFactorAuthConfigController extends BaseController { ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER) @PostMapping("/account/config/generate") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - public TwoFaAccountConfig generateTwoFaAccountConfig(@ApiParam(value = "2FA provider type to generate new account config for", defaultValue = "TOTP", required = true) + public TwoFaAccountConfig generateTwoFaAccountConfig(@Parameter(description = "2FA provider type to generate new account config for", schema = @Schema(defaultValue = "TOTP", required = true)) @RequestParam TwoFaProviderType providerType) throws Exception { SecurityUser user = getCurrentUser(); return twoFactorAuthService.generateNewAccountConfig(user, providerType); @@ -258,7 +259,7 @@ public class TwoFactorAuthConfigController extends BaseController { ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PostMapping("/settings") @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - public PlatformTwoFaSettings savePlatformTwoFaSettings(@ApiParam(value = "Settings value", required = true) + public PlatformTwoFaSettings savePlatformTwoFaSettings(@Parameter(description = "Settings value", required = true) @RequestBody PlatformTwoFaSettings twoFaSettings) throws ThingsboardException { return twoFaConfigManager.savePlatformTwoFaSettings(getTenantId(), twoFaSettings); } diff --git a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java index ee1db6868a..1478e21c76 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; +import jakarta.servlet.http.HttpServletRequest; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettin import org.thingsboard.server.common.data.security.model.mfa.account.EmailTwoFaAccountConfig; import org.thingsboard.server.common.data.security.model.mfa.account.SmsTwoFaAccountConfig; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService; @@ -44,7 +45,6 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.system.SystemSecurityService; -import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.List; import java.util.Optional; diff --git a/application/src/main/java/org/thingsboard/server/controller/UiSettingsController.java b/application/src/main/java/org/thingsboard/server/controller/UiSettingsController.java index a40d3b85af..11bd0e8364 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UiSettingsController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UiSettingsController.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.RequestMapping; @@ -23,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; @RestController diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index 5bf6a86307..b05d5feb83 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -17,8 +17,9 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -64,6 +65,7 @@ import org.thingsboard.server.common.data.settings.UserDashboardAction; import org.thingsboard.server.common.data.settings.UserDashboardsInfo; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.settings.UserSettingsType; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.user.TbUserService; @@ -75,7 +77,6 @@ import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.system.SystemSecurityService; -import jakarta.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -92,7 +93,6 @@ import static org.thingsboard.server.controller.ControllerConstants.HOME_DASHBOA import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; @@ -102,7 +102,6 @@ import static org.thingsboard.server.controller.ControllerConstants.TENANT_ID; import static org.thingsboard.server.controller.ControllerConstants.TENANT_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.USER_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.USER_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.USER_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; @@ -141,7 +140,7 @@ public class UserController extends BaseController { @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) @ResponseBody public User getUserById( - @ApiParam(value = USER_ID_PARAM_DESCRIPTION) + @Parameter(description = USER_ID_PARAM_DESCRIPTION) @PathVariable(USER_ID) String strUserId) throws ThingsboardException { checkParameter(USER_ID, strUserId); UserId userId = new UserId(toUUID(strUserId)); @@ -177,7 +176,7 @@ public class UserController extends BaseController { @RequestMapping(value = "/user/{userId}/token", method = RequestMethod.GET) @ResponseBody public JwtPair getUserToken( - @ApiParam(value = USER_ID_PARAM_DESCRIPTION) + @Parameter(description = USER_ID_PARAM_DESCRIPTION) @PathVariable(USER_ID) String strUserId) throws ThingsboardException { checkParameter(USER_ID, strUserId); if (!userTokenAccessEnabled) { @@ -205,9 +204,9 @@ public class UserController extends BaseController { @RequestMapping(value = "/user", method = RequestMethod.POST) @ResponseBody public User saveUser( - @ApiParam(value = "A JSON value representing the User.", required = true) + @Parameter(description = "A JSON value representing the User.", required = true) @RequestBody User user, - @ApiParam(value = "Send activation email (or use activation link)", defaultValue = "true") + @Parameter(description = "Send activation email (or use activation link)" , schema = @Schema(defaultValue = "true")) @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail, HttpServletRequest request) throws ThingsboardException { if (!Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { user.setTenantId(getCurrentUser().getTenantId()); @@ -222,7 +221,7 @@ public class UserController extends BaseController { @RequestMapping(value = "/user/sendActivationMail", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) public void sendActivationEmail( - @ApiParam(value = "Email of the user", required = true) + @Parameter(description = "Email of the user", required = true) @RequestParam(value = "email") String email, HttpServletRequest request) throws ThingsboardException { User user = checkNotNull(userService.findUserByEmail(getCurrentUser().getTenantId(), email)); @@ -248,7 +247,7 @@ public class UserController extends BaseController { @RequestMapping(value = "/user/{userId}/activationLink", method = RequestMethod.GET, produces = "text/plain") @ResponseBody public String getActivationLink( - @ApiParam(value = USER_ID_PARAM_DESCRIPTION) + @Parameter(description = USER_ID_PARAM_DESCRIPTION) @PathVariable(USER_ID) String strUserId, HttpServletRequest request) throws ThingsboardException { checkParameter(USER_ID, strUserId); @@ -273,7 +272,7 @@ public class UserController extends BaseController { @RequestMapping(value = "/user/{userId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteUser( - @ApiParam(value = USER_ID_PARAM_DESCRIPTION) + @Parameter(description = USER_ID_PARAM_DESCRIPTION) @PathVariable(USER_ID) String strUserId) throws ThingsboardException { checkParameter(USER_ID, strUserId); UserId userId = new UserId(toUUID(strUserId)); @@ -291,15 +290,15 @@ public class UserController extends BaseController { @RequestMapping(value = "/users", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getUsers( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = USER_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = USER_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = USER_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "firstName", "lastName", "email"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); SecurityUser currentUser = getCurrentUser(); @@ -317,15 +316,15 @@ public class UserController extends BaseController { @RequestMapping(value = "/users/info", method = RequestMethod.GET) @ResponseBody public PageData findUsersByQuery( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = USER_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = USER_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = USER_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "firstName", "lastName", "email"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { SecurityUser securityUser = getCurrentUser(); @@ -354,17 +353,17 @@ public class UserController extends BaseController { @RequestMapping(value = "/tenant/{tenantId}/users", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantAdmins( - @ApiParam(value = TENANT_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = TENANT_ID_PARAM_DESCRIPTION, required = true) @PathVariable(TENANT_ID) String strTenantId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = USER_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = USER_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = USER_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "firstName", "lastName", "email"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("tenantId", strTenantId); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); @@ -378,17 +377,17 @@ public class UserController extends BaseController { @RequestMapping(value = "/customer/{customerId}/users", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomerUsers( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) @PathVariable(CUSTOMER_ID) String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = USER_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = USER_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = USER_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "firstName", "lastName", "email"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); @@ -404,9 +403,9 @@ public class UserController extends BaseController { @RequestMapping(value = "/user/{userId}/userCredentialsEnabled", method = RequestMethod.POST) @ResponseBody public void setUserCredentialsEnabled( - @ApiParam(value = USER_ID_PARAM_DESCRIPTION) + @Parameter(description = USER_ID_PARAM_DESCRIPTION) @PathVariable(USER_ID) String strUserId, - @ApiParam(value = "Enable (\"true\") or disable (\"false\") the credentials.", defaultValue = "true") + @Parameter(description = "Enable (\"true\") or disable (\"false\") the credentials." , schema = @Schema(defaultValue = "true")) @RequestParam(required = false, defaultValue = "true") boolean userCredentialsEnabled) throws ThingsboardException { checkParameter(USER_ID, strUserId); UserId userId = new UserId(toUUID(strUserId)); @@ -427,17 +426,17 @@ public class UserController extends BaseController { @RequestMapping(value = "/users/assign/{alarmId}", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getUsersForAssign( - @ApiParam(value = ALARM_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = ALARM_ID_PARAM_DESCRIPTION, required = true) @PathVariable("alarmId") String strAlarmId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = USER_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = USER_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = USER_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "firstName", "lastName", "email"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { try { checkParameter("alarmId", strAlarmId); @@ -508,7 +507,7 @@ public class UserController extends BaseController { "Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/user/settings/{paths}", method = RequestMethod.DELETE) - public void deleteUserSettings(@ApiParam(value = PATHS) + public void deleteUserSettings(@Parameter(description = PATHS) @PathVariable(PATHS) String paths) throws ThingsboardException { checkParameter(USER_ID, paths); @@ -522,7 +521,7 @@ public class UserController extends BaseController { "{A:5, B:{C:10, D:30}}. The same could be achieved by putting {B.D:30}") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PutMapping(value = "/user/settings/{type}") - public void putUserSettings(@ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".") + public void putUserSettings(@Parameter(description = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".") @PathVariable("type") String strType, @RequestBody JsonNode settings) throws ThingsboardException { SecurityUser currentUser = getCurrentUser(); UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf); @@ -534,7 +533,7 @@ public class UserController extends BaseController { notes = "Fetch the User settings based on authorized user. ") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @GetMapping(value = "/user/settings/{type}") - public JsonNode getUserSettings(@ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".") + public JsonNode getUserSettings(@Parameter(description = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".") @PathVariable("type") String strType) throws ThingsboardException { SecurityUser currentUser = getCurrentUser(); UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf); @@ -548,9 +547,9 @@ public class UserController extends BaseController { "Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/user/settings/{type}/{paths}", method = RequestMethod.DELETE) - public void deleteUserSettings(@ApiParam(value = PATHS) + public void deleteUserSettings(@Parameter(description = PATHS) @PathVariable(PATHS) String paths, - @ApiParam(value = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".") + @Parameter(description = "Settings type, case insensitive, one of: \"general\", \"quick_links\", \"doc_links\" or \"dashboards\".") @PathVariable("type") String strType) throws ThingsboardException { checkParameter(USER_ID, paths); UserSettingsType type = checkEnumParameter("Settings type", strType, UserSettingsType::valueOf); @@ -574,9 +573,9 @@ public class UserController extends BaseController { @RequestMapping(value = "/user/dashboards/{dashboardId}/{action}", method = RequestMethod.GET) @ResponseBody public UserDashboardsInfo reportUserDashboardAction( - @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) + @Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION) @PathVariable(DashboardController.DASHBOARD_ID) String strDashboardId, - @ApiParam(value = "Dashboard action, one of: \"visit\", \"star\" or \"unstar\".") + @Parameter(description = "Dashboard action, one of: \"visit\", \"star\" or \"unstar\".") @PathVariable("action") String strAction) throws ThingsboardException { checkParameter(DashboardController.DASHBOARD_ID, strDashboardId); checkParameter("action", strAction); diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java index d1ecd0441a..9f71890e70 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; @@ -37,6 +36,7 @@ import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; @@ -67,7 +67,7 @@ public class WidgetTypeController extends AutoCommitController { @RequestMapping(value = "/widgetType/{widgetTypeId}", method = RequestMethod.GET) @ResponseBody public WidgetTypeDetails getWidgetTypeById( - @ApiParam(value = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException { checkParameter("widgetTypeId", strWidgetTypeId); WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId)); @@ -88,7 +88,7 @@ public class WidgetTypeController extends AutoCommitController { @RequestMapping(value = "/widgetType", method = RequestMethod.POST) @ResponseBody public WidgetTypeDetails saveWidgetType( - @ApiParam(value = "A JSON value representing the Widget Type Details.", required = true) + @Parameter(description = "A JSON value representing the Widget Type Details.", required = true) @RequestBody WidgetTypeDetails widgetTypeDetails) throws Exception { var currentUser = getCurrentUser(); if (Authority.SYS_ADMIN.equals(currentUser.getAuthority())) { @@ -119,7 +119,7 @@ public class WidgetTypeController extends AutoCommitController { @RequestMapping(value = "/widgetType/{widgetTypeId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteWidgetType( - @ApiParam(value = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("widgetTypeId") String strWidgetTypeId) throws Exception { checkParameter("widgetTypeId", strWidgetTypeId); var currentUser = getCurrentUser(); @@ -143,9 +143,9 @@ public class WidgetTypeController extends AutoCommitController { @RequestMapping(value = "/widgetTypes", params = {"isSystem", "bundleAlias"}, method = RequestMethod.GET) @ResponseBody public List getBundleWidgetTypes( - @ApiParam(value = "System or Tenant", required = true) + @Parameter(description = "System or Tenant", required = true) @RequestParam boolean isSystem, - @ApiParam(value = "Widget Bundle alias", required = true) + @Parameter(description = "Widget Bundle alias", required = true) @RequestParam String bundleAlias) throws ThingsboardException { TenantId tenantId; if (isSystem) { @@ -162,9 +162,9 @@ public class WidgetTypeController extends AutoCommitController { @RequestMapping(value = "/widgetTypesDetails", params = {"isSystem", "bundleAlias"}, method = RequestMethod.GET) @ResponseBody public List getBundleWidgetTypesDetails( - @ApiParam(value = "System or Tenant", required = true) + @Parameter(description = "System or Tenant", required = true) @RequestParam boolean isSystem, - @ApiParam(value = "Widget Bundle alias", required = true) + @Parameter(description = "Widget Bundle alias", required = true) @RequestParam String bundleAlias) throws ThingsboardException { TenantId tenantId; if (isSystem) { @@ -181,9 +181,9 @@ public class WidgetTypeController extends AutoCommitController { @RequestMapping(value = "/widgetTypesInfos", params = {"isSystem", "bundleAlias"}, method = RequestMethod.GET) @ResponseBody public List getBundleWidgetTypesInfos( - @ApiParam(value = "System or Tenant", required = true) + @Parameter(description = "System or Tenant", required = true) @RequestParam boolean isSystem, - @ApiParam(value = "Widget Bundle alias", required = true) + @Parameter(description = "Widget Bundle alias", required = true) @RequestParam String bundleAlias) throws ThingsboardException { TenantId tenantId; if (isSystem) { @@ -200,11 +200,11 @@ public class WidgetTypeController extends AutoCommitController { @RequestMapping(value = "/widgetType", params = {"isSystem", "bundleAlias", "alias"}, method = RequestMethod.GET) @ResponseBody public WidgetType getWidgetType( - @ApiParam(value = "System or Tenant", required = true) + @Parameter(description = "System or Tenant", required = true) @RequestParam boolean isSystem, - @ApiParam(value = "Widget Bundle alias", required = true) + @Parameter(description = "Widget Bundle alias", required = true) @RequestParam String bundleAlias, - @ApiParam(value = "Widget Type alias", required = true) + @Parameter(description = "Widget Type alias", required = true) @RequestParam String alias) throws ThingsboardException { TenantId tenantId; if (isSystem) { diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java index 737e6d54d5..9e36015ee1 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.controller; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.widgets.bundle.TbWidgetsBundleService; import org.thingsboard.server.service.security.permission.Operation; @@ -46,13 +47,11 @@ import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FO import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; import static org.thingsboard.server.controller.ControllerConstants.WIDGET_BUNDLE_ID_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.WIDGET_BUNDLE_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.WIDGET_BUNDLE_TEXT_SEARCH_DESCRIPTION; @RestController @@ -71,7 +70,7 @@ public class WidgetsBundleController extends BaseController { @RequestMapping(value = "/widgetsBundle/{widgetsBundleId}", method = RequestMethod.GET) @ResponseBody public WidgetsBundle getWidgetsBundleById( - @ApiParam(value = WIDGET_BUNDLE_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = WIDGET_BUNDLE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("widgetsBundleId") String strWidgetsBundleId) throws ThingsboardException { checkParameter("widgetsBundleId", strWidgetsBundleId); WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId)); @@ -92,7 +91,7 @@ public class WidgetsBundleController extends BaseController { @RequestMapping(value = "/widgetsBundle", method = RequestMethod.POST) @ResponseBody public WidgetsBundle saveWidgetsBundle( - @ApiParam(value = "A JSON value representing the Widget Bundle.", required = true) + @Parameter(description = "A JSON value representing the Widget Bundle.", required = true) @RequestBody WidgetsBundle widgetsBundle) throws Exception { var currentUser = getCurrentUser(); if (Authority.SYS_ADMIN.equals(currentUser.getAuthority())) { @@ -112,7 +111,7 @@ public class WidgetsBundleController extends BaseController { @RequestMapping(value = "/widgetsBundle/{widgetsBundleId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteWidgetsBundle( - @ApiParam(value = WIDGET_BUNDLE_ID_PARAM_DESCRIPTION, required = true) + @Parameter(description = WIDGET_BUNDLE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("widgetsBundleId") String strWidgetsBundleId) throws ThingsboardException { checkParameter("widgetsBundleId", strWidgetsBundleId); WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId)); @@ -127,15 +126,15 @@ public class WidgetsBundleController extends BaseController { @RequestMapping(value = "/widgetsBundles", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getWidgetsBundles( - @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @ApiParam(value = WIDGET_BUNDLE_TEXT_SEARCH_DESCRIPTION) + @Parameter(description = WIDGET_BUNDLE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = WIDGET_BUNDLE_SORT_PROPERTY_ALLOWABLE_VALUES) + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "title", "tenantId"})) @RequestParam(required = false) String sortProperty, - @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 73d763af34..c436461c65 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -15,6 +15,10 @@ */ package org.thingsboard.server.controller.plugin; +import jakarta.websocket.RemoteEndpoint; +import jakarta.websocket.SendHandler; +import jakarta.websocket.SendResult; +import jakarta.websocket.Session; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.BeanCreationNotAllowedException; import org.springframework.beans.factory.annotation.Autowired; @@ -47,10 +51,6 @@ import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.WebSocketSessionType; -import jakarta.websocket.RemoteEndpoint; -import jakarta.websocket.SendHandler; -import jakarta.websocket.SendResult; -import jakarta.websocket.Session; import java.io.IOException; import java.net.URI; import java.security.InvalidParameterException; diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java index 46e7205824..a055d4c94a 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java @@ -15,12 +15,11 @@ */ package org.thingsboard.server.exception; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.springframework.http.HttpStatus; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; -@ApiModel +@Schema public class ThingsboardCredentialsExpiredResponse extends ThingsboardErrorResponse { private final String resetToken; @@ -34,7 +33,7 @@ public class ThingsboardCredentialsExpiredResponse extends ThingsboardErrorRespo return new ThingsboardCredentialsExpiredResponse(message, resetToken); } - @ApiModelProperty(position = 5, value = "Password reset token", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Password reset token", accessMode = Schema.AccessMode.READ_ONLY) public String getResetToken() { return resetToken; } diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java index 73cc5b0902..e1a76bbcac 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java @@ -15,14 +15,13 @@ */ package org.thingsboard.server.exception; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.springframework.http.HttpStatus; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import java.util.Date; -@ApiModel +@Schema public class ThingsboardErrorResponse { // HTTP Response Status Code private final HttpStatus status; @@ -46,17 +45,17 @@ public class ThingsboardErrorResponse { return new ThingsboardErrorResponse(message, errorCode, status); } - @ApiModelProperty(position = 1, value = "HTTP Response Status Code", example = "401", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "HTTP Response Status Code", example = "401", accessMode = Schema.AccessMode.READ_ONLY) public Integer getStatus() { return status.value(); } - @ApiModelProperty(position = 2, value = "Error message", example = "Authentication failed", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Error message", example = "Authentication failed", accessMode = Schema.AccessMode.READ_ONLY) public String getMessage() { return message; } - @ApiModelProperty(position = 3, value = "Platform error code:" + + @Schema(description = "Platform error code:" + "\n* `2` - General error (HTTP: 500 - Internal Server Error)" + "\n\n* `10` - Authentication failed (HTTP: 401 - Unauthorized)" + "\n\n* `11` - JWT token expired (HTTP: 401 - Unauthorized)" + @@ -68,13 +67,13 @@ public class ThingsboardErrorResponse { "\n\n* `33` - Too many requests (HTTP: 429 - Too Many Requests)" + "\n\n* `34` - Too many updates (Too many updates over Websocket session)" + "\n\n* `40` - Subscription violation (HTTP: 403 - Forbidden)", - example = "10", dataType = "integer", - accessMode = ApiModelProperty.AccessMode.READ_ONLY) + example = "10", type = "integer", + accessMode = Schema.AccessMode.READ_ONLY) public ThingsboardErrorCode getErrorCode() { return errorCode; } - @ApiModelProperty(position = 4, value = "Timestamp", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp", accessMode = Schema.AccessMode.READ_ONLY) public Date getTimestamp() { return timestamp; } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java index 78cf6b1f98..6065466a62 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java @@ -17,10 +17,9 @@ package org.thingsboard.server.service.security.auth.rest; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; -@ApiModel +@Schema public class LoginRequest { private String username; @@ -33,12 +32,12 @@ public class LoginRequest { this.password = password; } - @ApiModelProperty(position = 1, required = true, value = "User email", example = "tenant@thingsboard.org") + @Schema(required = true, description = "User email", example = "tenant@thingsboard.org") public String getUsername() { return username; } - @ApiModelProperty(position = 2, required = true, value = "User password", example = "tenant") + @Schema(required = true, description = "User password", example = "tenant") public String getPassword() { return password; } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginResponse.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginResponse.java index 8fdd443fe8..91113a4caa 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginResponse.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginResponse.java @@ -15,19 +15,18 @@ */ package org.thingsboard.server.service.security.auth.rest; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@ApiModel +@Schema @Data public class LoginResponse { - @ApiModelProperty(position = 1, required = true, value = "JWT token", + @Schema(required = true, description = "JWT token", example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZW5hbnRAdGhpbmdzYm9hcmQub3JnIi...") private String token; - @ApiModelProperty(position = 2, required = true, value = "Refresh token", + @Schema(required = true, description = "Refresh token", example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZW5hbnRAdGhpbmdzYm9hcmQub3JnIi...") private String refreshToken; diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java index aff95ea246..a3f6057d61 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java @@ -15,16 +15,15 @@ */ package org.thingsboard.server.service.security.model; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@ApiModel +@Schema @Data public class ActivateUserRequest { - @ApiModelProperty(position = 1, value = "The activate token to verify", example = "AAB254FF67D..") + @Schema(description = "The activate token to verify", example = "AAB254FF67D..") private String activateToken; - @ApiModelProperty(position = 2, value = "The new password to set", example = "secret") + @Schema(description = "The new password to set", example = "secret") private String password; } diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java index 4b21acef5e..0596f8f43a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java @@ -15,17 +15,16 @@ */ package org.thingsboard.server.service.security.model; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@ApiModel +@Schema @Data public class ChangePasswordRequest { - @ApiModelProperty(position = 1, value = "The old password", example = "OldPassword") + @Schema(description = "The old password", example = "OldPassword") private String currentPassword; - @ApiModelProperty(position = 1, value = "The new password", example = "NewPassword") + @Schema(description = "The new password", example = "NewPassword") private String newPassword; } diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java index 93f9541986..fb4832a264 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java @@ -15,14 +15,13 @@ */ package org.thingsboard.server.service.security.model; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@ApiModel +@Schema @Data public class ResetPasswordEmailRequest { - @ApiModelProperty(position = 1, value = "The email of the user", example = "user@example.com") + @Schema(description = "The email of the user", example = "user@example.com") private String email; } diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java index 3869da9b02..6afeff730b 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java @@ -15,16 +15,15 @@ */ package org.thingsboard.server.service.security.model; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@ApiModel +@Schema @Data public class ResetPasswordRequest { - @ApiModelProperty(position = 1, value = "The reset token to verify", example = "AAB254FF67D..") + @Schema(description = "The reset token to verify", example = "AAB254FF67D..") private String resetToken; - @ApiModelProperty(position = 2, value = "The new password to set", example = "secret") + @Schema(description = "The new password to set", example = "secret") private String password; } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java index b7c7b281ca..58a56e05c1 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java @@ -15,10 +15,9 @@ */ package org.thingsboard.server.service.telemetry; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; -@ApiModel +@Schema public class AttributeData implements Comparable{ private final long lastUpdateTs; @@ -32,17 +31,17 @@ public class AttributeData implements Comparable{ this.value = value; } - @ApiModelProperty(position = 1, value = "Timestamp last updated attribute, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp last updated attribute, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) public long getLastUpdateTs() { return lastUpdateTs; } - @ApiModelProperty(position = 2, value = "String representing attribute key", example = "active", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "String representing attribute key", example = "active", accessMode = Schema.AccessMode.READ_ONLY) public String getKey() { return key; } - @ApiModelProperty(position = 3, value = "Object representing value of attribute key", example = "false", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Object representing value of attribute key", example = "false", accessMode = Schema.AccessMode.READ_ONLY) public Object getValue() { return value; } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java b/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java index d0af1080d3..d0f07ac094 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java @@ -15,10 +15,9 @@ */ package org.thingsboard.server.service.telemetry; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; -@ApiModel +@Schema public class TsData implements Comparable{ private final long ts; @@ -30,12 +29,12 @@ public class TsData implements Comparable{ this.value = value; } - @ApiModelProperty(position = 1, value = "Timestamp last updated timeseries, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp last updated timeseries, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) public long getTs() { return ts; } - @ApiModelProperty(position = 2, value = "Object representing value of timeseries key", example = "20", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Object representing value of timeseries key", example = "20", accessMode = Schema.AccessMode.READ_ONLY) public Object getValue() { return value; } diff --git a/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java b/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java deleted file mode 100644 index 6a222870b0..0000000000 --- a/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.springfox; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.stereotype.Component; -import org.springframework.util.ReflectionUtils; -import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; -import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider; - -import java.lang.reflect.Field; -import java.util.List; -import java.util.stream.Collectors; - -@Component -//TODO: remove after fixing issue https://github.com/springfox/springfox/issues/3462 or after migration from springfox to springdoc -public class SpringfoxHandlerProviderBeanPostProcessor implements BeanPostProcessor { - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof WebMvcRequestHandlerProvider) { - customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); - } - return bean; - } - - private void customizeSpringfoxHandlerMappings(List mappings) { - List copy = mappings.stream() - .filter(mapping -> mapping.getPatternParser() == null) - .collect(Collectors.toList()); - mappings.clear(); - mappings.addAll(copy); - } - - @SuppressWarnings("unchecked") - private List getHandlerMappings(Object bean) { - try { - Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); - field.setAccessible(true); - return (List) field.get(bean); - } catch (IllegalArgumentException | IllegalAccessException e) { - throw new IllegalStateException(e); - } - } -} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 6e68e1066f..7473121866 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -988,10 +988,12 @@ edges: state: persistToTelemetry: "${EDGES_PERSIST_STATE_TO_TELEMETRY:false}" +springdoc: + api-docs.enabled: "${SWAGGER_ENABLED:true}" + default-produces-media-type: "${SWAGGER_DEFAULT_PRODUCES_MEDIA_TYPE:application/json}" + swagger: - enabled: "${SWAGGER_ENABLED:true}" - api_path_regex: "${SWAGGER_API_PATH_REGEX:/api/.*}" - security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api/.*}" + api_path: "${SWAGGER_API_PATH:/api/**}" non_security_path_regex: "${SWAGGER_NON_SECURITY_PATH_REGEX:/api/(?:noauth|v1)/.*}" title: "${SWAGGER_TITLE:ThingsBoard REST API}" description: "${SWAGGER_DESCRIPTION: ThingsBoard open-source IoT platform REST API documentation.}" diff --git a/common/data/pom.xml b/common/data/pom.xml index cf528b9bbd..9aa6aed2c0 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -105,8 +105,8 @@ commons-codec - io.swagger - swagger-annotations + io.swagger.core.v3 + swagger-annotations-jakarta com.google.protobuf diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java index c1f296c26c..a70d235bd5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java @@ -16,14 +16,13 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.id.AdminSettingsId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -@ApiModel +@Schema public class AdminSettings extends BaseData implements HasTenantId { private static final long serialVersionUID = -7670322981725511892L; @@ -50,19 +49,19 @@ public class AdminSettings extends BaseData implements HasTenan this.jsonValue = adminSettings.getJsonValue(); } - @ApiModelProperty(position = 1, value = "The Id of the Administration Settings, auto-generated, UUID") + @Schema(description = "The Id of the Administration Settings, auto-generated, UUID") @Override public AdminSettingsId getId() { return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the settings creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the settings creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) public TenantId getTenantId() { return tenantId; } @@ -71,7 +70,7 @@ public class AdminSettings extends BaseData implements HasTenan this.tenantId = tenantId; } - @ApiModelProperty(position = 4, value = "The Administration Settings key, (e.g. 'general' or 'mail')", example = "mail") + @Schema(description = "The Administration Settings key, (e.g. 'general' or 'mail')", example = "mail") public String getKey() { return key; } @@ -80,7 +79,7 @@ public class AdminSettings extends BaseData implements HasTenan this.key = key; } - @ApiModelProperty(position = 5, value = "JSON representation of the Administration Settings value") + @Schema(description = "JSON representation of the Administration Settings value") public JsonNode getJsonValue() { return jsonValue; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java index 636ab0de38..6f9d6e4360 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java @@ -19,7 +19,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty.Access; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @@ -35,9 +36,9 @@ public class Customer extends ContactBased implements HasTenantId, E @NoXss @Length(fieldName = "title") - @ApiModelProperty(position = 3, value = "Title of the customer", example = "Company A") + @Schema(description = "Title of the customer", example = "Company A") private String title; - @ApiModelProperty(position = 5, required = true, value = "JSON object with Tenant Id") + @Schema(required = true, description = "JSON object with Tenant Id") private TenantId tenantId; @Getter @Setter @@ -74,7 +75,7 @@ public class Customer extends ContactBased implements HasTenantId, E this.title = title; } - @ApiModelProperty(position = 1, value = "JSON object with the customer Id. " + + @Schema(description = "JSON object with the customer Id. " + "Specify this field to update the customer. " + "Referencing non-existing customer Id will cause error. " + "Omit this field to create new customer." ) @@ -83,61 +84,61 @@ public class Customer extends ContactBased implements HasTenantId, E return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the customer creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the customer creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 6, required = true, value = "Country", example = "US") + @Schema(required = true, description = "Country", example = "US") @Override public String getCountry() { return super.getCountry(); } - @ApiModelProperty(position = 7, required = true, value = "State", example = "NY") + @Schema(required = true, description = "State", example = "NY") @Override public String getState() { return super.getState(); } - @ApiModelProperty(position = 8, required = true, value = "City", example = "New York") + @Schema(required = true, description = "City", example = "New York") @Override public String getCity() { return super.getCity(); } - @ApiModelProperty(position = 9, required = true, value = "Address Line 1", example = "42 Broadway Suite 12-400") + @Schema(required = true, description = "Address Line 1", example = "42 Broadway Suite 12-400") @Override public String getAddress() { return super.getAddress(); } - @ApiModelProperty(position = 10, required = true, value = "Address Line 2", example = "") + @Schema(required = true, description = "Address Line 2", example = "") @Override public String getAddress2() { return super.getAddress2(); } - @ApiModelProperty(position = 11, required = true, value = "Zip code", example = "10004") + @Schema(required = true, description = "Zip code", example = "10004") @Override public String getZip() { return super.getZip(); } - @ApiModelProperty(position = 12, required = true, value = "Phone number", example = "+1(415)777-7777") + @Schema(required = true, description = "Phone number", example = "+1(415)777-7777") @Override public String getPhone() { return super.getPhone(); } - @ApiModelProperty(position = 13, required = true, value = "Email", example = "example@company.com") + @Schema(required = true, description = "Email", example = "example@company.com") @Override public String getEmail() { return super.getEmail(); } - @ApiModelProperty(position = 14, value = "Additional parameters of the device", dataType = "com.fasterxml.jackson.databind.JsonNode") + @Schema(description = "Additional parameters of the device",implementation = com.fasterxml.jackson.databind.JsonNode.class) @Override public JsonNode getAdditionalInfo() { return super.getAdditionalInfo(); @@ -159,7 +160,7 @@ public class Customer extends ContactBased implements HasTenantId, E @Override @JsonProperty(access = Access.READ_ONLY) - @ApiModelProperty(position = 4, value = "Name of the customer. Read-only, duplicated from title for backward compatibility", example = "Company A", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Name of the customer. Read-only, duplicated from title for backward compatibility", example = "Company A", accessMode = Schema.AccessMode.READ_ONLY) public String getName() { return title; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java index 10496aaaa2..f0e09a1128 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java @@ -19,7 +19,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.Streams; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @@ -59,10 +59,10 @@ public class Dashboard extends DashboardInfo implements ExportableEntity implements HasName, HasTenantId, HasTitle { private TenantId tenantId; @@ -61,7 +60,7 @@ public class DashboardInfo extends BaseData implements HasName, Has this.mobileOrder = dashboardInfo.getMobileOrder(); } - @ApiModelProperty(position = 1, value = "JSON object with the dashboard Id. " + + @Schema(description = "JSON object with the dashboard Id. " + "Specify existing dashboard Id to update the dashboard. " + "Referencing non-existing dashboard id will cause error. " + "Omit this field to create new dashboard.") @@ -70,13 +69,13 @@ public class DashboardInfo extends BaseData implements HasName, Has return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the dashboard creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the dashboard creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the dashboard can't be changed.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id. Tenant Id of the dashboard can't be changed.", accessMode = Schema.AccessMode.READ_ONLY) public TenantId getTenantId() { return tenantId; } @@ -85,7 +84,7 @@ public class DashboardInfo extends BaseData implements HasName, Has this.tenantId = tenantId; } - @ApiModelProperty(position = 4, value = "Title of the dashboard.") + @Schema(description = "Title of the dashboard.") public String getTitle() { return title; } @@ -94,7 +93,7 @@ public class DashboardInfo extends BaseData implements HasName, Has this.title = title; } - @ApiModelProperty(position = 8, value = "Thumbnail picture for rendering of the dashboards in a grid view on mobile devices.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Thumbnail picture for rendering of the dashboards in a grid view on mobile devices.", accessMode = Schema.AccessMode.READ_ONLY) public String getImage() { return image; } @@ -103,7 +102,7 @@ public class DashboardInfo extends BaseData implements HasName, Has this.image = image; } - @ApiModelProperty(position = 5, value = "List of assigned customers with their info.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "List of assigned customers with their info.", accessMode = Schema.AccessMode.READ_ONLY) public Set getAssignedCustomers() { return assignedCustomers; } @@ -112,7 +111,7 @@ public class DashboardInfo extends BaseData implements HasName, Has this.assignedCustomers = assignedCustomers; } - @ApiModelProperty(position = 6, value = "Hide dashboard from mobile devices. Useful if the dashboard is not designed for small screens.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Hide dashboard from mobile devices. Useful if the dashboard is not designed for small screens.", accessMode = Schema.AccessMode.READ_ONLY) public boolean isMobileHide() { return mobileHide; } @@ -121,7 +120,7 @@ public class DashboardInfo extends BaseData implements HasName, Has this.mobileHide = mobileHide; } - @ApiModelProperty(position = 7, value = "Order on mobile devices. Useful to adjust sorting of the dashboards for mobile applications", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Order on mobile devices. Useful to adjust sorting of the dashboards for mobile applications", accessMode = Schema.AccessMode.READ_ONLY) public Integer getMobileOrder() { return mobileOrder; } @@ -179,7 +178,7 @@ public class DashboardInfo extends BaseData implements HasName, Has } } - @ApiModelProperty(position = 4, value = "Same as title of the dashboard. Read-only field. Update the 'title' to change the 'name' of the dashboard.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Same as title of the dashboard. Read-only field. Update the 'title' to change the 'name' of the dashboard.", accessMode = Schema.AccessMode.READ_ONLY) @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getName() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index 45e4244bcb..c8cb6384e2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -18,8 +18,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @@ -37,7 +36,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Optional; -@ApiModel +@Schema @EqualsAndHashCode(callSuper = true) @Slf4j public class Device extends BaseDataWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity { @@ -103,7 +102,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL return this; } - @ApiModelProperty(position = 1, value = "JSON object with the Device Id. " + + @Schema(description = "JSON object with the Device Id. " + "Specify this field to update the Device. " + "Referencing non-existing Device Id will cause error. " + "Omit this field to create new Device." ) @@ -112,13 +111,13 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the device creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the device creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Use 'assignDeviceToTenant' to change the Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id. Use 'assignDeviceToTenant' to change the Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) public TenantId getTenantId() { return tenantId; } @@ -127,7 +126,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL this.tenantId = tenantId; } - @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignDeviceToCustomer' to change the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Customer Id. Use 'assignDeviceToCustomer' to change the Customer Id.", accessMode = Schema.AccessMode.READ_ONLY) public CustomerId getCustomerId() { return customerId; } @@ -136,7 +135,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL this.customerId = customerId; } - @ApiModelProperty(position = 5, required = true, value = "Unique Device Name in scope of Tenant", example = "A4B72CCDFF33") + @Schema(required = true, description = "Unique Device Name in scope of Tenant", example = "A4B72CCDFF33") @Override public String getName() { return name; @@ -146,7 +145,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL this.name = name; } - @ApiModelProperty(position = 6, required = true, value = "Device Profile Name", example = "Temperature Sensor") + @Schema(required = true, description = "Device Profile Name", example = "Temperature Sensor") public String getType() { return type; } @@ -155,7 +154,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL this.type = type; } - @ApiModelProperty(position = 7, required = true, value = "Label that may be used in widgets", example = "Room 234 Sensor") + @Schema(required = true, description = "Label that may be used in widgets", example = "Room 234 Sensor") public String getLabel() { return label; } @@ -164,7 +163,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL this.label = label; } - @ApiModelProperty(position = 8, required = true, value = "JSON object with Device Profile Id.") + @Schema(required = true, description = "JSON object with Device Profile Id.") public DeviceProfileId getDeviceProfileId() { return deviceProfileId; } @@ -173,7 +172,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL this.deviceProfileId = deviceProfileId; } - @ApiModelProperty(position = 9, value = "JSON object with content specific to type of transport in the device profile.") + @Schema(description = "JSON object with content specific to type of transport in the device profile.") public DeviceData getDeviceData() { if (deviceData != null) { return deviceData; @@ -201,7 +200,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL } } - @ApiModelProperty(position = 10, value = "JSON object with Ota Package Id.") + @Schema(description = "JSON object with Ota Package Id.") public OtaPackageId getFirmwareId() { return firmwareId; } @@ -210,7 +209,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL this.firmwareId = firmwareId; } - @ApiModelProperty(position = 11, value = "JSON object with Ota Package Id.") + @Schema(description = "JSON object with Ota Package Id.") public OtaPackageId getSoftwareId() { return softwareId; } @@ -219,7 +218,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL this.softwareId = softwareId; } - @ApiModelProperty(position = 12, value = "Additional parameters of the device", dataType = "com.fasterxml.jackson.databind.JsonNode") + @Schema(description = "Additional parameters of the device",implementation = com.fasterxml.jackson.databind.JsonNode.class) @Override public JsonNode getAdditionalInfo() { return super.getAdditionalInfo(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java index 3204cc217a..80cc3a1d24 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java @@ -15,26 +15,25 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.DeviceId; -@ApiModel +@Schema @Data @EqualsAndHashCode(callSuper = true) public class DeviceInfo extends Device { private static final long serialVersionUID = -3004579925090663691L; - @ApiModelProperty(position = 13, value = "Title of the Customer that owns the device.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Title of the Customer that owns the device.", accessMode = Schema.AccessMode.READ_ONLY) private String customerTitle; - @ApiModelProperty(position = 14, value = "Indicates special 'Public' Customer that is auto-generated to use the devices on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Indicates special 'Public' Customer that is auto-generated to use the devices on public dashboards.", accessMode = Schema.AccessMode.READ_ONLY) private boolean customerIsPublic; - @ApiModelProperty(position = 15, value = "Name of the corresponding Device Profile.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Name of the corresponding Device Profile.", accessMode = Schema.AccessMode.READ_ONLY) private String deviceProfileName; - @ApiModelProperty(position = 16, value = "Device active flag.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Device active flag.", accessMode = Schema.AccessMode.READ_ONLY) private boolean active; public DeviceInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 2be110b258..090bafdb69 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -17,8 +17,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonProcessingException; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -36,7 +35,7 @@ import jakarta.validation.Valid; import java.io.ByteArrayInputStream; import java.io.IOException; -@ApiModel +@Schema @Data @ToString(exclude = {"image", "profileDataBytes"}) @EqualsAndHashCode(callSuper = true) @@ -45,34 +44,34 @@ public class DeviceProfile extends BaseData implements HasName, private static final long serialVersionUID = 6998485460273302018L; - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id that owns the profile.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id that owns the profile.", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; @NoXss @Length(fieldName = "name") - @ApiModelProperty(position = 4, value = "Unique Device Profile Name in scope of Tenant.", example = "Moisture Sensor") + @Schema(description = "Unique Device Profile Name in scope of Tenant.", example = "Moisture Sensor") private String name; @NoXss - @ApiModelProperty(position = 11, value = "Device Profile description. ") + @Schema(description = "Device Profile description. ") private String description; @Length(fieldName = "image", max = 1000000) - @ApiModelProperty(position = 12, value = "Either URL or Base64 data of the icon. Used in the mobile application to visualize set of device profiles in the grid view. ") + @Schema(description = "Either URL or Base64 data of the icon. Used in the mobile application to visualize set of device profiles in the grid view. ") private String image; private boolean isDefault; - @ApiModelProperty(position = 16, value = "Type of the profile. Always 'DEFAULT' for now. Reserved for future use.") + @Schema(description = "Type of the profile. Always 'DEFAULT' for now. Reserved for future use.") private DeviceProfileType type; - @ApiModelProperty(position = 14, value = "Type of the transport used to connect the device. Default transport supports HTTP, CoAP and MQTT.") + @Schema(description = "Type of the transport used to connect the device. Default transport supports HTTP, CoAP and MQTT.") private DeviceTransportType transportType; - @ApiModelProperty(position = 15, value = "Provisioning strategy.") + @Schema(description = "Provisioning strategy.") private DeviceProfileProvisionType provisionType; - @ApiModelProperty(position = 7, value = "Reference to the rule chain. " + + @Schema(description = "Reference to the rule chain. " + "If present, the specified rule chain will be used to process all messages related to device, including telemetry, attribute updates, etc. " + "Otherwise, the root rule chain will be used to process those messages.") private RuleChainId defaultRuleChainId; - @ApiModelProperty(position = 6, value = "Reference to the dashboard. Used in the mobile application to open the default dashboard when user navigates to device details.") + @Schema(description = "Reference to the dashboard. Used in the mobile application to open the default dashboard when user navigates to device details.") private DashboardId defaultDashboardId; @NoXss - @ApiModelProperty(position = 8, value = "Rule engine queue name. " + + @Schema(description = "Rule engine queue name. " + "If present, the specified queue will be used to store all unprocessed messages related to device, including telemetry, attribute updates, etc. " + "Otherwise, the 'Main' queue will be used to store those messages.") private String defaultQueueName; @@ -81,15 +80,15 @@ public class DeviceProfile extends BaseData implements HasName, @JsonIgnore private byte[] profileDataBytes; @NoXss - @ApiModelProperty(position = 13, value = "Unique provisioning key used by 'Device Provisioning' feature.") + @Schema(description = "Unique provisioning key used by 'Device Provisioning' feature.") private String provisionDeviceKey; - @ApiModelProperty(position = 9, value = "Reference to the firmware OTA package. If present, the specified package will be used as default device firmware. ") + @Schema(description = "Reference to the firmware OTA package. If present, the specified package will be used as default device firmware. ") private OtaPackageId firmwareId; - @ApiModelProperty(position = 10, value = "Reference to the software OTA package. If present, the specified package will be used as default device software. ") + @Schema(description = "Reference to the software OTA package. If present, the specified package will be used as default device software. ") private OtaPackageId softwareId; - @ApiModelProperty(position = 17, value = "Reference to the edge rule chain. " + + @Schema(description = "Reference to the edge rule chain. " + "If present, the specified edge rule chain will be used on the edge to process all messages related to device, including telemetry, attribute updates, etc. " + "Otherwise, the edge root rule chain will be used to process those messages.") private RuleChainId defaultEdgeRuleChainId; @@ -122,7 +121,7 @@ public class DeviceProfile extends BaseData implements HasName, this.externalId = deviceProfile.getExternalId(); } - @ApiModelProperty(position = 1, value = "JSON object with the device profile Id. " + + @Schema(description = "JSON object with the device profile Id. " + "Specify this field to update the device profile. " + "Referencing non-existing device profile Id will cause error. " + "Omit this field to create new device profile.") @@ -131,18 +130,18 @@ public class DeviceProfile extends BaseData implements HasName, return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the profile creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the profile creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 5, value = "Used to mark the default profile. Default profile is used when the device profile is not specified during device creation.") + @Schema(description = "Used to mark the default profile. Default profile is used when the device profile is not specified during device creation.") public boolean isDefault() { return isDefault; } - @ApiModelProperty(position = 16, value = "Complex JSON object that includes addition device profile configuration (transport, alarm rules, etc).") + @Schema(description = "Complex JSON object that includes addition device profile configuration (transport, alarm rules, etc).") public DeviceProfileData getProfileData() { if (profileData != null) { return profileData; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java index 295b9f6b99..93b094f901 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import lombok.ToString; import lombok.Value; @@ -33,16 +33,16 @@ import java.util.UUID; @ToString(callSuper = true, exclude = "image") public class DeviceProfileInfo extends EntityInfo { - @ApiModelProperty(position = 3, value = "Either URL or Base64 data of the icon. Used in the mobile application to visualize set of device profiles in the grid view. ") + @Schema(description = "Either URL or Base64 data of the icon. Used in the mobile application to visualize set of device profiles in the grid view. ") private final String image; - @ApiModelProperty(position = 4, value = "Reference to the dashboard. Used in the mobile application to open the default dashboard when user navigates to device details.") + @Schema(description = "Reference to the dashboard. Used in the mobile application to open the default dashboard when user navigates to device details.") private final DashboardId defaultDashboardId; - @ApiModelProperty(position = 5, value = "Type of the profile. Always 'DEFAULT' for now. Reserved for future use.") + @Schema(description = "Type of the profile. Always 'DEFAULT' for now. Reserved for future use.") private final DeviceProfileType type; - @ApiModelProperty(position = 6, value = "Type of the transport used to connect the device. Default transport supports HTTP, CoAP and MQTT.") + @Schema(description = "Type of the transport used to connect the device. Default transport supports HTTP, CoAP and MQTT.") private final DeviceTransportType transportType; - @ApiModelProperty(position = 7, value = "Tenant id.") + @Schema(description = "Tenant id.") private final TenantId tenantId; @JsonCreator diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityInfo.java index 6a86a65d1f..d3109c68d6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityInfo.java @@ -17,8 +17,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -26,13 +25,13 @@ import org.thingsboard.server.common.data.id.HasId; import java.util.UUID; -@ApiModel +@Schema @Data public class EntityInfo implements HasId, HasName { - @ApiModelProperty(position = 1, value = "JSON object with the entity Id. ") + @Schema(description = "JSON object with the entity Id. ") private final EntityId id; - @ApiModelProperty(position = 2, value = "Entity Name") + @Schema(description = "Entity Name") private final String name; @JsonCreator diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java index c0e44baeb3..72c7346c9e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; @@ -40,23 +40,23 @@ public class EntityView extends BaseDataWithAdditionalInfo private static final long serialVersionUID = 5582010124562018986L; - @ApiModelProperty(position = 7, required = true, value = "JSON object with the referenced Entity Id (Device or Asset).") + @Schema(required = true, description = "JSON object with the referenced Entity Id (Device or Asset).") private EntityId entityId; private TenantId tenantId; private CustomerId customerId; @NoXss @Length(fieldName = "name") - @ApiModelProperty(position = 5, required = true, value = "Entity View name", example = "A4B72CCDFF33") + @Schema(required = true, description = "Entity View name", example = "A4B72CCDFF33") private String name; @NoXss @Length(fieldName = "type") - @ApiModelProperty(position = 6, required = true, value = "Device Profile Name", example = "Temperature Sensor") + @Schema(required = true, description = "Device Profile Name", example = "Temperature Sensor") private String type; - @ApiModelProperty(position = 8, required = true, value = "Set of telemetry and attribute keys to expose via Entity View.") + @Schema(required = true, description = "Set of telemetry and attribute keys to expose via Entity View.") private TelemetryEntityView keys; - @ApiModelProperty(position = 9, value = "Represents the start time of the interval that is used to limit access to target device telemetry. Customer will not be able to see entity telemetry that is outside the specified interval;") + @Schema(description = "Represents the start time of the interval that is used to limit access to target device telemetry. Customer will not be able to see entity telemetry that is outside the specified interval;") private long startTimeMs; - @ApiModelProperty(position = 10, value = "Represents the end time of the interval that is used to limit access to target device telemetry. Customer will not be able to see entity telemetry that is outside the specified interval;") + @Schema(description = "Represents the end time of the interval that is used to limit access to target device telemetry. Customer will not be able to see entity telemetry that is outside the specified interval;") private long endTimeMs; private EntityViewId externalId; @@ -82,7 +82,7 @@ public class EntityView extends BaseDataWithAdditionalInfo this.externalId = entityView.getExternalId(); } - @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignEntityViewToCustomer' to change the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Customer Id. Use 'assignEntityViewToCustomer' to change the Customer Id.", accessMode = Schema.AccessMode.READ_ONLY) @Override public CustomerId getCustomerId() { return customerId; @@ -93,13 +93,13 @@ public class EntityView extends BaseDataWithAdditionalInfo return name; } - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) @Override public TenantId getTenantId() { return tenantId; } - @ApiModelProperty(position = 1, value = "JSON object with the Entity View Id. " + + @Schema(description = "JSON object with the Entity View Id. " + "Specify this field to update the Entity View. " + "Referencing non-existing Entity View Id will cause error. " + "Omit this field to create new Entity View." ) @@ -108,13 +108,13 @@ public class EntityView extends BaseDataWithAdditionalInfo return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the Entity View creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the Entity View creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 11, value = "Additional parameters of the device", dataType = "com.fasterxml.jackson.databind.JsonNode") + @Schema(description = "Additional parameters of the device",implementation = com.fasterxml.jackson.databind.JsonNode.class) @Override public JsonNode getAdditionalInfo() { return super.getAdditionalInfo(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java index 069ea3ae5d..ca3d26087f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.EntityViewId; @@ -24,9 +24,9 @@ import org.thingsboard.server.common.data.id.EntityViewId; @EqualsAndHashCode(callSuper = true) public class EntityViewInfo extends EntityView { - @ApiModelProperty(position = 12, value = "Title of the Customer that owns the entity view.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Title of the Customer that owns the entity view.", accessMode = Schema.AccessMode.READ_ONLY) private String customerTitle; - @ApiModelProperty(position = 13, value = "Indicates special 'Public' Customer that is auto-generated to use the entity view on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Indicates special 'Public' Customer that is auto-generated to use the entity view on public dashboards.", accessMode = Schema.AccessMode.READ_ONLY) private boolean customerIsPublic; public EntityViewInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EventInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/EventInfo.java index 11491db4f9..d20438a5b3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EventInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EventInfo.java @@ -16,8 +16,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EventId; @@ -27,18 +26,18 @@ import org.thingsboard.server.common.data.id.TenantId; * @author Andrew Shvayka */ @Data -@ApiModel +@Schema public class EventInfo extends BaseData { - @ApiModelProperty(position = 1, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; - @ApiModelProperty(position = 2, value = "Event type", example = "STATS") + @Schema(description = "Event type", example = "STATS") private String type; - @ApiModelProperty(position = 3, value = "string", example = "784f394c-42b6-435a-983c-b7beff2784f9") + @Schema(description = "string", example = "784f394c-42b6-435a-983c-b7beff2784f9") private String uid; - @ApiModelProperty(position = 4, value = "JSON object with Entity Id for which event is created.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Entity Id for which event is created.", accessMode = Schema.AccessMode.READ_ONLY) private EntityId entityId; - @ApiModelProperty(position = 5, value = "Event body.", dataType = "com.fasterxml.jackson.databind.JsonNode") + @Schema(description = "Event body.",implementation = com.fasterxml.jackson.databind.JsonNode.class) private transient JsonNode body; public EventInfo() { @@ -53,7 +52,7 @@ public class EventInfo extends BaseData { super(event); } - @ApiModelProperty(position = 6, value = "Timestamp of the event creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the event creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java b/common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java index 4677cff29b..a2b21d2a86 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.TenantId; @@ -24,7 +24,7 @@ public interface ExportableEntity extends HasId, HasName void setId(I id); - @ApiModelProperty(position = 100, value = "JSON object with External Id from the VCS", accessMode = ApiModelProperty.AccessMode.READ_ONLY, hidden = true) + @Schema(description = "JSON object with External Id from the VCS", accessMode = Schema.AccessMode.READ_ONLY, hidden = true) I getExternalId(); void setExternalId(I externalId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java index 6dfbfaa4ed..8971eb9f78 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java @@ -15,17 +15,16 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@ApiModel +@Schema @Data public class HomeDashboard extends Dashboard { public static final String HIDE_DASHBOARD_TOOLBAR_DESCRIPTION = "Hide dashboard toolbar flag. Useful for rendering dashboards on mobile."; - @ApiModelProperty(position = 10, value = HIDE_DASHBOARD_TOOLBAR_DESCRIPTION) + @Schema(description = HIDE_DASHBOARD_TOOLBAR_DESCRIPTION) private boolean hideDashboardToolbar; public HomeDashboard(Dashboard dashboard, boolean hideDashboardToolbar) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java index ae694fd61a..173b88259c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java @@ -15,18 +15,17 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; import org.thingsboard.server.common.data.id.DashboardId; -@ApiModel +@Schema @Data @AllArgsConstructor public class HomeDashboardInfo { - @ApiModelProperty(position = 1, value = "JSON object with the dashboard Id.") + @Schema(description = "JSON object with the dashboard Id.") private DashboardId dashboardId; - @ApiModelProperty(position = 1, value = HomeDashboard.HIDE_DASHBOARD_TOOLBAR_DESCRIPTION) + @Schema(description = HomeDashboard.HIDE_DASHBOARD_TOOLBAR_DESCRIPTION) private boolean hideDashboardToolbar; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java index d3aaddf3af..d7f4f20e02 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java @@ -15,22 +15,21 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.OtaPackageId; import java.nio.ByteBuffer; -@ApiModel +@Schema @Data @EqualsAndHashCode(callSuper = true) public class OtaPackage extends OtaPackageInfo { private static final long serialVersionUID = 3091601761339422546L; - @ApiModelProperty(position = 16, value = "OTA Package data.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "OTA Package data.", accessMode = Schema.AccessMode.READ_ONLY) private transient ByteBuffer data; public OtaPackage() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java index ab84724117..aa43567adc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java @@ -17,8 +17,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; @@ -30,7 +29,7 @@ import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -@ApiModel +@Schema @Slf4j @Data @EqualsAndHashCode(callSuper = true) @@ -38,44 +37,44 @@ public class OtaPackageInfo extends BaseDataWithAdditionalInfo imp private static final long serialVersionUID = 3168391583570815419L; - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the ota package can't be changed.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id. Tenant Id of the ota package can't be changed.", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; - @ApiModelProperty(position = 4, value = "JSON object with Device Profile Id. Device Profile Id of the ota package can't be changed.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Device Profile Id. Device Profile Id of the ota package can't be changed.", accessMode = Schema.AccessMode.READ_ONLY) private DeviceProfileId deviceProfileId; - @ApiModelProperty(position = 5, value = "OTA Package type.", example = "FIRMWARE", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "OTA Package type.", example = "FIRMWARE", accessMode = Schema.AccessMode.READ_ONLY) private OtaPackageType type; @Length(fieldName = "title") @NoXss - @ApiModelProperty(position = 6, value = "OTA Package title.", example = "fw", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "OTA Package title.", example = "fw", accessMode = Schema.AccessMode.READ_ONLY) private String title; @Length(fieldName = "version") @NoXss - @ApiModelProperty(position = 7, value = "OTA Package version.", example = "1.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "OTA Package version.", example = "1.0", accessMode = Schema.AccessMode.READ_ONLY) private String version; @Length(fieldName = "tag") @NoXss - @ApiModelProperty(position = 8, value = "OTA Package tag.", example = "fw_1.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "OTA Package tag.", example = "fw_1.0", accessMode = Schema.AccessMode.READ_ONLY) private String tag; @Length(fieldName = "url") @NoXss - @ApiModelProperty(position = 9, value = "OTA Package url.", example = "http://thingsboard.org/fw/1", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "OTA Package url.", example = "http://thingsboard.org/fw/1", accessMode = Schema.AccessMode.READ_ONLY) private String url; - @ApiModelProperty(position = 10, value = "Indicates OTA Package 'has data'. Field is returned from DB ('true' if data exists or url is set). If OTA Package 'has data' is 'false' we can not assign the OTA Package to the Device or Device Profile.", example = "true", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Indicates OTA Package 'has data'. Field is returned from DB ('true' if data exists or url is set). If OTA Package 'has data' is 'false' we can not assign the OTA Package to the Device or Device Profile.", example = "true", accessMode = Schema.AccessMode.READ_ONLY) private boolean hasData; @Length(fieldName = "file name") @NoXss - @ApiModelProperty(position = 11, value = "OTA Package file name.", example = "fw_1.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "OTA Package file name.", example = "fw_1.0", accessMode = Schema.AccessMode.READ_ONLY) private String fileName; @NoXss @Length(fieldName = "contentType") - @ApiModelProperty(position = 12, value = "OTA Package content type.", example = "APPLICATION_OCTET_STREAM", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "OTA Package content type.", example = "APPLICATION_OCTET_STREAM", accessMode = Schema.AccessMode.READ_ONLY) private String contentType; - @ApiModelProperty(position = 13, value = "OTA Package checksum algorithm.", example = "CRC32", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "OTA Package checksum algorithm.", example = "CRC32", accessMode = Schema.AccessMode.READ_ONLY) private ChecksumAlgorithm checksumAlgorithm; @Length(fieldName = "checksum", max = 1020) - @ApiModelProperty(position = 14, value = "OTA Package checksum.", example = "0xd87f7e0c", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "OTA Package checksum.", example = "0xd87f7e0c", accessMode = Schema.AccessMode.READ_ONLY) private String checksum; - @ApiModelProperty(position = 15, value = "OTA Package data size.", example = "8", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "OTA Package data size.", example = "8", accessMode = Schema.AccessMode.READ_ONLY) private Long dataSize; public OtaPackageInfo() { @@ -103,7 +102,7 @@ public class OtaPackageInfo extends BaseDataWithAdditionalInfo imp this.dataSize = otaPackageInfo.getDataSize(); } - @ApiModelProperty(position = 1, value = "JSON object with the ota package Id. " + + @Schema(description = "JSON object with the ota package Id. " + "Specify existing ota package Id to update the ota package. " + "Referencing non-existing ota package id will cause error. " + "Omit this field to create new ota package.") @@ -112,7 +111,7 @@ public class OtaPackageInfo extends BaseDataWithAdditionalInfo imp return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the ota package creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the ota package creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); @@ -129,7 +128,7 @@ public class OtaPackageInfo extends BaseDataWithAdditionalInfo imp return StringUtils.isNotEmpty(url); } - @ApiModelProperty(position = 17, value = "OTA Package description.", example = "Description for the OTA Package fw_1.0") + @Schema(description = "OTA Package description.", example = "Description for the OTA Package fw_1.0") @Override public JsonNode getAdditionalInfo() { return super.getAdditionalInfo(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SaveDeviceWithCredentialsRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/SaveDeviceWithCredentialsRequest.java index f8c74a5155..7714be385b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/SaveDeviceWithCredentialsRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SaveDeviceWithCredentialsRequest.java @@ -15,18 +15,17 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.security.DeviceCredentials; -@ApiModel +@Schema @Data public class SaveDeviceWithCredentialsRequest { - @ApiModelProperty(position = 1, value = "The JSON with device entity.", required = true) + @Schema(description = "The JSON with device entity.", required = true) private final Device device; - @ApiModelProperty(position = 2, value = "The JSON with credentials entity.", required = true) + @Schema(description = "The JSON with credentials entity.", required = true) private final DeviceCredentials credentials; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SaveOtaPackageInfoRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/SaveOtaPackageInfoRequest.java index 8a54de65fa..4c85c44d94 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/SaveOtaPackageInfoRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SaveOtaPackageInfoRequest.java @@ -15,18 +15,17 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -@ApiModel +@Schema @Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor public class SaveOtaPackageInfoRequest extends OtaPackageInfo { - @ApiModelProperty(position = 16, value = "Indicates OTA Package uses url. Should be 'true' if uses url or 'false' if will be used data.", example = "true", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Indicates OTA Package uses url. Should be 'true' if uses url or 'false' if will be used data.", example = "true", accessMode = Schema.AccessMode.READ_ONLY) boolean usesUrl; public SaveOtaPackageInfoRequest(OtaPackageInfo otaPackageInfo, boolean usesUrl) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java index 237a4d9a22..180180f569 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @@ -27,20 +26,20 @@ import org.thingsboard.server.common.data.validation.NoXss; * Created by igor on 2/27/18. */ -@ApiModel +@Schema @AllArgsConstructor public class ShortCustomerInfo { - @ApiModelProperty(position = 1, value = "JSON object with the customer Id.") + @Schema(description = "JSON object with the customer Id.") @Getter @Setter private CustomerId customerId; - @ApiModelProperty(position = 2, value = "Title of the customer.") + @Schema(description = "Title of the customer.") @Getter @Setter @NoXss private String title; - @ApiModelProperty(position = 3, value = "Indicates special 'Public' customer used to embed dashboards on public websites.") + @Schema(description = "Indicates special 'Public' customer used to embed dashboards on public websites.") @Getter @Setter private boolean isPublic; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfo.java index ea46c91239..93ff39ef7a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfo.java @@ -15,15 +15,15 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; @Data public class SystemInfo { - @ApiModelProperty(position = 1, value = "Is monolith.") + @Schema(description = "Is monolith.") private boolean isMonolith; - @ApiModelProperty(position = 2, value = "System data.") + @Schema(description = "System data.") private List systemData; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java b/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java index 5ae53565cf..1db6a5002a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfoData.java @@ -15,26 +15,26 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data public class SystemInfoData { - @ApiModelProperty(position = 1, value = "Service Id.") + @Schema(description = "Service Id.") private String serviceId; - @ApiModelProperty(position = 2, value = "Service type.") + @Schema(description = "Service type.") private String serviceType; - @ApiModelProperty(position = 3, value = "CPU usage, in percent.") + @Schema(description = "CPU usage, in percent.") private Long cpuUsage; - @ApiModelProperty(position = 4, value = "Total CPU usage.") + @Schema(description = "Total CPU usage.") private Long cpuCount; - @ApiModelProperty(position = 5, value = "Memory usage, in percent.") + @Schema(description = "Memory usage, in percent.") private Long memoryUsage; - @ApiModelProperty(position = 6, value = "Total memory in bytes.") + @Schema(description = "Total memory in bytes.") private Long totalMemory; - @ApiModelProperty(position = 7, value = "Disk usage, in percent.") + @Schema(description = "Disk usage, in percent.") private Long discUsage; - @ApiModelProperty(position = 8, value = "Total disc space in bytes.") + @Schema(description = "Total disc space in bytes.") private Long totalDiscSpace; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java index 91bb7ba383..c6aa878948 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; @@ -32,10 +32,10 @@ public class TbResource extends TbResourceInfo { @NoXss @Length(fieldName = "file name") - @ApiModelProperty(position = 8, value = "Resource file name.", example = "19.xml", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Resource file name.", example = "19.xml", accessMode = Schema.AccessMode.READ_ONLY) private String fileName; - @ApiModelProperty(position = 9, value = "Resource data.", example = "77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCEtLQpGSUxFIElORk9STUFUSU9OCgpPTUEgUGVybWFuZW50IERvY3VtZW50CiAgIEZpbGU6IE9NQS1TVVAtTHdNMk1fQmluYXJ5QXBwRGF0YUNvbnRhaW5lci1WMV8wXzEtMjAxOTAyMjEtQQogICBUeXBlOiB4bWwKClB1YmxpYyBSZWFjaGFibGUgSW5mb3JtYXRpb24KICAgUGF0aDogaHR0cDovL3d3dy5vcGVubW9iaWxlYWxsaWFuY2Uub3JnL3RlY2gvcHJvZmlsZXMKICAgTmFtZTogTHdNMk1fQmluYXJ5QXBwRGF0YUNvbnRhaW5lci12MV8wXzEueG1sCgpOT1JNQVRJVkUgSU5GT1JNQVRJT04KCiAgSW5mb3JtYXRpb24gYWJvdXQgdGhpcyBmaWxlIGNhbiBiZSBmb3VuZCBpbiB0aGUgbGF0ZXN0IHJldmlzaW9uIG9mCgogIE9NQS1UUy1MV00yTV9CaW5hcnlBcHBEYXRhQ29udGFpbmVyLVYxXzBfMQoKICBUaGlzIGlzIGF2YWlsYWJsZSBhdCBodHRwOi8vd3d3Lm9wZW5tb2JpbGVhbGxpYW5jZS5vcmcvCgogIFNlbmQgY29tbWVudHMgdG8gaHR0cHM6Ly9naXRodWIuY29tL09wZW5Nb2JpbGVBbGxpYW5jZS9PTUFfTHdNMk1fZm9yX0RldmVsb3BlcnMvaXNzdWVzCgpDSEFOR0UgSElTVE9SWQoKMTUwNjIwMTggU3RhdHVzIGNoYW5nZWQgdG8gQXBwcm92ZWQgYnkgRE0sIERvYyBSZWYgIyBPTUEtRE0mU0UtMjAxOC0wMDYxLUlOUF9MV00yTV9BUFBEQVRBX1YxXzBfRVJQX2Zvcl9maW5hbF9BcHByb3ZhbAoyMTAyMjAxOSBTdGF0dXMgY2hhbmdlZCB0byBBcHByb3ZlZCBieSBJUFNPLCBEb2MgUmVmICMgT01BLUlQU08tMjAxOS0wMDI1LUlOUF9Md00yTV9PYmplY3RfQXBwX0RhdGFfQ29udGFpbmVyXzFfMF8xX2Zvcl9GaW5hbF9BcHByb3ZhbAoKTEVHQUwgRElTQ0xBSU1FUgoKQ29weXJpZ2h0IDIwMTkgT3BlbiBNb2JpbGUgQWxsaWFuY2UuCgpSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zCmFyZSBtZXQ6CgoxLiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodApub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCjIuIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJpZ2h0Cm5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lciBpbiB0aGUKZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KMy4gTmVpdGhlciB0aGUgbmFtZSBvZiB0aGUgY29weXJpZ2h0IGhvbGRlciBub3IgdGhlIG5hbWVzIG9mIGl0cwpjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQKZnJvbSB0aGlzIHNvZnR3YXJlIHdpdGhvdXQgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLgoKVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwoiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVApMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUwpGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpDT1BZUklHSFQgSE9MREVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SIEFOWSBESVJFQ1QsIElORElSRUNULApJTkNJREVOVEFMLCBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLApCVVQgTk9UIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1IgU0VSVklDRVM7CkxPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJTlRFUlJVUFRJT04pIEhPV0VWRVIKQ0FVU0VEIEFORCBPTiBBTlkgVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUCkxJQUJJTElUWSwgT1IgVE9SVCAoSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOCkFOWSBXQVkgT1VUIE9GIFRIRSBVU0UgT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRQpQT1NTSUJJTElUWSBPRiBTVUNIIERBTUFHRS4KClRoZSBhYm92ZSBsaWNlbnNlIGlzIHVzZWQgYXMgYSBsaWNlbnNlIHVuZGVyIGNvcHlyaWdodCBvbmx5LiBQbGVhc2UKcmVmZXJlbmNlIHRoZSBPTUEgSVBSIFBvbGljeSBmb3IgcGF0ZW50IGxpY2Vuc2luZyB0ZXJtczoKaHR0cHM6Ly93d3cub21hc3BlY3dvcmtzLm9yZy9hYm91dC9pbnRlbGxlY3R1YWwtcHJvcGVydHktcmlnaHRzLwoKLS0+CjxMV00yTSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6bm9OYW1lc3BhY2VTY2hlbWFMb2NhdGlvbj0iaHR0cDovL29wZW5tb2JpbGVhbGxpYW5jZS5vcmcvdGVjaC9wcm9maWxlcy9MV00yTS54c2QiPgoJPE9iamVjdCBPYmplY3RUeXBlPSJNT0RlZmluaXRpb24iPgoJCTxOYW1lPkJpbmFyeUFwcERhdGFDb250YWluZXI8L05hbWU+CgkJPERlc2NyaXB0aW9uMT48IVtDREFUQVtUaGlzIEx3TTJNIE9iamVjdHMgcHJvdmlkZXMgdGhlIGFwcGxpY2F0aW9uIHNlcnZpY2UgZGF0YSByZWxhdGVkIHRvIGEgTHdNMk0gU2VydmVyLCBlZy4gV2F0ZXIgbWV0ZXIgZGF0YS4gClRoZXJlIGFyZSBzZXZlcmFsIG1ldGhvZHMgdG8gY3JlYXRlIGluc3RhbmNlIHRvIGluZGljYXRlIHRoZSBtZXNzYWdlIGRpcmVjdGlvbiBiYXNlZCBvbiB0aGUgbmVnb3RpYXRpb24gYmV0d2VlbiBBcHBsaWNhdGlvbiBhbmQgTHdNMk0uIFRoZSBDbGllbnQgYW5kIFNlcnZlciBzaG91bGQgbmVnb3RpYXRlIHRoZSBpbnN0YW5jZShzKSB1c2VkIHRvIGV4Y2hhbmdlIHRoZSBkYXRhLiBGb3IgZXhhbXBsZToKIC0gVXNpbmcgYSBzaW5nbGUgaW5zdGFuY2UgZm9yIGJvdGggZGlyZWN0aW9ucyBjb21tdW5pY2F0aW9uLCBmcm9tIENsaWVudCB0byBTZXJ2ZXIgYW5kIGZyb20gU2VydmVyIHRvIENsaWVudC4KIC0gVXNpbmcgYW4gaW5zdGFuY2UgZm9yIGNvbW11bmljYXRpb24gZnJvbSBDbGllbnQgdG8gU2VydmVyIGFuZCBhbm90aGVyIG9uZSBmb3IgY29tbXVuaWNhdGlvbiBmcm9tIFNlcnZlciB0byBDbGllbnQKIC0gVXNpbmcgc2V2ZXJhbCBpbnN0YW5jZXMKXV0+PC9EZXNjcmlwdGlvbjE+CgkJPE9iamVjdElEPjE5PC9PYmplY3RJRD4KCQk8T2JqZWN0VVJOPnVybjpvbWE6bHdtMm06b21hOjE5PC9PYmplY3RVUk4+CgkJPExXTTJNVmVyc2lvbj4xLjA8L0xXTTJNVmVyc2lvbj4KCQk8T2JqZWN0VmVyc2lvbj4xLjA8L09iamVjdFZlcnNpb24+CgkJPE11bHRpcGxlSW5zdGFuY2VzPk11bHRpcGxlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQk8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CgkJPFJlc291cmNlcz4KCQkJPEl0ZW0gSUQ9IjAiPjxOYW1lPkRhdGE8L05hbWU+CgkJCQk8T3BlcmF0aW9ucz5SVzwvT3BlcmF0aW9ucz4KCQkJCTxNdWx0aXBsZUluc3RhbmNlcz5NdWx0aXBsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CgkJCQk8TWFuZGF0b3J5Pk1hbmRhdG9yeTwvTWFuZGF0b3J5PgoJCQkJPFR5cGU+T3BhcXVlPC9UeXBlPgoJCQkJPFJhbmdlRW51bWVyYXRpb24gLz4KCQkJCTxVbml0cyAvPgoJCQkJPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgYXBwbGljYXRpb24gZGF0YSBjb250ZW50Ll1dPjwvRGVzY3JpcHRpb24+CgkJCTwvSXRlbT4KCQkJPEl0ZW0gSUQ9IjEiPjxOYW1lPkRhdGEgUHJpb3JpdHk8L05hbWU+CgkJCQk8T3BlcmF0aW9ucz5SVzwvT3BlcmF0aW9ucz4KCQkJCTxNdWx0aXBsZUluc3RhbmNlcz5TaW5nbGU8L011bHRpcGxlSW5zdGFuY2VzPgoJCQkJPE1hbmRhdG9yeT5PcHRpb25hbDwvTWFuZGF0b3J5PgoJCQkJPFR5cGU+SW50ZWdlcjwvVHlwZT4KCQkJCTxSYW5nZUVudW1lcmF0aW9uPjEgYnl0ZXM8L1JhbmdlRW51bWVyYXRpb24+CgkJCQk8VW5pdHMgLz4KCQkJCTxEZXNjcmlwdGlvbj48IVtDREFUQVtJbmRpY2F0ZXMgdGhlIEFwcGxpY2F0aW9uIGRhdGEgcHJpb3JpdHk6CjA6SW1tZWRpYXRlCjE6QmVzdEVmZm9ydAoyOkxhdGVzdAozLTEwMDogUmVzZXJ2ZWQgZm9yIGZ1dHVyZSB1c2UuCjEwMS0yNTQ6IFByb3ByaWV0YXJ5IG1vZGUuXV0+PC9EZXNjcmlwdGlvbj4KCQkJPC9JdGVtPgoJCQk8SXRlbSBJRD0iMiI+PE5hbWU+RGF0YSBDcmVhdGlvbiBUaW1lPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPlRpbWU8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbiAvPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBEYXRhIGluc3RhbmNlIGNyZWF0aW9uIHRpbWVzdGFtcC5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+CgkJCTxJdGVtIElEPSIzIj48TmFtZT5EYXRhIERlc2NyaXB0aW9uPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPlN0cmluZzwvVHlwZT4KCQkJCTxSYW5nZUVudW1lcmF0aW9uPjMyIGJ5dGVzPC9SYW5nZUVudW1lcmF0aW9uPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBkYXRhIGRlc2NyaXB0aW9uLgplLmcuICJtZXRlciByZWFkaW5nIi5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+CgkJCTxJdGVtIElEPSI0Ij48TmFtZT5EYXRhIEZvcm1hdDwvTmFtZT4KCQkJCTxPcGVyYXRpb25zPlJXPC9PcGVyYXRpb25zPgoJCQkJPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CgkJCQk8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CgkJCQk8VHlwZT5TdHJpbmc8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbj4zMiBieXRlczwvUmFuZ2VFbnVtZXJhdGlvbj4KCQkJCTxVbml0cyAvPgoJCQkJPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgZm9ybWF0IG9mIHRoZSBBcHBsaWNhdGlvbiBEYXRhLgplLmcuIFlHLU1ldGVyLVdhdGVyLVJlYWRpbmcKVVRGOC1zdHJpbmcKXV0+PC9EZXNjcmlwdGlvbj4KCQkJPC9JdGVtPgoJCQk8SXRlbSBJRD0iNSI+PE5hbWU+QXBwIElEPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPkludGVnZXI8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbj4yIGJ5dGVzPC9SYW5nZUVudW1lcmF0aW9uPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBkZXN0aW5hdGlvbiBBcHBsaWNhdGlvbiBJRC5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+PC9SZXNvdXJjZXM+CgkJPERlc2NyaXB0aW9uMj48IVtDREFUQVtdXT48L0Rlc2NyaXB0aW9uMj4KCTwvT2JqZWN0Pgo8L0xXTTJNPgo=", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Resource data.", example = "77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCEtLQpGSUxFIElORk9STUFUSU9OCgpPTUEgUGVybWFuZW50IERvY3VtZW50CiAgIEZpbGU6IE9NQS1TVVAtTHdNMk1fQmluYXJ5QXBwRGF0YUNvbnRhaW5lci1WMV8wXzEtMjAxOTAyMjEtQQogICBUeXBlOiB4bWwKClB1YmxpYyBSZWFjaGFibGUgSW5mb3JtYXRpb24KICAgUGF0aDogaHR0cDovL3d3dy5vcGVubW9iaWxlYWxsaWFuY2Uub3JnL3RlY2gvcHJvZmlsZXMKICAgTmFtZTogTHdNMk1fQmluYXJ5QXBwRGF0YUNvbnRhaW5lci12MV8wXzEueG1sCgpOT1JNQVRJVkUgSU5GT1JNQVRJT04KCiAgSW5mb3JtYXRpb24gYWJvdXQgdGhpcyBmaWxlIGNhbiBiZSBmb3VuZCBpbiB0aGUgbGF0ZXN0IHJldmlzaW9uIG9mCgogIE9NQS1UUy1MV00yTV9CaW5hcnlBcHBEYXRhQ29udGFpbmVyLVYxXzBfMQoKICBUaGlzIGlzIGF2YWlsYWJsZSBhdCBodHRwOi8vd3d3Lm9wZW5tb2JpbGVhbGxpYW5jZS5vcmcvCgogIFNlbmQgY29tbWVudHMgdG8gaHR0cHM6Ly9naXRodWIuY29tL09wZW5Nb2JpbGVBbGxpYW5jZS9PTUFfTHdNMk1fZm9yX0RldmVsb3BlcnMvaXNzdWVzCgpDSEFOR0UgSElTVE9SWQoKMTUwNjIwMTggU3RhdHVzIGNoYW5nZWQgdG8gQXBwcm92ZWQgYnkgRE0sIERvYyBSZWYgIyBPTUEtRE0mU0UtMjAxOC0wMDYxLUlOUF9MV00yTV9BUFBEQVRBX1YxXzBfRVJQX2Zvcl9maW5hbF9BcHByb3ZhbAoyMTAyMjAxOSBTdGF0dXMgY2hhbmdlZCB0byBBcHByb3ZlZCBieSBJUFNPLCBEb2MgUmVmICMgT01BLUlQU08tMjAxOS0wMDI1LUlOUF9Md00yTV9PYmplY3RfQXBwX0RhdGFfQ29udGFpbmVyXzFfMF8xX2Zvcl9GaW5hbF9BcHByb3ZhbAoKTEVHQUwgRElTQ0xBSU1FUgoKQ29weXJpZ2h0IDIwMTkgT3BlbiBNb2JpbGUgQWxsaWFuY2UuCgpSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zCmFyZSBtZXQ6CgoxLiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodApub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCjIuIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJpZ2h0Cm5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lciBpbiB0aGUKZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KMy4gTmVpdGhlciB0aGUgbmFtZSBvZiB0aGUgY29weXJpZ2h0IGhvbGRlciBub3IgdGhlIG5hbWVzIG9mIGl0cwpjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQKZnJvbSB0aGlzIHNvZnR3YXJlIHdpdGhvdXQgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLgoKVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwoiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVApMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUwpGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpDT1BZUklHSFQgSE9MREVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SIEFOWSBESVJFQ1QsIElORElSRUNULApJTkNJREVOVEFMLCBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLApCVVQgTk9UIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1IgU0VSVklDRVM7CkxPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJTlRFUlJVUFRJT04pIEhPV0VWRVIKQ0FVU0VEIEFORCBPTiBBTlkgVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUCkxJQUJJTElUWSwgT1IgVE9SVCAoSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOCkFOWSBXQVkgT1VUIE9GIFRIRSBVU0UgT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRQpQT1NTSUJJTElUWSBPRiBTVUNIIERBTUFHRS4KClRoZSBhYm92ZSBsaWNlbnNlIGlzIHVzZWQgYXMgYSBsaWNlbnNlIHVuZGVyIGNvcHlyaWdodCBvbmx5LiBQbGVhc2UKcmVmZXJlbmNlIHRoZSBPTUEgSVBSIFBvbGljeSBmb3IgcGF0ZW50IGxpY2Vuc2luZyB0ZXJtczoKaHR0cHM6Ly93d3cub21hc3BlY3dvcmtzLm9yZy9hYm91dC9pbnRlbGxlY3R1YWwtcHJvcGVydHktcmlnaHRzLwoKLS0+CjxMV00yTSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6bm9OYW1lc3BhY2VTY2hlbWFMb2NhdGlvbj0iaHR0cDovL29wZW5tb2JpbGVhbGxpYW5jZS5vcmcvdGVjaC9wcm9maWxlcy9MV00yTS54c2QiPgoJPE9iamVjdCBPYmplY3RUeXBlPSJNT0RlZmluaXRpb24iPgoJCTxOYW1lPkJpbmFyeUFwcERhdGFDb250YWluZXI8L05hbWU+CgkJPERlc2NyaXB0aW9uMT48IVtDREFUQVtUaGlzIEx3TTJNIE9iamVjdHMgcHJvdmlkZXMgdGhlIGFwcGxpY2F0aW9uIHNlcnZpY2UgZGF0YSByZWxhdGVkIHRvIGEgTHdNMk0gU2VydmVyLCBlZy4gV2F0ZXIgbWV0ZXIgZGF0YS4gClRoZXJlIGFyZSBzZXZlcmFsIG1ldGhvZHMgdG8gY3JlYXRlIGluc3RhbmNlIHRvIGluZGljYXRlIHRoZSBtZXNzYWdlIGRpcmVjdGlvbiBiYXNlZCBvbiB0aGUgbmVnb3RpYXRpb24gYmV0d2VlbiBBcHBsaWNhdGlvbiBhbmQgTHdNMk0uIFRoZSBDbGllbnQgYW5kIFNlcnZlciBzaG91bGQgbmVnb3RpYXRlIHRoZSBpbnN0YW5jZShzKSB1c2VkIHRvIGV4Y2hhbmdlIHRoZSBkYXRhLiBGb3IgZXhhbXBsZToKIC0gVXNpbmcgYSBzaW5nbGUgaW5zdGFuY2UgZm9yIGJvdGggZGlyZWN0aW9ucyBjb21tdW5pY2F0aW9uLCBmcm9tIENsaWVudCB0byBTZXJ2ZXIgYW5kIGZyb20gU2VydmVyIHRvIENsaWVudC4KIC0gVXNpbmcgYW4gaW5zdGFuY2UgZm9yIGNvbW11bmljYXRpb24gZnJvbSBDbGllbnQgdG8gU2VydmVyIGFuZCBhbm90aGVyIG9uZSBmb3IgY29tbXVuaWNhdGlvbiBmcm9tIFNlcnZlciB0byBDbGllbnQKIC0gVXNpbmcgc2V2ZXJhbCBpbnN0YW5jZXMKXV0+PC9EZXNjcmlwdGlvbjE+CgkJPE9iamVjdElEPjE5PC9PYmplY3RJRD4KCQk8T2JqZWN0VVJOPnVybjpvbWE6bHdtMm06b21hOjE5PC9PYmplY3RVUk4+CgkJPExXTTJNVmVyc2lvbj4xLjA8L0xXTTJNVmVyc2lvbj4KCQk8T2JqZWN0VmVyc2lvbj4xLjA8L09iamVjdFZlcnNpb24+CgkJPE11bHRpcGxlSW5zdGFuY2VzPk11bHRpcGxlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQk8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CgkJPFJlc291cmNlcz4KCQkJPEl0ZW0gSUQ9IjAiPjxOYW1lPkRhdGE8L05hbWU+CgkJCQk8T3BlcmF0aW9ucz5SVzwvT3BlcmF0aW9ucz4KCQkJCTxNdWx0aXBsZUluc3RhbmNlcz5NdWx0aXBsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CgkJCQk8TWFuZGF0b3J5Pk1hbmRhdG9yeTwvTWFuZGF0b3J5PgoJCQkJPFR5cGU+T3BhcXVlPC9UeXBlPgoJCQkJPFJhbmdlRW51bWVyYXRpb24gLz4KCQkJCTxVbml0cyAvPgoJCQkJPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgYXBwbGljYXRpb24gZGF0YSBjb250ZW50Ll1dPjwvRGVzY3JpcHRpb24+CgkJCTwvSXRlbT4KCQkJPEl0ZW0gSUQ9IjEiPjxOYW1lPkRhdGEgUHJpb3JpdHk8L05hbWU+CgkJCQk8T3BlcmF0aW9ucz5SVzwvT3BlcmF0aW9ucz4KCQkJCTxNdWx0aXBsZUluc3RhbmNlcz5TaW5nbGU8L011bHRpcGxlSW5zdGFuY2VzPgoJCQkJPE1hbmRhdG9yeT5PcHRpb25hbDwvTWFuZGF0b3J5PgoJCQkJPFR5cGU+SW50ZWdlcjwvVHlwZT4KCQkJCTxSYW5nZUVudW1lcmF0aW9uPjEgYnl0ZXM8L1JhbmdlRW51bWVyYXRpb24+CgkJCQk8VW5pdHMgLz4KCQkJCTxEZXNjcmlwdGlvbj48IVtDREFUQVtJbmRpY2F0ZXMgdGhlIEFwcGxpY2F0aW9uIGRhdGEgcHJpb3JpdHk6CjA6SW1tZWRpYXRlCjE6QmVzdEVmZm9ydAoyOkxhdGVzdAozLTEwMDogUmVzZXJ2ZWQgZm9yIGZ1dHVyZSB1c2UuCjEwMS0yNTQ6IFByb3ByaWV0YXJ5IG1vZGUuXV0+PC9EZXNjcmlwdGlvbj4KCQkJPC9JdGVtPgoJCQk8SXRlbSBJRD0iMiI+PE5hbWU+RGF0YSBDcmVhdGlvbiBUaW1lPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPlRpbWU8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbiAvPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBEYXRhIGluc3RhbmNlIGNyZWF0aW9uIHRpbWVzdGFtcC5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+CgkJCTxJdGVtIElEPSIzIj48TmFtZT5EYXRhIERlc2NyaXB0aW9uPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPlN0cmluZzwvVHlwZT4KCQkJCTxSYW5nZUVudW1lcmF0aW9uPjMyIGJ5dGVzPC9SYW5nZUVudW1lcmF0aW9uPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBkYXRhIGRlc2NyaXB0aW9uLgplLmcuICJtZXRlciByZWFkaW5nIi5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+CgkJCTxJdGVtIElEPSI0Ij48TmFtZT5EYXRhIEZvcm1hdDwvTmFtZT4KCQkJCTxPcGVyYXRpb25zPlJXPC9PcGVyYXRpb25zPgoJCQkJPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CgkJCQk8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CgkJCQk8VHlwZT5TdHJpbmc8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbj4zMiBieXRlczwvUmFuZ2VFbnVtZXJhdGlvbj4KCQkJCTxVbml0cyAvPgoJCQkJPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgZm9ybWF0IG9mIHRoZSBBcHBsaWNhdGlvbiBEYXRhLgplLmcuIFlHLU1ldGVyLVdhdGVyLVJlYWRpbmcKVVRGOC1zdHJpbmcKXV0+PC9EZXNjcmlwdGlvbj4KCQkJPC9JdGVtPgoJCQk8SXRlbSBJRD0iNSI+PE5hbWU+QXBwIElEPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPkludGVnZXI8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbj4yIGJ5dGVzPC9SYW5nZUVudW1lcmF0aW9uPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBkZXN0aW5hdGlvbiBBcHBsaWNhdGlvbiBJRC5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+PC9SZXNvdXJjZXM+CgkJPERlc2NyaXB0aW9uMj48IVtDREFUQVtdXT48L0Rlc2NyaXB0aW9uMj4KCTwvT2JqZWN0Pgo8L0xXTTJNPgo=", accessMode = Schema.AccessMode.READ_ONLY) private String data; public TbResource() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java index 331958ac58..f3e8f1bf8f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java @@ -16,8 +16,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonIgnore; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; @@ -26,7 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -@ApiModel +@Schema @Slf4j @Data @EqualsAndHashCode(callSuper = true) @@ -34,19 +33,19 @@ public class TbResourceInfo extends BaseData implements HasName, H private static final long serialVersionUID = 7282664529021651736L; - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the resource can't be changed.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id. Tenant Id of the resource can't be changed.", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; @NoXss @Length(fieldName = "title") - @ApiModelProperty(position = 4, value = "Resource title.", example = "BinaryAppDataContainer id=19 v1.0") + @Schema(description = "Resource title.", example = "BinaryAppDataContainer id=19 v1.0") private String title; - @ApiModelProperty(position = 5, value = "Resource type.", example = "LWM2M_MODEL", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Resource type.", example = "LWM2M_MODEL", accessMode = Schema.AccessMode.READ_ONLY) private ResourceType resourceType; @NoXss @Length(fieldName = "resourceKey") - @ApiModelProperty(position = 6, value = "Resource key.", example = "19_1.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Resource key.", example = "19_1.0", accessMode = Schema.AccessMode.READ_ONLY) private String resourceKey; - @ApiModelProperty(position = 7, value = "Resource search text.", example = "19_1.0:binaryappdatacontainer", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Resource search text.", example = "19_1.0:binaryappdatacontainer", accessMode = Schema.AccessMode.READ_ONLY) private String searchText; public TbResourceInfo() { @@ -66,7 +65,7 @@ public class TbResourceInfo extends BaseData implements HasName, H this.searchText = resourceInfo.getSearchText(); } - @ApiModelProperty(position = 1, value = "JSON object with the Resource Id. " + + @Schema(description = "JSON object with the Resource Id. " + "Specify this field to update the Resource. " + "Referencing non-existing Resource Id will cause error. " + "Omit this field to create new Resource." ) @@ -75,7 +74,7 @@ public class TbResourceInfo extends BaseData implements HasName, H return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the resource creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the resource creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java index 89cd9ec467..d01cc3aa41 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java @@ -18,15 +18,14 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -@ApiModel +@Schema @EqualsAndHashCode(callSuper = true) public class Tenant extends ContactBased implements HasTenantId, HasTitle { @@ -34,14 +33,14 @@ public class Tenant extends ContactBased implements HasTenantId, HasTi @Length(fieldName = "title") @NoXss - @ApiModelProperty(position = 3, value = "Title of the tenant", example = "Company A") + @Schema(description = "Title of the tenant", example = "Company A") private String title; @NoXss @Length(fieldName = "region") - @ApiModelProperty(position = 5, value = "Geo region of the tenant", example = "North America") + @Schema(description = "Geo region of the tenant", example = "North America") private String region; - @ApiModelProperty(position = 6, required = true, value = "JSON object with Tenant Profile Id") + @Schema(required = true, description = "JSON object with Tenant Profile Id") private TenantProfileId tenantProfileId; public Tenant() { @@ -74,7 +73,7 @@ public class Tenant extends ContactBased implements HasTenantId, HasTi } @Override - @ApiModelProperty(position = 4, value = "Name of the tenant. Read-only, duplicated from title for backward compatibility", example = "Company A", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Name of the tenant. Read-only, duplicated from title for backward compatibility", example = "Company A", accessMode = Schema.AccessMode.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getName() { return title; @@ -96,7 +95,7 @@ public class Tenant extends ContactBased implements HasTenantId, HasTi this.tenantProfileId = tenantProfileId; } - @ApiModelProperty(position = 1, value = "JSON object with the tenant Id. " + + @Schema(description = "JSON object with the tenant Id. " + "Specify this field to update the tenant. " + "Referencing non-existing tenant Id will cause error. " + "Omit this field to create new tenant." ) @@ -105,61 +104,61 @@ public class Tenant extends ContactBased implements HasTenantId, HasTi return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the tenant creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the tenant creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 7, required = true, value = "Country", example = "US") + @Schema(required = true, description = "Country", example = "US") @Override public String getCountry() { return super.getCountry(); } - @ApiModelProperty(position = 8, required = true, value = "State", example = "NY") + @Schema(required = true, description = "State", example = "NY") @Override public String getState() { return super.getState(); } - @ApiModelProperty(position = 9, required = true, value = "City", example = "New York") + @Schema(required = true, description = "City", example = "New York") @Override public String getCity() { return super.getCity(); } - @ApiModelProperty(position = 10, required = true, value = "Address Line 1", example = "42 Broadway Suite 12-400") + @Schema(required = true, description = "Address Line 1", example = "42 Broadway Suite 12-400") @Override public String getAddress() { return super.getAddress(); } - @ApiModelProperty(position = 11, required = true, value = "Address Line 2", example = "") + @Schema(required = true, description = "Address Line 2", example = "") @Override public String getAddress2() { return super.getAddress2(); } - @ApiModelProperty(position = 12, required = true, value = "Zip code", example = "10004") + @Schema(required = true, description = "Zip code", example = "10004") @Override public String getZip() { return super.getZip(); } - @ApiModelProperty(position = 13, required = true, value = "Phone number", example = "+1(415)777-7777") + @Schema(required = true, description = "Phone number", example = "+1(415)777-7777") @Override public String getPhone() { return super.getPhone(); } - @ApiModelProperty(position = 14, required = true, value = "Email", example = "example@company.com") + @Schema(required = true, description = "Email", example = "example@company.com") @Override public String getEmail() { return super.getEmail(); } - @ApiModelProperty(position = 15, value = "Additional parameters of the device", dataType = "com.fasterxml.jackson.databind.JsonNode") + @Schema(description = "Additional parameters of the device",implementation = com.fasterxml.jackson.databind.JsonNode.class) @Override public JsonNode getAdditionalInfo() { return super.getAdditionalInfo(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantInfo.java index e98e14d2d0..fd7ca7fc7f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TenantInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantInfo.java @@ -15,15 +15,14 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.id.TenantId; -@ApiModel +@Schema @Data public class TenantInfo extends Tenant { - @ApiModelProperty(position = 15, value = "Tenant Profile name", example = "Default") + @Schema(description = "Tenant Profile name", example = "Default") private String tenantProfileName; public TenantInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java index 5ce6c00fb8..d4b5f51e77 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java @@ -18,8 +18,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -34,7 +33,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Optional; -@ApiModel +@Schema @Data @ToString(exclude = {"profileDataBytes"}) @EqualsAndHashCode(callSuper = true) @@ -47,17 +46,17 @@ public class TenantProfile extends BaseData implements HasName @NoXss @Length(fieldName = "name") - @ApiModelProperty(position = 3, value = "Name of the tenant profile", example = "High Priority Tenants") + @Schema(description = "Name of the tenant profile", example = "High Priority Tenants") private String name; @NoXss - @ApiModelProperty(position = 4, value = "Description of the tenant profile", example = "Any text") + @Schema(description = "Description of the tenant profile", example = "Any text") private String description; - @ApiModelProperty(position = 5, value = "Default Tenant profile to be used.", example = "true") + @Schema(description = "Default Tenant profile to be used.", example = "true") private boolean isDefault; - @ApiModelProperty(position = 6, value = "If enabled, will push all messages related to this tenant and processed by the rule engine into separate queue. " + + @Schema(description = "If enabled, will push all messages related to this tenant and processed by the rule engine into separate queue. " + "Useful for complex microservices deployments, to isolate processing of the data for specific tenants", example = "true") private boolean isolatedTbRuleEngine; - @ApiModelProperty(position = 7, value = "Complex JSON object that contains profile settings: queue configs, max devices, max assets, rate limits, etc.") + @Schema(description = "Complex JSON object that contains profile settings: queue configs, max devices, max assets, rate limits, etc.") private transient TenantProfileData profileData; @JsonIgnore private byte[] profileDataBytes; @@ -79,7 +78,7 @@ public class TenantProfile extends BaseData implements HasName this.setProfileData(tenantProfile.getProfileData()); } - @ApiModelProperty(position = 1, value = "JSON object with the tenant profile Id. " + + @Schema(description = "JSON object with the tenant profile Id. " + "Specify this field to update the tenant profile. " + "Referencing non-existing tenant profile Id will cause error. " + "Omit this field to create new tenant profile.") @@ -88,7 +87,7 @@ public class TenantProfile extends BaseData implements HasName return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the tenant profile creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the tenant profile creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java b/common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java index 43210de9d0..522ef80c53 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java @@ -15,27 +15,26 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; -@ApiModel +@Schema @Data public class UpdateMessage implements Serializable { - @ApiModelProperty(position = 1, value = "'True' if new platform update is available.") + @Schema(description = "'True' if new platform update is available.") private final boolean updateAvailable; - @ApiModelProperty(position = 2, value = "Current ThingsBoard version.") + @Schema(description = "Current ThingsBoard version.") private final String currentVersion; - @ApiModelProperty(position = 3, value = "Latest ThingsBoard version.") + @Schema(description = "Latest ThingsBoard version.") private final String latestVersion; - @ApiModelProperty(position = 4, value = "Upgrade instructions URL.") + @Schema(description = "Upgrade instructions URL.") private final String upgradeInstructionsUrl; - @ApiModelProperty(position = 5, value = "Current ThingsBoard version release notes URL.") + @Schema(description = "Current ThingsBoard version release notes URL.") private final String currentVersionReleaseNotesUrl; - @ApiModelProperty(position = 6, value = "Latest ThingsBoard version release notes URL.") + @Schema(description = "Latest ThingsBoard version release notes URL.") private final String latestVersionReleaseNotesUrl; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java index b3d9268c6b..15bc41f22c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/User.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/User.java @@ -18,8 +18,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -32,7 +31,7 @@ import org.thingsboard.server.common.data.validation.NoXss; import static org.apache.commons.lang3.StringUtils.isNotEmpty; -@ApiModel +@Schema @EqualsAndHashCode(callSuper = true) public class User extends BaseDataWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId, NotificationRecipient { @@ -71,7 +70,7 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, } - @ApiModelProperty(position = 1, value = "JSON object with the User Id. " + + @Schema(description = "JSON object with the User Id. " + "Specify this field to update the device. " + "Referencing non-existing User Id will cause error. " + "Omit this field to create new customer.") @@ -80,13 +79,13 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the user creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the user creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "JSON object with the Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with the Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) public TenantId getTenantId() { return tenantId; } @@ -95,7 +94,7 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, this.tenantId = tenantId; } - @ApiModelProperty(position = 4, value = "JSON object with the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with the Customer Id.", accessMode = Schema.AccessMode.READ_ONLY) public CustomerId getCustomerId() { return customerId; } @@ -104,7 +103,7 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, this.customerId = customerId; } - @ApiModelProperty(position = 5, required = true, value = "Email of the user", example = "user@example.com") + @Schema(required = true, description = "Email of the user", example = "user@example.com") public String getEmail() { return email; } @@ -113,14 +112,14 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, this.email = email; } - @ApiModelProperty(position = 6, accessMode = ApiModelProperty.AccessMode.READ_ONLY, value = "Duplicates the email of the user, readonly", example = "user@example.com") + @Schema(accessMode = Schema.AccessMode.READ_ONLY, description = "Duplicates the email of the user, readonly", example = "user@example.com") @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getName() { return email; } - @ApiModelProperty(position = 7, required = true, value = "Authority", example = "SYS_ADMIN, TENANT_ADMIN or CUSTOMER_USER") + @Schema(required = true, description = "Authority", example = "SYS_ADMIN, TENANT_ADMIN or CUSTOMER_USER") public Authority getAuthority() { return authority; } @@ -129,7 +128,7 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, this.authority = authority; } - @ApiModelProperty(position = 8, required = true, value = "First name of the user", example = "John") + @Schema(required = true, description = "First name of the user", example = "John") public String getFirstName() { return firstName; } @@ -138,7 +137,7 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, this.firstName = firstName; } - @ApiModelProperty(position = 9, required = true, value = "Last name of the user", example = "Doe") + @Schema(required = true, description = "Last name of the user", example = "Doe") public String getLastName() { return lastName; } @@ -147,7 +146,7 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, this.lastName = lastName; } - @ApiModelProperty(position = 10, required = true, value = "Phone number of the user", example = "38012345123") + @Schema(required = true, description = "Phone number of the user", example = "38012345123") public String getPhone() { return phone; } @@ -156,7 +155,7 @@ public class User extends BaseDataWithAdditionalInfo implements HasName, this.phone = phone; } - @ApiModelProperty(position = 11, value = "Additional parameters of the user", dataType = "com.fasterxml.jackson.databind.JsonNode") + @Schema(description = "Additional parameters of the user", implementation = com.fasterxml.jackson.databind.JsonNode.class) @Override public JsonNode getAdditionalInfo() { return super.getAdditionalInfo(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/UserEmailInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/UserEmailInfo.java index f1eb95c805..644144efa5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/UserEmailInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/UserEmailInfo.java @@ -15,25 +15,24 @@ */ package org.thingsboard.server.common.data; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.UserId; -@ApiModel +@Schema @Data @AllArgsConstructor public class UserEmailInfo implements HasId { - @ApiModelProperty(position = 1, value = "User id") + @Schema(description = "User id") private UserId id; - @ApiModelProperty(position = 2, value = "User email", example = "john@gmail.com") + @Schema(description = "User email", example = "john@gmail.com") private String email; - @ApiModelProperty(position = 3, value = "User first name", example = "John") + @Schema(description = "User first name", example = "John") private String firstName; - @ApiModelProperty(position = 4, value = "User last name", example = "Brown") + @Schema(description = "User last name", example = "Brown") private String lastName; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index 34fec2cd82..222570e914 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java @@ -18,8 +18,7 @@ package org.thingsboard.server.common.data.alarm; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -41,7 +40,7 @@ import java.util.List; /** * Created by ashvayka on 11.05.17. */ -@ApiModel +@Schema @Data @EqualsAndHashCode(callSuper = true) @Builder @@ -49,45 +48,45 @@ import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) public class Alarm extends BaseData implements HasName, HasTenantId, HasCustomerId { - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; - @ApiModelProperty(position = 4, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Customer Id", accessMode = Schema.AccessMode.READ_ONLY) private CustomerId customerId; @NoXss - @ApiModelProperty(position = 6, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm") + @Schema(required = true, description = "representing type of the Alarm", example = "High Temperature Alarm") @Length(fieldName = "type") private String type; - @ApiModelProperty(position = 7, required = true, value = "JSON object with alarm originator id") + @Schema(required = true, description = "JSON object with alarm originator id") private EntityId originator; - @ApiModelProperty(position = 8, required = true, value = "Alarm severity", example = "CRITICAL") + @Schema(required = true, description = "Alarm severity", example = "CRITICAL") private AlarmSeverity severity; - @ApiModelProperty(position = 9, required = true, value = "Acknowledged", example = "true") + @Schema(required = true, description = "Acknowledged", example = "true") private boolean acknowledged; - @ApiModelProperty(position = 10, required = true, value = "Cleared", example = "false") + @Schema(required = true, description = "Cleared", example = "false") private boolean cleared; - @ApiModelProperty(position = 11, value = "Alarm assignee user id") + @Schema(description = "Alarm assignee user id") private UserId assigneeId; - @ApiModelProperty(position = 12, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565") + @Schema(description = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565") private long startTs; - @ApiModelProperty(position = 13, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") + @Schema(description = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") private long endTs; - @ApiModelProperty(position = 14, value = "Timestamp of the alarm acknowledgement, in milliseconds", example = "1634115221948") + @Schema(description = "Timestamp of the alarm acknowledgement, in milliseconds", example = "1634115221948") private long ackTs; - @ApiModelProperty(position = 15, value = "Timestamp of the alarm clearing, in milliseconds", example = "1634114528465") + @Schema(description = "Timestamp of the alarm clearing, in milliseconds", example = "1634114528465") private long clearTs; - @ApiModelProperty(position = 16, value = "Timestamp of the alarm assignment, in milliseconds", example = "1634115928465") + @Schema(description = "Timestamp of the alarm assignment, in milliseconds", example = "1634115928465") private long assignTs; - @ApiModelProperty(position = 17, value = "JSON object with alarm details") + @Schema(description = "JSON object with alarm details") private transient JsonNode details; - @ApiModelProperty(position = 18, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true") + @Schema(description = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true") private boolean propagate; - @ApiModelProperty(position = 19, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true") + @Schema(description = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true") private boolean propagateToOwner; - @ApiModelProperty(position = 20, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true") + @Schema(description = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true") private boolean propagateToTenant; - @ApiModelProperty(position = 21, value = "JSON array of relation types that should be used for propagation. " + + @Schema(description = "JSON array of relation types that should be used for propagation. " + "By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " + "This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.") private List propagateRelationTypes; @@ -125,12 +124,12 @@ public class Alarm extends BaseData implements HasName, HasTenantId, Ha @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) - @ApiModelProperty(position = 5, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm") + @Schema(required = true, description = "representing type of the Alarm", example = "High Temperature Alarm") public String getName() { return type; } - @ApiModelProperty(position = 1, value = "JSON object with the alarm Id. " + + @Schema(description = "JSON object with the alarm Id. " + "Specify this field to update the alarm. " + "Referencing non-existing alarm Id will cause error. " + "Omit this field to create new alarm.") @@ -140,14 +139,14 @@ public class Alarm extends BaseData implements HasName, HasTenantId, Ha } - @ApiModelProperty(position = 2, value = "Timestamp of the alarm creation, in milliseconds", example = "1634058704567", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the alarm creation, in milliseconds", example = "1634058704567", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } @JsonProperty(access = JsonProperty.Access.READ_ONLY) - @ApiModelProperty(position = 22, required = true, value = "status of the Alarm", example = "ACTIVE_UNACK", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(required = true, description = "status of the Alarm", example = "ACTIVE_UNACK", accessMode = Schema.AccessMode.READ_ONLY) public AlarmStatus getStatus() { return toStatus(cleared, acknowledged); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmComment.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmComment.java index 982e0f94d1..0ed046311f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmComment.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmComment.java @@ -17,8 +17,7 @@ package org.thingsboard.server.common.data.alarm; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -30,23 +29,23 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -@ApiModel +@Schema @Data @Builder @AllArgsConstructor public class AlarmComment extends BaseData implements HasName { - @ApiModelProperty(position = 3, value = "JSON object with Alarm id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Alarm id.", accessMode = Schema.AccessMode.READ_ONLY) private EntityId alarmId; - @ApiModelProperty(position = 4, value = "JSON object with User id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with User id.", accessMode = Schema.AccessMode.READ_ONLY) private UserId userId; - @ApiModelProperty(position = 5, value = "Defines origination of comment. System type means comment was created by TB. OTHER type means comment was created by user.", example = "SYSTEM/OTHER", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Defines origination of comment. System type means comment was created by TB. OTHER type means comment was created by user.", example = "SYSTEM/OTHER", accessMode = Schema.AccessMode.READ_ONLY) private AlarmCommentType type; - @ApiModelProperty(position = 6, value = "JSON object with text of comment.", dataType = "com.fasterxml.jackson.databind.JsonNode") + @Schema(description = "JSON object with text of comment.",implementation = com.fasterxml.jackson.databind.JsonNode.class) @NoXss @Length(fieldName = "comment", max = 10000) private transient JsonNode comment; - @ApiModelProperty(position = 1, value = "JSON object with the alarm comment Id. " + + @Schema(description = "JSON object with the alarm comment Id. " + "Specify this field to update the alarm comment. " + "Referencing non-existing alarm Id will cause error. " + "Omit this field to create new alarm." ) @@ -55,7 +54,7 @@ public class AlarmComment extends BaseData implements HasName { return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the alarm comment creation, in milliseconds", example = "1634058704567", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the alarm comment creation, in milliseconds", example = "1634058704567", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); @@ -71,7 +70,7 @@ public class AlarmComment extends BaseData implements HasName { @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) - @ApiModelProperty(position = 5, required = true, value = "representing comment text", example = "Please take a look") + @Schema(required = true, description = "representing comment text", example = "Please take a look") public String getName() { return comment.toString(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentInfo.java index e84cb78697..b68f771621 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCommentInfo.java @@ -15,24 +15,23 @@ */ package org.thingsboard.server.common.data.alarm; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; -@ApiModel +@Schema @Data @EqualsAndHashCode(callSuper = true) public class AlarmCommentInfo extends AlarmComment { private static final long serialVersionUID = 2807343093519543377L; - @ApiModelProperty(position = 19, value = "User first name", example = "John") + @Schema(description = "User first name", example = "John") private String firstName; - @ApiModelProperty(position = 19, value = "User last name", example = "Brown") + @Schema(description = "User last name", example = "Brown") private String lastName; - @ApiModelProperty(position = 19, value = "User email address", example = "johnBrown@gmail.com") + @Schema(description = "User email address", example = "johnBrown@gmail.com") private String email; public AlarmCommentInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java index 1c71e66b87..ea4649bcd6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.data.alarm; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.id.AlarmId; @@ -35,29 +35,29 @@ import jakarta.validation.constraints.NotNull; public class AlarmCreateOrUpdateActiveRequest implements AlarmModificationRequest { @NotNull - @ApiModelProperty(position = 1, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; - @ApiModelProperty(position = 2, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Customer Id", accessMode = Schema.AccessMode.READ_ONLY) private CustomerId customerId; @NotNull - @ApiModelProperty(position = 3, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm") + @Schema(required = true, description = "representing type of the Alarm", example = "High Temperature Alarm") @Length(fieldName = "type") private String type; @NotNull - @ApiModelProperty(position = 4, required = true, value = "JSON object with alarm originator id") + @Schema(required = true, description = "JSON object with alarm originator id") private EntityId originator; @NotNull - @ApiModelProperty(position = 5, required = true, value = "Alarm severity", example = "CRITICAL") + @Schema(required = true, description = "Alarm severity", example = "CRITICAL") private AlarmSeverity severity; - @ApiModelProperty(position = 6, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565") + @Schema(description = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565") private long startTs; - @ApiModelProperty(position = 7, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") + @Schema(description = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") private long endTs; @NoXss - @ApiModelProperty(position = 8, value = "JSON object with alarm details") + @Schema(description = "JSON object with alarm details") private JsonNode details; @Valid - @ApiModelProperty(position = 9, value = "JSON object with propagation details") + @Schema(description = "JSON object with propagation details") private AlarmPropagationInfo propagation; private UserId userId; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java index 9086c4aa7b..61e53044ad 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.alarm; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @@ -24,24 +23,24 @@ import lombok.ToString; @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -@ApiModel +@Schema public class AlarmInfo extends Alarm { private static final long serialVersionUID = 2807343093519543363L; @Getter @Setter - @ApiModelProperty(position = 19, value = "Alarm originator name", example = "Thermostat") + @Schema(description = "Alarm originator name", example = "Thermostat") private String originatorName; @Getter @Setter - @ApiModelProperty(position = 20, value = "Alarm originator label", example = "Thermostat label") + @Schema(description = "Alarm originator label", example = "Thermostat label") private String originatorLabel; @Getter @Setter - @ApiModelProperty(position = 21, value = "Alarm assignee") + @Schema(description = "Alarm assignee") private AlarmAssignee assignee; public AlarmInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java index 8ba744a85e..49ad2c4ded 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmPropagationInfo.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.alarm; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.validation.NoXss; @@ -29,14 +29,14 @@ public class AlarmPropagationInfo { public static AlarmPropagationInfo EMPTY = new AlarmPropagationInfo(false, false, false, Collections.emptyList()); - @ApiModelProperty(position = 1, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true") + @Schema(description = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true") private boolean propagate; - @ApiModelProperty(position = 2, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true") + @Schema(description = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true") private boolean propagateToOwner; - @ApiModelProperty(position = 3, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true") + @Schema(description = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true") private boolean propagateToTenant; @NoXss - @ApiModelProperty(position = 4, value = "JSON array of relation types that should be used for propagation. " + + @Schema(description = "JSON array of relation types that should be used for propagation. " + "By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " + "This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.") private List propagateRelationTypes; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java index e97b40e1f1..06bacb50fc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.data.alarm; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.id.AlarmId; @@ -32,26 +32,26 @@ import jakarta.validation.constraints.NotNull; public class AlarmUpdateRequest implements AlarmModificationRequest { @NotNull - @ApiModelProperty(position = 1, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; @NotNull - @ApiModelProperty(position = 2, value = "JSON object with the alarm Id. " + + @Schema(description = "JSON object with the alarm Id. " + "Specify this field to update the alarm. " + "Referencing non-existing alarm Id will cause error. " + "Omit this field to create new alarm.") private AlarmId alarmId; @NotNull - @ApiModelProperty(position = 3, required = true, value = "Alarm severity", example = "CRITICAL") + @Schema(required = true, description = "Alarm severity", example = "CRITICAL") private AlarmSeverity severity; - @ApiModelProperty(position = 4, value = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565") + @Schema(description = "Timestamp of the alarm start time, in milliseconds", example = "1634058704565") private long startTs; - @ApiModelProperty(position = 5, value = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") + @Schema(description = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") private long endTs; @NoXss - @ApiModelProperty(position = 6, value = "JSON object with alarm details") + @Schema(description = "JSON object with alarm details") private JsonNode details; @Valid - @ApiModelProperty(position = 7, value = "JSON object with propagation details") + @Schema(description = "JSON object with propagation details") private AlarmPropagationInfo propagation; private UserId userId; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java index bbb56305db..49bcc15875 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java @@ -16,8 +16,7 @@ package org.thingsboard.server.common.data.asset; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @@ -35,7 +34,7 @@ import org.thingsboard.server.common.data.validation.NoXss; import java.util.Optional; -@ApiModel +@Schema @EqualsAndHashCode(callSuper = true) public class Asset extends BaseDataWithAdditionalInfo implements HasLabel, HasTenantId, HasCustomerId, ExportableEntity { @@ -88,7 +87,7 @@ public class Asset extends BaseDataWithAdditionalInfo implements HasLab this.externalId = asset.getExternalId(); } - @ApiModelProperty(position = 1, value = "JSON object with the asset Id. " + + @Schema(description = "JSON object with the asset Id. " + "Specify this field to update the asset. " + "Referencing non-existing asset Id will cause error. " + "Omit this field to create new asset.") @@ -97,13 +96,13 @@ public class Asset extends BaseDataWithAdditionalInfo implements HasLab return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the asset creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the asset creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) public TenantId getTenantId() { return tenantId; } @@ -112,7 +111,7 @@ public class Asset extends BaseDataWithAdditionalInfo implements HasLab this.tenantId = tenantId; } - @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignAssetToCustomer' to change the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Customer Id. Use 'assignAssetToCustomer' to change the Customer Id.", accessMode = Schema.AccessMode.READ_ONLY) public CustomerId getCustomerId() { return customerId; } @@ -121,7 +120,7 @@ public class Asset extends BaseDataWithAdditionalInfo implements HasLab this.customerId = customerId; } - @ApiModelProperty(position = 5, required = true, value = "Unique Asset Name in scope of Tenant", example = "Empire State Building") + @Schema(required = true, description = "Unique Asset Name in scope of Tenant", example = "Empire State Building") @Override public String getName() { return name; @@ -131,7 +130,7 @@ public class Asset extends BaseDataWithAdditionalInfo implements HasLab this.name = name; } - @ApiModelProperty(position = 6, required = true, value = "Asset type", example = "Building") + @Schema(required = true, description = "Asset type", example = "Building") public String getType() { return type; } @@ -140,7 +139,7 @@ public class Asset extends BaseDataWithAdditionalInfo implements HasLab this.type = type; } - @ApiModelProperty(position = 7, required = true, value = "Label that may be used in widgets", example = "NY Building") + @Schema(required = true, description = "Label that may be used in widgets", example = "NY Building") public String getLabel() { return label; } @@ -149,7 +148,7 @@ public class Asset extends BaseDataWithAdditionalInfo implements HasLab this.label = label; } - @ApiModelProperty(position = 8, required = true, value = "JSON object with Asset Profile Id.") + @Schema(required = true, description = "JSON object with Asset Profile Id.") public AssetProfileId getAssetProfileId() { return assetProfileId; } @@ -158,7 +157,7 @@ public class Asset extends BaseDataWithAdditionalInfo implements HasLab this.assetProfileId = assetProfileId; } - @ApiModelProperty(position = 9, value = "Additional parameters of the asset", dataType = "com.fasterxml.jackson.databind.JsonNode") + @Schema(description = "Additional parameters of the asset",implementation = com.fasterxml.jackson.databind.JsonNode.class) @Override public JsonNode getAdditionalInfo() { return super.getAdditionalInfo(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java index b2d1fa95a2..08c084fde6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java @@ -15,25 +15,24 @@ */ package org.thingsboard.server.common.data.asset; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.AssetId; -@ApiModel +@Schema @Data @EqualsAndHashCode(callSuper = true) public class AssetInfo extends Asset { private static final long serialVersionUID = -4094528227011066194L; - @ApiModelProperty(position = 10, value = "Title of the Customer that owns the asset.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Title of the Customer that owns the asset.", accessMode = Schema.AccessMode.READ_ONLY) private String customerTitle; - @ApiModelProperty(position = 11, value = "Indicates special 'Public' Customer that is auto-generated to use the assets on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Indicates special 'Public' Customer that is auto-generated to use the assets on public dashboards.", accessMode = Schema.AccessMode.READ_ONLY) private boolean customerIsPublic; - @ApiModelProperty(position = 12, value = "Name of the corresponding Asset Profile.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Name of the corresponding Asset Profile.", accessMode = Schema.AccessMode.READ_ONLY) private String assetProfileName; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java index 5456ba36ce..f8acb0ef08 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.asset; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -34,7 +33,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -@ApiModel +@Schema @Data @ToString(exclude = {"image"}) @EqualsAndHashCode(callSuper = true) @@ -43,33 +42,33 @@ public class AssetProfile extends BaseData implements HasName, H private static final long serialVersionUID = 6998485460273302018L; - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id that owns the profile.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id that owns the profile.", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; @NoXss @Length(fieldName = "name") - @ApiModelProperty(position = 4, value = "Unique Asset Profile Name in scope of Tenant.", example = "Building") + @Schema(description = "Unique Asset Profile Name in scope of Tenant.", example = "Building") private String name; @NoXss - @ApiModelProperty(position = 11, value = "Asset Profile description. ") + @Schema(description = "Asset Profile description. ") private String description; @Length(fieldName = "image", max = 1000000) - @ApiModelProperty(position = 12, value = "Either URL or Base64 data of the icon. Used in the mobile application to visualize set of asset profiles in the grid view. ") + @Schema(description = "Either URL or Base64 data of the icon. Used in the mobile application to visualize set of asset profiles in the grid view. ") private String image; private boolean isDefault; - @ApiModelProperty(position = 7, value = "Reference to the rule chain. " + + @Schema(description = "Reference to the rule chain. " + "If present, the specified rule chain will be used to process all messages related to asset, including asset updates, telemetry, attribute updates, etc. " + "Otherwise, the root rule chain will be used to process those messages.") private RuleChainId defaultRuleChainId; - @ApiModelProperty(position = 6, value = "Reference to the dashboard. Used in the mobile application to open the default dashboard when user navigates to asset details.") + @Schema(description = "Reference to the dashboard. Used in the mobile application to open the default dashboard when user navigates to asset details.") private DashboardId defaultDashboardId; @NoXss - @ApiModelProperty(position = 8, value = "Rule engine queue name. " + + @Schema(description = "Rule engine queue name. " + "If present, the specified queue will be used to store all unprocessed messages related to asset, including asset updates, telemetry, attribute updates, etc. " + "Otherwise, the 'Main' queue will be used to store those messages.") private String defaultQueueName; - @ApiModelProperty(position = 13, value = "Reference to the edge rule chain. " + + @Schema(description = "Reference to the edge rule chain. " + "If present, the specified edge rule chain will be used on the edge to process all messages related to asset, including asset updates, telemetry, attribute updates, etc. " + "Otherwise, the edge root rule chain will be used to process those messages.") private RuleChainId defaultEdgeRuleChainId; @@ -98,7 +97,7 @@ public class AssetProfile extends BaseData implements HasName, H this.externalId = assetProfile.getExternalId(); } - @ApiModelProperty(position = 1, value = "JSON object with the asset profile Id. " + + @Schema(description = "JSON object with the asset profile Id. " + "Specify this field to update the asset profile. " + "Referencing non-existing asset profile Id will cause error. " + "Omit this field to create new asset profile.") @@ -107,13 +106,13 @@ public class AssetProfile extends BaseData implements HasName, H return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the profile creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the profile creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 5, value = "Used to mark the default profile. Default profile is used when the asset profile is not specified during asset creation.") + @Schema(description = "Used to mark the default profile. Default profile is used when the asset profile is not specified during asset creation.") public boolean isDefault(){ return isDefault; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfileInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfileInfo.java index e590c4c094..dc1adb1b63 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfileInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfileInfo.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.asset; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import lombok.ToString; import lombok.Value; @@ -35,12 +35,12 @@ import java.util.UUID; @ToString(callSuper = true, exclude = "image") public class AssetProfileInfo extends EntityInfo { - @ApiModelProperty(position = 3, value = "Either URL or Base64 data of the icon. Used in the mobile application to visualize set of asset profiles in the grid view. ") + @Schema(description = "Either URL or Base64 data of the icon. Used in the mobile application to visualize set of asset profiles in the grid view. ") private final String image; - @ApiModelProperty(position = 4, value = "Reference to the dashboard. Used in the mobile application to open the default dashboard when user navigates to asset details.") + @Schema(description = "Reference to the dashboard. Used in the mobile application to open the default dashboard when user navigates to asset details.") private final DashboardId defaultDashboardId; - @ApiModelProperty(position = 5, value = "Tenant id.") + @Schema(description = "Tenant id.") private final TenantId tenantId; @JsonCreator diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java index aeb693b02c..c8a7be5df5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.asset; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -32,11 +32,11 @@ import java.util.List; @Data public class AssetSearchQuery { - @ApiModelProperty(position = 3, value = "Main search parameters.") + @Schema(description = "Main search parameters.") private RelationsSearchParameters parameters; - @ApiModelProperty(position = 1, value = "Type of the relation between root entity and asset (e.g. 'Contains' or 'Manages').") + @Schema(description = "Type of the relation between root entity and asset (e.g. 'Contains' or 'Manages').") private String relationType; - @ApiModelProperty(position = 2, value = "Array of asset types to filter the related entities (e.g. 'Building', 'Vehicle').") + @Schema(description = "Array of asset types to filter the related entities (e.g. 'Building', 'Vehicle').") private List assetTypes; public EntityRelationsQuery toEntitySearchQuery() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java index 5b6b68e622..5e95a54bb1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java @@ -16,8 +16,7 @@ package org.thingsboard.server.common.data.audit; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; @@ -28,31 +27,31 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.validation.NoXss; -@ApiModel +@Schema @EqualsAndHashCode(callSuper = true) @Data public class AuditLog extends BaseData { - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; - @ApiModelProperty(position = 4, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Customer Id", accessMode = Schema.AccessMode.READ_ONLY) private CustomerId customerId; - @ApiModelProperty(position = 5, value = "JSON object with Entity id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Entity id", accessMode = Schema.AccessMode.READ_ONLY) private EntityId entityId; @NoXss - @ApiModelProperty(position = 6, value = "Name of the logged entity", example = "Thermometer", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Name of the logged entity", example = "Thermometer", accessMode = Schema.AccessMode.READ_ONLY) private String entityName; - @ApiModelProperty(position = 7, value = "JSON object with User id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with User id.", accessMode = Schema.AccessMode.READ_ONLY) private UserId userId; - @ApiModelProperty(position = 8, value = "Unique user name(email) of the user that performed some action on logged entity", example = "tenant@thingsboard.org", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Unique user name(email) of the user that performed some action on logged entity", example = "tenant@thingsboard.org", accessMode = Schema.AccessMode.READ_ONLY) private String userName; - @ApiModelProperty(position = 9, value = "String represented Action type", example = "ADDED", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "String represented Action type", example = "ADDED", accessMode = Schema.AccessMode.READ_ONLY) private ActionType actionType; - @ApiModelProperty(position = 10, value = "JsonNode represented action data", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JsonNode represented action data", accessMode = Schema.AccessMode.READ_ONLY) private JsonNode actionData; - @ApiModelProperty(position = 11, value = "String represented Action status", example = "SUCCESS", allowableValues = "SUCCESS,FAILURE", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "String represented Action status", example = "SUCCESS", allowableValues = "SUCCESS,FAILURE", accessMode = Schema.AccessMode.READ_ONLY) private ActionStatus actionStatus; - @ApiModelProperty(position = 12, value = "Failure action details info. An empty string in case of action status type 'SUCCESS', otherwise includes stack trace of the caused exception.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Failure action details info. An empty string in case of action status type 'SUCCESS', otherwise includes stack trace of the caused exception.", accessMode = Schema.AccessMode.READ_ONLY) private String actionFailureDetails; public AuditLog() { @@ -77,13 +76,13 @@ public class AuditLog extends BaseData { this.actionFailureDetails = auditLog.getActionFailureDetails(); } - @ApiModelProperty(position = 2, value = "Timestamp of the auditLog creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the auditLog creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 1, value = "JSON object with the auditLog Id") + @Schema(description = "JSON object with the auditLog Id") @Override public AuditLogId getId() { return super.getId(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java index dbde84abbe..f3157c1571 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.device; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -27,15 +26,15 @@ import org.thingsboard.server.common.data.relation.RelationsSearchParameters; import java.util.Collections; import java.util.List; -@ApiModel +@Schema @Data public class DeviceSearchQuery { - @ApiModelProperty(position = 3, value = "Main search parameters.") + @Schema(description = "Main search parameters.") private RelationsSearchParameters parameters; - @ApiModelProperty(position = 1, value = "Type of the relation between root entity and device (e.g. 'Contains' or 'Manages').") + @Schema(description = "Type of the relation between root entity and device (e.g. 'Contains' or 'Manages').") private String relationType; - @ApiModelProperty(position = 2, value = "Array of device types to filter the related entities (e.g. 'Temperature Sensor', 'Smoke Sensor').") + @Schema(description = "Array of device types to filter the related entities (e.g. 'Temperature Sensor', 'Smoke Sensor').") private List deviceTypes; public EntityRelationsQuery toEntitySearchQuery() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java index 9e7c85e6f4..a5dd1919e1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java @@ -15,11 +15,11 @@ */ package org.thingsboard.server.common.data.device.data; -import io.swagger.annotations.ApiModel; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.DeviceProfileType; -@ApiModel +@Schema @Data public class DefaultDeviceConfiguration implements DeviceConfiguration { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java index 79793b0fd7..aa1cb8dff8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java @@ -19,12 +19,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import io.swagger.annotations.ApiModel; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.DeviceProfileType; import java.io.Serializable; -@ApiModel +@Schema @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java index 5ec461c271..b362231691 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java @@ -15,21 +15,20 @@ */ package org.thingsboard.server.common.data.device.data; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; -@ApiModel +@Schema @Data public class DeviceData implements Serializable { private static final long serialVersionUID = -3771567735290681274L; - @ApiModelProperty(position = 1, value = "Device configuration for device profile type. DEFAULT is only supported value for now") + @Schema(description = "Device configuration for device profile type. DEFAULT is only supported value for now") private DeviceConfiguration configuration; - @ApiModelProperty(position = 2, value = "Device transport configuration used to connect the device") + @Schema(description = "Device transport configuration used to connect the device") private DeviceTransportConfiguration transportConfiguration; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java index c5f385df4a..ace00a129f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java @@ -19,12 +19,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import io.swagger.annotations.ApiModel; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.DeviceTransportType; import java.io.Serializable; -@ApiModel +@Schema @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java index a8b83a1cf6..3cb3ff99f1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java @@ -16,23 +16,22 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import jakarta.validation.Valid; import java.io.Serializable; import java.util.List; -@ApiModel +@Schema @Data @JsonIgnoreProperties(ignoreUnknown = true) public class AlarmCondition implements Serializable { @Valid - @ApiModelProperty(position = 1, value = "JSON array of alarm condition filters") + @Schema(description = "JSON array of alarm condition filters") private List condition; - @ApiModelProperty(position = 2, value = "JSON object representing alarm condition type") + @Schema(description = "JSON object representing alarm condition type") private AlarmConditionSpec spec; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java index 7a44d707f9..39360cdb3e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.device.profile; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.KeyFilterPredicate; @@ -25,20 +24,20 @@ import org.thingsboard.server.common.data.validation.NoXss; import jakarta.validation.Valid; import java.io.Serializable; -@ApiModel +@Schema @Data public class AlarmConditionFilter implements Serializable { @Valid - @ApiModelProperty(position = 1, value = "JSON object for specifying alarm condition by specific key") + @Schema(description = "JSON object for specifying alarm condition by specific key") private AlarmConditionFilterKey key; - @ApiModelProperty(position = 2, value = "String representation of the type of the value", example = "NUMERIC") + @Schema(description = "String representation of the type of the value", example = "NUMERIC") private EntityKeyValueType valueType; @NoXss - @ApiModelProperty(position = 3, value = "Value used in Constant comparison. For other types, such as TIME_SERIES or ATTRIBUTE, the predicate condition is used") + @Schema(description = "Value used in Constant comparison. For other types, such as TIME_SERIES or ATTRIBUTE, the predicate condition is used") private Object value; @Valid - @ApiModelProperty(position = 4, value = "JSON object representing filter condition") + @Schema(description = "JSON object representing filter condition") private KeyFilterPredicate predicate; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilterKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilterKey.java index 58dcedc993..a4782fdbff 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilterKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilterKey.java @@ -15,21 +15,20 @@ */ package org.thingsboard.server.common.data.device.profile; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.validation.NoXss; import java.io.Serializable; -@ApiModel +@Schema @Data public class AlarmConditionFilterKey implements Serializable { - @ApiModelProperty(position = 1, value = "The key type", example = "TIME_SERIES") + @Schema(description = "The key type", example = "TIME_SERIES") private final AlarmConditionKeyType type; @NoXss - @ApiModelProperty(position = 2, value = "String value representing the key", example = "temp") + @Schema(description = "String value representing the key", example = "temp") private final String key; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java index 0f23ae5624..83615dfd4f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.device.profile; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.validation.NoXss; @@ -24,20 +23,20 @@ import org.thingsboard.server.common.data.validation.NoXss; import jakarta.validation.Valid; import java.io.Serializable; -@ApiModel +@Schema @Data public class AlarmRule implements Serializable { @Valid - @ApiModelProperty(position = 1, value = "JSON object representing the alarm rule condition") + @Schema(description = "JSON object representing the alarm rule condition") private AlarmCondition condition; - @ApiModelProperty(position = 2, value = "JSON object representing time interval during which the rule is active") + @Schema(description = "JSON object representing time interval during which the rule is active") private AlarmSchedule schedule; // Advanced @NoXss - @ApiModelProperty(position = 3, value = "String value representing the additional details for an alarm rule") + @Schema(description = "String value representing the additional details for an alarm rule") private String alarmDetails; - @ApiModelProperty(position = 4, value = "JSON object with the dashboard Id representing the reference to alarm details dashboard used by mobile application") + @Schema(description = "JSON object with the dashboard Id representing the reference to alarm details dashboard used by mobile application") private DashboardId dashboardId; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java index 96f365a152..395149c52b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.device.profile; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.validation.Length; @@ -27,34 +26,34 @@ import java.io.Serializable; import java.util.List; import java.util.TreeMap; -@ApiModel +@Schema @Data public class DeviceProfileAlarm implements Serializable { - @ApiModelProperty(position = 1, value = "String value representing the alarm rule id", example = "highTemperatureAlarmID") + @Schema(description = "String value representing the alarm rule id", example = "highTemperatureAlarmID") private String id; @Length(fieldName = "alarm type") @NoXss - @ApiModelProperty(position = 2, value = "String value representing type of the alarm", example = "High Temperature Alarm") + @Schema(description = "String value representing type of the alarm", example = "High Temperature Alarm") private String alarmType; @Valid - @ApiModelProperty(position = 3, value = "Complex JSON object representing create alarm rules. The unique create alarm rule can be created for each alarm severity type. " + + @Schema(description = "Complex JSON object representing create alarm rules. The unique create alarm rule can be created for each alarm severity type. " + "There can be 5 create alarm rules configured per a single alarm type. See method implementation notes and AlarmRule model for more details") private TreeMap createRules; @Valid - @ApiModelProperty(position = 4, value = "JSON object representing clear alarm rule") + @Schema(description = "JSON object representing clear alarm rule") private AlarmRule clearRule; // Hidden in advanced settings - @ApiModelProperty(position = 5, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true") + @Schema(description = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true") private boolean propagate; - @ApiModelProperty(position = 6, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true") + @Schema(description = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true") private boolean propagateToOwner; - @ApiModelProperty(position = 7, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true") + @Schema(description = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true") private boolean propagateToTenant; - @ApiModelProperty(position = 8, value = "JSON array of relation types that should be used for propagation. " + + @Schema(description = "JSON array of relation types that should be used for propagation. " + "By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " + "This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.") private List propagateRelationTypes; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java index f6b2de915b..e2e7a646ea 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java @@ -15,27 +15,26 @@ */ package org.thingsboard.server.common.data.device.profile; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import jakarta.validation.Valid; import java.io.Serializable; import java.util.List; -@ApiModel +@Schema @Data public class DeviceProfileData implements Serializable { - @ApiModelProperty(position = 1, value = "JSON object of device profile configuration") + @Schema(description = "JSON object of device profile configuration") private DeviceProfileConfiguration configuration; @Valid - @ApiModelProperty(position = 2, value = "JSON object of device profile transport configuration") + @Schema(description = "JSON object of device profile transport configuration") private DeviceProfileTransportConfiguration transportConfiguration; - @ApiModelProperty(position = 3, value = "JSON object of provisioning strategy type per device profile") + @Schema(description = "JSON object of provisioning strategy type per device profile") private DeviceProfileProvisionConfiguration provisionConfiguration; @Valid - @ApiModelProperty(position = 4, value = "JSON array of alarm rules configuration per device profile") + @Schema(description = "JSON array of alarm rules configuration per device profile") private List alarms; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java index 4405115905..b59c5fadea 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java @@ -15,56 +15,55 @@ */ package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; -@ApiModel +@Schema @Data public class LwM2MServerSecurityConfig implements Serializable { - @ApiModelProperty(position = 1, value = "Server short Id. Used as link to associate server Object Instance. This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. " + + @Schema(description = "Server short Id. Used as link to associate server Object Instance. This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. " + "This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'. " + - "The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.", example = "123", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + "The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.", example = "123", accessMode = Schema.AccessMode.READ_ONLY) protected Integer shortServerId = 123; /** Security -> ObjectId = 0 'LWM2M Security' */ - @ApiModelProperty(position = 2, value = "Is Bootstrap Server or Lwm2m Server. " + + @Schema(description = "Is Bootstrap Server or Lwm2m Server. " + "The LwM2M Client MAY be configured to use one or more LwM2M Server Account(s). " + "The LwM2M Client MUST have at most one LwM2M Bootstrap-Server Account. " + - "(*) The LwM2M client MUST have at least one LwM2M server account after completing the boot sequence specified.", example = "true or false", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + "(*) The LwM2M client MUST have at least one LwM2M server account after completing the boot sequence specified.", example = "true or false", accessMode = Schema.AccessMode.READ_ONLY) protected boolean bootstrapServerIs = false; - @ApiModelProperty(position = 3, value = "Host for 'No Security' mode", example = "0.0.0.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Host for 'No Security' mode", example = "0.0.0.0", accessMode = Schema.AccessMode.READ_ONLY) protected String host; - @ApiModelProperty(position = 4, value = "Port for Lwm2m Server: 'No Security' mode: Lwm2m Server or Bootstrap Server", example = "'5685' or '5687'", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Port for Lwm2m Server: 'No Security' mode: Lwm2m Server or Bootstrap Server", example = "'5685' or '5687'", accessMode = Schema.AccessMode.READ_ONLY) protected Integer port; - @ApiModelProperty(position = 7, value = "Client Hold Off Time. The number of seconds to wait before initiating a Client Initiated Bootstrap once the LwM2M Client has determined it should initiate this bootstrap mode. (This information is relevant for use with a Bootstrap-Server only.)", example = "1", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Client Hold Off Time. The number of seconds to wait before initiating a Client Initiated Bootstrap once the LwM2M Client has determined it should initiate this bootstrap mode. (This information is relevant for use with a Bootstrap-Server only.)", example = "1", accessMode = Schema.AccessMode.READ_ONLY) protected Integer clientHoldOffTime = 1; - @ApiModelProperty(position = 8, value = "Server Public Key for 'Security' mode (DTLS): RPK or X509. Format: base64 encoded", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" + - "+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Server Public Key for 'Security' mode (DTLS): RPK or X509. Format: base64 encoded", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" + + "+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", accessMode = Schema.AccessMode.READ_ONLY) protected String serverPublicKey; - @ApiModelProperty(position = 9, value = "Server Public Key for 'Security' mode (DTLS): X509. Format: base64 encoded", example = "MMIICODCCAd6gAwIBAgIUI88U1zowOdrxDK/dOV+36gJxI2MwCgYIKoZIzj0EAwIwejELMAkGA1UEBhMCVUs\n" + + @Schema(description = "Server Public Key for 'Security' mode (DTLS): X509. Format: base64 encoded", example = "MMIICODCCAd6gAwIBAgIUI88U1zowOdrxDK/dOV+36gJxI2MwCgYIKoZIzj0EAwIwejELMAkGA1UEBhMCVUs\n" + "xEjAQBgNVBAgTCUt5aXYgY2l0eTENMAsGA1UEBxMES3lpdjEUMBIGA1UEChMLVGhpbmdzYm9hcmQxFzAVBgNVBAsMDkRFVkVMT1BFUl9URVNUMRkwFwYDVQQDDBBpbnRlcm1lZGlhdGVfY2EwMB4XDTIyMDEwOTEzMDMwMFoXDTI3MDEwODEzMDMwMFowFDESMBAGA1UEAxM\n" + "JbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUO3vBo/JTv0eooY7XHiKAIVDoWKFqtrU7C6q8AIKqpLcqhCdW+haFeBOH3PjY6EwaWkY04Bir4oanU0s7tz2uKOBpzCBpDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/\n" + "BAIwADAdBgNVHQ4EFgQUEjc3Q4a0TxzP/3x3EV4fHxYUg0YwHwYDVR0jBBgwFoAUuSquGycMU6Q0SYNcbtSkSD3TfH0wLwYDVR0RBCgwJoIVbG9jYWxob3N0LmxvY2FsZG9tYWlugglsb2NhbGhvc3SCAiAtMAoGCCqGSM49BAMCA0gAMEUCIQD7dbZObyUaoDiNbX+9fUNp\n" + - "AWrD7N7XuJUwZ9FcN75R3gIgb2RNjDkHoyUyF1YajwkBk+7XmIXNClmizNJigj908mw=", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + "AWrD7N7XuJUwZ9FcN75R3gIgb2RNjDkHoyUyF1YajwkBk+7XmIXNClmizNJigj908mw=", accessMode = Schema.AccessMode.READ_ONLY) protected String serverCertificate; - @ApiModelProperty(position = 10, value = "Bootstrap Server Account Timeout (If the value is set to 0, or if this resource is not instantiated, the Bootstrap-Server Account lifetime is infinite.)", example = "0", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Bootstrap Server Account Timeout (If the value is set to 0, or if this resource is not instantiated, the Bootstrap-Server Account lifetime is infinite.)", example = "0", accessMode = Schema.AccessMode.READ_ONLY) Integer bootstrapServerAccountTimeout = 0; /** Config -> ObjectId = 1 'LwM2M Server' */ - @ApiModelProperty(position = 11, value = "Specify the lifetime of the registration in seconds.", example = "300", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Specify the lifetime of the registration in seconds.", example = "300", accessMode = Schema.AccessMode.READ_ONLY) private Integer lifetime = 300; - @ApiModelProperty(position = 12, value = "The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation. " + - "If this Resource doesn’t exist, the default value is 0.", example = "1", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation. " + + "If this Resource doesn’t exist, the default value is 0.", example = "1", accessMode = Schema.AccessMode.READ_ONLY) private Integer defaultMinPeriod = 1; /** ResourceID=6 'Notification Storing When Disabled or Offline' */ - @ApiModelProperty(position = 13, value = "If true, the LwM2M Client stores “Notify” operations to the LwM2M Server while the LwM2M Server account is disabled or the LwM2M Client is offline. After the LwM2M Server account is enabled or the LwM2M Client is online, the LwM2M Client reports the stored “Notify” operations to the Server. " + + @Schema(description = "If true, the LwM2M Client stores “Notify” operations to the LwM2M Server while the LwM2M Server account is disabled or the LwM2M Client is offline. After the LwM2M Server account is enabled or the LwM2M Client is online, the LwM2M Client reports the stored “Notify” operations to the Server. " + "If false, the LwM2M Client discards all the “Notify” operations or temporarily disables the Observe function while the LwM2M Server is disabled or the LwM2M Client is offline. " + - "The default value is true.", example = "true", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + "The default value is true.", example = "true", accessMode = Schema.AccessMode.READ_ONLY) private boolean notifIfDisabled = true; - @ApiModelProperty(position = 14, value = "This Resource defines the transport binding configured for the LwM2M Client. " + - "If the LwM2M Client supports the binding specified in this Resource, the LwM2M Client MUST use that transport for the Current Binding Mode.", example = "U", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "This Resource defines the transport binding configured for the LwM2M Client. " + + "If the LwM2M Client supports the binding specified in this Resource, the LwM2M Client MUST use that transport for the Current Binding Mode.", example = "U", accessMode = Schema.AccessMode.READ_ONLY) private String binding = "U"; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java index ce622322d9..ea3e5a8dce 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java @@ -15,15 +15,14 @@ */ package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@ApiModel +@Schema @Data public class LwM2MServerSecurityConfigDefault extends LwM2MServerSecurityConfig { - @ApiModelProperty(position = 5, value = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", accessMode = Schema.AccessMode.READ_ONLY) protected String securityHost; - @ApiModelProperty(position = 6, value = "Port for 'Security' mode (DTLS): Lwm2m Server or Bootstrap Server", example = "5686 or 5688", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Port for 'Security' mode (DTLS): Lwm2m Server or Bootstrap Server", example = "5686 or 5688", accessMode = Schema.AccessMode.READ_ONLY) protected Integer securityPort; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java index 6d1d9390ba..169f4007f0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.edge; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import lombok.Setter; import lombok.ToString; @@ -31,7 +30,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -@ApiModel +@Schema @EqualsAndHashCode(callSuper = true) @ToString @Setter @@ -89,7 +88,7 @@ public class Edge extends BaseDataWithAdditionalInfo implements HasLabel this.secret = edge.getSecret(); } - @ApiModelProperty(position = 1, value = "JSON object with the Edge Id. " + + @Schema(description = "JSON object with the Edge Id. " + "Specify this field to update the Edge. " + "Referencing non-existing Edge Id will cause error. " + "Omit this field to create new Edge." ) @@ -98,51 +97,51 @@ public class Edge extends BaseDataWithAdditionalInfo implements HasLabel return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the edge creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the edge creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Use 'assignDeviceToTenant' to change the Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id. Use 'assignDeviceToTenant' to change the Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) @Override public TenantId getTenantId() { return this.tenantId; } - @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignEdgeToCustomer' to change the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Customer Id. Use 'assignEdgeToCustomer' to change the Customer Id.", accessMode = Schema.AccessMode.READ_ONLY) @Override public CustomerId getCustomerId() { return this.customerId; } - @ApiModelProperty(position = 5, value = "JSON object with Root Rule Chain Id. Use 'setEdgeRootRuleChain' to change the Root Rule Chain Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Root Rule Chain Id. Use 'setEdgeRootRuleChain' to change the Root Rule Chain Id.", accessMode = Schema.AccessMode.READ_ONLY) public RuleChainId getRootRuleChainId() { return this.rootRuleChainId; } - @ApiModelProperty(position = 6, required = true, value = "Unique Edge Name in scope of Tenant", example = "Silo_A_Edge") + @Schema(required = true, description = "Unique Edge Name in scope of Tenant", example = "Silo_A_Edge") @Override public String getName() { return this.name; } - @ApiModelProperty(position = 7, required = true, value = "Edge type", example = "Silos") + @Schema(required = true, description = "Edge type", example = "Silos") public String getType() { return this.type; } - @ApiModelProperty(position = 8, value = "Label that may be used in widgets", example = "Silo Edge on far field") + @Schema(description = "Label that may be used in widgets", example = "Silo Edge on far field") public String getLabel() { return this.label; } - @ApiModelProperty(position = 9, required = true, value = "Edge routing key ('username') to authorize on cloud") + @Schema(required = true, description = "Edge routing key ('username') to authorize on cloud") public String getRoutingKey() { return this.routingKey; } - @ApiModelProperty(position = 10, required = true, value = "Edge secret ('password') to authorize on cloud") + @Schema(required = true, description = "Edge secret ('password') to authorize on cloud") public String getSecret() { return this.secret; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstallInstructions.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstallInstructions.java index acfc458906..92a893c973 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstallInstructions.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstallInstructions.java @@ -15,18 +15,17 @@ */ package org.thingsboard.server.common.data.edge; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -@ApiModel +@Schema @Data @AllArgsConstructor @NoArgsConstructor public class EdgeInstallInstructions { - @ApiModelProperty(position = 1, value = "Markdown with docker install instructions") + @Schema(description = "Markdown with docker install instructions") private String dockerInstallInstructions; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeSearchQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeSearchQuery.java index cf029ba793..575e4cbba0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeSearchQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeSearchQuery.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.edge; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -29,11 +29,11 @@ import java.util.List; @Data public class EdgeSearchQuery { - @ApiModelProperty(position = 3, value = "Main search parameters.") + @Schema(description = "Main search parameters.") private RelationsSearchParameters parameters; - @ApiModelProperty(position = 1, value = "Type of the relation between root entity and edge (e.g. 'Contains' or 'Manages').") + @Schema(description = "Type of the relation between root entity and edge (e.g. 'Contains' or 'Manages').") private String relationType; - @ApiModelProperty(position = 2, value = "Array of edge types to filter the related entities (e.g. 'Silos', 'Stores').") + @Schema(description = "Array of edge types to filter the related entities (e.g. 'Silos', 'Stores').") private List edgeTypes; public EntityRelationsQuery toEntitySearchQuery() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java index c840d828fa..de77d3bb76 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.entityview; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -27,15 +26,15 @@ import org.thingsboard.server.common.data.relation.RelationsSearchParameters; import java.util.Collections; import java.util.List; -@ApiModel +@Schema @Data public class EntityViewSearchQuery { - @ApiModelProperty(position = 3, value = "Main search parameters.") + @Schema(description = "Main search parameters.") private RelationsSearchParameters parameters; - @ApiModelProperty(position = 1, value = "Type of the relation between root entity and device (e.g. 'Contains' or 'Manages').") + @Schema(description = "Type of the relation between root entity and device (e.g. 'Contains' or 'Manages').") private String relationType; - @ApiModelProperty(position = 2, value = "Array of entity view types to filter the related entities (e.g. 'Temperature Sensor', 'Smoke Sensor').") + @Schema(description = "Array of entity view types to filter the related entities (e.g. 'Temperature Sensor', 'Smoke Sensor').") private List entityViewTypes; public EntityRelationsQuery toEntitySearchQuery() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEventFilter.java index c6773838a7..14507abae7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEventFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEventFilter.java @@ -15,20 +15,19 @@ */ package org.thingsboard.server.common.data.event; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.StringUtils; @Data -@ApiModel +@Schema public abstract class DebugEventFilter implements EventFilter { - @ApiModelProperty(position = 1, value = "String value representing the server name, identifier or ip address where the platform is running", example = "ip-172-31-24-152") + @Schema(description = "String value representing the server name, identifier or ip address where the platform is running", example = "ip-172-31-24-152") protected String server; - @ApiModelProperty(position = 10, value = "Boolean value to filter the errors", allowableValues = "false, true") + @Schema(description = "Boolean value to filter the errors", allowableValues = "false, true") protected boolean isError; - @ApiModelProperty(position = 11, value = "The case insensitive 'contains' filter based on error message", example = "not present in the DB") + @Schema(description = "The case insensitive 'contains' filter based on error message", example = "not present in the DB") protected String errorStr; public void setIsError(boolean isError) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java index e20cd0dfa9..951965ee72 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java @@ -15,20 +15,19 @@ */ package org.thingsboard.server.common.data.event; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.StringUtils; @Data -@ApiModel +@Schema public class ErrorEventFilter implements EventFilter { - @ApiModelProperty(position = 1, value = "String value representing the server name, identifier or ip address where the platform is running", example = "ip-172-31-24-152") + @Schema(description = "String value representing the server name, identifier or ip address where the platform is running", example = "ip-172-31-24-152") protected String server; - @ApiModelProperty(position = 2, value = "String value representing the method name when the error happened", example = "onClusterEventMsg") + @Schema(description = "String value representing the method name when the error happened", example = "onClusterEventMsg") protected String method; - @ApiModelProperty(position = 3, value = "The case insensitive 'contains' filter based on error message", example = "not present in the DB") + @Schema(description = "The case insensitive 'contains' filter based on error message", example = "not present in the DB") protected String errorStr; @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/EventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/EventFilter.java index 7a520e212f..af4a283ef7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/event/EventFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/EventFilter.java @@ -17,10 +17,9 @@ package org.thingsboard.server.common.data.event; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; -@ApiModel +@Schema @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, @@ -34,7 +33,7 @@ import io.swagger.annotations.ApiModelProperty; }) public interface EventFilter { - @ApiModelProperty(position = 1, required = true, value = "String value representing the event type", example = "STATS") + @Schema(required = true, description = "String value representing the event type", example = "STATS") EventType getEventType(); boolean isNotEmpty(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java index 659a16a135..3d193a5064 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java @@ -15,22 +15,21 @@ */ package org.thingsboard.server.common.data.event; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.StringUtils; @Data -@ApiModel +@Schema public class LifeCycleEventFilter implements EventFilter { - @ApiModelProperty(position = 1, value = "String value representing the server name, identifier or ip address where the platform is running", example = "ip-172-31-24-152") + @Schema(description = "String value representing the server name, identifier or ip address where the platform is running", example = "ip-172-31-24-152") protected String server; - @ApiModelProperty(position = 2, value = "String value representing the lifecycle event type", example = "STARTED") + @Schema(description = "String value representing the lifecycle event type", example = "STARTED") protected String event; - @ApiModelProperty(position = 3, value = "String value representing status of the lifecycle event", allowableValues = "Success, Failure") + @Schema(description = "String value representing status of the lifecycle event", allowableValues = "Success, Failure") protected String status; - @ApiModelProperty(position = 4, value = "The case insensitive 'contains' filter based on error message", example = "not present in the DB") + @Schema(description = "The case insensitive 'contains' filter based on error message", example = "not present in the DB") protected String errorStr; @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/RuleChainDebugEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/RuleChainDebugEventFilter.java index 29e31cf0c8..7f00c26579 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/event/RuleChainDebugEventFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/RuleChainDebugEventFilter.java @@ -15,18 +15,17 @@ */ package org.thingsboard.server.common.data.event; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.StringUtils; @Data @EqualsAndHashCode(callSuper = true) -@ApiModel +@Schema public class RuleChainDebugEventFilter extends DebugEventFilter { - @ApiModelProperty(position = 2, value = "String value representing the message") + @Schema(description = "String value representing the message") protected String message; @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/RuleNodeDebugEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/RuleNodeDebugEventFilter.java index 589a70106e..b9cdfe8d37 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/event/RuleNodeDebugEventFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/RuleNodeDebugEventFilter.java @@ -15,32 +15,31 @@ */ package org.thingsboard.server.common.data.event; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.StringUtils; @Data @EqualsAndHashCode(callSuper = true) -@ApiModel +@Schema public class RuleNodeDebugEventFilter extends DebugEventFilter { - @ApiModelProperty(position = 2, value = "String value representing msg direction type (incoming to entity or outcoming from entity)", allowableValues = "IN, OUT") + @Schema(description = "String value representing msg direction type (incoming to entity or outcoming from entity)", allowableValues = "IN, OUT") protected String msgDirectionType; - @ApiModelProperty(position = 3, value = "String value representing the entity id in the event body (originator of the message)", example = "de9d54a0-2b7a-11ec-a3cc-23386423d98f") + @Schema(description = "String value representing the entity id in the event body (originator of the message)", example = "de9d54a0-2b7a-11ec-a3cc-23386423d98f") protected String entityId; - @ApiModelProperty(position = 4, value = "String value representing the entity type", allowableValues = "DEVICE") + @Schema(description = "String value representing the entity type", allowableValues = "DEVICE") protected String entityType; - @ApiModelProperty(position = 5, value = "String value representing the message id in the rule engine", example = "de9d54a0-2b7a-11ec-a3cc-23386423d98f") + @Schema(description = "String value representing the message id in the rule engine", example = "de9d54a0-2b7a-11ec-a3cc-23386423d98f") protected String msgId; - @ApiModelProperty(position = 6, value = "String value representing the message type", example = "POST_TELEMETRY_REQUEST") + @Schema(description = "String value representing the message type", example = "POST_TELEMETRY_REQUEST") protected String msgType; - @ApiModelProperty(position = 7, value = "String value representing the type of message routing", example = "Success") + @Schema(description = "String value representing the type of message routing", example = "Success") protected String relationType; - @ApiModelProperty(position = 8, value = "The case insensitive 'contains' filter based on data (key and value) for the message.", example = "humidity") + @Schema(description = "The case insensitive 'contains' filter based on data (key and value) for the message.", example = "humidity") protected String dataSearch; - @ApiModelProperty(position = 9, value = "The case insensitive 'contains' filter based on metadata (key and value) for the message.", example = "deviceName") + @Schema(description = "The case insensitive 'contains' filter based on metadata (key and value) for the message.", example = "deviceName") protected String metadataSearch; @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java index 15ac1c9c90..24485dd297 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java @@ -15,24 +15,23 @@ */ package org.thingsboard.server.common.data.event; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.StringUtils; @Data -@ApiModel +@Schema public class StatisticsEventFilter implements EventFilter { - @ApiModelProperty(position = 1, value = "String value representing the server name, identifier or ip address where the platform is running", example = "ip-172-31-24-152") + @Schema(description = "String value representing the server name, identifier or ip address where the platform is running", example = "ip-172-31-24-152") protected String server; - @ApiModelProperty(position = 2, value = "The minimum number of successfully processed messages", example = "25") + @Schema(description = "The minimum number of successfully processed messages", example = "25") protected Integer minMessagesProcessed; - @ApiModelProperty(position = 3, value = "The maximum number of successfully processed messages", example = "250") + @Schema(description = "The maximum number of successfully processed messages", example = "250") protected Integer maxMessagesProcessed; - @ApiModelProperty(position = 4, value = "The minimum number of errors occurred during messages processing", example = "30") + @Schema(description = "The minimum number of errors occurred during messages processing", example = "30") protected Integer minErrorsOccurred; - @ApiModelProperty(position = 5, value = "The maximum number of errors occurred during messages processing", example = "300") + @Schema(description = "The maximum number of errors occurred during messages processing", example = "300") protected Integer maxErrorsOccurred; @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmCommentId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmCommentId.java index 1cccaa4147..fdd8902127 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmCommentId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmCommentId.java @@ -17,11 +17,11 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModel; +import io.swagger.v3.oas.annotations.media.Schema; import java.util.UUID; -@ApiModel +@Schema public class AlarmCommentId extends UUIDBased { private static final long serialVersionUID = 1L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java index 74e61ce9f3..be60f8f329 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java @@ -17,13 +17,12 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@ApiModel +@Schema public class AlarmId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -37,7 +36,7 @@ public class AlarmId extends UUIDBased implements EntityId { return new AlarmId(UUID.fromString(alarmId)); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "ALARM", allowableValues = "ALARM") + @Schema(required = true, description = "string", example = "ALARM", allowableValues = "ALARM") @Override public EntityType getEntityType() { return EntityType.ALARM; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java index 50e24a0f22..0cdf879eac 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java @@ -17,13 +17,12 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@ApiModel +@Schema public class ApiUsageStateId extends UUIDBased implements EntityId { @JsonCreator @@ -35,7 +34,7 @@ public class ApiUsageStateId extends UUIDBased implements EntityId { return new ApiUsageStateId(UUID.fromString(userId)); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "API_USAGE_STATE", allowableValues = "API_USAGE_STATE") + @Schema(required = true, description = "string", example = "API_USAGE_STATE", allowableValues = "API_USAGE_STATE") @Override public EntityType getEntityType() { return EntityType.API_USAGE_STATE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java index 593c305a07..01fdc7680b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java @@ -17,13 +17,12 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@ApiModel +@Schema public class AssetId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -37,7 +36,7 @@ public class AssetId extends UUIDBased implements EntityId { return new AssetId(UUID.fromString(assetId)); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "ASSET", allowableValues = "ASSET") + @Schema(required = true, description = "string", example = "ASSET", allowableValues = "ASSET") @Override public EntityType getEntityType() { return EntityType.ASSET; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.java index 5de08e53e5..7fbf28c4d3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; @@ -35,7 +35,7 @@ public class AssetProfileId extends UUIDBased implements EntityId { return new AssetProfileId(UUID.fromString(assetProfileId)); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "ASSET_PROFILE", allowableValues = "ASSET_PROFILE") + @Schema(required = true, description = "string", example = "ASSET_PROFILE", allowableValues = "ASSET_PROFILE") @Override public EntityType getEntityType() { return EntityType.ASSET_PROFILE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java index beaa331ecf..dccd89b73b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java @@ -17,13 +17,12 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@ApiModel +@Schema public final class CustomerId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -33,7 +32,7 @@ public final class CustomerId extends UUIDBased implements EntityId { super(id); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "CUSTOMER", allowableValues = "CUSTOMER") + @Schema(required = true, description = "string", example = "CUSTOMER", allowableValues = "CUSTOMER") @Override public EntityType getEntityType() { return EntityType.CUSTOMER; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java index 740a801651..3af27d5457 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java @@ -17,13 +17,12 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@ApiModel +@Schema public class DashboardId extends UUIDBased implements EntityId { @JsonCreator @@ -35,7 +34,7 @@ public class DashboardId extends UUIDBased implements EntityId { return new DashboardId(UUID.fromString(dashboardId)); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "DASHBOARD", allowableValues = "DASHBOARD") + @Schema(required = true, description = "string", example = "DASHBOARD", allowableValues = "DASHBOARD") @Override public EntityType getEntityType() { return EntityType.DASHBOARD; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java index e39c5805ce..25c1fb6be4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java @@ -17,13 +17,12 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@ApiModel +@Schema public class DeviceId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -38,7 +37,7 @@ public class DeviceId extends UUIDBased implements EntityId { } @Override - @ApiModelProperty(position = 2, required = true, value = "string", example = "DEVICE", allowableValues = "DEVICE") + @Schema(required = true, description = "string", example = "DEVICE", allowableValues = "DEVICE") public EntityType getEntityType() { return EntityType.DEVICE; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceProfileId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceProfileId.java index 26bc6d2b96..25c22ce8c1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceProfileId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceProfileId.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; @@ -35,7 +35,7 @@ public class DeviceProfileId extends UUIDBased implements EntityId { return new DeviceProfileId(UUID.fromString(deviceProfileId)); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "DEVICE_PROFILE", allowableValues = "DEVICE_PROFILE") + @Schema(required = true, description = "string", example = "DEVICE_PROFILE", allowableValues = "DEVICE_PROFILE") @Override public EntityType getEntityType() { return EntityType.DEVICE_PROFILE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java index fb96f15740..b5a6632b44 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java @@ -18,7 +18,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType; import org.thingsboard.server.common.data.EntityType; @@ -41,7 +41,7 @@ public class EdgeId extends UUIDBased implements EntityId { return new EdgeId(UUID.fromString(edgeId)); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "EDGE", allowableValues = "EDGE") + @Schema(required = true, description = "string", example = "EDGE", allowableValues = "EDGE") @Override public EntityType getEntityType() { return EntityType.EDGE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java index 3f112e92ed..a6addb1d11 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java @@ -18,8 +18,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.io.Serializable; @@ -31,15 +30,15 @@ import java.util.UUID; @JsonDeserialize(using = EntityIdDeserializer.class) @JsonSerialize(using = EntityIdSerializer.class) -@ApiModel +@Schema public interface EntityId extends HasUUID, Serializable { //NOSONAR, the constant is closely related to EntityId UUID NULL_UUID = UUID.fromString("13814000-1dd2-11b2-8080-808080808080"); - @ApiModelProperty(position = 1, required = true, value = "ID of the entity, time-based UUID v1", example = "784f394c-42b6-435a-983c-b7beff2784f9") + @Schema(required = true, description = "ID of the entity, time-based UUID v1", example = "784f394c-42b6-435a-983c-b7beff2784f9") UUID getId(); - @ApiModelProperty(position = 2, required = true, example = "DEVICE") + @Schema(required = true, example = "DEVICE") EntityType getEntityType(); @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java index 9cf7e83fa3..0a57acb813 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; @@ -38,7 +38,7 @@ public class EntityViewId extends UUIDBased implements EntityId { return new EntityViewId(UUID.fromString(entityViewID)); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "ENTITY_VIEW", allowableValues = "ENTITY_VIEW") + @Schema(required = true, description = "string", example = "ENTITY_VIEW", allowableValues = "ENTITY_VIEW") @Override public EntityType getEntityType() { return EntityType.ENTITY_VIEW; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java index 8977da637b..2799ecfdab 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; @@ -35,7 +35,7 @@ public class OtaPackageId extends UUIDBased implements EntityId { return new OtaPackageId(UUID.fromString(firmwareId)); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "OTA_PACKAGE", allowableValues = "OTA_PACKAGE") + @Schema(required = true, description = "string", example = "OTA_PACKAGE", allowableValues = "OTA_PACKAGE") @Override public EntityType getEntityType() { return EntityType.OTA_PACKAGE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java index 7a3045c8a8..4f640303b9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; @@ -31,7 +31,7 @@ public final class RpcId extends UUIDBased implements EntityId { super(id); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "RPC", allowableValues = "RPC") + @Schema(required = true, description = "string", example = "RPC", allowableValues = "RPC") @Override public EntityType getEntityType() { return EntityType.RPC; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java index cb3e2ca334..a4e4b9019c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; @@ -29,7 +29,7 @@ public class RuleChainId extends UUIDBased implements EntityId { super(id); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "RULE_CHAIN", allowableValues = "RULE_CHAIN") + @Schema(required = true, description = "string", example = "RULE_CHAIN", allowableValues = "RULE_CHAIN") @Override public EntityType getEntityType() { return EntityType.RULE_CHAIN; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java index db410dfea2..a4fc103ada 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; @@ -29,7 +29,7 @@ public class RuleNodeId extends UUIDBased implements EntityId { super(id); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "RULE_NODE", allowableValues = "RULE_NODE") + @Schema(required = true, description = "string", example = "RULE_NODE", allowableValues = "RULE_NODE") @Override public EntityType getEntityType() { return EntityType.RULE_NODE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java index e2716b0f43..eb1ce5cd13 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; @@ -31,7 +31,7 @@ public class TbResourceId extends UUIDBased implements EntityId { super(id); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "TB_RESOURCE", allowableValues = "TB_RESOURCE") + @Schema(required = true, description = "string", example = "TB_RESOURCE", allowableValues = "TB_RESOURCE") @Override public EntityType getEntityType() { return EntityType.TB_RESOURCE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java index 6f5fb09927..9a4264ca60 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java @@ -18,7 +18,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType; import org.thingsboard.server.common.data.EntityType; @@ -50,7 +50,7 @@ public final class TenantId extends UUIDBased implements EntityId { return this.equals(SYS_TENANT_ID); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "TENANT", allowableValues = "TENANT") + @Schema(required = true, description = "string", example = "TENANT", allowableValues = "TENANT") @Override public EntityType getEntityType() { return EntityType.TENANT; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantProfileId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantProfileId.java index f1e67a9435..e73d94df9f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantProfileId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantProfileId.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; @@ -35,7 +35,7 @@ public class TenantProfileId extends UUIDBased implements EntityId { return new TenantProfileId(UUID.fromString(tenantProfileId)); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "TENANT_PROFILE", allowableValues = "TENANT_PROFILE") + @Schema(required = true, description = "string", example = "TENANT_PROFILE", allowableValues = "TENANT_PROFILE") @Override public EntityType getEntityType() { return EntityType.TENANT_PROFILE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java index b234ee5961..5b52bd0af9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java @@ -15,13 +15,12 @@ */ package org.thingsboard.server.common.data.id; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import java.io.Serializable; import java.util.UUID; -@ApiModel +@Schema public abstract class UUIDBased implements HasUUID, Serializable { private static final long serialVersionUID = 1L; @@ -37,7 +36,7 @@ public abstract class UUIDBased implements HasUUID, Serializable { this.id = id; } - @ApiModelProperty(position = 1, required = true, value = "string", example = "784f394c-42b6-435a-983c-b7beff2784f9") + @Schema(required = true, description = "string", example = "784f394c-42b6-435a-983c-b7beff2784f9") public UUID getId() { return id; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java index 999e762a12..4053ada987 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; @@ -33,7 +33,7 @@ public class UserId extends UUIDBased implements EntityId { return new UserId(UUID.fromString(userId)); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "USER", allowableValues = "USER") + @Schema(required = true, description = "string", example = "USER", allowableValues = "USER") @Override public EntityType getEntityType() { return EntityType.USER; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java index ff5fe2b62f..3dfef42d57 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; @@ -31,7 +31,7 @@ public final class WidgetTypeId extends UUIDBased implements EntityId { super(id); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "WIDGET_TYPE", allowableValues = "WIDGET_TYPE") + @Schema(required = true, description = "string", example = "WIDGET_TYPE", allowableValues = "WIDGET_TYPE") @Override public EntityType getEntityType() { return EntityType.WIDGET_TYPE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java index b6e6d2cc00..46509c1fe4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; @@ -31,7 +31,7 @@ public final class WidgetsBundleId extends UUIDBased implements EntityId { super(id); } - @ApiModelProperty(position = 2, required = true, value = "string", example = "WIDGETS_BUNDLE", allowableValues = "WIDGETS_BUNDLE") + @Schema(required = true, description = "string", example = "WIDGETS_BUNDLE", allowableValues = "WIDGETS_BUNDLE") @Override public EntityType getEntityType() { return EntityType.WIDGETS_BUNDLE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mInstance.java b/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mInstance.java index 382dca5415..7614501c56 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mInstance.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mInstance.java @@ -15,16 +15,15 @@ */ package org.thingsboard.server.common.data.lwm2m; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@ApiModel +@Schema @Data public class LwM2mInstance { - @ApiModelProperty(position = 1, value = "LwM2M Instance id.", example = "0") + @Schema(description = "LwM2M Instance id.", example = "0") int id; - @ApiModelProperty(position = 2, value = "LwM2M Resource observe.") + @Schema(description = "LwM2M Resource observe.") LwM2mResourceObserve[] resources; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mObject.java b/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mObject.java index 18de593227..44165bccd1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mObject.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mObject.java @@ -15,23 +15,22 @@ */ package org.thingsboard.server.common.data.lwm2m; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@ApiModel +@Schema @Data public class LwM2mObject { - @ApiModelProperty(position = 1, value = "LwM2M Object id.", example = "19") + @Schema(description = "LwM2M Object id.", example = "19") int id; - @ApiModelProperty(position = 2, value = "LwM2M Object key id.", example = "19_1.0") + @Schema(description = "LwM2M Object key id.", example = "19_1.0") String keyId; - @ApiModelProperty(position = 3, value = "LwM2M Object name.", example = "BinaryAppDataContainer") + @Schema(description = "LwM2M Object name.", example = "BinaryAppDataContainer") String name; - @ApiModelProperty(position = 4, value = "LwM2M Object multiple.", example = "true") + @Schema(description = "LwM2M Object multiple.", example = "true") boolean multiple; - @ApiModelProperty(position = 5, value = "LwM2M Object mandatory.", example = "false") + @Schema(description = "LwM2M Object mandatory.", example = "false") boolean mandatory; - @ApiModelProperty(position = 6, value = "LwM2M Object instances.") + @Schema(description = "LwM2M Object instances.") LwM2mInstance [] instances; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mResourceObserve.java b/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mResourceObserve.java index a40f422ad0..611d3ca66a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mResourceObserve.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/LwM2mResourceObserve.java @@ -15,28 +15,27 @@ */ package org.thingsboard.server.common.data.lwm2m; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; import java.util.stream.Stream; -@ApiModel +@Schema @Data @AllArgsConstructor public class LwM2mResourceObserve { - @ApiModelProperty(position = 1, value = "LwM2M Resource Observe id.", example = "0") + @Schema(description = "LwM2M Resource Observe id.", example = "0") int id; - @ApiModelProperty(position = 2, value = "LwM2M Resource Observe name.", example = "Data") + @Schema(description = "LwM2M Resource Observe name.", example = "Data") String name; - @ApiModelProperty(position = 3, value = "LwM2M Resource Observe observe.", example = "false") + @Schema(description = "LwM2M Resource Observe observe.", example = "false") boolean observe; - @ApiModelProperty(position = 4, value = "LwM2M Resource Observe attribute.", example = "false") + @Schema(description = "LwM2M Resource Observe attribute.", example = "false") boolean attribute; - @ApiModelProperty(position = 5, value = "LwM2M Resource Observe telemetry.", example = "false") + @Schema(description = "LwM2M Resource Observe telemetry.", example = "false") boolean telemetry; - @ApiModelProperty(position = 6, value = "LwM2M Resource Observe key name.", example = "data") + @Schema(description = "LwM2M Resource Observe key name.", example = "data") String keyName; public LwM2mResourceObserve(int id, String name, boolean observe, boolean attribute, boolean telemetry) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java index ba86a5781c..96ab5ccbed 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.oauth2; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; @@ -27,31 +26,31 @@ import org.thingsboard.server.common.data.validation.Length; @EqualsAndHashCode @Data @ToString -@ApiModel +@Schema public class OAuth2BasicMapperConfig { @Length(fieldName = "emailAttributeKey", max = 31) - @ApiModelProperty(value = "Email attribute key of OAuth2 principal attributes. " + + @Schema(description = "Email attribute key of OAuth2 principal attributes. " + "Must be specified for BASIC mapper type and cannot be specified for GITHUB type") private final String emailAttributeKey; @Length(fieldName = "firstNameAttributeKey", max = 31) - @ApiModelProperty(value = "First name attribute key") + @Schema(description = "First name attribute key") private final String firstNameAttributeKey; @Length(fieldName = "lastNameAttributeKey", max = 31) - @ApiModelProperty(value = "Last name attribute key") + @Schema(description = "Last name attribute key") private final String lastNameAttributeKey; - @ApiModelProperty(value = "Tenant naming strategy. For DOMAIN type, domain for tenant name will be taken from the email (substring before '@')", required = true) + @Schema(description = "Tenant naming strategy. For DOMAIN type, domain for tenant name will be taken from the email (substring before '@')", required = true) private final TenantNameStrategyType tenantNameStrategy; @Length(fieldName = "tenantNamePattern") - @ApiModelProperty(value = "Tenant name pattern for CUSTOM naming strategy. " + + @Schema(description = "Tenant name pattern for CUSTOM naming strategy. " + "OAuth2 attributes in the pattern can be used by enclosing attribute key in '%{' and '}'", example = "%{email}") private final String tenantNamePattern; @Length(fieldName = "customerNamePattern") - @ApiModelProperty(value = "Customer name pattern. When creating a user on the first OAuth2 log in, if specified, " + + @Schema(description = "Customer name pattern. When creating a user on the first OAuth2 log in, if specified, " + "customer name will be used to create or find existing customer in the platform and assign customerId to the user") private final String customerNamePattern; @Length(fieldName = "defaultDashboardName") - @ApiModelProperty(value = "Name of the tenant's dashboard to set as default dashboard for newly created user") + @Schema(description = "Name of the tenant's dashboard to set as default dashboard for newly created user") private final String defaultDashboardName; - @ApiModelProperty(value = "Whether default dashboard should be open in full screen") + @Schema(description = "Whether default dashboard should be open in full screen") private final boolean alwaysFullScreen; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java index c7770cca0f..be8d2c9d54 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.oauth2; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; @@ -26,14 +25,14 @@ import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor -@ApiModel +@Schema public class OAuth2ClientInfo { - @ApiModelProperty(value = "OAuth2 client name", example = "GitHub") + @Schema(description = "OAuth2 client name", example = "GitHub") private String name; - @ApiModelProperty(value = "Name of the icon, displayed on OAuth2 log in button", example = "github-logo") + @Schema(description = "Name of the icon, displayed on OAuth2 log in button", example = "github-logo") private String icon; - @ApiModelProperty(value = "URI for OAuth2 log in. On HTTP GET request to this URI, it redirects to the OAuth2 provider page", + @Schema(description = "URI for OAuth2 log in. On HTTP GET request to this URI, it redirects to the OAuth2 provider page", example = "/oauth2/authorization/8352f191-2b4d-11ec-9ed1-cbf57c026ecc") private String url; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java index 57945bf93e..cf56547e4d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.oauth2; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -33,45 +32,45 @@ import java.util.List; @Data @ToString @NoArgsConstructor -@ApiModel +@Schema public class OAuth2ClientRegistrationTemplate extends BaseDataWithAdditionalInfo implements HasName { @Length(fieldName = "providerId") - @ApiModelProperty(value = "OAuth2 provider identifier (e.g. its name)", required = true) + @Schema(description = "OAuth2 provider identifier (e.g. its name)", required = true) private String providerId; @Valid - @ApiModelProperty(value = "Default config for mapping OAuth2 log in response to platform entities") + @Schema(description = "Default config for mapping OAuth2 log in response to platform entities") private OAuth2MapperConfig mapperConfig; @Length(fieldName = "authorizationUri") - @ApiModelProperty(value = "Default authorization URI of the OAuth2 provider") + @Schema(description = "Default authorization URI of the OAuth2 provider") private String authorizationUri; @Length(fieldName = "accessTokenUri") - @ApiModelProperty(value = "Default access token URI of the OAuth2 provider") + @Schema(description = "Default access token URI of the OAuth2 provider") private String accessTokenUri; - @ApiModelProperty(value = "Default OAuth scopes that will be requested from OAuth2 platform") + @Schema(description = "Default OAuth scopes that will be requested from OAuth2 platform") private List scope; @Length(fieldName = "userInfoUri") - @ApiModelProperty(value = "Default user info URI of the OAuth2 provider") + @Schema(description = "Default user info URI of the OAuth2 provider") private String userInfoUri; @Length(fieldName = "userNameAttributeName") - @ApiModelProperty(value = "Default name of the username attribute in OAuth2 provider log in response") + @Schema(description = "Default name of the username attribute in OAuth2 provider log in response") private String userNameAttributeName; @Length(fieldName = "jwkSetUri") - @ApiModelProperty(value = "Default JSON Web Key URI of the OAuth2 provider") + @Schema(description = "Default JSON Web Key URI of the OAuth2 provider") private String jwkSetUri; @Length(fieldName = "clientAuthenticationMethod") - @ApiModelProperty(value = "Default client authentication method to use: 'BASIC' or 'POST'") + @Schema(description = "Default client authentication method to use: 'BASIC' or 'POST'") private String clientAuthenticationMethod; - @ApiModelProperty(value = "Comment for OAuth2 provider") + @Schema(description = "Comment for OAuth2 provider") private String comment; @Length(fieldName = "loginButtonIcon") - @ApiModelProperty(value = "Default log in button icon for OAuth2 provider") + @Schema(description = "Default log in button icon for OAuth2 provider") private String loginButtonIcon; @Length(fieldName = "loginButtonLabel") - @ApiModelProperty(value = "Default OAuth2 provider label") + @Schema(description = "Default OAuth2 provider label") private String loginButtonLabel; @Length(fieldName = "helpLink") - @ApiModelProperty(value = "Help link for OAuth2 provider") + @Schema(description = "Help link for OAuth2 provider") private String helpLink; public OAuth2ClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java index 06a175b441..71108ef137 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.oauth2; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -30,10 +29,10 @@ import lombok.ToString; @NoArgsConstructor @AllArgsConstructor @Builder -@ApiModel +@Schema public class OAuth2DomainInfo { - @ApiModelProperty(value = "Domain scheme. Mixed scheme means than both HTTP and HTTPS are going to be used", required = true) + @Schema(description = "Domain scheme. Mixed scheme means than both HTTP and HTTPS are going to be used", required = true) private SchemeType scheme; - @ApiModelProperty(value = "Domain name. Cannot be empty", required = true) + @Schema(description = "Domain name. Cannot be empty", required = true) private String name; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java index 30531c3241..5c292ef23e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.oauth2; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -32,10 +31,10 @@ import java.util.List; @Builder(toBuilder = true) @NoArgsConstructor @AllArgsConstructor -@ApiModel +@Schema public class OAuth2Info { - @ApiModelProperty("Whether OAuth2 settings are enabled or not") + @Schema(description = "Whether OAuth2 settings are enabled or not") private boolean enabled; - @ApiModelProperty(value = "List of configured OAuth2 clients. Cannot contain null values", required = true) + @Schema(description = "List of configured OAuth2 clients. Cannot contain null values", required = true) private List oauth2ParamsInfos; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java index 89c8bcaa07..0896734d84 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.oauth2; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; @@ -28,16 +28,16 @@ import jakarta.validation.Valid; @Data @ToString public class OAuth2MapperConfig { - @ApiModelProperty(value = "Whether user should be created if not yet present on the platform after successful authentication") + @Schema(description = "Whether user should be created if not yet present on the platform after successful authentication") private boolean allowUserCreation; - @ApiModelProperty(value = "Whether user credentials should be activated when user is created after successful authentication") + @Schema(description = "Whether user credentials should be activated when user is created after successful authentication") private boolean activateUser; - @ApiModelProperty(value = "Type of OAuth2 mapper. Depending on this param, different mapper config fields must be specified", required = true) + @Schema(description = "Type of OAuth2 mapper. Depending on this param, different mapper config fields must be specified", required = true) private MapperType type; @Valid - @ApiModelProperty(value = "Mapper config for BASIC and GITHUB mapper types") + @Schema(description = "Mapper config for BASIC and GITHUB mapper types") private OAuth2BasicMapperConfig basic; @Valid - @ApiModelProperty(value = "Mapper config for CUSTOM mapper type") + @Schema(description = "Mapper config for CUSTOM mapper type") private OAuth2CustomMapperConfig custom; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MobileInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MobileInfo.java index 2f6717adfc..f8999fa44b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MobileInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MobileInfo.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.oauth2; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -30,10 +29,10 @@ import lombok.ToString; @NoArgsConstructor @AllArgsConstructor @Builder -@ApiModel +@Schema public class OAuth2MobileInfo { - @ApiModelProperty(value = "Application package name. Cannot be empty", required = true) + @Schema(description = "Application package name. Cannot be empty", required = true) private String pkgName; - @ApiModelProperty(value = "Application secret. The length must be at least 16 characters", required = true) + @Schema(description = "Application secret. The length must be at least 16 characters", required = true) private String appSecret; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java index 8dcb827d71..9e34a5e598 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.oauth2; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -32,16 +31,16 @@ import java.util.List; @Builder(toBuilder = true) @NoArgsConstructor @AllArgsConstructor -@ApiModel +@Schema public class OAuth2ParamsInfo { - @ApiModelProperty(value = "List of configured domains where OAuth2 platform will redirect a user after successful " + + @Schema(description = "List of configured domains where OAuth2 platform will redirect a user after successful " + "authentication. Cannot be empty. There have to be only one domain with specific name with scheme type 'MIXED'. " + "Configured domains with the same name must have different scheme types", required = true) private List domainInfos; - @ApiModelProperty(value = "Mobile applications settings. Application package name must be unique within the list", required = true) + @Schema(description = "Mobile applications settings. Application package name must be unique within the list", required = true) private List mobileInfos; - @ApiModelProperty(value = "List of OAuth2 provider settings. Cannot be empty", required = true) + @Schema(description = "List of OAuth2 provider settings. Cannot be empty", required = true) private List clientRegistrations; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java index e212585efb..3ccbb72bdb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java @@ -16,8 +16,7 @@ package org.thingsboard.server.common.data.oauth2; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -33,34 +32,34 @@ import java.util.List; @NoArgsConstructor @AllArgsConstructor @Builder -@ApiModel +@Schema public class OAuth2RegistrationInfo { - @ApiModelProperty(value = "Config for mapping OAuth2 log in response to platform entities", required = true) + @Schema(description = "Config for mapping OAuth2 log in response to platform entities", required = true) private OAuth2MapperConfig mapperConfig; - @ApiModelProperty(value = "OAuth2 client ID. Cannot be empty", required = true) + @Schema(description = "OAuth2 client ID. Cannot be empty", required = true) private String clientId; - @ApiModelProperty(value = "OAuth2 client secret. Cannot be empty", required = true) + @Schema(description = "OAuth2 client secret. Cannot be empty", required = true) private String clientSecret; - @ApiModelProperty(value = "Authorization URI of the OAuth2 provider. Cannot be empty", required = true) + @Schema(description = "Authorization URI of the OAuth2 provider. Cannot be empty", required = true) private String authorizationUri; - @ApiModelProperty(value = "Access token URI of the OAuth2 provider. Cannot be empty", required = true) + @Schema(description = "Access token URI of the OAuth2 provider. Cannot be empty", required = true) private String accessTokenUri; - @ApiModelProperty(value = "OAuth scopes that will be requested from OAuth2 platform. Cannot be empty", required = true) + @Schema(description = "OAuth scopes that will be requested from OAuth2 platform. Cannot be empty", required = true) private List scope; - @ApiModelProperty(value = "User info URI of the OAuth2 provider") + @Schema(description = "User info URI of the OAuth2 provider") private String userInfoUri; - @ApiModelProperty(value = "Name of the username attribute in OAuth2 provider response. Cannot be empty") + @Schema(description = "Name of the username attribute in OAuth2 provider response. Cannot be empty") private String userNameAttributeName; - @ApiModelProperty(value = "JSON Web Key URI of the OAuth2 provider") + @Schema(description = "JSON Web Key URI of the OAuth2 provider") private String jwkSetUri; - @ApiModelProperty(value = "Client authentication method to use: 'BASIC' or 'POST'. Cannot be empty", required = true) + @Schema(description = "Client authentication method to use: 'BASIC' or 'POST'. Cannot be empty", required = true) private String clientAuthenticationMethod; - @ApiModelProperty(value = "OAuth2 provider label. Cannot be empty", required = true) + @Schema(description = "OAuth2 provider label. Cannot be empty", required = true) private String loginButtonLabel; - @ApiModelProperty(value = "Log in button icon for OAuth2 provider") + @Schema(description = "Log in button icon for OAuth2 provider") private String loginButtonIcon; - @ApiModelProperty(value = "List of platforms for which usage of the OAuth2 client is allowed (empty for all allowed)") + @Schema(description = "List of platforms for which usage of the OAuth2 client is allowed (empty for all allowed)") private List platforms; - @ApiModelProperty(value = "Additional info of OAuth2 client (e.g. providerName)", required = true) + @Schema(description = "Additional info of OAuth2 client (e.g. providerName)", required = true) private JsonNode additionalInfo; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java index 44f9ab0a2f..b2e71bf947 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.objects; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.NoArgsConstructor; @@ -28,15 +27,15 @@ import java.util.List; * Created by Victor Basanets on 9/05/2017. */ @Data -@ApiModel +@Schema @NoArgsConstructor public class AttributesEntityView implements Serializable { - @ApiModelProperty(position = 1, required = true, value = "List of client-side attribute keys to expose", example = "currentConfiguration") + @Schema(required = true, description = "List of client-side attribute keys to expose", example = "currentConfiguration") private List cs = new ArrayList<>(); - @ApiModelProperty(position = 3, required = true, value = "List of server-side attribute keys to expose", example = "model") + @Schema(required = true, description = "List of server-side attribute keys to expose", example = "model") private List ss = new ArrayList<>(); - @ApiModelProperty(position = 2, required = true, value = "List of shared attribute keys to expose", example = "targetConfiguration") + @Schema(required = true, description = "List of shared attribute keys to expose", example = "targetConfiguration") private List sh = new ArrayList<>(); public AttributesEntityView(List cs, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java index 2dd115138e..ad538128b7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.objects; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.NoArgsConstructor; @@ -27,14 +26,14 @@ import java.util.List; /** * Created by Victor Basanets on 9/05/2017. */ -@ApiModel +@Schema @Data @NoArgsConstructor public class TelemetryEntityView implements Serializable { - @ApiModelProperty(position = 1, required = true, value = "List of time-series data keys to expose", example = "temperature, humidity") + @Schema(required = true, description = "List of time-series data keys to expose", example = "temperature, humidity") private List timeseries; - @ApiModelProperty(position = 2, required = true, value = "JSON object with attributes to expose") + @Schema(required = true, description = "JSON object with attributes to expose") private AttributesEntityView attributes; public TelemetryEntityView(List timeseries, AttributesEntityView attributes) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java index 2ae94a0064..4f510ebb2b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java @@ -17,15 +17,14 @@ package org.thingsboard.server.common.data.page; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import java.util.Collections; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; -@ApiModel +@Schema public class PageData { private final List data; @@ -48,22 +47,22 @@ public class PageData { this.hasNext = hasNext; } - @ApiModelProperty(position = 1, value = "Array of the entities", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Array of the entities", accessMode = Schema.AccessMode.READ_ONLY) public List getData() { return data; } - @ApiModelProperty(position = 2, value = "Total number of available pages. Calculated based on the 'pageSize' request parameter and total number of entities that match search criteria", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Total number of available pages. Calculated based on the 'pageSize' request parameter and total number of entities that match search criteria", accessMode = Schema.AccessMode.READ_ONLY) public int getTotalPages() { return totalPages; } - @ApiModelProperty(position = 3, value = "Total number of elements in all available pages", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Total number of elements in all available pages", accessMode = Schema.AccessMode.READ_ONLY) public long getTotalElements() { return totalElements; } - @ApiModelProperty(position = 4, value = "'false' value indicates the end of the result set", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "'false' value indicates the end of the result set", accessMode = Schema.AccessMode.READ_ONLY) @JsonProperty("hasNext") public boolean hasNext() { return hasNext; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java index 1c186f2697..3387f85e6f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java @@ -16,8 +16,7 @@ package org.thingsboard.server.common.data.plugin; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -29,27 +28,27 @@ import org.thingsboard.server.common.data.validation.Length; /** * @author Andrew Shvayka */ -@ApiModel +@Schema @ToString public class ComponentDescriptor extends BaseData { private static final long serialVersionUID = 1L; - @ApiModelProperty(position = 3, value = "Type of the Rule Node", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Type of the Rule Node", accessMode = Schema.AccessMode.READ_ONLY) @Getter @Setter private ComponentType type; - @ApiModelProperty(position = 4, value = "Scope of the Rule Node. Always set to 'TENANT', since no rule chains on the 'SYSTEM' level yet.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, allowableValues = "TENANT", example = "TENANT") + @Schema(description = "Scope of the Rule Node. Always set to 'TENANT', since no rule chains on the 'SYSTEM' level yet.", accessMode = Schema.AccessMode.READ_ONLY, allowableValues = "TENANT", example = "TENANT") @Getter @Setter private ComponentScope scope; - @ApiModelProperty(position = 5, value = "Clustering mode of the RuleNode. This mode represents the ability to start Rule Node in multiple microservices.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, allowableValues = "USER_PREFERENCE, ENABLED, SINGLETON", example = "ENABLED") + @Schema(description = "Clustering mode of the RuleNode. This mode represents the ability to start Rule Node in multiple microservices.", accessMode = Schema.AccessMode.READ_ONLY, allowableValues = "USER_PREFERENCE, ENABLED, SINGLETON", example = "ENABLED") @Getter @Setter private ComponentClusteringMode clusteringMode; @Length(fieldName = "name") - @ApiModelProperty(position = 6, value = "Name of the Rule Node. Taken from the @RuleNode annotation.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, example = "Custom Rule Node") + @Schema(description = "Name of the Rule Node. Taken from the @RuleNode annotation.", accessMode = Schema.AccessMode.READ_ONLY, example = "Custom Rule Node") @Getter @Setter private String name; - @ApiModelProperty(position = 7, value = "Full name of the Java class that implements the Rule Engine Node interface.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, example = "com.mycompany.CustomRuleNode") + @Schema(description = "Full name of the Java class that implements the Rule Engine Node interface.", accessMode = Schema.AccessMode.READ_ONLY, example = "com.mycompany.CustomRuleNode") @Getter @Setter private String clazz; - @ApiModelProperty(position = 8, value = "Complex JSON object that represents the Rule Node configuration.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Complex JSON object that represents the Rule Node configuration.", accessMode = Schema.AccessMode.READ_ONLY) @Getter @Setter private transient JsonNode configurationDescriptor; @Length(fieldName = "actions") - @ApiModelProperty(position = 9, value = "Rule Node Actions. Deprecated. Always null.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Rule Node Actions. Deprecated. Always null.", accessMode = Schema.AccessMode.READ_ONLY) @Getter @Setter private String actions; public ComponentDescriptor() { @@ -70,7 +69,7 @@ public class ComponentDescriptor extends BaseData { this.actions = plugin.getActions(); } - @ApiModelProperty(position = 1, value = "JSON object with the descriptor Id. " + + @Schema(description = "JSON object with the descriptor Id. " + "Specify existing descriptor id to update the descriptor. " + "Referencing non-existing descriptor Id will cause error. " + "Omit this field to create new descriptor." ) @@ -79,7 +78,7 @@ public class ComponentDescriptor extends BaseData { return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the descriptor creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the descriptor creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityCountQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityCountQuery.java index 2665abe7e9..e85cbc2fb8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityCountQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityCountQuery.java @@ -15,14 +15,14 @@ */ package org.thingsboard.server.common.data.query; -import io.swagger.annotations.ApiModel; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.ToString; import java.util.Collections; import java.util.List; -@ApiModel +@Schema @ToString public class EntityCountQuery { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKey.java index f5362dd824..76d90087fe 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKey.java @@ -15,12 +15,12 @@ */ package org.thingsboard.server.common.data.query; -import io.swagger.annotations.ApiModel; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; -@ApiModel +@Schema @Data public class EntityKey implements Serializable { private static final long serialVersionUID = -6421575477523085543L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/KeyFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/KeyFilter.java index ad5d42e31d..c8999cce2b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/KeyFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/KeyFilter.java @@ -15,12 +15,12 @@ */ package org.thingsboard.server.common.data.query; -import io.swagger.annotations.ApiModel; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; -@ApiModel +@Schema @Data public class KeyFilter implements Serializable { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java index fa2d52f3f4..ecde8d82dc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java @@ -17,8 +17,7 @@ package org.thingsboard.server.common.data.relation; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.BaseDataWithAdditionalInfo; import org.thingsboard.server.common.data.id.EntityId; @@ -27,7 +26,7 @@ import org.thingsboard.server.common.data.validation.Length; import java.io.Serializable; @Slf4j -@ApiModel +@Schema public class EntityRelation implements Serializable { private static final long serialVersionUID = 2807343040519543363L; @@ -73,7 +72,7 @@ public class EntityRelation implements Serializable { this.additionalInfo = entityRelation.getAdditionalInfo(); } - @ApiModelProperty(position = 1, value = "JSON object with [from] Entity Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with [from] Entity Id.", accessMode = Schema.AccessMode.READ_ONLY) public EntityId getFrom() { return from; } @@ -82,7 +81,7 @@ public class EntityRelation implements Serializable { this.from = from; } - @ApiModelProperty(position = 2, value = "JSON object with [to] Entity Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with [to] Entity Id.", accessMode = Schema.AccessMode.READ_ONLY) public EntityId getTo() { return to; } @@ -91,7 +90,7 @@ public class EntityRelation implements Serializable { this.to = to; } - @ApiModelProperty(position = 3, value = "String value of relation type.", example = "Contains") + @Schema(description = "String value of relation type.", example = "Contains") public String getType() { return type; } @@ -100,7 +99,7 @@ public class EntityRelation implements Serializable { this.type = type; } - @ApiModelProperty(position = 4, value = "Represents the type group of the relation.", example = "COMMON") + @Schema(description = "Represents the type group of the relation.", example = "COMMON") public RelationTypeGroup getTypeGroup() { return typeGroup; } @@ -109,7 +108,7 @@ public class EntityRelation implements Serializable { this.typeGroup = typeGroup; } - @ApiModelProperty(position = 5, value = "Additional parameters of the relation", dataType = "com.fasterxml.jackson.databind.JsonNode") + @Schema(description = "Additional parameters of the relation",implementation = com.fasterxml.jackson.databind.JsonNode.class) public JsonNode getAdditionalInfo() { return BaseDataWithAdditionalInfo.getJson(() -> additionalInfo, () -> additionalInfoBytes); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java index 44acbb6a5f..364d40b062 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.relation; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; public class EntityRelationInfo extends EntityRelation { @@ -32,7 +32,7 @@ public class EntityRelationInfo extends EntityRelation { super(entityRelation); } - @ApiModelProperty(position = 6, value = "Name of the entity for [from] direction.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, example = "A4B72CCDFF33") + @Schema(description = "Name of the entity for [from] direction.", accessMode = Schema.AccessMode.READ_ONLY, example = "A4B72CCDFF33") public String getFromName() { return fromName; } @@ -41,7 +41,7 @@ public class EntityRelationInfo extends EntityRelation { this.fromName = fromName; } - @ApiModelProperty(position = 7, value = "Name of the entity for [to] direction.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, example = "A4B72CCDFF35") + @Schema(description = "Name of the entity for [to] direction.", accessMode = Schema.AccessMode.READ_ONLY, example = "A4B72CCDFF35") public String getToName() { return toName; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationsQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationsQuery.java index b3f052d59e..cefd2fa4f4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationsQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationsQuery.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.relation; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; @@ -25,12 +24,12 @@ import java.util.List; * Created by ashvayka on 02.05.17. */ @Data -@ApiModel +@Schema public class EntityRelationsQuery { - @ApiModelProperty(position = 2, value = "Main search parameters.") + @Schema(description = "Main search parameters.") private RelationsSearchParameters parameters; - @ApiModelProperty(position = 1, value = "Main filters.") + @Schema(description = "Main filters.") private List filters; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationEntityTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationEntityTypeFilter.java index 80ff4fe874..b8e5e94a19 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationEntityTypeFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationEntityTypeFilter.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.relation; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; import org.thingsboard.server.common.data.EntityType; @@ -28,12 +27,12 @@ import java.util.List; */ @Data @AllArgsConstructor -@ApiModel +@Schema public class RelationEntityTypeFilter { - @ApiModelProperty(position = 1, value = "Type of the relation between root entity and other entity (e.g. 'Contains' or 'Manages').", example = "Contains") + @Schema(description = "Type of the relation between root entity and other entity (e.g. 'Contains' or 'Manages').", example = "Contains") private String relationType; - @ApiModelProperty(position = 2, value = "Array of entity types to filter the related entities (e.g. 'DEVICE', 'ASSET').") + @Schema(description = "Array of entity types to filter the related entities (e.g. 'DEVICE', 'ASSET').") private List entityTypes; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java index 23a3a6407c..cfc338ff55 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java @@ -16,8 +16,7 @@ package org.thingsboard.server.common.data.relation; import com.fasterxml.jackson.annotation.JsonIgnore; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; import org.thingsboard.server.common.data.EntityType; @@ -29,22 +28,22 @@ import java.util.UUID; /** * Created by ashvayka on 03.05.17. */ -@ApiModel +@Schema @Data @AllArgsConstructor public class RelationsSearchParameters { - @ApiModelProperty(position = 1, value = "Root entity id to start search from.", example = "784f394c-42b6-435a-983c-b7beff2784f9") + @Schema(description = "Root entity id to start search from.", example = "784f394c-42b6-435a-983c-b7beff2784f9") private UUID rootId; - @ApiModelProperty(position = 2, value = "Type of the root entity.") + @Schema(description = "Type of the root entity.") private EntityType rootType; - @ApiModelProperty(position = 3, value = "Type of the root entity.") + @Schema(description = "Type of the root entity.") private EntitySearchDirection direction; - @ApiModelProperty(position = 4, value = "Type of the relation.") + @Schema(description = "Type of the relation.") private RelationTypeGroup relationTypeGroup; - @ApiModelProperty(position = 5, value = "Maximum level of the search depth.") + @Schema(description = "Maximum level of the search depth.") private int maxLevel = 1; - @ApiModelProperty(position = 6, value = "Fetch entities that match the last level of search. Useful to find Devices that are strictly 'maxLevel' relations away from the root entity.") + @Schema(description = "Fetch entities that match the last level of search. Useful to find Devices that are strictly 'maxLevel' relations away from the root entity.") private boolean fetchLastLevelOnly; public RelationsSearchParameters(EntityId entityId, EntitySearchDirection direction, int maxLevel, boolean fetchLastLevelOnly) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java index 154a725761..d764a1fe0b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java @@ -16,8 +16,7 @@ package org.thingsboard.server.common.data.rpc; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; @@ -26,24 +25,24 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.TenantId; -@ApiModel +@Schema @Data @EqualsAndHashCode(callSuper = true) public class Rpc extends BaseData implements HasTenantId { - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; - @ApiModelProperty(position = 4, value = "JSON object with Device Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Device Id.", accessMode = Schema.AccessMode.READ_ONLY) private DeviceId deviceId; - @ApiModelProperty(position = 5, value = "Expiration time of the request.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Expiration time of the request.", accessMode = Schema.AccessMode.READ_ONLY) private long expirationTime; - @ApiModelProperty(position = 6, value = "The request body that will be used to send message to device.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "The request body that will be used to send message to device.", accessMode = Schema.AccessMode.READ_ONLY) private JsonNode request; - @ApiModelProperty(position = 7, value = "The response from the device.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "The response from the device.", accessMode = Schema.AccessMode.READ_ONLY) private JsonNode response; - @ApiModelProperty(position = 8, value = "The current status of the RPC call.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "The current status of the RPC call.", accessMode = Schema.AccessMode.READ_ONLY) private RpcStatus status; - @ApiModelProperty(position = 9, value = "Additional info used in the rule engine to process the updates to the RPC state.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Additional info used in the rule engine to process the updates to the RPC state.", accessMode = Schema.AccessMode.READ_ONLY) private JsonNode additionalInfo; public Rpc() { @@ -65,13 +64,13 @@ public class Rpc extends BaseData implements HasTenantId { this.additionalInfo = rpc.getAdditionalInfo(); } - @ApiModelProperty(position = 1, value = "JSON object with the rpc Id. Referencing non-existing rpc Id will cause error.") + @Schema(description = "JSON object with the rpc Id. Referencing non-existing rpc Id will cause error.") @Override public RpcId getId() { return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the rpc creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the rpc creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/DefaultRuleChainCreateRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/DefaultRuleChainCreateRequest.java index 0df8b4e03a..3ab6912824 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/DefaultRuleChainCreateRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/DefaultRuleChainCreateRequest.java @@ -15,21 +15,20 @@ */ package org.thingsboard.server.common.data.rule; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.io.Serializable; -@ApiModel +@Schema @Data @Slf4j public class DefaultRuleChainCreateRequest implements Serializable { private static final long serialVersionUID = 5600333716030561537L; - @ApiModelProperty(position = 1, required = true, value = "Name of the new rule chain", example = "Root Rule Chain") + @Schema(required = true, description = "Name of the new rule chain", example = "Root Rule Chain") private String name; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/NodeConnectionInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/NodeConnectionInfo.java index 4bccbf6366..febf3e29f0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/NodeConnectionInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/NodeConnectionInfo.java @@ -15,20 +15,19 @@ */ package org.thingsboard.server.common.data.rule; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * Created by ashvayka on 21.03.18. */ -@ApiModel +@Schema @Data public class NodeConnectionInfo { - @ApiModelProperty(position = 1, required = true, value = "Index of rule node in the 'nodes' array of the RuleChainMetaData. Indicates the 'from' part of the connection.") + @Schema(required = true, description = "Index of rule node in the 'nodes' array of the RuleChainMetaData. Indicates the 'from' part of the connection.") private int fromIndex; - @ApiModelProperty(position = 2, required = true, value = "Index of rule node in the 'nodes' array of the RuleChainMetaData. Indicates the 'to' part of the connection.") + @Schema(required = true, description = "Index of rule node in the 'nodes' array of the RuleChainMetaData. Indicates the 'to' part of the connection.") private int toIndex; - @ApiModelProperty(position = 3, required = true, value = "Type of the relation. Typically indicated the result of processing by the 'from' rule node. For example, 'Success' or 'Failure'") + @Schema(required = true, description = "Type of the relation. Typically indicated the result of processing by the 'from' rule node. For example, 'Success' or 'Failure'") private String type; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java index edb70cefda..9d4099755e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java @@ -17,8 +17,7 @@ package org.thingsboard.server.common.data.rule; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; @@ -32,7 +31,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -@ApiModel +@Schema @Data @EqualsAndHashCode(callSuper = true) @Slf4j @@ -40,21 +39,21 @@ public class RuleChain extends BaseDataWithAdditionalInfo implement private static final long serialVersionUID = -5656679015121935465L; - @ApiModelProperty(position = 3, required = true, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(required = true, description = "JSON object with Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; @NoXss @Length(fieldName = "name") - @ApiModelProperty(position = 4, required = true, value = "Rule Chain name", example = "Humidity data processing") + @Schema(required = true, description = "Rule Chain name", example = "Humidity data processing") private String name; - @ApiModelProperty(position = 5, value = "Rule Chain type. 'EDGE' rule chains are processing messages on the edge devices only.", example = "A4B72CCDFF33") + @Schema(description = "Rule Chain type. 'EDGE' rule chains are processing messages on the edge devices only.", example = "A4B72CCDFF33") private RuleChainType type; - @ApiModelProperty(position = 6, value = "JSON object with Rule Chain Id. Pointer to the first rule node that should receive all messages pushed to this rule chain.") + @Schema(description = "JSON object with Rule Chain Id. Pointer to the first rule node that should receive all messages pushed to this rule chain.") private RuleNodeId firstRuleNodeId; - @ApiModelProperty(position = 7, value = "Indicates root rule chain. The root rule chain process messages from all devices and entities by default. User may configure default rule chain per device profile.") + @Schema(description = "Indicates root rule chain. The root rule chain process messages from all devices and entities by default. User may configure default rule chain per device profile.") private boolean root; - @ApiModelProperty(position = 8, value = "Reserved for future usage.") + @Schema(description = "Reserved for future usage.") private boolean debugMode; - @ApiModelProperty(position = 9, value = "Reserved for future usage. The actual list of rule nodes and their relations is stored in the database separately.") + @Schema(description = "Reserved for future usage. The actual list of rule nodes and their relations is stored in the database separately.") private transient JsonNode configuration; private RuleChainId externalId; @@ -86,7 +85,7 @@ public class RuleChain extends BaseDataWithAdditionalInfo implement return name; } - @ApiModelProperty(position = 1, value = "JSON object with the Rule Chain Id. " + + @Schema(description = "JSON object with the Rule Chain Id. " + "Specify this field to update the Rule Chain. " + "Referencing non-existing Rule Chain Id will cause error. " + "Omit this field to create new rule chain." ) @@ -95,7 +94,7 @@ public class RuleChain extends BaseDataWithAdditionalInfo implement return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the rule chain creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the rule chain creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainConnectionInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainConnectionInfo.java index edd22564f1..87a7dde0ca 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainConnectionInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainConnectionInfo.java @@ -16,23 +16,22 @@ package org.thingsboard.server.common.data.rule; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.id.RuleChainId; /** * Created by ashvayka on 21.03.18. */ -@ApiModel +@Schema @Data public class RuleChainConnectionInfo { - @ApiModelProperty(position = 1, required = true, value = "Index of rule node in the 'nodes' array of the RuleChainMetaData. Indicates the 'from' part of the connection.") + @Schema(required = true, description = "Index of rule node in the 'nodes' array of the RuleChainMetaData. Indicates the 'from' part of the connection.") private int fromIndex; - @ApiModelProperty(position = 2, required = true, value = "JSON object with the Rule Chain Id.") + @Schema(required = true, description = "JSON object with the Rule Chain Id.") private RuleChainId targetRuleChainId; - @ApiModelProperty(position = 3, required = true, value = "JSON object with the additional information about the connection.") + @Schema(required = true, description = "JSON object with the additional information about the connection.") private JsonNode additionalInfo; - @ApiModelProperty(position = 4, required = true, value = "Type of the relation. Typically indicated the result of processing by the 'from' rule node. For example, 'Success' or 'Failure'") + @Schema(required = true, description = "Type of the relation. Typically indicated the result of processing by the 'from' rule node. For example, 'Success' or 'Failure'") private String type; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainData.java index ee53811ce1..74aaaa2c12 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainData.java @@ -15,18 +15,17 @@ */ package org.thingsboard.server.common.data.rule; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; -@ApiModel +@Schema @Data public class RuleChainData { - @ApiModelProperty(position = 1, required = true, value = "List of the Rule Chain objects.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(required = true, description = "List of the Rule Chain objects.", accessMode = Schema.AccessMode.READ_ONLY) List ruleChains; - @ApiModelProperty(position = 2, required = true, value = "List of the Rule Chain metadata objects.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(required = true, description = "List of the Rule Chain metadata objects.", accessMode = Schema.AccessMode.READ_ONLY) List metadata; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java index 6ebdeed1dc..79d2f75254 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.rule; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.id.RuleChainId; @@ -26,23 +25,23 @@ import java.util.List; /** * Created by igor on 3/13/18. */ -@ApiModel +@Schema @Data public class RuleChainMetaData { - @ApiModelProperty(position = 1, required = true, value = "JSON object with Rule Chain Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(required = true, description = "JSON object with Rule Chain Id.", accessMode = Schema.AccessMode.READ_ONLY) private RuleChainId ruleChainId; - @ApiModelProperty(position = 2, required = true, value = "Index of the first rule node in the 'nodes' list") + @Schema(required = true, description = "Index of the first rule node in the 'nodes' list") private Integer firstNodeIndex; - @ApiModelProperty(position = 3, required = true, value = "List of rule node JSON objects") + @Schema(required = true, description = "List of rule node JSON objects") private List nodes; - @ApiModelProperty(position = 4, required = true, value = "List of JSON objects that represent connections between rule nodes") + @Schema(required = true, description = "List of JSON objects that represent connections between rule nodes") private List connections; - @ApiModelProperty(position = 5, required = true, value = "List of JSON objects that represent connections between rule nodes and other rule chains.") + @Schema(required = true, description = "List of JSON objects that represent connections between rule nodes and other rule chains.") private List ruleChainConnections; public void addConnectionInfo(int fromIndex, int toIndex, String type) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainOutputLabelsUsage.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainOutputLabelsUsage.java index 7a2b2c5b8d..f148aaa9f4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainOutputLabelsUsage.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainOutputLabelsUsage.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.rule; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.RuleChainId; @@ -24,21 +23,21 @@ import org.thingsboard.server.common.data.id.RuleNodeId; import java.util.Set; -@ApiModel +@Schema @Data @Slf4j public class RuleChainOutputLabelsUsage { - @ApiModelProperty(position = 1, required = true, value = "Rule Chain Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(required = true, description = "Rule Chain Id", accessMode = Schema.AccessMode.READ_ONLY) private RuleChainId ruleChainId; - @ApiModelProperty(position = 2, required = true, value = "Rule Node Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(required = true, description = "Rule Node Id", accessMode = Schema.AccessMode.READ_ONLY) private RuleNodeId ruleNodeId; - @ApiModelProperty(position = 3, required = true, value = "Rule Chain Name", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(required = true, description = "Rule Chain Name", accessMode = Schema.AccessMode.READ_ONLY) private String ruleChainName; - @ApiModelProperty(position = 4, required = true, value = "Rule Node Name", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(required = true, description = "Rule Node Name", accessMode = Schema.AccessMode.READ_ONLY) private String ruleNodeName; - @ApiModelProperty(position = 5, required = true, value = "Output labels", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(required = true, description = "Output labels", accessMode = Schema.AccessMode.READ_ONLY) private Set labels; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java index 16a127e48a..1926bb8c3c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java @@ -17,8 +17,7 @@ package org.thingsboard.server.common.data.rule; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; @@ -29,7 +28,7 @@ import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -@ApiModel +@Schema @Data @EqualsAndHashCode(callSuper = true) @Slf4j @@ -37,20 +36,20 @@ public class RuleNode extends BaseDataWithAdditionalInfo implements private static final long serialVersionUID = -5656679015121235465L; - @ApiModelProperty(position = 3, value = "JSON object with the Rule Chain Id. ", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with the Rule Chain Id. ", accessMode = Schema.AccessMode.READ_ONLY) private RuleChainId ruleChainId; @Length(fieldName = "type") - @ApiModelProperty(position = 4, value = "Full Java Class Name of the rule node implementation. ", example = "com.mycompany.iot.rule.engine.ProcessingNode") + @Schema(description = "Full Java Class Name of the rule node implementation. ", example = "com.mycompany.iot.rule.engine.ProcessingNode") private String type; @NoXss @Length(fieldName = "name") - @ApiModelProperty(position = 5, value = "User defined name of the rule node. Used on UI and for logging. ", example = "Process sensor reading") + @Schema(description = "User defined name of the rule node. Used on UI and for logging. ", example = "Process sensor reading") private String name; - @ApiModelProperty(position = 6, value = "Enable/disable debug. ", example = "false") + @Schema(description = "Enable/disable debug. ", example = "false") private boolean debugMode; - @ApiModelProperty(position = 7, value = "Enable/disable singleton mode. ", example = "false") + @Schema(description = "Enable/disable singleton mode. ", example = "false") private boolean singletonMode; - @ApiModelProperty(position = 8, value = "JSON with the rule node configuration. Structure depends on the rule node implementation.", dataType = "com.fasterxml.jackson.databind.JsonNode") + @Schema(description = "JSON with the rule node configuration. Structure depends on the rule node implementation.",implementation = com.fasterxml.jackson.databind.JsonNode.class) private transient JsonNode configuration; @JsonIgnore private byte[] configurationBytes; @@ -89,7 +88,7 @@ public class RuleNode extends BaseDataWithAdditionalInfo implements setJson(data, json -> this.configuration = json, bytes -> this.configurationBytes = bytes); } - @ApiModelProperty(position = 1, value = "JSON object with the Rule Node Id. " + + @Schema(description = "JSON object with the Rule Node Id. " + "Specify this field to update the Rule Node. " + "Referencing non-existing Rule Node Id will cause error. " + "Omit this field to create new rule node." ) @@ -98,13 +97,13 @@ public class RuleNode extends BaseDataWithAdditionalInfo implements return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the rule node creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the rule node creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 8, value = "Additional parameters of the rule node. Contains 'layoutX' and 'layoutY' properties for visualization.", dataType = "com.fasterxml.jackson.databind.JsonNode") + @Schema(description = "Additional parameters of the rule node. Contains 'layoutX' and 'layoutY' properties for visualization.",implementation = com.fasterxml.jackson.databind.JsonNode.class) @Override public JsonNode getAdditionalInfo() { return super.getAdditionalInfo(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java index e12aa43cbc..cdd5950fbb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java @@ -15,14 +15,13 @@ */ package org.thingsboard.server.common.data.security; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.DeviceCredentialsId; import org.thingsboard.server.common.data.id.DeviceId; -@ApiModel +@Schema @EqualsAndHashCode(callSuper = true) public class DeviceCredentials extends BaseData implements DeviceCredentialsFilter { @@ -48,7 +47,7 @@ public class DeviceCredentials extends BaseData implements this.credentialsValue = deviceCredentials.getCredentialsValue(); } - @ApiModelProperty(position = 1, required = true, accessMode = ApiModelProperty.AccessMode.READ_ONLY, value = "The Id is automatically generated during device creation. " + + @Schema(required = true, accessMode = Schema.AccessMode.READ_ONLY, description = "The Id is automatically generated during device creation. " + "Use 'getDeviceCredentialsByDeviceId' to obtain the id based on device id. " + "Use 'updateDeviceCredentials' to update device credentials. ", example = "784f394c-42b6-435a-983c-b7beff2784f9") @Override @@ -56,13 +55,13 @@ public class DeviceCredentials extends BaseData implements return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the device credentials creation, in milliseconds", example = "1609459200000") + @Schema(description = "Timestamp of the device credentials creation, in milliseconds", example = "1609459200000") @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, required = true, value = "JSON object with the device Id.") + @Schema(required = true, description = "JSON object with the device Id.") public DeviceId getDeviceId() { return deviceId; } @@ -71,7 +70,7 @@ public class DeviceCredentials extends BaseData implements this.deviceId = deviceId; } - @ApiModelProperty(position = 4, value = "Type of the credentials", allowableValues="ACCESS_TOKEN, X509_CERTIFICATE, MQTT_BASIC, LWM2M_CREDENTIALS") + @Schema(description = "Type of the credentials", allowableValues ="ACCESS_TOKEN, X509_CERTIFICATE, MQTT_BASIC, LWM2M_CREDENTIALS") @Override public DeviceCredentialsType getCredentialsType() { return credentialsType; @@ -81,7 +80,7 @@ public class DeviceCredentials extends BaseData implements this.credentialsType = credentialsType; } - @ApiModelProperty(position = 5, required = true, value = "Unique Credentials Id per platform instance. " + + @Schema(required = true, description = "Unique Credentials Id per platform instance. " + "Used to lookup credentials from the database. " + "By default, new access token for your device. " + "Depends on the type of the credentials." @@ -95,7 +94,7 @@ public class DeviceCredentials extends BaseData implements this.credentialsId = credentialsId; } - @ApiModelProperty(position = 6, value = "Value of the credentials. " + + @Schema(description = "Value of the credentials. " + "Null in case of ACCESS_TOKEN credentials type. Base64 value in case of X509_CERTIFICATE. " + "Complex object in case of MQTT_BASIC and LWM2M_CREDENTIALS", example = "Null in case of ACCESS_TOKEN. See model definition.") public String getCredentialsValue() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java index 62f15499e3..af2962f314 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java @@ -15,20 +15,19 @@ */ package org.thingsboard.server.common.data.security.model; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.security.Authority; -@ApiModel(value = "JWT Pair") +@Schema(description = "JWT Pair") @Data @NoArgsConstructor public class JwtPair { - @ApiModelProperty(position = 1, value = "The JWT Access Token. Used to perform API calls.", example = "AAB254FF67D..") + @Schema(description = "The JWT Access Token. Used to perform API calls.", example = "AAB254FF67D..") private String token; - @ApiModelProperty(position = 1, value = "The JWT Refresh Token. Used to get new JWT Access Token if old one has expired.", example = "AAB254FF67D..") + @Schema(description = "The JWT Refresh Token. Used to get new JWT Access Token if old one has expired.", example = "AAB254FF67D..") private String refreshToken; private Authority scope; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtSettings.java index 33bbca468b..53c0096a22 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtSettings.java @@ -15,13 +15,12 @@ */ package org.thingsboard.server.common.data.security.model; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -@ApiModel(value = "JWT Settings") +@Schema(description = "JWT Settings") @AllArgsConstructor @NoArgsConstructor @Data @@ -30,26 +29,26 @@ public class JwtSettings { /** * {@link JwtToken} will expire after this time. */ - @ApiModelProperty(position = 1, value = "The JWT will expire after seconds.", example = "9000") + @Schema(description = "The JWT will expire after seconds.", example = "9000") private Integer tokenExpirationTime; /** * {@link JwtToken} can be refreshed during this timeframe. */ - @ApiModelProperty(position = 2, value = "The JWT can be refreshed during seconds.", example = "604800") + @Schema(description = "The JWT can be refreshed during seconds.", example = "604800") private Integer refreshTokenExpTime; /** * Token issuer. */ - @ApiModelProperty(position = 3, value = "The JWT issuer.", example = "thingsboard.io") + @Schema(description = "The JWT issuer.", example = "thingsboard.io") private String tokenIssuer; /** * Key is used to sign {@link JwtToken}. * Base64 encoded */ - @ApiModelProperty(position = 4, value = "The JWT key is used to sing token. Base64 encoded.", example = "cTU4WnNqemI2aU5wbWVjdm1vYXRzanhjNHRUcXliMjE=") + @Schema(description = "The JWT key is used to sing token. Base64 encoded.", example = "cTU4WnNqemI2aU5wbWVjdm1vYXRzanhjNHRUcXliMjE=") private String tokenSigningKey; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java index 4c19b1fa5d..bdfbf975ad 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java @@ -15,22 +15,21 @@ */ package org.thingsboard.server.common.data.security.model; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; -@ApiModel +@Schema @Data public class SecuritySettings implements Serializable { private static final long serialVersionUID = -1307613974597312465L; - @ApiModelProperty(position = 1, value = "The user password policy object." ) + @Schema(description = "The user password policy object." ) private UserPasswordPolicy passwordPolicy; - @ApiModelProperty(position = 2, value = "Maximum number of failed login attempts allowed before user account is locked." ) + @Schema(description = "Maximum number of failed login attempts allowed before user account is locked." ) private Integer maxFailedLoginAttempts; - @ApiModelProperty(position = 3, value = "Email to use for notifications about locked users." ) + @Schema(description = "Email to use for notifications about locked users." ) private String userLockoutNotificationEmail; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java index 881af89e27..d94183ba02 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java @@ -15,32 +15,31 @@ */ package org.thingsboard.server.common.data.security.model; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; -@ApiModel +@Schema @Data public class UserPasswordPolicy implements Serializable { - @ApiModelProperty(position = 1, value = "Minimum number of symbols in the password." ) + @Schema(description = "Minimum number of symbols in the password." ) private Integer minimumLength; - @ApiModelProperty(position = 1, value = "Minimum number of uppercase letters in the password." ) + @Schema(description = "Minimum number of uppercase letters in the password." ) private Integer minimumUppercaseLetters; - @ApiModelProperty(position = 1, value = "Minimum number of lowercase letters in the password." ) + @Schema(description = "Minimum number of lowercase letters in the password." ) private Integer minimumLowercaseLetters; - @ApiModelProperty(position = 1, value = "Minimum number of digits in the password." ) + @Schema(description = "Minimum number of digits in the password." ) private Integer minimumDigits; - @ApiModelProperty(position = 1, value = "Minimum number of special in the password." ) + @Schema(description = "Minimum number of special in the password." ) private Integer minimumSpecialCharacters; - @ApiModelProperty(position = 1, value = "Allow whitespaces") + @Schema(description = "Allow whitespaces") private Boolean allowWhitespaces = true; - @ApiModelProperty(position = 1, value = "Password expiration period (days). Force expiration of the password." ) + @Schema(description = "Password expiration period (days). Force expiration of the password." ) private Integer passwordExpirationPeriodDays; - @ApiModelProperty(position = 1, value = "Password reuse frequency (days). Disallow to use the same password for the defined number of days" ) + @Schema(description = "Password reuse frequency (days). Disallow to use the same password for the defined number of days" ) private Integer passwordReuseFrequencyDays; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/AbstractUserDashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/AbstractUserDashboardInfo.java index ce214cb603..21ab795f1d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/settings/AbstractUserDashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/AbstractUserDashboardInfo.java @@ -15,23 +15,22 @@ */ package org.thingsboard.server.common.data.settings; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.HasTitle; import java.io.Serializable; import java.util.UUID; -@ApiModel +@Schema @Data public abstract class AbstractUserDashboardInfo implements HasTitle, Serializable { private static final long serialVersionUID = -6461562426034242608L; - @ApiModelProperty(position = 1, value = "JSON object with Dashboard id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Dashboard id.", accessMode = Schema.AccessMode.READ_ONLY) private UUID id; - @ApiModelProperty(position = 2, value = "Title of the dashboard.") + @Schema(description = "Title of the dashboard.") private String title; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/LastVisitedDashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/LastVisitedDashboardInfo.java index e49910eb43..0085f059f0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/settings/LastVisitedDashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/LastVisitedDashboardInfo.java @@ -15,23 +15,22 @@ */ package org.thingsboard.server.common.data.settings; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serializable; @EqualsAndHashCode(callSuper = true) -@ApiModel +@Schema @Data public class LastVisitedDashboardInfo extends AbstractUserDashboardInfo implements Serializable { private static final long serialVersionUID = -6461562426034242608L; - @ApiModelProperty(position = 3, value = "Starred flag") + @Schema(description = "Starred flag") private boolean starred; - @ApiModelProperty(position = 4, value = "Last visit timestamp") + @Schema(description = "Last visit timestamp") private long lastVisited; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/StarredDashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/StarredDashboardInfo.java index e71404c392..6fba8798e8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/settings/StarredDashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/StarredDashboardInfo.java @@ -15,20 +15,19 @@ */ package org.thingsboard.server.common.data.settings; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serializable; @EqualsAndHashCode(callSuper = true) -@ApiModel +@Schema @Data public class StarredDashboardInfo extends AbstractUserDashboardInfo implements Serializable { private static final long serialVersionUID = -7830828696329673361L; - @ApiModelProperty(position = 4, value = "Starred timestamp") + @Schema(description = "Starred timestamp") private long starredAt; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardsInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardsInfo.java index ef0b01889c..61056a3ae0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardsInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserDashboardsInfo.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.common.data.settings; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; @@ -25,7 +24,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -@ApiModel +@Schema @Data @AllArgsConstructor public class UserDashboardsInfo implements Serializable { @@ -33,10 +32,10 @@ public class UserDashboardsInfo implements Serializable { private static final long serialVersionUID = 2628320657987010348L; public static final UserDashboardsInfo EMPTY = new UserDashboardsInfo(Collections.emptyList(), Collections.emptyList()); - @ApiModelProperty(position = 1, value = "List of last visited dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "List of last visited dashboards.", accessMode = Schema.AccessMode.READ_ONLY) private List last; - @ApiModelProperty(position = 2, value = "List of starred dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "List of starred dashboards.", accessMode = Schema.AccessMode.READ_ONLY) private List starred; public UserDashboardsInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java index cf48d9c215..34e80ae1d5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/settings/UserSettings.java @@ -17,8 +17,7 @@ package org.thingsboard.server.common.data.settings; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.validation.Length; @@ -29,21 +28,21 @@ import java.io.Serializable; import static org.thingsboard.server.common.data.BaseDataWithAdditionalInfo.getJson; import static org.thingsboard.server.common.data.BaseDataWithAdditionalInfo.setJson; -@ApiModel +@Schema @Data public class UserSettings implements Serializable { private static final long serialVersionUID = 2628320657987010348L; - @ApiModelProperty(position = 1, value = "JSON object with User id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with User id.", accessMode = Schema.AccessMode.READ_ONLY) private UserId userId; - @ApiModelProperty(position = 2, value = "Type of the settings.") + @Schema(description = "Type of the settings.") @NoXss @Length(fieldName = "type", max = 50) private UserSettingsType type; - @ApiModelProperty(position = 3, value = "JSON object with user settings.", dataType = "com.fasterxml.jackson.databind.JsonNode") + @Schema(description = "JSON object with user settings.",implementation = com.fasterxml.jackson.databind.JsonNode.class) @NoXss @Length(fieldName = "settings", max = 100000) private transient JsonNode settings; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java index c153c7a0a0..7c76f01807 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java @@ -15,19 +15,18 @@ */ package org.thingsboard.server.common.data.sms.config; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@ApiModel +@Schema @Data public class AwsSnsSmsProviderConfiguration implements SmsProviderConfiguration { - @ApiModelProperty(position = 1, value = "The AWS SNS Access Key ID.") + @Schema(description = "The AWS SNS Access Key ID.") private String accessKeyId; - @ApiModelProperty(position = 2, value = "The AWS SNS Access Key.") + @Schema(description = "The AWS SNS Access Key.") private String secretAccessKey; - @ApiModelProperty(position = 3, value = "The AWS region.") + @Schema(description = "The AWS region.") private String region; @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmppSmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmppSmsProviderConfiguration.java index fddb4016b1..ba1f4461d4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmppSmsProviderConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmppSmsProviderConfiguration.java @@ -15,34 +15,34 @@ */ package org.thingsboard.server.common.data.sms.config; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data public class SmppSmsProviderConfiguration implements SmsProviderConfiguration { - @ApiModelProperty(value = "SMPP version", allowableValues = "3.3, 3.4", required = true) + @Schema(description = "SMPP version", allowableValues = "3.3, 3.4", required = true) private String protocolVersion; - @ApiModelProperty(value = "SMPP host", required = true) + @Schema(description = "SMPP host", required = true) private String host; - @ApiModelProperty(value = "SMPP port", required = true) + @Schema(description = "SMPP port", required = true) private Integer port; - @ApiModelProperty(value = "System ID", required = true) + @Schema(description = "System ID", required = true) private String systemId; - @ApiModelProperty(value = "Password", required = true) + @Schema(description = "Password", required = true) private String password; - @ApiModelProperty(value = "System type", required = false) + @Schema(description = "System type", required = false) private String systemType; - @ApiModelProperty(value = "TX - Transmitter, RX - Receiver, TRX - Transciever. By default TX is used", required = false) + @Schema(description = "TX - Transmitter, RX - Receiver, TRX - Transciever. By default TX is used", required = false) private SmppBindType bindType; - @ApiModelProperty(value = "Service type", required = false) + @Schema(description = "Service type", required = false) private String serviceType; - @ApiModelProperty(value = "Source address", required = false) + @Schema(description = "Source address", required = false) private String sourceAddress; - @ApiModelProperty(value = "Source TON (Type of Number). Needed is source address is set. 5 by default.\n" + + @Schema(description = "Source TON (Type of Number). Needed is source address is set. 5 by default.\n" + "0 - Unknown\n" + "1 - International\n" + "2 - National\n" + @@ -51,7 +51,7 @@ public class SmppSmsProviderConfiguration implements SmsProviderConfiguration { "5 - Alphanumeric\n" + "6 - Abbreviated", required = false) private Byte sourceTon; - @ApiModelProperty(value = "Source NPI (Numbering Plan Identification). Needed is source address is set. 0 by default.\n" + + @Schema(description = "Source NPI (Numbering Plan Identification). Needed is source address is set. 0 by default.\n" + "0 - Unknown\n" + "1 - ISDN/telephone numbering plan (E163/E164)\n" + "3 - Data numbering plan (X.121)\n" + @@ -64,7 +64,7 @@ public class SmppSmsProviderConfiguration implements SmsProviderConfiguration { "18 - WAP Client Id (to be defined by WAP Forum)", required = false) private Byte sourceNpi; - @ApiModelProperty(value = "Destination TON (Type of Number). 5 by default.\n" + + @Schema(description = "Destination TON (Type of Number). 5 by default.\n" + "0 - Unknown\n" + "1 - International\n" + "2 - National\n" + @@ -73,7 +73,7 @@ public class SmppSmsProviderConfiguration implements SmsProviderConfiguration { "5 - Alphanumeric\n" + "6 - Abbreviated", required = false) private Byte destinationTon; - @ApiModelProperty(value = "Destination NPI (Numbering Plan Identification). 0 by default.\n" + + @Schema(description = "Destination NPI (Numbering Plan Identification). 0 by default.\n" + "0 - Unknown\n" + "1 - ISDN/telephone numbering plan (E163/E164)\n" + "3 - Data numbering plan (X.121)\n" + @@ -86,11 +86,11 @@ public class SmppSmsProviderConfiguration implements SmsProviderConfiguration { "18 - WAP Client Id (to be defined by WAP Forum)", required = false) private Byte destinationNpi; - @ApiModelProperty(value = "Address range", required = false) + @Schema(description = "Address range", required = false) private String addressRange; - @ApiModelProperty(allowableValues = "0-10,13-14", - value = "0 - SMSC Default Alphabet (ASCII for short and long code and to GSM for toll-free, used as default)\n" + + @Schema(allowableValues = "0-10,13-14", + description = "0 - SMSC Default Alphabet (ASCII for short and long code and to GSM for toll-free, used as default)\n" + "1 - IA5 (ASCII for short and long code, Latin 9 for toll-free (ISO-8859-9))\n" + "2 - Octet Unspecified (8-bit binary)\n" + "3 - Latin 1 (ISO-8859-1)\n" + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java index 9a7e5fbfbf..5f792fc869 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java @@ -15,19 +15,18 @@ */ package org.thingsboard.server.common.data.sms.config; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@ApiModel +@Schema @Data public class TestSmsRequest { - @ApiModelProperty(position = 1, value = "The SMS provider configuration") + @Schema(description = "The SMS provider configuration") private SmsProviderConfiguration providerConfiguration; - @ApiModelProperty(position = 2, value = "The phone number or other identifier to specify as a recipient of the SMS.") + @Schema(description = "The phone number or other identifier to specify as a recipient of the SMS.") private String numberTo; - @ApiModelProperty(position = 3, value = "The test message") + @Schema(description = "The test message") private String message; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java index 55cce68703..f9a5fb5c77 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java @@ -15,19 +15,18 @@ */ package org.thingsboard.server.common.data.sms.config; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@ApiModel +@Schema @Data public class TwilioSmsProviderConfiguration implements SmsProviderConfiguration { - @ApiModelProperty(position = 1, value = "Twilio account Sid.") + @Schema(description = "Twilio account Sid.") private String accountSid; - @ApiModelProperty(position = 2, value = "Twilio account Token.") + @Schema(description = "Twilio account Token.") private String accountToken; - @ApiModelProperty(position = 3, value = "The number/id of a sender.") + @Schema(description = "The number/id of a sender.") private String numberFrom; @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java index 1def815cff..67427ac29d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java @@ -15,20 +15,19 @@ */ package org.thingsboard.server.common.data.tenant.profile; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; -@ApiModel +@Schema @Data public class TenantProfileData { - @ApiModelProperty(position = 1, value = "Complex JSON object that contains profile settings: max devices, max assets, rate limits, etc.") + @Schema(description = "Complex JSON object that contains profile settings: max devices, max assets, rate limits, etc.") private TenantProfileConfiguration configuration; - @ApiModelProperty(position = 2, value = "JSON array of queue configuration per tenant profile") + @Schema(description = "JSON array of queue configuration per tenant profile") private List queueConfiguration; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java index a7e9fee82a..7580bca240 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.widget; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasName; @@ -30,19 +30,19 @@ public class BaseWidgetType extends BaseData implements HasName, H private static final long serialVersionUID = 8388684344603660756L; - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; @NoXss @Length(fieldName = "bundleAlias") - @ApiModelProperty(position = 4, value = "Reference to widget bundle", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Reference to widget bundle", accessMode = Schema.AccessMode.READ_ONLY) private String bundleAlias; @NoXss @Length(fieldName = "alias") - @ApiModelProperty(position = 5, value = "Unique alias that is used in dashboards as a reference widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Unique alias that is used in dashboards as a reference widget type", accessMode = Schema.AccessMode.READ_ONLY) private String alias; @NoXss @Length(fieldName = "name") - @ApiModelProperty(position = 6, value = "Widget name used in search and UI", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Widget name used in search and UI", accessMode = Schema.AccessMode.READ_ONLY) private String name; public BaseWidgetType() { @@ -61,7 +61,7 @@ public class BaseWidgetType extends BaseData implements HasName, H this.name = widgetType.getName(); } - @ApiModelProperty(position = 1, value = "JSON object with the Widget Type Id. " + + @Schema(description = "JSON object with the Widget Type Id. " + "Specify this field to update the Widget Type. " + "Referencing non-existing Widget Type Id will cause error. " + "Omit this field to create new Widget Type." ) @@ -70,7 +70,7 @@ public class BaseWidgetType extends BaseData implements HasName, H return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the Widget Type creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the Widget Type creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java index f726cfb71a..de6421c24c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java @@ -16,14 +16,14 @@ package org.thingsboard.server.common.data.widget; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.id.WidgetTypeId; @Data public class WidgetType extends BaseWidgetType { - @ApiModelProperty(position = 7, value = "Complex JSON object that describes the widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Complex JSON object that describes the widget type", accessMode = Schema.AccessMode.READ_ONLY) private transient JsonNode descriptor; public WidgetType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java index e6faece85d..0e06012c70 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.data.widget; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.validation.Length; @@ -27,11 +27,11 @@ import org.thingsboard.server.common.data.validation.NoXss; public class WidgetTypeDetails extends WidgetType { @Length(fieldName = "image", max = 1000000) - @ApiModelProperty(position = 8, value = "Base64 encoded thumbnail", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Base64 encoded thumbnail", accessMode = Schema.AccessMode.READ_ONLY) private String image; @NoXss @Length(fieldName = "description") - @ApiModelProperty(position = 9, value = "Description of the widget", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Description of the widget", accessMode = Schema.AccessMode.READ_ONLY) private String description; public WidgetTypeDetails() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java index f5c4abd66d..0ba8761af6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.widget; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.validation.NoXss; @@ -23,13 +23,13 @@ import org.thingsboard.server.common.data.validation.NoXss; @Data public class WidgetTypeInfo extends BaseWidgetType { - @ApiModelProperty(position = 7, value = "Base64 encoded widget thumbnail", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Base64 encoded widget thumbnail", accessMode = Schema.AccessMode.READ_ONLY) private String image; @NoXss - @ApiModelProperty(position = 7, value = "Description of the widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Description of the widget type", accessMode = Schema.AccessMode.READ_ONLY) private String description; @NoXss - @ApiModelProperty(position = 8, value = "Type of the widget (timeseries, latest, control, alarm or static)", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Type of the widget (timeseries, latest, control, alarm or static)", accessMode = Schema.AccessMode.READ_ONLY) private String widgetType; public WidgetTypeInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java index 0ceef991ad..c93d08612f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java @@ -16,8 +16,7 @@ package org.thingsboard.server.common.data.widget; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @@ -32,7 +31,7 @@ import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -@ApiModel +@Schema @EqualsAndHashCode(callSuper = true) public class WidgetsBundle extends BaseData implements HasName, HasTenantId, ExportableEntity, HasTitle { @@ -40,34 +39,34 @@ public class WidgetsBundle extends BaseData implements HasName, @Getter @Setter - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "JSON object with Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; @NoXss @Length(fieldName = "alias") @Getter @Setter - @ApiModelProperty(position = 4, value = "Unique alias that is used in widget types as a reference widget bundle", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Unique alias that is used in widget types as a reference widget bundle", accessMode = Schema.AccessMode.READ_ONLY) private String alias; @NoXss @Length(fieldName = "title") @Getter @Setter - @ApiModelProperty(position = 5, value = "Title used in search and UI", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Title used in search and UI", accessMode = Schema.AccessMode.READ_ONLY) private String title; @Length(fieldName = "image", max = 1000000) @Getter @Setter - @ApiModelProperty(position = 6, value = "Base64 encoded thumbnail", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Base64 encoded thumbnail", accessMode = Schema.AccessMode.READ_ONLY) private String image; @NoXss @Length(fieldName = "description") @Getter @Setter - @ApiModelProperty(position = 7, value = "Description", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Description", accessMode = Schema.AccessMode.READ_ONLY) private String description; @Getter @@ -92,7 +91,7 @@ public class WidgetsBundle extends BaseData implements HasName, this.externalId = widgetsBundle.getExternalId(); } - @ApiModelProperty(position = 1, value = "JSON object with the Widget Bundle Id. " + + @Schema(description = "JSON object with the Widget Bundle Id. " + "Specify this field to update the Widget Bundle. " + "Referencing non-existing Widget Bundle Id will cause error. " + "Omit this field to create new Widget Bundle." ) @@ -101,13 +100,13 @@ public class WidgetsBundle extends BaseData implements HasName, return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the Widget Bundle creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Timestamp of the Widget Bundle creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "Same as title of the Widget Bundle. Read-only field. Update the 'title' to change the 'name' of the Widget Bundle.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + @Schema(description = "Same as title of the Widget Bundle. Read-only field. Update the 'title' to change the 'name' of the Widget Bundle.", accessMode = Schema.AccessMode.READ_ONLY) @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getName() { diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index cf0b25ec90..a5e4f2af54 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -17,8 +17,11 @@ package org.thingsboard.server.transport.http; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -127,22 +130,22 @@ public class DeviceApiController implements TbTransportService { @Autowired private HttpTransportContext transportContext; - @ApiOperation(value = "Get attributes (getDeviceAttributes)", - notes = "Returns all attributes that belong to device. " + @Operation(summary = "Get attributes (getDeviceAttributes)", + description = "Returns all attributes that belong to device. " + "Use optional 'clientKeys' and/or 'sharedKeys' parameter to return specific attributes. " + "\n Example of the result: " + MARKDOWN_CODE_BLOCK_START + ATTRIBUTE_PAYLOAD_EXAMPLE + MARKDOWN_CODE_BLOCK_END + REQUIRE_ACCESS_TOKEN, - produces = MediaType.APPLICATION_JSON_VALUE) - @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) + @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public DeferredResult getDeviceAttributes( - @ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN") + @Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")) @PathVariable("deviceToken") String deviceToken, - @ApiParam(value = "Comma separated key names for attribute with client scope", required = true, defaultValue = "state") + @Parameter(description = "Comma separated key names for attribute with client scope", required = true , schema = @Schema(defaultValue = "state")) @RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys, - @ApiParam(value = "Comma separated key names for attribute with shared scope", required = true, defaultValue = "configuration") + @Parameter(description = "Comma separated key names for attribute with shared scope", required = true , schema = @Schema(defaultValue = "configuration")) @RequestParam(value = "sharedKeys", required = false, defaultValue = "") String sharedKeys) { DeferredResult responseWriter = new DeferredResult<>(); transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), @@ -165,19 +168,19 @@ public class DeviceApiController implements TbTransportService { return responseWriter; } - @ApiOperation(value = "Post attributes (postDeviceAttributes)", - notes = "Post client attribute updates on behalf of device. " + @Operation(summary = "Post attributes (postDeviceAttributes)", + description = "Post client attribute updates on behalf of device. " + "\n Example of the request: " + MARKDOWN_CODE_BLOCK_START + ATTRIBUTE_PAYLOAD_EXAMPLE + MARKDOWN_CODE_BLOCK_END + REQUIRE_ACCESS_TOKEN, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.POST) public DeferredResult postDeviceAttributes( - @ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN") + @Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true , schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")) @PathVariable("deviceToken") String deviceToken, - @ApiParam(value = "JSON with attribute key-value pairs. See API call description for example.") + @Parameter(description = "JSON with attribute key-value pairs. See API call description for example.") @RequestBody String json) { DeferredResult responseWriter = new DeferredResult<>(); transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), @@ -189,15 +192,15 @@ public class DeviceApiController implements TbTransportService { return responseWriter; } - @ApiOperation(value = "Post time-series data (postTelemetry)", - notes = "Post time-series data on behalf of device. " + @Operation(summary = "Post time-series data (postTelemetry)", + description = "Post time-series data on behalf of device. " + "\n Example of the request: " + TS_PAYLOAD + REQUIRE_ACCESS_TOKEN, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @RequestMapping(value = "/{deviceToken}/telemetry", method = RequestMethod.POST) public DeferredResult postTelemetry( - @ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN") + @Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true , schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")) @PathVariable("deviceToken") String deviceToken, @RequestBody String json, HttpServletRequest request) { DeferredResult responseWriter = new DeferredResult(); @@ -210,8 +213,8 @@ public class DeviceApiController implements TbTransportService { return responseWriter; } - @ApiOperation(value = "Save claiming information (claimDevice)", - notes = "Saves the information required for user to claim the device. " + + @Operation(summary = "Save claiming information (claimDevice)", + description = "Saves the information required for user to claim the device. " + "See more info about claiming in the corresponding 'Claiming devices' platform documentation." + "\n Example of the request payload: " + MARKDOWN_CODE_BLOCK_START @@ -220,10 +223,10 @@ public class DeviceApiController implements TbTransportService { + "Note: both 'secretKey' and 'durationMs' is optional parameters. " + "In case the secretKey is not specified, the empty string as a default value is used. In case the durationMs is not specified, the system parameter device.claim.duration is used.\n\n" + REQUIRE_ACCESS_TOKEN, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @RequestMapping(value = "/{deviceToken}/claim", method = RequestMethod.POST) public DeferredResult claimDevice( - @ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN") + @Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true , schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")) @PathVariable("deviceToken") String deviceToken, @RequestBody(required = false) String json) { DeferredResult responseWriter = new DeferredResult<>(); @@ -237,17 +240,17 @@ public class DeviceApiController implements TbTransportService { return responseWriter; } - @ApiOperation(value = "Subscribe to RPC commands (subscribeToCommands) (Deprecated)", - notes = "Subscribes to RPC commands using http long polling. " + + @Operation(summary = "Subscribe to RPC commands (subscribeToCommands) (Deprecated)", + description = "Subscribes to RPC commands using http long polling. " + "Deprecated, since long polling is resource and network consuming. " + "Consider using MQTT or CoAP protocol for light-weight real-time updates. \n\n" + REQUIRE_ACCESS_TOKEN, - produces = MediaType.APPLICATION_JSON_VALUE) - @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.GET, produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) + @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public DeferredResult subscribeToCommands( - @ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN") + @Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true , schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")) @PathVariable("deviceToken") String deviceToken, - @ApiParam(value = "Optional timeout of the long poll. Typically less then 60 seconds, since limited on the server side.") + @Parameter(description = "Optional timeout of the long poll. Typically less then 60 seconds, since limited on the server side.") @RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout) { DeferredResult responseWriter = new DeferredResult<>(); transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), @@ -263,17 +266,17 @@ public class DeviceApiController implements TbTransportService { return responseWriter; } - @ApiOperation(value = "Reply to RPC commands (replyToCommand)", - notes = "Replies to server originated RPC command identified by 'requestId' parameter. The response is arbitrary JSON.\n\n" + + @Operation(summary = "Reply to RPC commands (replyToCommand)", + description = "Replies to server originated RPC command identified by 'requestId' parameter. The response is arbitrary JSON.\n\n" + REQUIRE_ACCESS_TOKEN, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @RequestMapping(value = "/{deviceToken}/rpc/{requestId}", method = RequestMethod.POST) public DeferredResult replyToCommand( - @ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN") + @Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true , schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")) @PathVariable("deviceToken") String deviceToken, - @ApiParam(value = "RPC request id from the incoming RPC request", required = true, defaultValue = "123") + @Parameter(description = "RPC request id from the incoming RPC request", required = true , schema = @Schema(defaultValue = "123")) @PathVariable("requestId") Integer requestId, - @ApiParam(value = "Reply to the RPC request, JSON. For example: {\"status\":\"success\"}", required = true) + @Parameter(description = "Reply to the RPC request, JSON. For example: {\"status\":\"success\"}", required = true) @RequestBody String json) { DeferredResult responseWriter = new DeferredResult(); transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), @@ -284,8 +287,8 @@ public class DeviceApiController implements TbTransportService { return responseWriter; } - @ApiOperation(value = "Send the RPC command (postRpcRequest)", - notes = "Send the RPC request to server. The request payload is a JSON document that contains 'method' and 'params'. For example:" + + @Operation(summary = "Send the RPC command (postRpcRequest)", + description = "Send the RPC request to server. The request payload is a JSON document that contains 'method' and 'params'. For example:" + MARKDOWN_CODE_BLOCK_START + "{\"method\": \"sumOnServer\", \"params\":{\"a\":2, \"b\":2}}" + MARKDOWN_CODE_BLOCK_END + @@ -294,12 +297,12 @@ public class DeviceApiController implements TbTransportService { "{\"result\": 4}" + MARKDOWN_CODE_BLOCK_END + REQUIRE_ACCESS_TOKEN, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.POST) public DeferredResult postRpcRequest( - @ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN") + @Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true , schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")) @PathVariable("deviceToken") String deviceToken, - @ApiParam(value = "The RPC request JSON", required = true) + @Parameter(description = "The RPC request JSON", required = true) @RequestBody String json) { DeferredResult responseWriter = new DeferredResult(); transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), @@ -317,17 +320,17 @@ public class DeviceApiController implements TbTransportService { return responseWriter; } - @ApiOperation(value = "Subscribe to attribute updates (subscribeToAttributes) (Deprecated)", - notes = "Subscribes to client and shared scope attribute updates using http long polling. " + + @Operation(summary = "Subscribe to attribute updates (subscribeToAttributes) (Deprecated)", + description = "Subscribes to client and shared scope attribute updates using http long polling. " + "Deprecated, since long polling is resource and network consuming. " + "Consider using MQTT or CoAP protocol for light-weight real-time updates. \n\n" + REQUIRE_ACCESS_TOKEN, - produces = MediaType.APPLICATION_JSON_VALUE) - @RequestMapping(value = "/{deviceToken}/attributes/updates", method = RequestMethod.GET, produces = "application/json") + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) + @RequestMapping(value = "/{deviceToken}/attributes/updates", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public DeferredResult subscribeToAttributes( - @ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN") + @Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true , schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")) @PathVariable("deviceToken") String deviceToken, - @ApiParam(value = "Optional timeout of the long poll. Typically less then 60 seconds, since limited on the server side.") + @Parameter(description = "Optional timeout of the long poll. Typically less then 60 seconds, since limited on the server side.") @RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout) { DeferredResult responseWriter = new DeferredResult<>(); transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), @@ -343,8 +346,8 @@ public class DeviceApiController implements TbTransportService { return responseWriter; } - @ApiOperation(value = "Get Device Firmware (getFirmware)", - notes = "Downloads the current firmware package." + + @Operation(summary = "Get Device Firmware (getFirmware)", + description = "Downloads the current firmware package." + "When the platform initiates firmware update, " + "it informs the device by updating the 'fw_title', 'fw_version', 'fw_checksum' and 'fw_checksum_algorithm' shared attributes." + "The 'fw_title' and 'fw_version' parameters must be supplied in this request to double-check " + @@ -354,24 +357,24 @@ public class DeviceApiController implements TbTransportService { "For example, device may request first 16 KB of firmware using 'chunk'=0 and 'size'=16384. " + "Next 16KB using 'chunk'=1 and 'size'=16384. The last chunk should have less bytes then requested using 'size' parameter. \n\n" + REQUIRE_ACCESS_TOKEN, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @RequestMapping(value = "/{deviceToken}/firmware", method = RequestMethod.GET) public DeferredResult getFirmware( - @ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN") + @Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true , schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")) @PathVariable("deviceToken") String deviceToken, - @ApiParam(value = "Title of the firmware, corresponds to the value of 'fw_title' attribute.", required = true) + @Parameter(description = "Title of the firmware, corresponds to the value of 'fw_title' attribute.", required = true) @RequestParam(value = "title") String title, - @ApiParam(value = "Version of the firmware, corresponds to the value of 'fw_version' attribute.", required = true) + @Parameter(description = "Version of the firmware, corresponds to the value of 'fw_version' attribute.", required = true) @RequestParam(value = "version") String version, - @ApiParam(value = "Size of the chunk. Optional. Omit to download the entire file without chunks.") + @Parameter(description = "Size of the chunk. Optional. Omit to download the entire file without chunks.") @RequestParam(value = "size", required = false, defaultValue = "0") int size, - @ApiParam(value = "Index of the chunk. Optional. Omit to download the entire file without chunks.") + @Parameter(description = "Index of the chunk. Optional. Omit to download the entire file without chunks.") @RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) { return getOtaPackageCallback(deviceToken, title, version, size, chunk, OtaPackageType.FIRMWARE); } - @ApiOperation(value = "Get Device Software (getSoftware)", - notes = "Downloads the current software package." + + @Operation(summary = "Get Device Software (getSoftware)", + description = "Downloads the current software package." + "When the platform initiates software update, " + "it informs the device by updating the 'sw_title', 'sw_version', 'sw_checksum' and 'sw_checksum_algorithm' shared attributes." + "The 'sw_title' and 'sw_version' parameters must be supplied in this request to double-check " + @@ -381,24 +384,24 @@ public class DeviceApiController implements TbTransportService { "For example, device may request first 16 KB of software using 'chunk'=0 and 'size'=16384. " + "Next 16KB using 'chunk'=1 and 'size'=16384. The last chunk should have less bytes then requested using 'size' parameter. \n\n" + REQUIRE_ACCESS_TOKEN, - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @RequestMapping(value = "/{deviceToken}/software", method = RequestMethod.GET) public DeferredResult getSoftware( - @ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN") + @Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true , schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")) @PathVariable("deviceToken") String deviceToken, - @ApiParam(value = "Title of the software, corresponds to the value of 'sw_title' attribute.", required = true) + @Parameter(description = "Title of the software, corresponds to the value of 'sw_title' attribute.", required = true) @RequestParam(value = "title") String title, - @ApiParam(value = "Version of the software, corresponds to the value of 'sw_version' attribute.", required = true) + @Parameter(description = "Version of the software, corresponds to the value of 'sw_version' attribute.", required = true) @RequestParam(value = "version") String version, - @ApiParam(value = "Size of the chunk. Optional. Omit to download the entire file without using chunks.") + @Parameter(description = "Size of the chunk. Optional. Omit to download the entire file without using chunks.") @RequestParam(value = "size", required = false, defaultValue = "0") int size, - @ApiParam(value = "Index of the chunk. Optional. Omit to download the entire file without using chunks.") + @Parameter(description = "Index of the chunk. Optional. Omit to download the entire file without using chunks.") @RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) { return getOtaPackageCallback(deviceToken, title, version, size, chunk, OtaPackageType.SOFTWARE); } - @ApiOperation(value = "Provision new device (provisionDevice)", - notes = "Exchange the provision request to the device credentials. " + + @Operation(summary = "Provision new device (provisionDevice)", + description = "Exchange the provision request to the device credentials. " + "See more info about provisioning in the corresponding 'Device provisioning' platform documentation." + "Requires valid JSON request with the following format: " + MARKDOWN_CODE_BLOCK_START + @@ -417,10 +420,10 @@ public class DeviceApiController implements TbTransportService { " \"status\":\"SUCCESS\"\n" + "}" + MARKDOWN_CODE_BLOCK_END , - produces = MediaType.APPLICATION_JSON_VALUE) + responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) @RequestMapping(value = "/provision", method = RequestMethod.POST) public DeferredResult provisionDevice( - @ApiParam(value = "JSON with provision request. See API call description for example.") + @Parameter(description = "JSON with provision request. See API call description for example.") @RequestBody String json) { DeferredResult responseWriter = new DeferredResult<>(); transportContext.getTransportService().process(JsonConverter.convertToProvisionRequestMsg(json), diff --git a/pom.xml b/pom.xml index b5475cc655..9d0fc4fe15 100755 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ 3.11.14 31.1-jre 2.6.1 - 3.4 + 3.12.0 1.15 2.11.0 1.2 @@ -92,8 +92,8 @@ 4.8.0 3.0.0-M9 3.0.2 - 3.0.4 - 1.6.3 + 2.1.0 + 2.2.9 0.7 1.18.2 1.69 @@ -135,7 +135,7 @@ 1.5.2 5.9.3 2.6.0 - 5.13.1 + 5.15.0 1.3.0 1.2.7 @@ -143,7 +143,7 @@ 3.23.1 5.2.0 1.3 - 1.17.3 + 1.18.3 1.12 3.0.0 6.1.0.202203080745-r @@ -1768,13 +1768,13 @@ ${curator.version} - org.thingsboard - springfox-boot-starter - ${springfox-swagger.version} + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc-swagger.version} - io.swagger - swagger-annotations + io.swagger.core.v3 + swagger-annotations-jakarta ${swagger-annotations.version} @@ -2010,6 +2010,16 @@ mockserver-netty ${mock-server.version} test + + + io.swagger.parser.v3 + swagger-parser-v2-converter + + + io.swagger.parser.v3 + swagger-parser + + org.mock-server From 0e72c36628168e8c0362bbeb7948621e0a8613a5 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 12 Jun 2023 14:42:50 +0200 Subject: [PATCH 014/209] minor improvements --- pom.xml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pom.xml b/pom.xml index 9d0fc4fe15..9419e1aede 100755 --- a/pom.xml +++ b/pom.xml @@ -2010,16 +2010,6 @@ mockserver-netty ${mock-server.version} test - - - io.swagger.parser.v3 - swagger-parser-v2-converter - - - io.swagger.parser.v3 - swagger-parser - - org.mock-server From c6771d9fc90e8297b9fed320b2f979c305e6c807 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 12 Jun 2023 23:02:43 +0200 Subject: [PATCH 015/209] fixed TestRestClient, swagger config improvements --- .../server/config/SwaggerConfiguration.java | 46 +++++++++++-------- .../server/controller/AlarmController.java | 23 ++++------ .../server/msa/TestRestClient.java | 2 +- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 50104e866d..4de7cee7c4 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -89,7 +89,7 @@ public class SwaggerConfiguration { private String appVersion; @Bean - public OpenAPI tbOpenAPI() { + public OpenAPI thingsboardApi() { Contact contact = new Contact() .name(contactName) .url(contactUrl) @@ -193,7 +193,7 @@ public class SwaggerConfiguration { } @Bean - public GroupedOpenApi thingsboardApi() { + public GroupedOpenApi groupedApi() { return GroupedOpenApi.builder() .group("thingsboard") .pathsToMatch(apiPath) @@ -230,24 +230,32 @@ public class SwaggerConfiguration { } public OpenApiCustomizer customOpenApiCustomizer() { - SecurityRequirement loginForm = new SecurityRequirement().addList("HTTP login form"); - + var loginForm = new SecurityRequirement().addList("HTTP login form"); return openAPI -> openAPI.getPaths().entrySet().stream().peek(entry -> { - if (!(entry.getKey().matches(nonSecurityPathRegex) || entry.getKey().equals(LOGIN_ENDPOINT))) { - entry.getValue() - .readOperationsMap() - .values() - .forEach(operation -> operation.addSecurityItem(loginForm)); - } - - entry.getValue().readOperationsMap().forEach(((httpMethod, operation) -> { - operation.setResponses(getResponses(operation.getResponses(), httpMethod.equals(PathItem.HttpMethod.POST))); - })); - - }).map(entry -> { - String tagItem = entry.getValue().readOperationsMap().values().stream().findAny().get().getTags().get(0); - return tagFromTagItem(tagItem); - }).forEach(openAPI::addTagsItem); + securityCustomization(loginForm, entry); + defaultErrorResponsesCustomization(entry.getValue()); + }).map(this::tagsCustomization).forEach(openAPI::addTagsItem); + } + + private Tag tagsCustomization(Map.Entry entry) { + var operations = entry.getValue().readOperationsMap().values(); + var tagItem = operations.stream().findAny().get().getTags().get(0); + return tagFromTagItem(tagItem); + } + + private void defaultErrorResponsesCustomization(PathItem pathItem) { + pathItem.readOperationsMap().forEach(((httpMethod, operation) -> { + operation.setResponses(getResponses(operation.getResponses(), httpMethod.equals(PathItem.HttpMethod.POST))); + })); + } + + private void securityCustomization(SecurityRequirement loginForm, Map.Entry entry) { + if (!(entry.getKey().matches(nonSecurityPathRegex) || entry.getKey().equals(LOGIN_ENDPOINT))) { + entry.getValue() + .readOperationsMap() + .values() + .forEach(operation -> operation.addSecurityItem(loginForm)); + } } private Tag tagFromTagItem(String tagItem) { diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 18ad89f1a0..183ffbd0de 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -90,11 +90,8 @@ public class AlarmController extends BaseController { private static final String ALARM_QUERY_SEARCH_STATUS_DESCRIPTION = "A string value representing one of the AlarmSearchStatus enumeration value"; private static final String ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION = "A list of string values separated by comma ',' representing one of the AlarmSearchStatus enumeration value"; - private static final String ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES = "ANY, ACTIVE, CLEARED, ACK, UNACK"; private static final String ALARM_QUERY_STATUS_DESCRIPTION = "A string value representing one of the AlarmStatus enumeration value"; - private static final String ALARM_QUERY_STATUS_ALLOWABLE_VALUES = "ACTIVE_UNACK, ACTIVE_ACK, CLEARED_UNACK, CLEARED_ACK"; private static final String ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION = "A list of string values separated by comma ',' representing one of the AlarmSeverity enumeration value"; - private static final String ALARM_QUERY_SEVERITY_ALLOWABLE_VALUES = "CRITICAL, MAJOR, MINOR, WARNING, INDETERMINATE"; private static final String ALARM_QUERY_TYPE_ARRAY_DESCRIPTION = "A list of string values separated by comma ',' representing alarm types"; private static final String ALARM_QUERY_ASSIGNEE_DESCRIPTION = "A string value representing the assignee user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; @@ -246,9 +243,9 @@ public class AlarmController extends BaseController { @PathVariable(ENTITY_TYPE) String strEntityType, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_ID) String strEntityId, - @Parameter(description = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)) + @Parameter(description = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, schema = @Schema(allowableValues = {"ANY", "ACTIVE", "CLEARED", "ACK", "UNACK"})) @RequestParam(required = false) String searchStatus, - @Parameter(description = ALARM_QUERY_STATUS_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES)) + @Parameter(description = ALARM_QUERY_STATUS_DESCRIPTION, schema = @Schema(allowableValues = {"ACTIVE_UNACK", "ACTIVE_ACK", "CLEARED_UNACK", "CLEARED_ACK"})) @RequestParam(required = false) String status, @Parameter(description = ALARM_QUERY_ASSIGNEE_DESCRIPTION) @RequestParam(required = false) String assigneeId, @@ -298,9 +295,9 @@ public class AlarmController extends BaseController { @RequestMapping(value = "/alarms", method = RequestMethod.GET) @ResponseBody public PageData getAllAlarms( - @Parameter(description = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)) + @Parameter(description = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, schema = @Schema(allowableValues = {"ANY", "ACTIVE", "CLEARED", "ACK", "UNACK"})) @RequestParam(required = false) String searchStatus, - @Parameter(description = ALARM_QUERY_STATUS_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES)) + @Parameter(description = ALARM_QUERY_STATUS_DESCRIPTION, schema = @Schema(allowableValues = {"ACTIVE_UNACK", "ACTIVE_ACK", "CLEARED_UNACK", "CLEARED_ACK"})) @RequestParam(required = false) String status, @Parameter(description = ALARM_QUERY_ASSIGNEE_DESCRIPTION) @RequestParam(required = false) String assigneeId, @@ -351,9 +348,9 @@ public class AlarmController extends BaseController { @PathVariable(ENTITY_TYPE) String strEntityType, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_ID) String strEntityId, - @Parameter(description = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)) + @Parameter(description = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = {"ANY", "ACTIVE", "CLEARED", "ACK", "UNACK"})) @RequestParam(required = false) String[] statusList, - @Parameter(description = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEVERITY_ALLOWABLE_VALUES)) + @Parameter(description = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = {"CRITICAL", "MAJOR", "MINOR", "WARNING", "INDETERMINATE"})) @RequestParam(required = false) String[] severityList, @Parameter(description = ALARM_QUERY_TYPE_ARRAY_DESCRIPTION) @RequestParam(required = false) String[] typeList, @@ -413,9 +410,9 @@ public class AlarmController extends BaseController { @RequestMapping(value = "/v2/alarms", method = RequestMethod.GET) @ResponseBody public PageData getAllAlarmsV2( - @Parameter(description = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)) + @Parameter(description = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = {"ANY", "ACTIVE", "CLEARED", "ACK", "UNACK"})) @RequestParam(required = false) String[] statusList, - @Parameter(description = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEVERITY_ALLOWABLE_VALUES)) + @Parameter(description = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = {"CRITICAL", "MAJOR", "MINOR", "WARNING", "INDETERMINATE"})) @RequestParam(required = false) String[] severityList, @Parameter(description = ALARM_QUERY_TYPE_ARRAY_DESCRIPTION) @RequestParam(required = false) String[] typeList, @@ -478,9 +475,9 @@ public class AlarmController extends BaseController { @PathVariable(ENTITY_TYPE) String strEntityType, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable(ENTITY_ID) String strEntityId, - @Parameter(description = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)) + @Parameter(description = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, schema = @Schema(allowableValues = {"ANY", "ACTIVE", "CLEARED", "ACK", "UNACK"})) @RequestParam(required = false) String searchStatus, - @Parameter(description = ALARM_QUERY_STATUS_DESCRIPTION, schema = @Schema(allowableValues = ALARM_QUERY_STATUS_ALLOWABLE_VALUES)) + @Parameter(description = ALARM_QUERY_STATUS_DESCRIPTION, schema = @Schema(allowableValues = {"ACTIVE_UNACK", "ACTIVE_ACK", "CLEARED_UNACK", "CLEARED_ACK"})) @RequestParam(required = false) String status, @Parameter(description = ALARM_QUERY_ASSIGNEE_DESCRIPTION) @RequestParam(required = false) String assigneeId diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java index f04331e01e..4d489b116e 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java @@ -183,7 +183,7 @@ public class TestRestClient { public ValidatableResponse postAttribute(String accessToken, JsonNode attribute) { return given().spec(requestSpec).body(attribute) - .post("/api/v1/{accessToken}/attributes/", accessToken) + .post("/api/v1/{accessToken}/attributes", accessToken) .then() .statusCode(HTTP_OK); } From d67d15b0d755dc7ceae0a0d6e6e2c1d99ba64303 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 14 Jun 2023 23:25:51 +0200 Subject: [PATCH 016/209] added ssl context for TbHttpClient --- .../java/org/thingsboard/rule/engine/rest/TbHttpClient.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 7a5ca407af..e7e76aa52e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.rest; import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.timeout.ReadTimeoutHandler; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -113,6 +114,8 @@ public class TbHttpClient { o.username(proxyUser).password(u -> proxyPassword); } }); + SslContext sslContext = SslContextBuilder.forClient().build(); + httpClient.secure(t -> t.sslContext(sslContext)); } } else if (!config.isUseSimpleClientHttpFactory()) { if (CredentialsType.CERT_PEM == config.getCredentials().getType()) { From 85887fa9af8dfd42b800aba558759e28ca99920c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 16 Jun 2023 11:25:32 +0200 Subject: [PATCH 017/209] remove default error responses from login endpoint --- .../org/thingsboard/server/config/SwaggerConfiguration.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 4de7cee7c4..7be938274e 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -233,7 +233,9 @@ public class SwaggerConfiguration { var loginForm = new SecurityRequirement().addList("HTTP login form"); return openAPI -> openAPI.getPaths().entrySet().stream().peek(entry -> { securityCustomization(loginForm, entry); - defaultErrorResponsesCustomization(entry.getValue()); + if (!entry.getKey().equals(LOGIN_ENDPOINT)) { + defaultErrorResponsesCustomization(entry.getValue()); + } }).map(this::tagsCustomization).forEach(openAPI::addTagsItem); } From 16c639b8a5b5d4ff3cd171a9240512619700c58e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 30 Jun 2023 11:47:08 +0200 Subject: [PATCH 018/209] improvements --- .../server/service/mail/DefaultTbMailConfigTemplateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultTbMailConfigTemplateService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultTbMailConfigTemplateService.java index cb502e7e51..e671a146b4 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultTbMailConfigTemplateService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultTbMailConfigTemplateService.java @@ -21,7 +21,7 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.io.IOException; @Service From e00e67c9c7a2c4ff4771d4bda9561191c4c5141e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 30 Jun 2023 15:07:27 +0200 Subject: [PATCH 019/209] TbHttpClient EventLoop inprovements --- .../java/org/thingsboard/rule/engine/rest/TbHttpClient.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 20d43a021c..1a71124054 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -125,12 +125,9 @@ public class TbHttpClient { if (CredentialsType.CERT_PEM == config.getCredentials().getType()) { throw new TbNodeException("Simple HTTP Factory does not support CERT PEM credentials!"); } - - httpClient = HttpClient.create(); - } else { SslContext sslContext = config.getCredentials().initSslContext(); - httpClient = HttpClient.create().secure(t -> t.sslContext(sslContext)); + httpClient = httpClient.secure(t -> t.sslContext(sslContext)); } this.webClient = WebClient.builder() From b194f98ebf8ec6adc4fe78b10010c78b681fe553 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 3 Jul 2023 14:26:10 +0200 Subject: [PATCH 020/209] fixed resource cache --- .../server/cache/resourceInfo/ResourceInfoRedisCache.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/resourceInfo/ResourceInfoRedisCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/resourceInfo/ResourceInfoRedisCache.java index fee14e1ca1..f17bbd2797 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/resourceInfo/ResourceInfoRedisCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/resourceInfo/ResourceInfoRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJavaRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.TbResourceInfo; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.TbResourceInfo; public class ResourceInfoRedisCache extends RedisTbTransactionalCache { public ResourceInfoRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.RESOURCE_INFO_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.RESOURCE_INFO_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJavaRedisSerializer<>()); } } From 1bcea1a3fc9ebb6c937704e07a867d8944a10f35 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 11 Jul 2023 12:19:01 +0200 Subject: [PATCH 021/209] swagger improvements --- .../org/thingsboard/server/config/SwaggerConfiguration.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 7be938274e..946d2cf2a2 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -33,6 +33,7 @@ import io.swagger.v3.oas.models.responses.ApiResponse; import io.swagger.v3.oas.models.responses.ApiResponses; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; import io.swagger.v3.oas.models.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springdoc.core.customizers.OpenApiCustomizer; @@ -52,6 +53,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.rest.LoginRequest; import org.thingsboard.server.service.security.auth.rest.LoginResponse; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -113,12 +115,13 @@ public class SwaggerConfiguration { SecurityScheme securityScheme = new SecurityScheme() .type(SecurityScheme.Type.HTTP) - .scheme("basic") + .scheme("bearer") .bearerFormat("JWT") .in(SecurityScheme.In.HEADER) .description("Enter Username / Password"); var openApi = new OpenAPI() + .addServersItem(new Server().url("/").description("Default Server URL")) .components(new Components().addSecuritySchemes("HTTP login form", securityScheme)) .info(info); addLoginOperation(openApi); From 85361c609a9b86cdb86820e66301135e4db3900e Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 21 Aug 2023 15:32:19 +0300 Subject: [PATCH 022/209] EntityStateListener: process entity broadcasting service to handle all event --- .../device/DeviceActorMessageProcessor.java | 5 +- .../actors/ruleChain/DefaultTbContext.java | 6 - .../server/controller/BaseController.java | 4 +- .../server/controller/DeviceController.java | 5 +- .../controller/TelemetryController.java | 10 +- .../DefaultTbApiUsageStateService.java | 1 - .../device/ClaimDevicesServiceImpl.java | 4 - .../device/DeviceProvisionServiceImpl.java | 6 +- .../edge/EdgeEventSourcingListener.java | 31 ++- .../edge/rpc/processor/BaseEdgeProcessor.java | 9 +- .../processor/device/BaseDeviceProcessor.java | 1 - .../rpc/sync/DefaultEdgeRequestsService.java | 6 +- .../entitiy/AbstractTbEntityService.java | 2 +- .../DefaultTbLogEntityActionService.java | 79 +++++++ .../DefaultTbNotificationEntityService.java | 211 ------------------ .../entitiy/EntityStateSourcingListener.java | 208 +++++++++++++++++ ...ice.java => TbLogEntityActionService.java} | 28 +-- .../alarm/DefaultTbAlarmCommentService.java | 6 +- .../entitiy/alarm/DefaultTbAlarmService.java | 17 +- .../entitiy/asset/DefaultTbAssetService.java | 32 ++- .../profile/DefaultTbAssetProfileService.java | 21 +- .../customer/DefaultTbCustomerService.java | 9 +- .../dashboard/DefaultTbDashboardService.java | 46 ++-- .../device/DefaultTbDeviceService.java | 70 +++--- .../entitiy/device/TbDeviceService.java | 2 +- .../DefaultTbDeviceProfileService.java | 38 +--- .../entitiy/edge/DefaultTbEdgeService.java | 32 +-- .../DefaultTbEntityRelationService.java | 12 +- .../DefaultTbEntityViewService.java | 37 ++- .../ota/DefaultTbOtaPackageService.java | 12 +- .../entitiy/queue/DefaultTbQueueService.java | 1 + .../tenant/DefaultTbTenantService.java | 4 - .../DefaultTbTenantProfileService.java | 6 - .../entitiy/user/DefaultUserService.java | 8 +- .../bundle/DefaultWidgetsBundleService.java | 8 +- .../queue/DefaultTbClusterService.java | 16 +- .../resource/DefaultTbResourceService.java | 19 +- .../rule/DefaultTbRuleChainService.java | 54 +++-- .../oauth2/AbstractOAuth2ClientMapper.java | 7 - .../DefaultEntitiesExportImportService.java | 6 +- .../impl/AssetProfileImportService.java | 6 +- .../impl/BaseEntityImportService.java | 8 +- .../importing/impl/DeviceImportService.java | 9 - .../impl/DeviceProfileImportService.java | 16 +- .../impl/EntityViewImportService.java | 2 - .../impl/RuleChainImportService.java | 1 - .../DefaultEntitiesVersionControlService.java | 10 +- .../transport/DefaultTransportApiService.java | 6 +- .../controller/AbstractNotifyEntityTest.java | 22 +- .../server/controller/EdgeControllerTest.java | 12 +- .../controller/EntityViewControllerTest.java | 10 +- .../server/edge/DeviceEdgeTest.java | 1 - .../server/edge/TelemetryEdgeTest.java | 7 - .../alarm/DefaultTbAlarmServiceTest.java | 12 +- .../DefaultTbAlarmCommentServiceTest.java | 4 +- .../sync/ie/ExportImportServiceSqlTest.java | 4 +- .../server/cluster/TbClusterService.java | 2 +- .../server/queue/TbQueueClusterService.java | 1 + .../server/dao/device/DeviceService.java | 3 +- .../server/dao/resource/ResourceService.java | 2 +- .../server/common/data/EdgeUtils.java | 13 ++ .../common/data/edge/EdgeEventActionType.java | 54 +++-- .../dao/asset/AssetProfileServiceImpl.java | 4 +- .../device/DeviceCredentialsServiceImpl.java | 8 +- .../dao/device/DeviceProfileServiceImpl.java | 6 +- .../server/dao/device/DeviceServiceImpl.java | 27 ++- .../server/dao/edge/BaseEdgeEventService.java | 24 +- .../server/dao/edge/EdgeServiceImpl.java | 13 +- .../dao/eventsourcing/ActionEntityEvent.java | 5 +- .../dao/eventsourcing/SaveEntityEvent.java | 1 + .../server/dao/queue/BaseQueueService.java | 5 +- .../dao/resource/BaseResourceService.java | 13 +- .../dao/tenant/TenantProfileServiceImpl.java | 11 +- .../server/dao/tenant/TenantServiceImpl.java | 7 +- .../usagerecord/ApiUsageStateServiceImpl.java | 6 +- .../dao/service/DashboardServiceTest.java | 24 +- .../timeseries/BaseTimeseriesServiceTest.java | 13 +- .../rule/engine/api/TbContext.java | 3 - .../action/TbAbstractRelationActionNode.java | 1 - .../rule/engine/edge/TbMsgPushToEdgeNode.java | 6 +- .../rule/engine/rpc/TbSendRPCReplyNode.java | 1 - 81 files changed, 733 insertions(+), 729 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbLogEntityActionService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java rename application/src/main/java/org/thingsboard/server/service/entitiy/{TbNotificationEntityService.java => TbLogEntityActionService.java} (60%) diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index 0ce0ae7884..2ac637add5 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -844,10 +844,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, EdgeEventType.DEVICE, EdgeEventActionType.RPC_CALL, deviceId, body); - return Futures.transform(systemContext.getEdgeEventService().saveAsync(edgeEvent), unused -> { - systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId); - return null; - }, systemContext.getDbCallbackExecutor()); + return systemContext.getEdgeEventService().saveAsync(edgeEvent); } private List toTsKvProtos(@Nullable List result) { diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index f5a0cedf08..0ea2179276 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -53,7 +53,6 @@ import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -446,11 +445,6 @@ class DefaultTbContext implements TbContext { return entityActionMsg(originator, tbMsgMetaData, msgData, actionMsgType, profile); } - @Override - public void onEdgeEventUpdate(TenantId tenantId, EdgeId edgeId) { - mainCtx.getClusterService().onEdgeEventUpdate(tenantId, edgeId); - } - public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, TbMsgType actionMsgType) { return entityActionMsg(entity, id, ruleNodeId, actionMsgType, null); } diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 0af55d7c00..af325f4d84 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -140,7 +140,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.action.EntityActionService; import org.thingsboard.server.service.component.ComponentDiscoveryService; -import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.entitiy.TbLogEntityActionService; import org.thingsboard.server.service.entitiy.user.TbUserSettingsService; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; @@ -298,7 +298,7 @@ public abstract class BaseController { protected EdgeService edgeService; @Autowired - protected TbNotificationEntityService notificationEntityService; + protected TbLogEntityActionService logEntityActionService; @Autowired protected EntityActionService entityActionService; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index d73915b617..5b845b231f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -180,13 +180,12 @@ public class DeviceController extends BaseController { @ApiParam(value = "Optional value of the device credentials to be used during device creation. " + "If omitted, access token will be auto-generated.") @RequestParam(name = "accessToken", required = false) String accessToken) throws Exception { device.setTenantId(getCurrentUser().getTenantId()); - Device oldDevice = null; if (device.getId() != null) { - oldDevice = checkDeviceId(device.getId(), Operation.WRITE); + checkDeviceId(device.getId(), Operation.WRITE); } else { checkEntity(null, device, Resource.DEVICE); } - return tbDeviceService.save(device, oldDevice, accessToken, getCurrentUser()); + return tbDeviceService.save(device, accessToken, getCurrentUser()); } @ApiOperation(value = "Create Device (saveDevice) with credentials ", diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 05f56e93bd..d522ce9fa7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -823,28 +823,28 @@ public class TelemetryController extends BaseController { } private void logTimeseriesDeleted(SecurityUser user, EntityId entityId, List keys, long startTs, long endTs, Throwable e) { - notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.TIMESERIES_DELETED, user, + logEntityActionService.logEntityAction(user.getTenantId(), entityId, ActionType.TIMESERIES_DELETED, user, toException(e), keys, startTs, endTs); } private void logTelemetryUpdated(SecurityUser user, EntityId entityId, List telemetry, Throwable e) { - notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.TIMESERIES_UPDATED, user, + logEntityActionService.logEntityAction(user.getTenantId(), entityId, ActionType.TIMESERIES_UPDATED, user, toException(e), telemetry); } private void logAttributesDeleted(SecurityUser user, EntityId entityId, String scope, List keys, Throwable e) { - notificationEntityService.logEntityAction(user.getTenantId(), (UUIDBased & EntityId) entityId, + logEntityActionService.logEntityAction(user.getTenantId(), (UUIDBased & EntityId) entityId, ActionType.ATTRIBUTES_DELETED, user, toException(e), scope, keys); } private void logAttributesUpdated(SecurityUser user, EntityId entityId, String scope, List attributes, Throwable e) { - notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.ATTRIBUTES_UPDATED, user, + logEntityActionService.logEntityAction(user.getTenantId(), entityId, ActionType.ATTRIBUTES_UPDATED, user, toException(e), scope, attributes); } private void logAttributesRead(SecurityUser user, EntityId entityId, String scope, List keys, Throwable e) { - notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.ATTRIBUTES_READ, user, + logEntityActionService.logEntityAction(user.getTenantId(), entityId, ActionType.ATTRIBUTES_READ, user, toException(e), scope, keys); } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index ef6e4fa0bf..2b1f7a0f6b 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -343,7 +343,6 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService private void persistAndNotify(BaseApiUsageState state, Map result) { log.info("[{}] Detected update of the API state for {}: {}", state.getEntityId(), state.getEntityType(), result); apiUsageStateService.update(state.getApiUsageState()); - clusterService.onApiStateChange(state.getApiUsageState(), null); long ts = System.currentTimeMillis(); List stateTelemetry = new ArrayList<>(); result.forEach((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name())))); diff --git a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java index ebc0fd0d24..9f393f3835 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java @@ -67,8 +67,6 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { private static final String CLAIM_ATTRIBUTE_NAME = "claimingAllowed"; private static final String CLAIM_DATA_ATTRIBUTE_NAME = "claimingData"; - @Autowired - private TbClusterService clusterService; @Autowired private DeviceService deviceService; @Autowired @@ -153,7 +151,6 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { if (device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { device.setCustomerId(customerId); Device savedDevice = deviceService.saveDevice(device); - clusterService.onDeviceUpdated(savedDevice, device); return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS), MoreExecutors.directExecutor()); } return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED), MoreExecutors.directExecutor()); @@ -180,7 +177,6 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { Customer unassignedCustomer = customerService.findCustomerById(tenantId, device.getCustomerId()); device.setCustomerId(null); Device savedDevice = deviceService.saveDevice(device); - clusterService.onDeviceUpdated(savedDevice, device); if (isAllowedClaimingByDefault) { return Futures.immediateFuture(new ReclaimResult(unassignedCustomer)); } diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 763ce01116..c0c5f751c0 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -22,7 +22,6 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -81,7 +80,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private static final String DEVICE_PROVISION_STATE = "provisionState"; private static final String PROVISIONED_STATE = "provisioned"; - private final TbClusterService clusterService; private final DeviceProfileService deviceProfileService; private final DeviceService deviceService; private final DeviceCredentialsService deviceCredentialsService; @@ -89,9 +87,8 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private final AuditLogService auditLogService; private final PartitionService partitionService; - public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider, TbClusterService clusterService, DeviceProfileService deviceProfileService, DeviceService deviceService, DeviceCredentialsService deviceCredentialsService, AttributesService attributesService, AuditLogService auditLogService, PartitionService partitionService) { + public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider, DeviceProfileService deviceProfileService, DeviceService deviceService, DeviceCredentialsService deviceCredentialsService, AttributesService attributesService, AuditLogService auditLogService, PartitionService partitionService) { ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer(); - this.clusterService = clusterService; this.deviceProfileService = deviceProfileService; this.deviceService = deviceService; this.deviceCredentialsService = deviceCredentialsService; @@ -220,7 +217,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { provisionRequest.setDeviceName(newDeviceName); } Device savedDevice = deviceService.saveDevice(provisionRequest, profile); - clusterService.onDeviceUpdated(savedDevice, null); saveProvisionStateAttribute(savedDevice).get(); pushDeviceCreatedEventToRuleEngine(savedDevice); notify(savedDevice, provisionRequest, TbMsgType.PROVISION_SUCCESS, true); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 43b05094a4..57dba7e86e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -21,9 +21,16 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -39,9 +46,6 @@ import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import javax.annotation.PostConstruct; -import static org.thingsboard.server.service.entitiy.DefaultTbNotificationEntityService.edgeTypeByActionType; - - /** * This event listener does not support async event processing because relay on ThreadLocal * Another possible approach is to implement a special annotation and a bunch of classes similar to TransactionalApplicationListener @@ -92,7 +96,13 @@ public class EdgeEventSourcingListener { if (edgeSynchronizationManager.isSync()) { return; } + EntityType entityType = event.getEntityId().getEntityType(); try { + if (EntityType.EDGE.equals(entityType) || + EntityType.TENANT.equals(entityType) || + EntityType.TB_RESOURCE.equals(entityType)) { + return; + } log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event); tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), event.getEdgeId(), event.getEntityId(), JacksonUtil.toString(event.getEntity()), null, EdgeEventActionType.DELETED); @@ -102,14 +112,18 @@ public class EdgeEventSourcingListener { } @TransactionalEventListener(fallbackExecution = true) - public void handleEvent(ActionEntityEvent event) { + public void handleEvent(ActionEntityEvent event) { if (edgeSynchronizationManager.isSync()) { return; } + if (EntityType.DEVICE.equals(event.getEntityId().getEntityType()) + && ActionType.ASSIGNED_TO_TENANT.equals(event.getActionType())) { + return; + } try { log.trace("[{}] ActionEntityEvent called: {}", event.getTenantId(), event); tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), event.getEdgeId(), event.getEntityId(), - event.getBody(), null, edgeTypeByActionType(event.getActionType())); + event.getBody(), null, EdgeUtils.getEdgeEventActionTypeByActionType(event.getActionType())); } catch (Exception e) { log.error("[{}] failed to process ActionEntityEvent: {}", event.getTenantId(), event); } @@ -132,7 +146,7 @@ public class EdgeEventSourcingListener { } log.trace("[{}] RelationActionEvent called: {}", event.getTenantId(), event); tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), null, null, - JacksonUtil.toString(relation), EdgeEventType.RELATION, edgeTypeByActionType(event.getActionType())); + JacksonUtil.toString(relation), EdgeEventType.RELATION, EdgeUtils.getEdgeEventActionTypeByActionType(event.getActionType())); } catch (Exception e) { log.error("[{}] failed to process RelationActionEvent: {}", event.getTenantId(), event); } @@ -151,6 +165,11 @@ public class EdgeEventSourcingListener { } else if (entity instanceof AlarmApiCallResult) { AlarmApiCallResult alarmApiCallResult = (AlarmApiCallResult) entity; return alarmApiCallResult.isModified(); + } else if (entity instanceof Edge || + entity instanceof ApiUsageState || + entity instanceof TbResource || + entity instanceof EdgeEvent) { + return false; } // Default: If the entity doesn't match any of the conditions, consider it as valid. return true; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index 11679ebe38..a3688e7339 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -92,7 +92,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.TenantProfileMsgConst import org.thingsboard.server.service.edge.rpc.constructor.UserMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.WidgetTypeMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor; -import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.entitiy.TbLogEntityActionService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; @@ -116,7 +116,7 @@ public abstract class BaseEdgeProcessor { protected TelemetrySubscriptionService tsSubService; @Autowired - protected TbNotificationEntityService notificationEntityService; + protected TbLogEntityActionService logEntityActionService; @Autowired protected RuleChainService ruleChainService; @@ -281,10 +281,7 @@ public abstract class BaseEdgeProcessor { EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body); - return Futures.transform(edgeEventService.saveAsync(edgeEvent), unused -> { - tbClusterService.onEdgeEventUpdate(tenantId, edgeId); - return null; - }, dbCallbackExecutorService); + return edgeEventService.saveAsync(edgeEvent); } protected ListenableFuture processActionForAllEdges(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java index 1421cf32c0..977a4c4909 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java @@ -97,7 +97,6 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { deviceCredentials.setCredentialsId(StringUtils.randomAlphanumeric(20)); deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); } - tbClusterService.onDeviceUpdated(savedDevice, created ? null : device); } finally { deviceCreationLock.unlock(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java index 700e0f3861..f479b5a001 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java @@ -397,11 +397,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { tenantId, edgeId, type, action, entityId, body); EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body); - - return Futures.transform(edgeEventService.saveAsync(edgeEvent), unused -> { - tbClusterService.onEdgeEventUpdate(tenantId, edgeId); - return null; - }, dbCallbackExecutorService); + return edgeEventService.saveAsync(edgeEvent); } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java index 11ff6a3321..7536b46c52 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java @@ -58,7 +58,7 @@ public abstract class AbstractTbEntityService { @Autowired protected DbCallbackExecutorService dbExecutor; @Autowired(required = false) - protected TbNotificationEntityService notificationEntityService; + protected TbLogEntityActionService logEntityActionService; @Autowired(required = false) protected EdgeService edgeService; @Autowired diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbLogEntityActionService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbLogEntityActionService.java new file mode 100644 index 0000000000..07371291e7 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbLogEntityActionService.java @@ -0,0 +1,79 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.entitiy; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.service.action.EntityActionService; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DefaultTbLogEntityActionService implements TbLogEntityActionService { + + private final EntityActionService entityActionService; + + @Override + public void logEntityAction(TenantId tenantId, I entityId, ActionType actionType, + User user, Exception e, Object... additionalInfo) { + logEntityAction(tenantId, entityId, null, null, actionType, user, e, additionalInfo); + } + + @Override + public void logEntityAction(TenantId tenantId, I entityId, E entity, + ActionType actionType, User user, Object... additionalInfo) { + logEntityAction(tenantId, entityId, entity, null, actionType, user, null, additionalInfo); + } + + @Override + public void logEntityAction(TenantId tenantId, I entityId, E entity, + ActionType actionType, User user, Exception e, + Object... additionalInfo) { + logEntityAction(tenantId, entityId, entity, null, actionType, user, e, additionalInfo); + } + + @Override + public void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId, + ActionType actionType, User user, Object... additionalInfo) { + logEntityAction(tenantId, entityId, entity, customerId, actionType, user, null, additionalInfo); + } + + @Override + public void logEntityAction(TenantId tenantId, I entityId, E entity, + CustomerId customerId, ActionType actionType, + User user, Exception e, Object... additionalInfo) { + if (user != null) { + entityActionService.logEntityAction(user, entityId, entity, customerId, actionType, e, additionalInfo); + } else if (e == null) { + entityActionService.pushEntityActionToRuleEngine(entityId, entity, tenantId, customerId, actionType, null, additionalInfo); + } + } + + @Override + public void logEntityRelationAction(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user, + ActionType actionType, Exception e, Object... additionalInfo) { + logEntityAction(tenantId, relation.getFrom(), null, customerId, actionType, user, e, additionalInfo); + logEntityAction(tenantId, relation.getTo(), null, customerId, actionType, user, e, additionalInfo); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java deleted file mode 100644 index f2465c9398..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java +++ /dev/null @@ -1,211 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.entitiy; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; -import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.HasName; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EdgeId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.msg.TbMsgType; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; -import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.service.action.EntityActionService; -import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; - -@Slf4j -@Service -@RequiredArgsConstructor -public class DefaultTbNotificationEntityService implements TbNotificationEntityService { - - private final EntityActionService entityActionService; - private final TbClusterService tbClusterService; - private final GatewayNotificationsService gatewayNotificationsService; - - @Override - public void logEntityAction(TenantId tenantId, I entityId, ActionType actionType, - User user, Exception e, Object... additionalInfo) { - logEntityAction(tenantId, entityId, null, null, actionType, user, e, additionalInfo); - } - - @Override - public void logEntityAction(TenantId tenantId, I entityId, E entity, - ActionType actionType, User user, Object... additionalInfo) { - logEntityAction(tenantId, entityId, entity, null, actionType, user, null, additionalInfo); - } - - @Override - public void logEntityAction(TenantId tenantId, I entityId, E entity, - ActionType actionType, User user, Exception e, - Object... additionalInfo) { - logEntityAction(tenantId, entityId, entity, null, actionType, user, e, additionalInfo); - } - - @Override - public void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId, - ActionType actionType, User user, Object... additionalInfo) { - logEntityAction(tenantId, entityId, entity, customerId, actionType, user, null, additionalInfo); - } - - @Override - public void logEntityAction(TenantId tenantId, I entityId, E entity, - CustomerId customerId, ActionType actionType, - User user, Exception e, Object... additionalInfo) { - if (user != null) { - entityActionService.logEntityAction(user, entityId, entity, customerId, actionType, e, additionalInfo); - } else if (e == null) { - entityActionService.pushEntityActionToRuleEngine(entityId, entity, tenantId, customerId, actionType, null, additionalInfo); - } - } - - @Override - public void notifyCreateOrUpdateTenant(Tenant tenant, ComponentLifecycleEvent event) { - tbClusterService.onTenantChange(tenant, null); - tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), event); - } - - @Override - public void notifyDeleteTenant(Tenant tenant) { - tbClusterService.onTenantDelete(tenant, null); - tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), ComponentLifecycleEvent.DELETED); - } - - @Override - public void notifyCreateOrUpdateDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, - Device device, Device oldDevice, ActionType actionType, - User user, Object... additionalInfo) { - tbClusterService.onDeviceUpdated(device, oldDevice); - logEntityAction(tenantId, deviceId, device, customerId, actionType, user, additionalInfo); - } - - @Override - public void notifyDeleteDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, - User user, Object... additionalInfo) { - gatewayNotificationsService.onDeviceDeleted(device); - tbClusterService.onDeviceDeleted(device, null); - logEntityAction(tenantId, deviceId, device, customerId, ActionType.DELETED, user, additionalInfo); - } - - @Override - public void notifyUpdateDeviceCredentials(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, - DeviceCredentials deviceCredentials, User user) { - tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(tenantId, deviceCredentials.getDeviceId(), deviceCredentials), null); - logEntityAction(tenantId, deviceId, device, customerId, ActionType.CREDENTIALS_UPDATED, user, deviceCredentials); - } - - @Override - public void notifyAssignDeviceToTenant(TenantId tenantId, TenantId newTenantId, DeviceId deviceId, CustomerId customerId, - Device device, Tenant tenant, User user, Object... additionalInfo) { - logEntityAction(tenantId, deviceId, device, customerId, ActionType.ASSIGNED_TO_TENANT, user, additionalInfo); - pushAssignedFromNotification(tenant, newTenantId, device); - } - - @Override - public void notifyCreateOrUpdateOrDeleteEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, - ActionType actionType, User user, Object... additionalInfo) { - ComponentLifecycleEvent lifecycleEvent; - switch (actionType) { - case ADDED: - lifecycleEvent = ComponentLifecycleEvent.CREATED; - break; - case UPDATED: - lifecycleEvent = ComponentLifecycleEvent.UPDATED; - break; - case DELETED: - lifecycleEvent = ComponentLifecycleEvent.DELETED; - break; - default: - throw new IllegalArgumentException("Unknown actionType: " + actionType); - } - tbClusterService.broadcastEntityStateChangeEvent(tenantId, edgeId, lifecycleEvent); - logEntityAction(tenantId, edgeId, edge, customerId, actionType, user, additionalInfo); - } - - @Override - public void logEntityRelationAction(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user, - ActionType actionType, Exception e, Object... additionalInfo) { - logEntityAction(tenantId, relation.getFrom(), null, customerId, actionType, user, e, additionalInfo); - logEntityAction(tenantId, relation.getTo(), null, customerId, actionType, user, e, additionalInfo); - } - - private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { - String data = JacksonUtil.toString(JacksonUtil.valueToTree(assignedDevice)); - if (data != null) { - TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), - assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); - tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null); - } - } - - private TbMsgMetaData getMetaDataForAssignedFrom(Tenant tenant) { - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("assignedFromTenantId", tenant.getId().getId().toString()); - metaData.putValue("assignedFromTenantName", tenant.getName()); - return metaData; - } - - public static EdgeEventActionType edgeTypeByActionType(ActionType actionType) { - switch (actionType) { - case ADDED: - return EdgeEventActionType.ADDED; - case UPDATED: - return EdgeEventActionType.UPDATED; - case ALARM_ACK: - return EdgeEventActionType.ALARM_ACK; - case ALARM_CLEAR: - return EdgeEventActionType.ALARM_CLEAR; - case ALARM_ASSIGNED: - return EdgeEventActionType.ALARM_ASSIGNED; - case ALARM_UNASSIGNED: - return EdgeEventActionType.ALARM_UNASSIGNED; - case DELETED: - return EdgeEventActionType.DELETED; - case RELATION_ADD_OR_UPDATE: - return EdgeEventActionType.RELATION_ADD_OR_UPDATE; - case RELATION_DELETED: - return EdgeEventActionType.RELATION_DELETED; - case ASSIGNED_TO_CUSTOMER: - return EdgeEventActionType.ASSIGNED_TO_CUSTOMER; - case UNASSIGNED_FROM_CUSTOMER: - return EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER; - case ASSIGNED_TO_EDGE: - return EdgeEventActionType.ASSIGNED_TO_EDGE; - case UNASSIGNED_FROM_EDGE: - return EdgeEventActionType.UNASSIGNED_FROM_EDGE; - case CREDENTIALS_UPDATED: - return EdgeEventActionType.CREDENTIALS_UPDATED; - default: - return null; - } - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java new file mode 100644 index 0000000000..57d2d265ab --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java @@ -0,0 +1,208 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.entitiy; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionalEventListener; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgDataType; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; + +import javax.annotation.PostConstruct; + +@Component +@RequiredArgsConstructor +@Slf4j +public class EntityStateSourcingListener { + + private final TbClusterService tbClusterService; + + @PostConstruct + public void init() { + log.info("EntityStateSourcingListener initiated"); + } + + @TransactionalEventListener(fallbackExecution = true) + public void handleEvent(SaveEntityEvent event) { + log.trace("[{}] SaveEntityEvent called: {}", event.getTenantId(), event); + TenantId tenantId = event.getTenantId(); + EntityId entityId = event.getEntityId(); + EntityType entityType = entityId.getEntityType(); + boolean isCreated = event.getAdded() != null && event.getAdded(); + ComponentLifecycleEvent lifecycleEvent = isCreated ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED; + + if (isCommonEntityStateUpdated(entityId)) { + tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent); + } else if (EntityType.TENANT.equals(entityType)) { + onTenantUpdate((Tenant) event.getEntity(), isCreated); + } else if (EntityType.TENANT_PROFILE.equals(entityType)) { + onTenantProfileUpdate((TenantProfile) event.getEntity(), isCreated); + } else if (EntityType.DEVICE.equals(entityType)) { + onDeviceUpdate(event.getEntity(), event.getOldEntity()); + } else if (EntityType.DEVICE_PROFILE.equals(entityType)) { + onDeviceProfileUpdate((DeviceProfile) event.getEntity(), event.getOldEntity(), isCreated); + } else if (EntityType.EDGE.equals(entityType)) { + handleEdgeEvent(tenantId, entityId, event.getEntity(), lifecycleEvent); + } else if (EntityType.TB_RESOURCE.equals(entityType)) { + tbClusterService.onResourceChange((TbResource) event.getEntity(), null); + } else if (EntityType.API_USAGE_STATE.equals(entityType) && !event.getAdded()) { + tbClusterService.onApiStateChange((ApiUsageState) event.getEntity(), null); + } + } + + @TransactionalEventListener(fallbackExecution = true) + public void handleEvent(DeleteEntityEvent event) { + log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event); + TenantId tenantId = event.getTenantId(); + EntityId entityId = event.getEntityId(); + EntityType entityType = entityId.getEntityType(); + + if (isCommonEntityStateUpdated(entityId) || EntityType.CUSTOMER.equals(entityType)) { + tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); + } else if (EntityType.TENANT.equals(event.getEntityId().getEntityType())) { + onTenantDeleted((Tenant) event.getEntity()); + } else if (EntityType.TENANT_PROFILE.equals(entityType)) { + tbClusterService.onTenantProfileDelete((TenantProfile) event.getEntity(), null); + } else if (EntityType.DEVICE.equals(entityType)) { + tbClusterService.onDeviceDeleted((Device) event.getEntity(), null); + } else if (EntityType.DEVICE_PROFILE.equals(entityType)) { + onDeviceProfileDelete(event.getTenantId(), event.getEntityId(), (DeviceProfile) event.getEntity()); + } else if (EntityType.EDGE.equals(entityType)) { + tbClusterService.broadcastEntityStateChangeEvent(event.getTenantId(), event.getEntityId(), ComponentLifecycleEvent.DELETED); + } else if (EntityType.TB_RESOURCE.equals(entityType)) { + tbClusterService.onResourceDeleted((TbResource) event.getEntity(), null); + } + } + + private void onDeviceProfileDelete(TenantId tenantId, EntityId entityId, DeviceProfile deviceProfile) { + tbClusterService.onDeviceProfileDelete(deviceProfile, null); + tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); + } + + @TransactionalEventListener(fallbackExecution = true) + public void handleEvent(ActionEntityEvent event) { + log.trace("[{}] ActionEntityEvent called: {}", event.getTenantId(), event); + + if (ActionType.CREDENTIALS_UPDATED.equals(event.getActionType()) && EntityType.DEVICE.equals(event.getEntityId().getEntityType()) + && event.getEntity() instanceof DeviceCredentials) { + tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg( + event.getTenantId(), (DeviceId) event.getEntityId(), (DeviceCredentials) event.getEntity()), null); + } else if (ActionType.ASSIGNED_TO_TENANT.equals(event.getActionType()) && event.getEntity() instanceof Device) { + Tenant tenant = JacksonUtil.fromString(event.getBody(), Tenant.class); + pushAssignedFromNotification(tenant, event.getTenantId(), (Device) event.getEntity()); + } + } + + private void onTenantUpdate(Tenant tenant, boolean isCreated) { + tbClusterService.onTenantChange(tenant, null); + tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), isCreated ? + ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + } + + private void onTenantProfileUpdate(TenantProfile tenantProfile, boolean isCreated) { + tbClusterService.onTenantProfileChange(tenantProfile, null); + tbClusterService.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, tenantProfile.getId(), + isCreated ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + } + + private boolean isCommonEntityStateUpdated(EntityId entityId) { + switch (entityId.getEntityType()) { + case ASSET: + case ASSET_PROFILE: + case ENTITY_VIEW: + return true; + } + return false; + } + + private void onDeviceProfileUpdate(DeviceProfile deviceProfile, Object oldEntity, boolean isCreated) { + DeviceProfile oldDeviceProfile = null; + if (!isCreated) { + oldDeviceProfile = getOldDeviceProfile(oldEntity); + } + tbClusterService.onDeviceProfileChange(deviceProfile, oldDeviceProfile, null); + } + + private DeviceProfile getOldDeviceProfile(Object oldEntity) { + if (oldEntity instanceof DeviceProfile) { + return (DeviceProfile) oldEntity; + } + return null; + } + + private void onDeviceUpdate(Object entity, Object oldEntity) { + Device device = (Device) entity; + Device oldDevice = null; + if (oldEntity instanceof Device) { + oldDevice = (Device) oldEntity; + } + tbClusterService.onDeviceUpdated(device, oldDevice); + } + + private void onTenantDeleted(Tenant tenant) { + tbClusterService.onTenantDelete(tenant, null); + tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), ComponentLifecycleEvent.DELETED); + } + + private void handleEdgeEvent(TenantId tenantId, EntityId entityId, Object entity, ComponentLifecycleEvent lifecycleEvent) { + if (entity instanceof Edge) { + tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent); + } else if (entity instanceof EdgeEvent) { + tbClusterService.onEdgeEventUpdate(tenantId, (EdgeId) entityId); + } + } + + private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { + String data = JacksonUtil.toString(JacksonUtil.valueToTree(assignedDevice)); + if (data != null) { + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), + assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); + tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null); + } + } + + private TbMsgMetaData getMetaDataForAssignedFrom(Tenant tenant) { + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("assignedFromTenantId", tenant.getId().getId().toString()); + metaData.putValue("assignedFromTenantName", tenant.getName()); + return metaData; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/TbLogEntityActionService.java similarity index 60% rename from application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/TbLogEntityActionService.java index d14b2c6293..6df0287b36 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/TbLogEntityActionService.java @@ -15,22 +15,15 @@ */ package org.thingsboard.server.service.entitiy; -import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.HasName; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.security.DeviceCredentials; -public interface TbNotificationEntityService { +public interface TbLogEntityActionService { void logEntityAction(TenantId tenantId, I entityId, ActionType actionType, User user, Exception e, Object... additionalInfo); @@ -48,25 +41,6 @@ public interface TbNotificationEntityService { ActionType actionType, User user, Exception e, Object... additionalInfo); - void notifyCreateOrUpdateTenant(Tenant tenant, ComponentLifecycleEvent event); - - void notifyDeleteTenant(Tenant tenant); - - void notifyCreateOrUpdateDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, - Device oldDevice, ActionType actionType, User user, Object... additionalInfo); - - void notifyDeleteDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, - User user, Object... additionalInfo); - - void notifyUpdateDeviceCredentials(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, - DeviceCredentials deviceCredentials, User user); - - void notifyAssignDeviceToTenant(TenantId tenantId, TenantId newTenantId, DeviceId deviceId, CustomerId customerId, - Device device, Tenant tenant, User user, Object... additionalInfo); - - void notifyCreateOrUpdateOrDeleteEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, - User user, Object... additionalInfo); - void logEntityRelationAction(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user, ActionType actionType, Exception e, Object... additionalInfo); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java index 282d11ab2a..bd1f99ae68 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java @@ -45,11 +45,11 @@ public class DefaultTbAlarmCommentService extends AbstractTbEntityService implem } try { AlarmComment savedAlarmComment = checkNotNull(alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment)); - notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getId(), alarm, alarm.getCustomerId(), actionType, user, savedAlarmComment); + logEntityActionService.logEntityAction(alarm.getTenantId(), alarm.getId(), alarm, alarm.getCustomerId(), actionType, user, savedAlarmComment); return savedAlarmComment; } catch (Exception e) { - notificationEntityService.logEntityAction(alarm.getTenantId(), emptyId(EntityType.ALARM), alarm, actionType, user, e, alarmComment); + logEntityActionService.logEntityAction(alarm.getTenantId(), emptyId(EntityType.ALARM), alarm, actionType, user, e, alarmComment); throw e; } } @@ -63,7 +63,7 @@ public class DefaultTbAlarmCommentService extends AbstractTbEntityService implem String.format("User %s deleted his comment", (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))); AlarmComment savedAlarmComment = checkNotNull(alarmCommentService.saveAlarmComment(alarm.getTenantId(), alarmComment)); - notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getId(), alarm, alarm.getCustomerId(), ActionType.DELETED_COMMENT, user, savedAlarmComment); + logEntityActionService.logEntityAction(alarm.getTenantId(), alarm.getId(), alarm, alarm.getCustomerId(), ActionType.DELETED_COMMENT, user, savedAlarmComment); } else { throw new ThingsboardException("System comment could not be deleted", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index e776c040c0..d30064e0fc 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.TimePageLink; @@ -82,12 +81,12 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb resultAlarm = unassign(alarm, alarm.getAssignTs(), user); } if (result.isModified()) { - notificationEntityService.logEntityAction(tenantId, alarm.getOriginator(), resultAlarm, + logEntityActionService.logEntityAction(tenantId, alarm.getOriginator(), resultAlarm, resultAlarm.getCustomerId(), actionType, user); } return new Alarm(resultAlarm); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ALARM), alarm, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ALARM), alarm, actionType, user, e); throw e; } } @@ -118,7 +117,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } catch (ThingsboardException e) { log.error("Failed to save alarm comment", e); } - notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarmInfo, + logEntityActionService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarmInfo, alarmInfo.getCustomerId(), ActionType.ALARM_ACK, user); } else { throw new ThingsboardException("Alarm was already acknowledged!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); @@ -152,7 +151,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } catch (ThingsboardException e) { log.error("Failed to save alarm comment", e); } - notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarmInfo, + logEntityActionService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarmInfo, alarmInfo.getCustomerId(), ActionType.ALARM_CLEAR, user); } else { throw new ThingsboardException("Alarm was already cleared!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); @@ -184,7 +183,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } catch (ThingsboardException e) { log.error("Failed to save alarm comment", e); } - notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarmInfo, + logEntityActionService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarmInfo, alarmInfo.getCustomerId(), ActionType.ALARM_ASSIGNED, user); } else { throw new ThingsboardException("Alarm was already assigned to this user!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); @@ -213,7 +212,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } catch (ThingsboardException e) { log.error("Failed to save alarm comment", e); } - notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarmInfo, + logEntityActionService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarmInfo, alarmInfo.getCustomerId(), ActionType.ALARM_UNASSIGNED, user); } else { throw new ThingsboardException("Alarm was already unassigned!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); @@ -245,7 +244,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } catch (ThingsboardException e) { log.error("Failed to save alarm comment", e); } - notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), result.getAlarm(), + logEntityActionService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), result.getAlarm(), alarm.getCustomerId(), ActionType.ALARM_UNASSIGNED, user); } } @@ -258,7 +257,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb @Override public Boolean delete(Alarm alarm, User user) { TenantId tenantId = alarm.getTenantId(); - notificationEntityService.logEntityAction(tenantId, alarm.getOriginator(), alarm, alarm.getCustomerId(), + logEntityActionService.logEntityAction(tenantId, alarm.getOriginator(), alarm, alarm.getCustomerId(), ActionType.DELETED, user); return alarmSubscriptionService.deleteAlarm(tenantId, alarm.getId()); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java index 6510ecfb7f..45c3881475 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java @@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.profile.TbAssetProfileCache; @@ -60,13 +59,11 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb } Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); autoCommit(user, savedAsset.getId()); - notificationEntityService.logEntityAction(tenantId, savedAsset.getId(), savedAsset, asset.getCustomerId(), + logEntityActionService.logEntityAction(tenantId, savedAsset.getId(), savedAsset, asset.getCustomerId(), actionType, user); - tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedAsset.getId(), - asset.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return savedAsset; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), asset, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ASSET), asset, actionType, user, e); throw e; } } @@ -78,11 +75,10 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb AssetId assetId = asset.getId(); try { assetService.deleteAsset(tenantId, assetId); - notificationEntityService.logEntityAction(tenantId, assetId, asset, asset.getCustomerId(), actionType, user, assetId.toString()); - tbClusterService.broadcastEntityStateChangeEvent(tenantId, assetId, ComponentLifecycleEvent.DELETED); + logEntityActionService.logEntityAction(tenantId, assetId, asset, asset.getCustomerId(), actionType, user, assetId.toString()); return removeAlarmsByEntityId(tenantId, assetId); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, assetId.toString()); throw e; } @@ -94,12 +90,12 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb CustomerId customerId = customer.getId(); try { Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, customerId)); - notificationEntityService.logEntityAction(tenantId, assetId, savedAsset, customerId, actionType, user, + logEntityActionService.logEntityAction(tenantId, assetId, savedAsset, customerId, actionType, user, assetId.toString(), customerId.toString(), customer.getName()); return savedAsset; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, assetId.toString(), customerId.toString()); throw e; } @@ -111,12 +107,12 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb try { Asset savedAsset = checkNotNull(assetService.unassignAssetFromCustomer(tenantId, assetId)); CustomerId customerId = customer.getId(); - notificationEntityService.logEntityAction(tenantId, assetId, savedAsset, customerId, actionType, user, + logEntityActionService.logEntityAction(tenantId, assetId, savedAsset, customerId, actionType, user, assetId.toString(), customerId.toString(), customer.getName()); return savedAsset; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, assetId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, assetId.toString()); throw e; } } @@ -128,12 +124,12 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, publicCustomer.getId())); CustomerId customerId = publicCustomer.getId(); - notificationEntityService.logEntityAction(tenantId, assetId, savedAsset, customerId, actionType, user, + logEntityActionService.logEntityAction(tenantId, assetId, savedAsset, customerId, actionType, user, assetId.toString(), customerId.toString(), publicCustomer.getName()); return savedAsset; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, assetId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, assetId.toString()); throw e; } } @@ -144,11 +140,11 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb EdgeId edgeId = edge.getId(); try { Asset savedAsset = checkNotNull(assetService.assignAssetToEdge(tenantId, assetId, edgeId)); - notificationEntityService.logEntityAction(tenantId, assetId, savedAsset, savedAsset.getCustomerId(), + logEntityActionService.logEntityAction(tenantId, assetId, savedAsset, savedAsset.getCustomerId(), actionType, user, assetId.toString(), edgeId.toString(), edge.getName()); return savedAsset; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, assetId.toString(), edgeId.toString()); throw e; } @@ -161,12 +157,12 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb EdgeId edgeId = edge.getId(); try { Asset savedAsset = checkNotNull(assetService.unassignAssetFromEdge(tenantId, assetId, edgeId)); - notificationEntityService.logEntityAction(tenantId, assetId, asset, asset.getCustomerId(), + logEntityActionService.logEntityAction(tenantId, assetId, asset, asset.getCustomerId(), actionType, user, assetId.toString(), edgeId.toString(), edge.getName()); return savedAsset; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, assetId.toString(), edgeId.toString()); throw e; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java index 00b181a52a..084cb925c2 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java @@ -26,7 +26,6 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; @@ -56,14 +55,11 @@ public class DefaultTbAssetProfileService extends AbstractTbEntityService implem } AssetProfile savedAssetProfile = checkNotNull(assetProfileService.saveAssetProfile(assetProfile)); autoCommit(user, savedAssetProfile.getId()); - tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedAssetProfile.getId(), - actionType.equals(ActionType.ADDED) ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); - - notificationEntityService.logEntityAction(tenantId, savedAssetProfile.getId(), savedAssetProfile, + logEntityActionService.logEntityAction(tenantId, savedAssetProfile.getId(), savedAssetProfile, null, actionType, user); return savedAssetProfile; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET_PROFILE), assetProfile, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ASSET_PROFILE), assetProfile, actionType, user, e); throw e; } } @@ -75,12 +71,10 @@ public class DefaultTbAssetProfileService extends AbstractTbEntityService implem TenantId tenantId = assetProfile.getTenantId(); try { assetProfileService.deleteAssetProfile(tenantId, assetProfileId); - - tbClusterService.broadcastEntityStateChangeEvent(tenantId, assetProfileId, ComponentLifecycleEvent.DELETED); - notificationEntityService.logEntityAction(tenantId, assetProfileId, assetProfile, null, + logEntityActionService.logEntityAction(tenantId, assetProfileId, assetProfile, null, actionType, user, assetProfileId.toString()); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET_PROFILE), actionType, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ASSET_PROFILE), actionType, user, e, assetProfileId.toString()); throw e; } @@ -94,16 +88,15 @@ public class DefaultTbAssetProfileService extends AbstractTbEntityService implem if (assetProfileService.setDefaultAssetProfile(tenantId, assetProfileId)) { if (previousDefaultAssetProfile != null) { previousDefaultAssetProfile = assetProfileService.findAssetProfileById(tenantId, previousDefaultAssetProfile.getId()); - notificationEntityService.logEntityAction(tenantId, previousDefaultAssetProfile.getId(), previousDefaultAssetProfile, + logEntityActionService.logEntityAction(tenantId, previousDefaultAssetProfile.getId(), previousDefaultAssetProfile, ActionType.UPDATED, user); } assetProfile = assetProfileService.findAssetProfileById(tenantId, assetProfileId); - - notificationEntityService.logEntityAction(tenantId, assetProfileId, assetProfile, ActionType.UPDATED, user); + logEntityActionService.logEntityAction(tenantId, assetProfileId, assetProfile, ActionType.UPDATED, user); } return assetProfile; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET_PROFILE), ActionType.UPDATED, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ASSET_PROFILE), ActionType.UPDATED, user, e, assetProfileId.toString()); throw e; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java index 96611bb3b5..5e8cc24913 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java @@ -37,10 +37,10 @@ public class DefaultTbCustomerService extends AbstractTbEntityService implements try { Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer)); autoCommit(user, savedCustomer.getId()); - notificationEntityService.logEntityAction(tenantId, savedCustomer.getId(), savedCustomer, null, actionType, user); + logEntityActionService.logEntityAction(tenantId, savedCustomer.getId(), savedCustomer, null, actionType, user); return savedCustomer; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.CUSTOMER), customer, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.CUSTOMER), customer, actionType, user, e); throw e; } } @@ -52,11 +52,10 @@ public class DefaultTbCustomerService extends AbstractTbEntityService implements CustomerId customerId = customer.getId(); try { customerService.deleteCustomer(tenantId, customerId); - notificationEntityService.logEntityAction(tenantId, customer.getId(), customer, customerId, actionType, + logEntityActionService.logEntityAction(tenantId, customer.getId(), customer, customerId, actionType, user, customerId.toString()); - tbClusterService.broadcastEntityStateChangeEvent(tenantId, customer.getId(), ComponentLifecycleEvent.DELETED); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.CUSTOMER), actionType, user, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.CUSTOMER), actionType, user, e, customerId.toString()); throw e; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java index 70918486a3..5138d93ff1 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java @@ -50,11 +50,11 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement try { Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard)); autoCommit(user, savedDashboard.getId()); - notificationEntityService.logEntityAction(tenantId, savedDashboard.getId(), savedDashboard, null, + logEntityActionService.logEntityAction(tenantId, savedDashboard.getId(), savedDashboard, null, actionType, user); return savedDashboard; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), dashboard, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), dashboard, actionType, user, e); throw e; } } @@ -66,9 +66,9 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement TenantId tenantId = dashboard.getTenantId(); try { dashboardService.deleteDashboard(tenantId, dashboardId); - notificationEntityService.logEntityAction(tenantId, dashboardId, dashboard, null, actionType, user, dashboardId.toString()); + logEntityActionService.logEntityAction(tenantId, dashboardId, dashboard, null, actionType, user, dashboardId.toString()); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); throw e; } } @@ -81,11 +81,11 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement DashboardId dashboardId = dashboard.getId(); try { Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId)); - notificationEntityService.logEntityAction(tenantId, dashboardId, savedDashboard, customerId, actionType, + logEntityActionService.logEntityAction(tenantId, dashboardId, savedDashboard, customerId, actionType, user, dashboardId.toString(), customerId.toString(), customer.getName()); return savedDashboard; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString(), customerId.toString()); throw e; } @@ -99,11 +99,11 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement try { Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, publicCustomer.getId())); - notificationEntityService.logEntityAction(tenantId, dashboardId, savedDashboard, publicCustomer.getId(), + logEntityActionService.logEntityAction(tenantId, dashboardId, savedDashboard, publicCustomer.getId(), actionType, user, dashboardId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedDashboard; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); throw e; } } @@ -116,11 +116,11 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement try { Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, publicCustomer.getId())); - notificationEntityService.logEntityAction(tenantId, dashboardId, dashboard, publicCustomer.getId(), actionType, + logEntityActionService.logEntityAction(tenantId, dashboardId, dashboard, publicCustomer.getId(), actionType, user, dashboardId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedDashboard; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); throw e; } } @@ -155,20 +155,20 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement for (CustomerId customerId : addedCustomerIds) { savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId)); ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId); - notificationEntityService.logEntityAction(tenantId, savedDashboard.getId(), savedDashboard, customerId, + logEntityActionService.logEntityAction(tenantId, savedDashboard.getId(), savedDashboard, customerId, actionType, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; for (CustomerId customerId : removedCustomerIds) { ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId); savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customerId)); - notificationEntityService.logEntityAction(tenantId, savedDashboard.getId(), savedDashboard, customerId, + logEntityActionService.logEntityAction(tenantId, savedDashboard.getId(), savedDashboard, customerId, actionType, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } return savedDashboard; } } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); throw e; } } @@ -192,13 +192,13 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement for (CustomerId customerId : addedCustomerIds) { savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId)); ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId); - notificationEntityService.logEntityAction(tenantId, dashboardId, savedDashboard, customerId, + logEntityActionService.logEntityAction(tenantId, dashboardId, savedDashboard, customerId, actionType, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } return savedDashboard; } } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); throw e; } } @@ -222,13 +222,13 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement for (CustomerId customerId : removedCustomerIds) { ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId); savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customerId)); - notificationEntityService.logEntityAction(tenantId, dashboardId, savedDashboard, customerId, + logEntityActionService.logEntityAction(tenantId, dashboardId, savedDashboard, customerId, actionType, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } return savedDashboard; } } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); throw e; } } @@ -239,11 +239,11 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement EdgeId edgeId = edge.getId(); try { Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToEdge(tenantId, dashboardId, edgeId)); - notificationEntityService.logEntityAction(tenantId, dashboardId, savedDashboard, null, actionType, + logEntityActionService.logEntityAction(tenantId, dashboardId, savedDashboard, null, actionType, user, dashboardId.toString(), edgeId.toString(), edge.getName()); return savedDashboard; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, user, e, dashboardId.toString(), edgeId); throw e; } @@ -257,11 +257,11 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement EdgeId edgeId = edge.getId(); try { Dashboard savedDevice = checkNotNull(dashboardService.unassignDashboardFromEdge(tenantId, dashboardId, edgeId)); - notificationEntityService.logEntityAction(tenantId, dashboardId, dashboard, null, actionType, + logEntityActionService.logEntityAction(tenantId, dashboardId, dashboard, null, actionType, user, dashboardId.toString(), edgeId.toString(), edge.getName()); return savedDevice; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString(), edgeId.toString()); throw e; } @@ -274,11 +274,11 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement DashboardId dashboardId = dashboard.getId(); try { Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customer.getId())); - notificationEntityService.logEntityAction(tenantId, dashboardId, savedDashboard, customer.getId(), + logEntityActionService.logEntityAction(tenantId, dashboardId, savedDashboard, customer.getId(), actionType, user, dashboardId.toString(), customer.getId().toString(), customer.getName()); return savedDashboard; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java index 544a199498..ab5b5088fc 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java @@ -56,52 +56,51 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T private final TenantService tenantService; @Override - public Device save(Device device, Device oldDevice, String accessToken, User user) throws Exception { + public Device save(Device device, String accessToken, User user) throws Exception { ActionType actionType = device.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = device.getTenantId(); try { Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); autoCommit(user, savedDevice.getId()); - notificationEntityService.notifyCreateOrUpdateDevice(tenantId, savedDevice.getId(), savedDevice.getCustomerId(), - savedDevice, oldDevice, actionType, user); + logEntityActionService.logEntityAction(tenantId, savedDevice.getId(), savedDevice, savedDevice.getCustomerId(), + actionType, user); return savedDevice; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), device, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), device, actionType, user, e); throw e; } } @Override public Device saveDeviceWithCredentials(Device device, DeviceCredentials credentials, User user) throws ThingsboardException { - boolean isCreate = device.getId() == null; - ActionType actionType = isCreate ? ActionType.ADDED : ActionType.UPDATED; + ActionType actionType = device.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = device.getTenantId(); try { - Device oldDevice = isCreate ? null : deviceService.findDeviceById(tenantId, device.getId()); Device savedDevice = checkNotNull(deviceService.saveDeviceWithCredentials(device, credentials)); - notificationEntityService.notifyCreateOrUpdateDevice(tenantId, savedDevice.getId(), savedDevice.getCustomerId(), - savedDevice, oldDevice, actionType, user); + logEntityActionService.logEntityAction(tenantId, savedDevice.getId(), savedDevice, savedDevice.getCustomerId(), + actionType, user); return savedDevice; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), device, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), device, actionType, user, e); throw e; } } @Override public ListenableFuture delete(Device device, User user) { + ActionType actionType = ActionType.DELETED; TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); try { deviceService.deleteDevice(tenantId, deviceId); - notificationEntityService.notifyDeleteDevice(tenantId, deviceId, device.getCustomerId(), device, + logEntityActionService.logEntityAction(tenantId, deviceId, device, device.getCustomerId(), actionType, user, deviceId.toString()); return removeAlarmsByEntityId(tenantId, deviceId); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), ActionType.DELETED, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, user, e, deviceId.toString()); throw e; } @@ -113,12 +112,12 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T CustomerId customerId = customer.getId(); try { Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(tenantId, deviceId, customerId)); - notificationEntityService.logEntityAction(tenantId, deviceId, savedDevice, customerId, actionType, user, + logEntityActionService.logEntityAction(tenantId, deviceId, savedDevice, customerId, actionType, user, deviceId.toString(), customerId.toString(), customer.getName()); return savedDevice; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, user, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, user, e, deviceId.toString(), customerId.toString()); throw e; } @@ -133,12 +132,12 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T Device savedDevice = checkNotNull(deviceService.unassignDeviceFromCustomer(tenantId, deviceId)); CustomerId customerId = customer.getId(); - notificationEntityService.logEntityAction(tenantId, deviceId, savedDevice, customerId, actionType, user, + logEntityActionService.logEntityAction(tenantId, deviceId, savedDevice, customerId, actionType, user, deviceId.toString(), customerId.toString(), customer.getName()); return savedDevice; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, user, e, deviceId.toString()); throw e; } @@ -151,12 +150,12 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T try { Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(tenantId, deviceId, publicCustomer.getId())); - notificationEntityService.logEntityAction(tenantId, deviceId, savedDevice, savedDevice.getCustomerId(), + logEntityActionService.logEntityAction(tenantId, deviceId, savedDevice, savedDevice.getCustomerId(), actionType, user, deviceId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedDevice; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, user, e, deviceId.toString()); throw e; } @@ -168,11 +167,11 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T DeviceId deviceId = device.getId(); try { DeviceCredentials deviceCredentials = checkNotNull(deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, deviceId)); - notificationEntityService.logEntityAction(tenantId, deviceId, device, device.getCustomerId(), + logEntityActionService.logEntityAction(tenantId, deviceId, device, device.getCustomerId(), ActionType.CREDENTIALS_READ, user, deviceId.toString()); return deviceCredentials; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), ActionType.CREDENTIALS_READ, user, e, deviceId.toString()); throw e; } @@ -180,15 +179,17 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T @Override public DeviceCredentials updateDeviceCredentials(Device device, DeviceCredentials deviceCredentials, User user) throws ThingsboardException { + ActionType actionType = ActionType.CREDENTIALS_UPDATED; TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); try { DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials)); - notificationEntityService.notifyUpdateDeviceCredentials(tenantId, deviceId, device.getCustomerId(), device, result, user); + logEntityActionService.logEntityAction(tenantId, deviceId, device, device.getCustomerId(), + actionType, user, deviceCredentials); return result; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), - ActionType.CREDENTIALS_UPDATED, user, e, deviceCredentials); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), + actionType, user, e, deviceCredentials); throw e; } } @@ -199,7 +200,7 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T return Futures.transform(future, result -> { if (result != null && result.getResponse().equals(ClaimResponse.SUCCESS)) { - notificationEntityService.logEntityAction(tenantId, device.getId(), result.getDevice(), customerId, + logEntityActionService.logEntityAction(tenantId, device.getId(), result.getDevice(), customerId, ActionType.ASSIGNED_TO_CUSTOMER, user, device.getId().toString(), customerId.toString(), customerService.findCustomerById(tenantId, customerId).getName()); } @@ -214,7 +215,7 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T return Futures.transform(future, result -> { Customer unassignedCustomer = result.getUnassignedCustomer(); if (unassignedCustomer != null) { - notificationEntityService.logEntityAction(tenantId, device.getId(), device, device.getCustomerId(), + logEntityActionService.logEntityAction(tenantId, device.getId(), device, device.getCustomerId(), ActionType.UNASSIGNED_FROM_CUSTOMER, user, device.getId().toString(), unassignedCustomer.getId().toString(), unassignedCustomer.getName()); } @@ -224,20 +225,21 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T @Override public Device assignDeviceToTenant(Device device, Tenant newTenant, User user) { + ActionType actionType = ActionType.ASSIGNED_TO_TENANT; TenantId tenantId = device.getTenantId(); TenantId newTenantId = newTenant.getId(); DeviceId deviceId = device.getId(); try { Tenant tenant = tenantService.findTenantById(tenantId); - Device assignedDevice = deviceService.assignDeviceToTenant(newTenantId, device); + Device assignedDevice = deviceService.assignDeviceToTenant(tenant, newTenantId, device); - notificationEntityService.notifyAssignDeviceToTenant(tenantId, newTenantId, deviceId, - assignedDevice.getCustomerId(), assignedDevice, tenant, user, newTenantId.toString(), newTenant.getName()); + logEntityActionService.logEntityAction(tenantId, deviceId, assignedDevice, assignedDevice.getCustomerId(), + actionType, user, newTenantId.toString(), newTenant.getName()); return assignedDevice; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), - ActionType.ASSIGNED_TO_TENANT, user, e, deviceId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), + actionType, user, e, deviceId.toString()); throw e; } } @@ -248,12 +250,12 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T EdgeId edgeId = edge.getId(); try { Device savedDevice = checkNotNull(deviceService.assignDeviceToEdge(tenantId, deviceId, edgeId)); - notificationEntityService.logEntityAction(tenantId, deviceId, savedDevice, savedDevice.getCustomerId(), + logEntityActionService.logEntityAction(tenantId, deviceId, savedDevice, savedDevice.getCustomerId(), actionType, user, deviceId.toString(), edgeId.toString(), edge.getName()); return savedDevice; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, user, e, deviceId.toString(), edgeId.toString()); throw e; } @@ -267,12 +269,12 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T EdgeId edgeId = edge.getId(); try { Device savedDevice = checkNotNull(deviceService.unassignDeviceFromEdge(tenantId, deviceId, edgeId)); - notificationEntityService.logEntityAction(tenantId, deviceId, savedDevice, savedDevice.getCustomerId(), + logEntityActionService.logEntityAction(tenantId, deviceId, savedDevice, savedDevice.getCustomerId(), actionType, user, deviceId.toString(), edgeId.toString(), edge.getName()); return savedDevice; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, user, e, deviceId.toString(), edgeId.toString()); throw e; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java index e9665c6e3e..268ddbe4ae 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java @@ -31,7 +31,7 @@ import org.thingsboard.server.dao.device.claim.ReclaimResult; public interface TbDeviceService { - Device save(Device device, Device oldDevice, String accessToken, User user) throws Exception; + Device save(Device device, String accessToken, User user) throws Exception; Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials, User user) throws ThingsboardException; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java index 70ee722493..2cf4dcbc69 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java @@ -25,13 +25,9 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.ota.OtaPackageStateService; - -import java.util.Objects; @Service @TbCoreComponent @@ -40,38 +36,20 @@ import java.util.Objects; public class DefaultTbDeviceProfileService extends AbstractTbEntityService implements TbDeviceProfileService { private final DeviceProfileService deviceProfileService; - private final OtaPackageStateService otaPackageStateService; @Override public DeviceProfile save(DeviceProfile deviceProfile, User user) throws Exception { ActionType actionType = deviceProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = deviceProfile.getTenantId(); try { - boolean isFirmwareChanged = false; - boolean isSoftwareChanged = false; - - if (actionType.equals(ActionType.UPDATED)) { - DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfile.getId()); - if (!Objects.equals(deviceProfile.getFirmwareId(), oldDeviceProfile.getFirmwareId())) { - isFirmwareChanged = true; - } - if (!Objects.equals(deviceProfile.getSoftwareId(), oldDeviceProfile.getSoftwareId())) { - isSoftwareChanged = true; - } - } DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); autoCommit(user, savedDeviceProfile.getId()); - tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); - tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedDeviceProfile.getId(), - actionType.equals(ActionType.ADDED) ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); - - otaPackageStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged); - notificationEntityService.logEntityAction(tenantId, savedDeviceProfile.getId(), savedDeviceProfile, + logEntityActionService.logEntityAction(tenantId, savedDeviceProfile.getId(), savedDeviceProfile, null, actionType, user); return savedDeviceProfile; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE_PROFILE), deviceProfile, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE_PROFILE), deviceProfile, actionType, user, e); throw e; } } @@ -84,12 +62,10 @@ public class DefaultTbDeviceProfileService extends AbstractTbEntityService imple try { deviceProfileService.deleteDeviceProfile(tenantId, deviceProfileId); - tbClusterService.onDeviceProfileDelete(deviceProfile, null); - tbClusterService.broadcastEntityStateChangeEvent(tenantId, deviceProfileId, ComponentLifecycleEvent.DELETED); - notificationEntityService.logEntityAction(tenantId, deviceProfileId, deviceProfile, null, + logEntityActionService.logEntityAction(tenantId, deviceProfileId, deviceProfile, null, actionType, user, deviceProfileId.toString()); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE_PROFILE), actionType, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE_PROFILE), actionType, user, e, deviceProfileId.toString()); throw e; } @@ -103,16 +79,16 @@ public class DefaultTbDeviceProfileService extends AbstractTbEntityService imple if (deviceProfileService.setDefaultDeviceProfile(tenantId, deviceProfileId)) { if (previousDefaultDeviceProfile != null) { previousDefaultDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, previousDefaultDeviceProfile.getId()); - notificationEntityService.logEntityAction(tenantId, previousDefaultDeviceProfile.getId(), previousDefaultDeviceProfile, + logEntityActionService.logEntityAction(tenantId, previousDefaultDeviceProfile.getId(), previousDefaultDeviceProfile, ActionType.UPDATED, user); } deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); - notificationEntityService.logEntityAction(tenantId, deviceProfileId, deviceProfile, ActionType.UPDATED, user); + logEntityActionService.logEntityAction(tenantId, deviceProfileId, deviceProfile, ActionType.UPDATED, user); } return deviceProfile; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE_PROFILE), ActionType.UPDATED, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DEVICE_PROFILE), ActionType.UPDATED, user, e, deviceProfileId.toString()); throw e; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java index adc20c21d0..8b0a04631f 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java @@ -48,36 +48,37 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE ActionType actionType = edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = edge.getTenantId(); try { - if (actionType == ActionType.ADDED && edge.getRootRuleChainId() == null) { + if (ActionType.ADDED.equals(actionType) && edge.getRootRuleChainId() == null) { edge.setRootRuleChainId(edgeTemplateRootRuleChain.getId()); } Edge savedEdge = checkNotNull(edgeService.saveEdge(edge)); EdgeId edgeId = savedEdge.getId(); - if (actionType == ActionType.ADDED) { + if (ActionType.ADDED.equals(actionType)) { ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), edgeId); edgeNotificationService.setEdgeRootRuleChain(tenantId, savedEdge, edgeTemplateRootRuleChain.getId()); edgeService.assignDefaultRuleChainsToEdge(tenantId, edgeId); } - notificationEntityService.notifyCreateOrUpdateOrDeleteEdge(tenantId, edgeId, savedEdge.getCustomerId(), savedEdge, actionType, user); + logEntityActionService.logEntityAction(tenantId, edgeId, savedEdge, savedEdge.getCustomerId(), actionType, user); return savedEdge; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), edge, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.EDGE), edge, actionType, user, e); throw e; } } @Override public void delete(Edge edge, User user) { + ActionType actionType = ActionType.DELETED; EdgeId edgeId = edge.getId(); TenantId tenantId = edge.getTenantId(); try { edgeService.deleteEdge(tenantId, edgeId); - notificationEntityService.notifyCreateOrUpdateOrDeleteEdge(tenantId, edgeId, edge.getCustomerId(), edge, ActionType.DELETED, user, edgeId.toString()); + logEntityActionService.logEntityAction(tenantId, edgeId, edge, edge.getCustomerId(), actionType, user, edgeId.toString()); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), ActionType.DELETED, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.EDGE), actionType, user, e, edgeId.toString()); throw e; } @@ -89,12 +90,12 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE CustomerId customerId = customer.getId(); try { Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(tenantId, edgeId, customerId)); - notificationEntityService.logEntityAction(tenantId, edgeId, savedEdge, customerId, actionType, + logEntityActionService.logEntityAction(tenantId, edgeId, savedEdge, customerId, actionType, user, edgeId.toString(), customerId.toString(), customer.getName()); return savedEdge; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.EDGE), ActionType.ASSIGNED_TO_CUSTOMER, user, e, edgeId.toString(), customerId.toString()); throw e; } @@ -108,11 +109,11 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE CustomerId customerId = customer.getId(); try { Edge savedEdge = checkNotNull(edgeService.unassignEdgeFromCustomer(tenantId, edgeId)); - notificationEntityService.logEntityAction(tenantId, edgeId, savedEdge, customerId, actionType, + logEntityActionService.logEntityAction(tenantId, edgeId, savedEdge, customerId, actionType, user, edgeId.toString(), customerId.toString(), customer.getName()); return savedEdge; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.EDGE), ActionType.UNASSIGNED_FROM_CUSTOMER, user, e, edgeId.toString()); throw e; } @@ -120,18 +121,19 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE @Override public Edge assignEdgeToPublicCustomer(TenantId tenantId, EdgeId edgeId, User user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); CustomerId customerId = publicCustomer.getId(); try { Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(tenantId, edgeId, customerId)); - notificationEntityService.notifyCreateOrUpdateOrDeleteEdge(tenantId, edgeId, customerId, savedEdge, ActionType.ASSIGNED_TO_CUSTOMER, user, + logEntityActionService.logEntityAction(tenantId, edgeId, savedEdge, customerId, actionType, user, edgeId.toString(), customerId.toString(), publicCustomer.getName()); return savedEdge; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), - ActionType.ASSIGNED_TO_CUSTOMER, user, e, edgeId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.EDGE), + actionType, user, e, edgeId.toString()); throw e; } } @@ -142,10 +144,10 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE EdgeId edgeId = edge.getId(); try { Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(tenantId, edge, ruleChainId); - notificationEntityService.logEntityAction(tenantId, edgeId, edge, null, ActionType.UPDATED, user); + logEntityActionService.logEntityAction(tenantId, edgeId, edge, null, ActionType.UPDATED, user); return updatedEdge; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.EDGE), ActionType.UPDATED, user, e, edgeId.toString()); throw e; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java index 904d98653e..a99233f87c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java @@ -43,10 +43,10 @@ public class DefaultTbEntityRelationService extends AbstractTbEntityService impl ActionType actionType = ActionType.RELATION_ADD_OR_UPDATE; try { relationService.saveRelation(tenantId, relation); - notificationEntityService.logEntityRelationAction(tenantId, customerId, + logEntityActionService.logEntityRelationAction(tenantId, customerId, relation, user, actionType, null, relation); } catch (Exception e) { - notificationEntityService.logEntityRelationAction(tenantId, customerId, + logEntityActionService.logEntityRelationAction(tenantId, customerId, relation, user, actionType, e, relation); throw e; } @@ -60,9 +60,9 @@ public class DefaultTbEntityRelationService extends AbstractTbEntityService impl if (!found) { throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND); } - notificationEntityService.logEntityRelationAction(tenantId, customerId, relation, user, actionType, null, relation); + logEntityActionService.logEntityRelationAction(tenantId, customerId, relation, user, actionType, null, relation); } catch (Exception e) { - notificationEntityService.logEntityRelationAction(tenantId, customerId, + logEntityActionService.logEntityRelationAction(tenantId, customerId, relation, user, actionType, e, relation); throw e; } @@ -72,9 +72,9 @@ public class DefaultTbEntityRelationService extends AbstractTbEntityService impl public void deleteCommonRelations(TenantId tenantId, CustomerId customerId, EntityId entityId, User user) throws ThingsboardException { try { relationService.deleteEntityCommonRelations(tenantId, entityId); - notificationEntityService.logEntityAction(tenantId, entityId, null, customerId, ActionType.RELATIONS_DELETED, user); + logEntityActionService.logEntityAction(tenantId, entityId, null, customerId, ActionType.RELATIONS_DELETED, user); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, entityId, null, customerId, + logEntityActionService.logEntityAction(tenantId, entityId, null, customerId, ActionType.RELATIONS_DELETED, user, e); throw e; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java index f587e19ef2..1796ebe64d 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java @@ -81,14 +81,12 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView)); this.updateEntityViewAttributes(tenantId, savedEntityView, existingEntityView, user); autoCommit(user, savedEntityView.getId()); - notificationEntityService.logEntityAction(savedEntityView.getTenantId(), savedEntityView.getId(), savedEntityView, + logEntityActionService.logEntityAction(savedEntityView.getTenantId(), savedEntityView.getId(), savedEntityView, null, actionType, user); localCache.computeIfAbsent(savedEntityView.getTenantId(), (k) -> new ConcurrentReferenceHashMap<>()).clear(); - tbClusterService.broadcastEntityStateChangeEvent(savedEntityView.getTenantId(), savedEntityView.getId(), - entityView.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return savedEntityView; } catch (Exception e) { - notificationEntityService.logEntityAction(user.getTenantId(), emptyId(EntityType.ENTITY_VIEW), entityView, actionType, user, e); + logEntityActionService.logEntityAction(user.getTenantId(), emptyId(EntityType.ENTITY_VIEW), entityView, actionType, user, e); throw e; } } @@ -130,13 +128,12 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen EntityViewId entityViewId = entityView.getId(); try { entityViewService.deleteEntityView(tenantId, entityViewId); - notificationEntityService.logEntityAction(tenantId, entityViewId, entityView, entityView.getCustomerId(), + logEntityActionService.logEntityAction(tenantId, entityViewId, entityView, entityView.getCustomerId(), ActionType.DELETED, user, entityViewId.toString()); localCache.computeIfAbsent(tenantId, (k) -> new ConcurrentReferenceHashMap<>()).clear(); - tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityViewId, ComponentLifecycleEvent.DELETED); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), ActionType.DELETED, user, e, entityViewId.toString()); throw e; } @@ -148,11 +145,11 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen CustomerId customerId = customer.getId(); try { EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(tenantId, entityViewId, customerId)); - notificationEntityService.logEntityAction(tenantId, entityViewId, savedEntityView, savedEntityView.getCustomerId(), + logEntityActionService.logEntityAction(tenantId, entityViewId, savedEntityView, savedEntityView.getCustomerId(), actionType, user, entityViewId.toString(), customerId.toString(), customer.getName()); return savedEntityView; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), actionType, user, e, entityViewId.toString(), customerId.toString()); throw e; } @@ -163,11 +160,11 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; try { EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(tenantId, entityViewId)); - notificationEntityService.logEntityAction(tenantId, entityViewId, savedEntityView, customer.getId(), + logEntityActionService.logEntityAction(tenantId, entityViewId, savedEntityView, customer.getId(), actionType, user, savedEntityView.getId().toString(), customer.getId().toString(), customer.getName()); return savedEntityView; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), actionType, user, e, entityViewId.toString()); throw e; } @@ -180,11 +177,11 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen try { EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(tenantId, entityViewId, publicCustomer.getId())); - notificationEntityService.logEntityAction(tenantId, entityViewId, savedEntityView, savedEntityView.getCustomerId(), + logEntityActionService.logEntityAction(tenantId, entityViewId, savedEntityView, savedEntityView.getCustomerId(), actionType, user, savedEntityView.getId().toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedEntityView; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), actionType, user, e, entityViewId.toString()); throw e; } @@ -196,11 +193,11 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen EdgeId edgeId = edge.getId(); try { EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToEdge(tenantId, entityViewId, edgeId)); - notificationEntityService.logEntityAction(tenantId, entityViewId, savedEntityView, customerId, actionType, + logEntityActionService.logEntityAction(tenantId, entityViewId, savedEntityView, customerId, actionType, user, savedEntityView.getEntityId().toString(), edgeId.toString(), edge.getName()); return savedEntityView; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), actionType, user, e, entityViewId.toString(), edgeId.toString()); throw e; } @@ -214,11 +211,11 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen EdgeId edgeId = edge.getId(); try { EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromEdge(tenantId, entityViewId, edgeId)); - notificationEntityService.logEntityAction(tenantId, entityViewId, savedEntityView, customerId, actionType, + logEntityActionService.logEntityAction(tenantId, entityViewId, savedEntityView, customerId, actionType, user, entityViewId.toString(), edgeId.toString(), edge.getName()); return savedEntityView; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), actionType, user, e, entityViewId.toString(), edgeId.toString()); throw e; } @@ -434,15 +431,15 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen } private void logAttributesUpdated(TenantId tenantId, User user, EntityId entityId, String scope, List attributes, Throwable e) throws ThingsboardException { - notificationEntityService.logEntityAction(tenantId, entityId, ActionType.ATTRIBUTES_UPDATED, user, toException(e), scope, attributes); + logEntityActionService.logEntityAction(tenantId, entityId, ActionType.ATTRIBUTES_UPDATED, user, toException(e), scope, attributes); } private void logAttributesDeleted(TenantId tenantId, User user, EntityId entityId, String scope, List keys, Throwable e) throws ThingsboardException { - notificationEntityService.logEntityAction(tenantId, entityId, ActionType.ATTRIBUTES_DELETED, user, toException(e), scope, keys); + logEntityActionService.logEntityAction(tenantId, entityId, ActionType.ATTRIBUTES_DELETED, user, toException(e), scope, keys); } private void logTimeseriesDeleted(TenantId tenantId, User user, EntityId entityId, List keys, Throwable e) throws ThingsboardException { - notificationEntityService.logEntityAction(tenantId, entityId, ActionType.TIMESERIES_DELETED, user, toException(e), keys); + logEntityActionService.logEntityAction(tenantId, entityId, ActionType.TIMESERIES_DELETED, user, toException(e), keys); } public static Exception toException(Throwable error) { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java index 3cc444caf5..0f05b0be4d 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java @@ -50,12 +50,12 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen try { OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(new OtaPackageInfo(saveOtaPackageInfoRequest), saveOtaPackageInfoRequest.isUsesUrl()); - notificationEntityService.logEntityAction(tenantId, savedOtaPackageInfo.getId(), savedOtaPackageInfo, + logEntityActionService.logEntityAction(tenantId, savedOtaPackageInfo.getId(), savedOtaPackageInfo, null, actionType, user); return savedOtaPackageInfo; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), saveOtaPackageInfoRequest, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), saveOtaPackageInfoRequest, actionType, user, e); throw e; } @@ -87,10 +87,10 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen otaPackage.setData(ByteBuffer.wrap(data)); otaPackage.setDataSize((long) data.length); OtaPackageInfo savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage); - notificationEntityService.logEntityAction(tenantId, savedOtaPackage.getId(), savedOtaPackage, null, actionType, user); + logEntityActionService.logEntityAction(tenantId, savedOtaPackage.getId(), savedOtaPackage, null, actionType, user); return savedOtaPackage; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), actionType, user, e, otaPackageId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), actionType, user, e, otaPackageId.toString()); throw e; } } @@ -102,10 +102,10 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen OtaPackageId otaPackageId = otaPackageInfo.getId(); try { otaPackageService.deleteOtaPackage(tenantId, otaPackageId); - notificationEntityService.logEntityAction(tenantId, otaPackageId, otaPackageInfo, null, + logEntityActionService.logEntityAction(tenantId, otaPackageId, otaPackageInfo, null, actionType, user, otaPackageInfo.getId().toString()); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), actionType, user, e, otaPackageId.toString()); throw e; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java index 40d294d238..9b34f04acb 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java @@ -43,6 +43,7 @@ import java.util.stream.Collectors; @TbCoreComponent @AllArgsConstructor public class DefaultTbQueueService extends AbstractTbEntityService implements TbQueueService { + private static final long DELETE_DELAY = 30; private final QueueService queueService; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java index 19d4126989..8b1ffcfcf0 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java @@ -20,7 +20,6 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; @@ -56,8 +55,6 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T installScripts.createDefaultEdgeRuleChains(savedTenant.getId()); } tenantProfileCache.evict(savedTenant.getId()); - notificationEntityService.notifyCreateOrUpdateTenant(savedTenant, created ? - ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); TenantProfile oldTenantProfile = oldTenant != null ? tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, oldTenant.getTenantProfileId()) : null; TenantProfile newTenantProfile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, savedTenant.getTenantProfileId()); @@ -70,7 +67,6 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T TenantId tenantId = tenant.getId(); tenantService.deleteTenant(tenantId); tenantProfileCache.evict(tenantId); - notificationEntityService.notifyDeleteTenant(tenant); versionControlService.deleteVersionControlSettings(tenantId).get(1, TimeUnit.MINUTES); } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java index 1219a5b929..76ae1dea67 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java @@ -48,18 +48,12 @@ public class DefaultTbTenantProfileService extends AbstractTbEntityService imple List tenantIds = tenantService.findTenantIdsByTenantProfileId(savedTenantProfile.getId()); tbQueueService.updateQueuesByTenants(tenantIds, savedTenantProfile, oldTenantProfile); } - tenantProfileCache.put(savedTenantProfile); - tbClusterService.onTenantProfileChange(savedTenantProfile, null); - tbClusterService.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, savedTenantProfile.getId(), - tenantProfile.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); - return savedTenantProfile; } @Override public void delete(TenantId tenantId, TenantProfile tenantProfile) throws ThingsboardException { tenantProfileService.deleteTenantProfile(tenantId, tenantProfile.getId()); - tbClusterService.onTenantProfileDelete(tenantProfile, null); } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index 58bd40d070..c9760bc260 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -68,10 +68,10 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse throw e; } } - notificationEntityService.logEntityAction(tenantId, savedUser.getId(), savedUser, customerId, actionType, user); + logEntityActionService.logEntityAction(tenantId, savedUser.getId(), savedUser, customerId, actionType, user); return savedUser; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.USER), tbUser, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.USER), tbUser, actionType, user, e); throw e; } } @@ -84,9 +84,9 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse try { tbAlarmService.unassignUserAlarms(tbUser.getTenantId(), tbUser, System.currentTimeMillis()); userService.deleteUser(tenantId, userId); - notificationEntityService.logEntityAction(tenantId, userId, tbUser, customerId, actionType, user, customerId.toString()); + logEntityActionService.logEntityAction(tenantId, userId, tbUser, customerId, actionType, user, customerId.toString()); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.USER), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.USER), actionType, user, e, userId.toString()); throw e; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java index efc20cd422..9cb01ba958 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java @@ -40,11 +40,11 @@ public class DefaultWidgetsBundleService extends AbstractTbEntityService impleme try { WidgetsBundle savedWidgetsBundle = checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle)); autoCommit(user, savedWidgetsBundle.getId()); - notificationEntityService.logEntityAction(tenantId, savedWidgetsBundle.getId(), savedWidgetsBundle, + logEntityActionService.logEntityAction(tenantId, savedWidgetsBundle.getId(), savedWidgetsBundle, null, actionType, user); return savedWidgetsBundle; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.WIDGETS_BUNDLE), widgetsBundle, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.WIDGETS_BUNDLE), widgetsBundle, actionType, user, e); throw e; } } @@ -55,9 +55,9 @@ public class DefaultWidgetsBundleService extends AbstractTbEntityService impleme TenantId tenantId = widgetsBundle.getTenantId(); try { widgetsBundleService.deleteWidgetsBundle(widgetsBundle.getTenantId(), widgetsBundle.getId()); - notificationEntityService.logEntityAction(tenantId, widgetsBundle.getId(), widgetsBundle, null, actionType, user); + logEntityActionService.logEntityAction(tenantId, widgetsBundle.getId(), widgetsBundle, null, actionType, user); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.WIDGETS_BUNDLE), actionType, user, e, widgetsBundle.getId()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.WIDGETS_BUNDLE), actionType, user, e, widgetsBundle.getId()); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 53ef38d96a..0cabbb68dc 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -77,6 +77,7 @@ import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; @@ -203,6 +204,7 @@ public class DefaultTbClusterService implements TbClusterService { } return null; } + private TbMsg transformMsg(TbMsg tbMsg, HasRuleEngineProfile ruleEngineProfile) { if (ruleEngineProfile != null) { RuleChainId targetRuleChainId = ruleEngineProfile.getDefaultRuleChainId(); @@ -258,8 +260,17 @@ public class DefaultTbClusterService implements TbClusterService { } @Override - public void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback) { + public void onDeviceProfileChange(DeviceProfile deviceProfile, DeviceProfile oldDeviceProfile, TbQueueCallback callback) { + boolean isFirmwareChanged = false; + boolean isSoftwareChanged = false; + if (oldDeviceProfile != null) { + isFirmwareChanged = !Objects.equals(deviceProfile.getFirmwareId(), oldDeviceProfile.getFirmwareId()); + isSoftwareChanged = !Objects.equals(deviceProfile.getSoftwareId(), oldDeviceProfile.getSoftwareId()); + } broadcastEntityChangeToTransport(deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile, callback); + broadcastEntityStateChangeEvent(deviceProfile.getTenantId(), deviceProfile.getId(), + oldDeviceProfile == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + otaPackageStateService.update(deviceProfile, isFirmwareChanged, isSoftwareChanged); } @Override @@ -295,6 +306,7 @@ public class DefaultTbClusterService implements TbClusterService { @Override public void onDeviceDeleted(Device device, TbQueueCallback callback) { + gatewayNotificationsService.onDeviceDeleted(device); broadcastEntityDeleteToTransport(device.getTenantId(), device.getId(), device.getName(), callback); sendDeviceStateServiceEvent(device.getTenantId(), device.getId(), false, false, true); broadcastEntityStateChangeEvent(device.getTenantId(), device.getId(), ComponentLifecycleEvent.DELETED); @@ -327,7 +339,7 @@ public class DefaultTbClusterService implements TbClusterService { broadcast(transportMsg, callback); } - public void broadcastEntityChangeToTransport(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { + private void broadcastEntityChangeToTransport(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName(); log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName); TransportProtos.EntityUpdateMsg entityUpdateMsg = TransportProtos.EntityUpdateMsg.newBuilder() diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index 064511abd9..b5c2b387ce 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -27,15 +27,12 @@ import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.TbResourceInfoFilter; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.lwm2m.LwM2mObject; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.widget.BaseWidgetType; -import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.widget.WidgetTypeService; @@ -140,11 +137,10 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements TenantId tenantId = tbResource.getTenantId(); try { TbResource savedResource = checkNotNull(doSave(tbResource)); - tbClusterService.onResourceChange(savedResource, null); - notificationEntityService.logEntityAction(tenantId, savedResource.getId(), savedResource, actionType, user); + logEntityActionService.logEntityAction(tenantId, savedResource.getId(), savedResource, actionType, user); return savedResource; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE), tbResource, actionType, user, e); throw e; } @@ -152,15 +148,16 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements @Override public void delete(TbResource tbResource, User user) { + ActionType actionType = ActionType.DELETED; TbResourceId resourceId = tbResource.getId(); TenantId tenantId = tbResource.getTenantId(); try { - resourceService.deleteResource(tenantId, resourceId); - tbClusterService.onResourceDeleted(tbResource, null); - notificationEntityService.logEntityAction(tenantId, resourceId, tbResource, ActionType.DELETED, user, resourceId.toString()); + resourceService.deleteResource(tenantId, tbResource); + logEntityActionService.logEntityAction(tenantId, resourceId, tbResource, + actionType, user, resourceId.toString()); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE), - ActionType.DELETED, user, e, resourceId.toString()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE), + actionType, user, e, resourceId.toString()); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java index 884c614231..0091fc88c5 100644 --- a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java +++ b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java @@ -174,20 +174,19 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement @Override public RuleChain save(RuleChain ruleChain, User user) throws Exception { - TenantId tenantId = ruleChain.getTenantId(); ActionType actionType = ruleChain.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + TenantId tenantId = ruleChain.getTenantId(); try { RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain)); autoCommit(user, savedRuleChain.getId()); - if (RuleChainType.CORE.equals(savedRuleChain.getType())) { tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedRuleChain.getId(), actionType.equals(ActionType.ADDED) ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); } - notificationEntityService.logEntityAction(tenantId, savedRuleChain.getId(), savedRuleChain, null, actionType, user); + logEntityActionService.logEntityAction(tenantId, savedRuleChain.getId(), savedRuleChain, null, actionType, user); return savedRuleChain; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ruleChain, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ruleChain, actionType, user, e); throw e; } } @@ -212,9 +211,9 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChain.getId(), ComponentLifecycleEvent.DELETED); } - notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, null, ActionType.DELETED, user, ruleChainId.toString()); + logEntityActionService.logEntityAction(tenantId, ruleChainId, ruleChain, null, ActionType.DELETED, user, ruleChainId.toString()); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.DELETED, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.DELETED, user, e, ruleChainId.toString()); throw e; } @@ -226,19 +225,19 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement RuleChain savedRuleChain = installScripts.createDefaultRuleChain(tenantId, request.getName()); autoCommit(user, savedRuleChain.getId()); tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedRuleChain.getId(), ComponentLifecycleEvent.CREATED); - notificationEntityService.logEntityAction(tenantId, savedRuleChain.getId(), savedRuleChain, ActionType.ADDED, user); + logEntityActionService.logEntityAction(tenantId, savedRuleChain.getId(), savedRuleChain, ActionType.ADDED, user); return savedRuleChain; } catch (Exception e) { RuleChain ruleChain = new RuleChain(); ruleChain.setName(request.getName()); - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ruleChain, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ruleChain, ActionType.ADDED, user, e); throw e; } } @Override - public RuleChain setRootRuleChain(TenantId tenantId, RuleChain ruleChain, User user) throws ThingsboardException { + public RuleChain setRootRuleChain(TenantId tenantId, RuleChain ruleChain, User user) { RuleChainId ruleChainId = ruleChain.getId(); try { RuleChain previousRootRuleChain = ruleChainService.getRootTenantRuleChain(tenantId); @@ -249,18 +248,18 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement tbClusterService.broadcastEntityStateChangeEvent(tenantId, previousRootRuleChainId, ComponentLifecycleEvent.UPDATED); - notificationEntityService.logEntityAction(tenantId, previousRootRuleChainId, previousRootRuleChain, + logEntityActionService.logEntityAction(tenantId, previousRootRuleChainId, previousRootRuleChain, ActionType.UPDATED, user); } ruleChain = ruleChainService.findRuleChainById(tenantId, ruleChainId); tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChainId, ComponentLifecycleEvent.UPDATED); - notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); + logEntityActionService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); } return ruleChain; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.UPDATED, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.UPDATED, user, e, ruleChainId.toString()); throw e; } @@ -295,23 +294,22 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement if (RuleChainType.CORE.equals(ruleChain.getType())) { tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChainId, ComponentLifecycleEvent.UPDATED); - updatedRuleChains.forEach(updatedRuleChain -> { - tbClusterService.broadcastEntityStateChangeEvent(tenantId, updatedRuleChain.getId(), ComponentLifecycleEvent.UPDATED); - }); + updatedRuleChains.forEach(updatedRuleChain -> + tbClusterService.broadcastEntityStateChangeEvent(tenantId, updatedRuleChain.getId(), ComponentLifecycleEvent.UPDATED)); } - notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user, ruleChainMetaData); + logEntityActionService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user, ruleChainMetaData); for (RuleChain updatedRuleChain : updatedRuleChains) { if (RuleChainType.CORE.equals(ruleChain.getType())) { RuleChainMetaData updatedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, updatedRuleChain.getId())); - notificationEntityService.logEntityAction(tenantId, updatedRuleChain.getId(), updatedRuleChain, + logEntityActionService.logEntityAction(tenantId, updatedRuleChain.getId(), updatedRuleChain, ActionType.UPDATED, user, updatedRuleChainMetaData); } } return savedRuleChainMetaData; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.ADDED, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.ADDED, user, e, ruleChainMetaData); throw e; } @@ -324,11 +322,11 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement EdgeId edgeId = edge.getId(); try { RuleChain savedRuleChain = checkNotNull(ruleChainService.assignRuleChainToEdge(tenantId, ruleChainId, edgeId)); - notificationEntityService.logEntityAction(tenantId, ruleChainId, savedRuleChain, null, actionType, + logEntityActionService.logEntityAction(tenantId, ruleChainId, savedRuleChain, null, actionType, user, ruleChainId.toString(), edgeId.toString(), edge.getName()); return savedRuleChain; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), actionType, user, e, ruleChainId.toString(), edgeId.toString()); throw e; } @@ -341,11 +339,11 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement EdgeId edgeId = edge.getId(); try { RuleChain savedRuleChain = checkNotNull(ruleChainService.unassignRuleChainFromEdge(tenantId, ruleChainId, edgeId, false)); - notificationEntityService.logEntityAction(tenantId, ruleChainId, savedRuleChain, null, actionType, + logEntityActionService.logEntityAction(tenantId, ruleChainId, savedRuleChain, null, actionType, user, ruleChainId.toString(), edgeId.toString(), edge.getName()); return savedRuleChain; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), actionType, user, e, ruleChainId, edgeId); throw e; } @@ -356,10 +354,10 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement RuleChainId ruleChainId = ruleChain.getId(); try { ruleChainService.setEdgeTemplateRootRuleChain(tenantId, ruleChainId); - notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); + logEntityActionService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); return ruleChain; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.UPDATED, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.UPDATED, user, e, ruleChainId.toString()); throw e; } @@ -370,10 +368,10 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement RuleChainId ruleChainId = ruleChain.getId(); try { ruleChainService.setAutoAssignToEdgeRuleChain(tenantId, ruleChainId); - notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); + logEntityActionService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); return ruleChain; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.UPDATED, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.UPDATED, user, e, ruleChainId.toString()); throw e; } @@ -384,10 +382,10 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement RuleChainId ruleChainId = ruleChain.getId(); try { ruleChainService.unsetAutoAssignToEdgeRuleChain(tenantId, ruleChainId); - notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); + logEntityActionService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); return ruleChain; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.UPDATED, + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.UPDATED, user, e, ruleChainId.toString()); throw e; } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index 429f6a1e8c..c2288a6f62 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -38,7 +38,6 @@ import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.customer.CustomerService; @@ -86,9 +85,6 @@ public abstract class AbstractOAuth2ClientMapper { @Autowired protected TbTenantProfileCache tenantProfileCache; - @Autowired - protected TbClusterService tbClusterService; - @Value("${edges.enabled}") @Getter private boolean edgesEnabled; @@ -181,9 +177,6 @@ public abstract class AbstractOAuth2ClientMapper { installScripts.createDefaultRuleChains(tenant.getId()); installScripts.createDefaultEdgeRuleChains(tenant.getId()); tenantProfileCache.evict(tenant.getId()); - tbClusterService.onTenantChange(tenant, null); - tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), - ComponentLifecycleEvent.CREATED); } else { tenant = tenants.get(0); } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java index dcb6764201..3217005f5d 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java @@ -34,7 +34,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.util.limits.RateLimitService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.entitiy.TbLogEntityActionService; import org.thingsboard.server.service.sync.ie.exporting.EntityExportService; import org.thingsboard.server.service.sync.ie.exporting.impl.BaseEntityExportService; import org.thingsboard.server.service.sync.ie.exporting.impl.DefaultEntityExportService; @@ -62,7 +62,7 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS private final RelationService relationService; private final RateLimitService rateLimitService; - private final TbNotificationEntityService entityNotificationService; + private final TbLogEntityActionService logEntityActionService; protected static final List SUPPORTED_ENTITY_TYPES = List.of( EntityType.CUSTOMER, EntityType.ASSET_PROFILE, EntityType.ASSET, EntityType.RULE_CHAIN, @@ -119,7 +119,7 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS relationService.saveRelations(ctx.getTenantId(), new ArrayList<>(ctx.getRelations())); for (EntityRelation relation : ctx.getRelations()) { - entityNotificationService.logEntityRelationAction(ctx.getTenantId(), null, + logEntityActionService.logEntityRelationAction(ctx.getTenantId(), null, relation, ctx.getUser(), ActionType.RELATION_ADD_OR_UPDATE, null, relation); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java index 3255c418c5..9aa412f27b 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java @@ -55,10 +55,8 @@ public class AssetProfileImportService extends BaseEntityImportService importEntity(EntitiesImportCtx ctx, D exportData) throws ThingsboardException { @@ -211,7 +211,7 @@ public abstract class BaseEntityImportService { - entityNotificationService.logEntityRelationAction(tenantId, null, + logEntityActionService.logEntityRelationAction(tenantId, null, existingRelation, ctx.getUser(), ActionType.RELATION_DELETED, null, existingRelation); }); } else if (Objects.equal(relation.getAdditionalInfo(), existingRelation.getAdditionalInfo())) { @@ -266,7 +266,7 @@ public abstract class BaseEntityImportService> { private final DeviceProfileService deviceProfileService; - private final OtaPackageStateService otaPackageStateService; @Override protected void setOwner(TenantId tenantId, DeviceProfile deviceProfile, IdProvider idProvider) { @@ -61,14 +55,8 @@ public class DeviceProfileImportService extends BaseEntityImportService taskCache; @@ -431,10 +431,8 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont if (ctx.getImportedEntities().get(entityType) == null || !ctx.getImportedEntities().get(entityType).contains(entity.getId())) { exportableEntitiesService.removeById(ctx.getTenantId(), entity.getId()); - ctx.addEventCallback(() -> { - entityNotificationService.logEntityAction(ctx.getTenantId(), entity.getId(), entity, null, - ActionType.DELETED, ctx.getUser()); - }); + ctx.addEventCallback(() -> logEntityActionService.logEntityAction(ctx.getTenantId(), entity.getId(), entity, null, + ActionType.DELETED, ctx.getUser())); ctx.registerDeleted(entityType); } }); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 89a7480708..fa9020b7f7 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -332,9 +332,7 @@ public class DefaultTransportApiService implements TransportApiService { ObjectNode additionalInfo = JacksonUtil.newObjectNode(); additionalInfo.put(DataConstants.LAST_CONNECTED_GATEWAY, gatewayId.toString()); device.setAdditionalInfo(additionalInfo); - Device savedDevice = deviceService.saveDevice(device); - tbClusterService.onDeviceUpdated(savedDevice, null); - device = savedDevice; + device = deviceService.saveDevice(device); relationService.saveRelation(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created")); @@ -360,7 +358,6 @@ public class DefaultTransportApiService implements TransportApiService { ObjectNode newDeviceAdditionalInfo = (ObjectNode) deviceAdditionalInfo; newDeviceAdditionalInfo.put(DataConstants.LAST_CONNECTED_GATEWAY, gatewayId.toString()); Device savedDevice = deviceService.saveDevice(device); - tbClusterService.onDeviceUpdated(savedDevice, device); } } GetOrCreateDeviceFromGatewayResponseMsg.Builder builder = GetOrCreateDeviceFromGatewayResponseMsg.newBuilder() @@ -665,7 +662,6 @@ public class DefaultTransportApiService implements TransportApiService { device.setName(deviceName); device.setType("LwM2M"); device = deviceService.saveDevice(device); - tbClusterService.onDeviceUpdated(device, null); } TransportProtos.LwM2MRegistrationResponseMsg registrationResponseMsg = TransportProtos.LwM2MRegistrationResponseMsg.newBuilder() diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java index 71c244890d..18e874b4cd 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java @@ -20,6 +20,7 @@ import org.mockito.ArgumentMatcher; import org.mockito.Mockito; import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; @@ -47,7 +48,6 @@ import java.util.stream.Collectors; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; -import static org.thingsboard.server.service.entitiy.DefaultTbNotificationEntityService.edgeTypeByActionType; @Slf4j public abstract class AbstractNotifyEntityTest extends AbstractWebTest { @@ -91,7 +91,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { int cntTime = 1; Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), Mockito.isNull(), Mockito.isNull(), Mockito.any(), Mockito.eq(EdgeEventType.RELATION), - Mockito.eq(edgeTypeByActionType(actionType))); + Mockito.eq(EdgeUtils.getEdgeEventActionTypeByActionType(actionType))); ArgumentMatcher matcherOriginatorId = argument -> argument.equals(relation.getTo()); ArgumentMatcher matcherEntityClassEquals = Objects::isNull; ArgumentMatcher matcherCustomerId = customerId == null ? @@ -111,7 +111,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { ActionType actionType, int cntTime) { Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), Mockito.isNull(), Mockito.isNull(), Mockito.any(), Mockito.eq(EdgeEventType.RELATION), - Mockito.eq(edgeTypeByActionType(actionType))); + Mockito.eq(EdgeUtils.getEdgeEventActionTypeByActionType(actionType))); ArgumentMatcher matcherOriginatorId = argument -> argument.getClass().equals(relation.getFrom().getClass()); ArgumentMatcher matcherEntityClassEquals = Objects::isNull; ArgumentMatcher matcherCustomerId = customerId == null ? @@ -236,15 +236,15 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { Mockito.reset(tbClusterService, auditLogService); } - protected void testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId, - TenantId tenantId, CustomerId customerId, UserId userId, String userName, - ActionType actionType, Object... additionalInfo) { + protected void testNotifyEntityBroadcastEntityStateChangeEventManyTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, int cntTimeBroadcast, Object... additionalInfo) { int cntTime = 1; testNotificationMsgToEdgeServiceNeverWithActionType(entityId, actionType); testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTime); - testBroadcastEntityStateChangeEventTime(entityId, tenantId, cntTime); + testBroadcastEntityStateChangeEventTime(entityId, tenantId, cntTimeBroadcast); Mockito.reset(tbClusterService, auditLogService); } @@ -266,7 +266,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { testLogEntityActionAdditionalInfoAny(matcherEntityClassEquals, matcherOriginatorId, tenantId, matcherCustomerId, matcherUserId, userName, actionType, cntTime, cntAdditionalInfo); testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTimeRuleEngine); - testBroadcastEntityStateChangeEventTime(entityId, tenantId, cntTime); + testBroadcastEntityStateChangeEventTime(entityId, tenantId, cntTime * 2); } protected void testNotifyEntityMsgToEdgePushMsgToCoreOneTime(HasName entity, EntityId entityId, EntityId originatorId, @@ -317,7 +317,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { private void testNotificationMsgToEdgeServiceNeverWithActionType(EntityId entityId, ActionType actionType) { EdgeEventActionType edgeEventActionType = ActionType.CREDENTIALS_UPDATED.equals(actionType) ? - EdgeEventActionType.CREDENTIALS_UPDATED : edgeTypeByActionType(actionType); + EdgeEventActionType.CREDENTIALS_UPDATED : EdgeUtils.getEdgeEventActionTypeByActionType(actionType); Mockito.verify(tbClusterService, never()).sendNotificationMsgToEdge(Mockito.any(), Mockito.any(), Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any(), Mockito.eq(edgeEventActionType)); } @@ -353,7 +353,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { private void testNotificationMsgToEdgeServiceTime(EntityId entityId, TenantId tenantId, ActionType actionType, int cntTime) { EdgeEventActionType edgeEventActionType = ActionType.CREDENTIALS_UPDATED.equals(actionType) ? - EdgeEventActionType.CREDENTIALS_UPDATED : edgeTypeByActionType(actionType); + EdgeEventActionType.CREDENTIALS_UPDATED : EdgeUtils.getEdgeEventActionTypeByActionType(actionType); ArgumentMatcher matcherEntityId = cntTime == 1 ? argument -> argument.equals(entityId) : argument -> argument.getClass().equals(entityId.getClass()); Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), @@ -364,7 +364,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { private void testSendNotificationMsgToEdgeServiceTimeEntityEqAny(TenantId tenantId, ActionType actionType, int cntTime) { Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), Mockito.any(), Mockito.any(EntityId.class), Mockito.any(), Mockito.isNull(), - Mockito.eq(edgeTypeByActionType(actionType))); + Mockito.eq(EdgeUtils.getEdgeEventActionTypeByActionType(actionType))); } protected void testBroadcastEntityStateChangeEventTime(EntityId entityId, TenantId tenantId, int cntTime) { diff --git a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java index bdd5f8d352..f39a6f2dd8 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java @@ -137,9 +137,9 @@ public class EdgeControllerTest extends AbstractControllerTest { Assert.assertEquals(NULL_UUID, savedEdge.getCustomerId().getId()); Assert.assertEquals(edge.getName(), savedEdge.getName()); - testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedEdge, savedEdge.getId(), savedEdge.getId(), + testNotifyEntityBroadcastEntityStateChangeEventManyTimeMsgToEdgeServiceNever(savedEdge, savedEdge.getId(), savedEdge.getId(), tenantId, tenantAdminUser.getCustomerId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(), - ActionType.ADDED); + ActionType.ADDED, 2); savedEdge.setName("My new edge"); doPost("/api/edge", savedEdge, Edge.class); @@ -147,9 +147,9 @@ public class EdgeControllerTest extends AbstractControllerTest { Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class); Assert.assertEquals(foundEdge.getName(), savedEdge.getName()); - testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(foundEdge, foundEdge.getId(), foundEdge.getId(), + testNotifyEntityBroadcastEntityStateChangeEventManyTimeMsgToEdgeServiceNever(foundEdge, foundEdge.getId(), foundEdge.getId(), tenantId, tenantAdminUser.getCustomerId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(), - ActionType.UPDATED); + ActionType.UPDATED, 1); } @Test @@ -244,9 +244,9 @@ public class EdgeControllerTest extends AbstractControllerTest { doDelete("/api/edge/" + savedEdge.getId().getId().toString()) .andExpect(status().isOk()); - testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedEdge, savedEdge.getId(), savedEdge.getId(), + testNotifyEntityBroadcastEntityStateChangeEventManyTimeMsgToEdgeServiceNever(savedEdge, savedEdge.getId(), savedEdge.getId(), tenantId, tenantAdminUser.getCustomerId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(), - ActionType.DELETED, savedEdge.getId().getId().toString()); + ActionType.DELETED, 1, savedEdge.getId().getId().toString()); doGet("/api/edge/" + savedEdge.getId().getId().toString()) .andExpect(status().isNotFound()) diff --git a/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java index beabcf77d1..6f0c97d296 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java @@ -298,7 +298,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { EntityView foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); assertEquals(savedCustomer.getId(), foundView.getCustomerId()); - testBroadcastEntityStateChangeEventNever(foundView.getId()); + testBroadcastEntityStateChangeEventTime(foundView.getId(), foundView.getTenantId(), 1); testNotifyAssignUnassignEntityAllOneTime(foundView, foundView.getId(), foundView.getId(), tenantId, foundView.getCustomerId(), tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ASSIGNED_TO_CUSTOMER, ActionType.UPDATED, @@ -310,7 +310,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); assertEquals(ModelConstants.NULL_UUID, foundView.getCustomerId().getId()); - testBroadcastEntityStateChangeEventNever(foundView.getId()); + testBroadcastEntityStateChangeEventTime(foundView.getId(), foundView.getTenantId(), 1); testNotifyAssignUnassignEntityAllOneTime(unAssignedView, savedView.getId(), savedView.getId(), tenantId, savedView.getCustomerId(), tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UPDATED, @@ -328,7 +328,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { Customer publicCustomer = doGet("/api/customer/" + assignedView.getCustomerId(), Customer.class); Assert.assertTrue(publicCustomer.isPublic()); - testBroadcastEntityStateChangeEventNever(assignedView.getId()); + testBroadcastEntityStateChangeEventTime(assignedView.getId(), assignedView.getTenantId(), 1); testNotifyAssignUnassignEntityAllOneTime(assignedView, assignedView.getId(), assignedView.getId(), tenantId, assignedView.getCustomerId(), tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ASSIGNED_TO_CUSTOMER, ActionType.UPDATED, @@ -343,7 +343,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); assertEquals(ModelConstants.NULL_UUID, foundView.getCustomerId().getId()); - testBroadcastEntityStateChangeEventNever(foundView.getId()); + testBroadcastEntityStateChangeEventTime(assignedView.getId(), assignedView.getTenantId(), 1); testNotifyAssignUnassignEntityAllOneTime(unAssignedView, unAssignedView.getId(), unAssignedView.getId(), tenantId, publicCustomer.getId(), tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UPDATED, @@ -462,7 +462,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { } Futures.allAsList(deleteFutures).get(TIMEOUT, SECONDS); - testBroadcastEntityStateChangeEventNever(loadedNamesOfView1.get(0).getId()); + testBroadcastEntityStateChangeEventTime(loadedNamesOfView1.get(0).getId(), loadedNamesOfView1.get(0).getTenantId(), cntEntity); testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new EntityView(), new EntityView(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UPDATED, cntEntity, cntEntity, 3); diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java index 1c9a6359d6..e0b83bcfe3 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java @@ -584,7 +584,6 @@ public class DeviceEdgeTest extends AbstractEdgeTest { device.getId().getId(), EdgeEventType.DEVICE, body); edgeImitator.expectMessageAmount(1); edgeEventService.saveAsync(edgeEvent).get(); - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); diff --git a/application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java index 2ee267b88c..088950dc39 100644 --- a/application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java @@ -59,7 +59,6 @@ public class TelemetryEdgeTest extends AbstractEdgeTest { EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); edgeEventService.saveAsync(edgeEvent).get(); - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); } Assert.assertTrue(edgeImitator.waitForMessages(120)); @@ -89,7 +88,6 @@ public class TelemetryEdgeTest extends AbstractEdgeTest { EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.POST_ATTRIBUTES, device.getId().getId(), EdgeEventType.DEVICE, postAttributesEntityData); edgeImitator.expectMessageAmount(1); edgeEventService.saveAsync(edgeEvent).get(); - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); @@ -114,7 +112,6 @@ public class TelemetryEdgeTest extends AbstractEdgeTest { EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_DELETED, device.getId().getId(), EdgeEventType.DEVICE, deleteAttributesEntityData); edgeImitator.expectMessageAmount(1); edgeEventService.saveAsync(edgeEvent).get(); - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); @@ -142,7 +139,6 @@ public class TelemetryEdgeTest extends AbstractEdgeTest { edgeImitator.expectMessageAmount(1); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); edgeEventService.saveAsync(edgeEvent).get(); - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); @@ -198,8 +194,6 @@ public class TelemetryEdgeTest extends AbstractEdgeTest { EdgeEvent successEdgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.UPDATED, device.getId().getId(), EdgeEventType.DEVICE, null); edgeEventService.saveAsync(successEdgeEvent).get(); - - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); } Assert.assertTrue(edgeImitator.waitForMessages(120)); @@ -224,7 +218,6 @@ public class TelemetryEdgeTest extends AbstractEdgeTest { EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, entityId.getId(), EdgeEventType.valueOf(entityId.getEntityType().name()), attributesEntityData); edgeImitator.expectMessageAmount(1); edgeEventService.saveAsync(edgeEvent1).get(); - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java index 229ea5d9f9..f316eba2af 100644 --- a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java @@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.edge.EdgeService; -import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.entitiy.TbLogEntityActionService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; @@ -59,7 +59,7 @@ public class DefaultTbAlarmServiceTest { @MockBean protected DbCallbackExecutorService dbExecutor; @MockBean - protected TbNotificationEntityService notificationEntityService; + protected TbLogEntityActionService logEntityActionService; @MockBean protected EdgeService edgeService; @MockBean @@ -88,7 +88,7 @@ public class DefaultTbAlarmServiceTest { .build()); service.save(alarm, new User()); - verify(notificationEntityService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.ADDED), any()); + verify(logEntityActionService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.ADDED), any()); verify(alarmSubscriptionService, times(1)).createAlarm(any()); } @@ -100,7 +100,7 @@ public class DefaultTbAlarmServiceTest { service.ack(alarm, new User(new UserId(UUID.randomUUID()))); verify(alarmCommentService, times(1)).saveAlarmComment(any(), any(), any()); - verify(notificationEntityService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.ALARM_ACK), any()); + verify(logEntityActionService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.ALARM_ACK), any()); verify(alarmSubscriptionService, times(1)).acknowledgeAlarm(any(), any(), anyLong()); } @@ -113,7 +113,7 @@ public class DefaultTbAlarmServiceTest { service.clear(alarm, new User(new UserId(UUID.randomUUID()))); verify(alarmCommentService, times(1)).saveAlarmComment(any(), any(), any()); - verify(notificationEntityService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.ALARM_CLEAR), any()); + verify(logEntityActionService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.ALARM_CLEAR), any()); verify(alarmSubscriptionService, times(1)).clearAlarm(any(), any(), anyLong(), any()); } @@ -121,7 +121,7 @@ public class DefaultTbAlarmServiceTest { public void testDelete() { service.delete(new Alarm(), new User()); - verify(notificationEntityService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.DELETED), any()); + verify(logEntityActionService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.DELETED), any()); verify(alarmSubscriptionService, times(1)).deleteAlarm(any(), any()); } } diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java index 50055b8d1a..7a5e1d2139 100644 --- a/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java @@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.dao.alarm.AlarmCommentService; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.customer.CustomerService; -import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.entitiy.TbLogEntityActionService; import org.thingsboard.server.service.entitiy.alarm.DefaultTbAlarmCommentService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; @@ -61,7 +61,7 @@ public class DefaultTbAlarmCommentServiceTest { @MockBean protected DbCallbackExecutorService dbExecutor; @MockBean - protected TbNotificationEntityService notificationEntityService; + protected TbLogEntityActionService notificationEntityService; @MockBean protected AlarmService alarmService; @MockBean diff --git a/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java b/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java index e493916b8a..0e6766d13e 100644 --- a/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java @@ -548,10 +548,8 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest { DeviceProfile importedDeviceProfile = (DeviceProfile) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.DEVICE_PROFILE)).getSavedEntity(); verify(entityActionService).logEntityAction(any(), eq(importedDeviceProfile.getId()), eq(importedDeviceProfile), any(), eq(ActionType.ADDED), isNull()); - verify(tbClusterService).onDeviceProfileChange(eq(importedDeviceProfile), any()); - verify(tbClusterService).broadcastEntityStateChangeEvent(any(), eq(importedDeviceProfile.getId()), eq(ComponentLifecycleEvent.CREATED)); + verify(tbClusterService).onDeviceProfileChange(eq(importedDeviceProfile), any(), any()); verify(tbClusterService).sendNotificationMsgToEdge(any(), any(), eq(importedDeviceProfile.getId()), any(), any(), eq(EdgeEventActionType.ADDED)); - verify(otaPackageStateService).update(eq(importedDeviceProfile), eq(false), eq(false)); Device importedDevice = (Device) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.DEVICE)).getSavedEntity(); verify(entityActionService).logEntityAction(any(), eq(importedDevice.getId()), eq(importedDevice), diff --git a/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java b/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java index e81aaed39d..59c3475cbb 100644 --- a/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java +++ b/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java @@ -64,7 +64,7 @@ public interface TbClusterService extends TbQueueClusterService { void broadcastEntityStateChangeEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state); - void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback); + void onDeviceProfileChange(DeviceProfile deviceProfile, DeviceProfile oldDeviceProfile, TbQueueCallback callback); void onDeviceProfileDelete(DeviceProfile deviceProfile, TbQueueCallback callback); diff --git a/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueClusterService.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueClusterService.java index c852a24fa7..2e246e0598 100644 --- a/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueClusterService.java +++ b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueClusterService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.queue; import org.thingsboard.server.common.data.queue.Queue; public interface TbQueueClusterService { + void onQueueChange(Queue queue); void onQueueDelete(Queue queue); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index a90ea9a572..b60a0be639 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.DeviceInfoFilter; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; @@ -97,7 +98,7 @@ public interface DeviceService extends EntityDaoService { ListenableFuture> findDeviceTypesByTenantId(TenantId tenantId); - Device assignDeviceToTenant(TenantId tenantId, Device device); + Device assignDeviceToTenant(Tenant oldTenant, TenantId tenantId, Device device); PageData findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType, PageLink pageLink); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java index 6f0e362209..96ddf48b38 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java @@ -48,7 +48,7 @@ public interface ResourceService extends EntityDaoService { PageData findTenantResourcesByResourceTypeAndPageLink(TenantId tenantId, ResourceType lwm2mModel, PageLink pageLink); - void deleteResource(TenantId tenantId, TbResourceId resourceId); + void deleteResource(TenantId tenantId, TbResource resource); void deleteResourcesByTenantId(TenantId tenantId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java index f74ea6fd07..05972c9771 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Throwables; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; @@ -32,6 +33,7 @@ import java.util.concurrent.ThreadLocalRandom; public final class EdgeUtils { private static final EnumMap entityTypeEdgeEventTypeEnumMap; + private static final EnumMap actionTypeEdgeEventActionTypeEnumMap; static { entityTypeEdgeEventTypeEnumMap = new EnumMap<>(EntityType.class); @@ -40,6 +42,13 @@ public final class EdgeUtils { entityTypeEdgeEventTypeEnumMap.put(edgeEventType.getEntityType(), edgeEventType); } } + + actionTypeEdgeEventActionTypeEnumMap = new EnumMap<>(ActionType.class); + for (EdgeEventActionType edgeEventActionType : EdgeEventActionType.values()) { + if (edgeEventActionType.getActionType() != null) { + actionTypeEdgeEventActionTypeEnumMap.put(edgeEventActionType.getActionType(), edgeEventActionType); + } + } } private static final int STACK_TRACE_LIMIT = 10; @@ -54,6 +63,10 @@ public final class EdgeUtils { return entityTypeEdgeEventTypeEnumMap.get(entityType); } + public static EdgeEventActionType getEdgeEventActionTypeByActionType(ActionType actionType) { + return actionTypeEdgeEventActionTypeEnumMap.get(actionType); + } + public static EdgeEvent constructEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventType type, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java index 4d81dc071c..2cf0393a98 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java @@ -15,26 +15,36 @@ */ package org.thingsboard.server.common.data.edge; +import lombok.Getter; +import org.thingsboard.server.common.data.audit.ActionType; + +@Getter public enum EdgeEventActionType { - ADDED, - DELETED, - UPDATED, - POST_ATTRIBUTES, - ATTRIBUTES_UPDATED, - ATTRIBUTES_DELETED, - TIMESERIES_UPDATED, - CREDENTIALS_UPDATED, - ASSIGNED_TO_CUSTOMER, - UNASSIGNED_FROM_CUSTOMER, - RELATION_ADD_OR_UPDATE, - RELATION_DELETED, - RPC_CALL, - ALARM_ACK, - ALARM_CLEAR, - ALARM_ASSIGNED, - ALARM_UNASSIGNED, - ASSIGNED_TO_EDGE, - UNASSIGNED_FROM_EDGE, - CREDENTIALS_REQUEST, - ENTITY_MERGE_REQUEST // deprecated -} \ No newline at end of file + ADDED(ActionType.ADDED), + UPDATED(ActionType.UPDATED), + DELETED(ActionType.DELETED), + POST_ATTRIBUTES(null), + ATTRIBUTES_UPDATED(null), + ATTRIBUTES_DELETED(null), + TIMESERIES_UPDATED(null), + CREDENTIALS_UPDATED(ActionType.CREDENTIALS_UPDATED), + ASSIGNED_TO_CUSTOMER(ActionType.ASSIGNED_TO_CUSTOMER), + UNASSIGNED_FROM_CUSTOMER(ActionType.UNASSIGNED_FROM_CUSTOMER), + RELATION_ADD_OR_UPDATE(ActionType.RELATION_ADD_OR_UPDATE), + RELATION_DELETED(ActionType.RELATION_DELETED), + RPC_CALL(null), + ALARM_ACK(ActionType.ALARM_ACK), + ALARM_CLEAR(ActionType.ALARM_CLEAR), + ALARM_ASSIGNED(ActionType.ALARM_ASSIGNED), + ALARM_UNASSIGNED(ActionType.ALARM_UNASSIGNED), + ASSIGNED_TO_EDGE(ActionType.ASSIGNED_TO_EDGE), + UNASSIGNED_FROM_EDGE(ActionType.UNASSIGNED_FROM_EDGE), + CREDENTIALS_REQUEST(null), + ENTITY_MERGE_REQUEST(null); // deprecated + + private final ActionType actionType; + + EdgeEventActionType(ActionType actionType) { + this.actionType = actionType; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java index b3bc89c34d..9306a94c01 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java @@ -120,8 +120,8 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(device.getTenantId(), device.getId()); + TenantId oldTenantId = device.getTenantId(); + List entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(oldTenantId, device.getId()); if (!CollectionUtils.isEmpty(entityViews)) { throw new DataValidationException("Can't assign device that has entity views to another tenant!"); } - eventService.removeEvents(device.getTenantId(), device.getId()); - - relationService.removeRelations(device.getTenantId(), device.getId()); + eventService.removeEvents(oldTenantId, device.getId()); - TenantId oldTenantId = device.getTenantId(); + relationService.removeRelations(oldTenantId, device.getId()); device.setTenantId(tenantId); device.setCustomerId(null); @@ -522,6 +522,9 @@ public class DeviceServiceImpl extends AbstractCachedEntityService edgeEventValidator; + private final ApplicationEventPublisher eventPublisher; + @Override public ListenableFuture saveAsync(EdgeEvent edgeEvent) { edgeEventValidator.validate(edgeEvent, EdgeEvent::getTenantId); - return edgeEventDao.saveAsync(edgeEvent); + + ListenableFuture saveFuture = edgeEventDao.saveAsync(edgeEvent); + + Futures.addCallback(saveFuture, new FutureCallback<>() { + @Override + public void onSuccess(Void result) { + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(edgeEvent.getTenantId()) + .entity(edgeEvent).entityId(edgeEvent.getEdgeId()).build()); + } + + @Override + public void onFailure(@NotNull Throwable throwable) {} + }, MoreExecutors.directExecutor()); + + return saveFuture; } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index 4e7e3f0892..d4e45961d9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -33,7 +33,6 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; @@ -49,7 +48,6 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -58,13 +56,14 @@ import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; import javax.annotation.Nullable; @@ -104,9 +103,6 @@ public class EdgeServiceImpl extends AbstractCachedEntityService edgeValidator; @@ -168,8 +164,10 @@ public class EdgeServiceImpl extends AbstractCachedEntityService { private final TenantId tenantId; - private final EdgeId edgeId; + private final T entity; private final EntityId entityId; + private final EdgeId edgeId; private final String body; private final ActionType actionType; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/eventsourcing/SaveEntityEvent.java b/dao/src/main/java/org/thingsboard/server/dao/eventsourcing/SaveEntityEvent.java index 205f592d43..cc2e854f59 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/eventsourcing/SaveEntityEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/eventsourcing/SaveEntityEvent.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId; public class SaveEntityEvent { private final TenantId tenantId; private final T entity; + private final T oldEntity; private final EntityId entityId; private final Boolean added; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java b/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java index c1b0c66875..ed36ede7a0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java @@ -57,16 +57,13 @@ public class BaseQueueService extends AbstractEntityService implements QueueServ @Autowired private DataValidator queueValidator; -// @Autowired -// private QueueStatsService queueStatsService; - @Override public Queue saveQueue(Queue queue) { log.trace("Executing createOrUpdateQueue [{}]", queue); queueValidator.validate(queue, Queue::getTenantId); Queue savedQueue = queueDao.save(queue.getTenantId(), queue); eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedQueue.getTenantId()) - .entityId(savedQueue.getId()).added(queue.getId() == null).build()); + .entityId(savedQueue.getId()).entity(savedQueue).added(queue.getId() == null).build()); return savedQueue; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java index 7697217b6b..68cba90e1d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -21,10 +21,9 @@ import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.event.TransactionalEventListener; -import org.thingsboard.server.cache.device.DeviceCacheKey; +import org.thingsboard.server.cache.resourceInfo.ResourceInfoCacheKey; import org.thingsboard.server.cache.resourceInfo.ResourceInfoEvictEvent; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.cache.resourceInfo.ResourceInfoCacheKey; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; @@ -36,6 +35,8 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -63,6 +64,8 @@ public class BaseResourceService extends AbstractCachedEntityService { - dashboardService.saveDashboard(dashboard); - }); + Assertions.assertThrows(DataValidationException.class, () -> + dashboardService.saveDashboard(dashboard)); } @Test @@ -100,9 +99,8 @@ public class DashboardServiceTest extends AbstractServiceTest { Dashboard dashboard = new Dashboard(); dashboard.setTitle("My dashboard"); dashboard.setTenantId(TenantId.fromUUID(Uuids.timeBased())); - Assertions.assertThrows(DataValidationException.class, () -> { - dashboardService.saveDashboard(dashboard); - }); + Assertions.assertThrows(DataValidationException.class, () -> + dashboardService.saveDashboard(dashboard)); } @Test @@ -112,9 +110,8 @@ public class DashboardServiceTest extends AbstractServiceTest { dashboard.setTenantId(tenantId); Dashboard savedDashboard = dashboardService.saveDashboard(dashboard); try { - Assertions.assertThrows(DataValidationException.class, () -> { - dashboardService.assignDashboardToCustomer(tenantId, savedDashboard.getId(), new CustomerId(Uuids.timeBased())); - }); + Assertions.assertThrows(DataValidationException.class, () -> + dashboardService.assignDashboardToCustomer(tenantId, savedDashboard.getId(), new CustomerId(Uuids.timeBased()))); } finally { dashboardService.deleteDashboard(tenantId, savedDashboard.getId()); } @@ -221,7 +218,7 @@ public class DashboardServiceTest extends AbstractServiceTest { List loadedMobileDashboards = new ArrayList<>(); PageLink pageLink = new PageLink(16, 0, null, new SortOrder("title", SortOrder.Direction.ASC)); - PageData pageData = null; + PageData pageData; do { pageData = dashboardService.findMobileDashboardsByTenantId(tenantId, pageLink); loadedMobileDashboards.addAll(pageData.getData()); @@ -235,7 +232,7 @@ public class DashboardServiceTest extends AbstractServiceTest { Integer order2 = o2.getMobileOrder(); if (order1 == null && order2 == null) { return o1.getTitle().compareTo(o2.getTitle()); - } else if (order1 == null && order2 != null) { + } else if (order1 == null) { return 1; } else if (order2 == null) { return -1; @@ -403,9 +400,8 @@ public class DashboardServiceTest extends AbstractServiceTest { edge.setRoutingKey(StringUtils.randomAlphanumeric(20)); Edge savedEdge = edgeService.saveEdge(edge); try { - Assertions.assertThrows(DataValidationException.class, () -> { - dashboardService.assignDashboardToEdge(tenantId, savedDashboard.getId(), savedEdge.getId()); - }); + Assertions.assertThrows(DataValidationException.class, () -> + dashboardService.assignDashboardToEdge(tenantId, savedDashboard.getId(), savedEdge.getId())); } finally { dashboardService.deleteDashboard(tenantId, savedDashboard.getId()); tenantService.deleteTenant(tenant.getId()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java index 962dc7e246..26e7ace413 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java @@ -47,6 +47,7 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -116,18 +117,18 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { assertNotNull(tsList); assertEquals(4, tsList.size()); - for (int i = 0; i < tsList.size(); i++) { - assertEquals(TS, tsList.get(i).getTs()); + for (TsKvEntry entry : tsList) { + assertEquals(TS, entry.getTs()); } - Collections.sort(tsList, (o1, o2) -> o1.getKey().compareTo(o2.getKey())); + Collections.sort(tsList, Comparator.comparing(KvEntry::getKey)); List expected = Arrays.asList( toTsEntry(TS, stringKvEntry), toTsEntry(TS, longKvEntry), toTsEntry(TS, doubleKvEntry), toTsEntry(TS, booleanKvEntry)); - Collections.sort(expected, (o1, o2) -> o1.getKey().compareTo(o2.getKey())); + Collections.sort(expected, Comparator.comparing(KvEntry::getKey)); assertEquals(expected, tsList); } @@ -187,7 +188,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1)); Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(2)); - EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY)); + EntityView entityView = saveAndCreateEntityView(deviceId, List.of(STRING_KEY)); entries = tsService.findAll(tenantId, entityView.getId(), queries).get(MAX_TIMEOUT, TimeUnit.SECONDS); Assert.assertEquals(3, entries.size()); @@ -347,7 +348,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1)); Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(2)); - EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY)); + EntityView entityView = saveAndCreateEntityView(deviceId, List.of(STRING_KEY)); entries = tsService.findAll(tenantId, entityView.getId(), queries).get(MAX_TIMEOUT, TimeUnit.SECONDS); Assert.assertEquals(3, entries.size()); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index fa47faf8aa..b56cf32309 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -30,7 +30,6 @@ import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -234,8 +233,6 @@ public interface TbContext { TbMsg attributesDeletedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List keys); - void onEdgeEventUpdate(TenantId tenantId, EdgeId edgeId); - /* * * METHODS TO PROCESS THE MESSAGES diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java index ea53138d10..ba2d6ef183 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java @@ -192,7 +192,6 @@ public abstract class TbAbstractRelationActionNode log.trace("Pushed Device Created message: {}", savedDevice), throwable -> log.warn("Failed to push Device Created message: {}", savedDevice, throwable)); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java index a09ecb677e..faab861c4f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java @@ -170,11 +170,7 @@ public class TbMsgPushToEdgeNode extends AbstractTbMsgPushNode notifyEdge(TbContext ctx, EdgeEvent edgeEvent, EdgeId edgeId) { edgeEvent.setEdgeId(edgeId); - ListenableFuture future = ctx.getEdgeEventService().saveAsync(edgeEvent); - return Futures.transform(future, result -> { - ctx.onEdgeEventUpdate(ctx.getTenantId(), edgeId); - return null; - }, ctx.getDbCallbackExecutor()); + return ctx.getEdgeEventService().saveAsync(edgeEvent); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java index 373e314577..e1b0bef7d2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java @@ -109,7 +109,6 @@ public class TbSendRPCReplyNode implements TbNode { Futures.addCallback(future, new FutureCallback<>() { @Override public void onSuccess(Void result) { - ctx.onEdgeEventUpdate(ctx.getTenantId(), edgeId); ctx.tellSuccess(msg); } From b2b5573a61b7ae6f5b9cb2109ffc5ef82632c49c Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 11 Sep 2023 16:57:28 +0300 Subject: [PATCH 023/209] added ssl support for redis --- .../src/main/resources/thingsboard.yml | 8 ++++ .../cache/TBRedisCacheConfiguration.java | 41 +++++++++++++++++++ .../cache/TBRedisClusterConfiguration.java | 23 +++++++++-- .../cache/TBRedisSentinelConfiguration.java | 24 ++++++++--- .../cache/TBRedisStandaloneConfiguration.java | 34 ++++++++------- .../TbRedisSslCredentialsConfiguration.java | 36 ++++++++++++++++ 6 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/TbRedisSslCredentialsConfiguration.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 3e1aa5a8e6..432caa4c27 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -548,6 +548,14 @@ redis: db: "${REDIS_DB:0}" # db password password: "${REDIS_PASSWORD:}" + # ssl config + ssl: + enabled: "${TB_REDIS_SSL_ENABLED:true}" + truststoreLocation: "${TB_REDIS_SSL_TRUSTSTORE_LOCATION:}" + truststorePassword: "${TB_REDIS_SSL_TRUSTSTORE_PASSWORD:}" + # client authentication could be optional and depends on redis server configuration + keystoreLocation: "${TB_REDIS_SSL_KEYSTORE_LOCATION:}" + keystorePassword: "${TB_REDIS_SSL_KEYSTORE_PASSWORD:}" # pool config pool_config: maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}" diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java index 36f3dddc3d..dc0b301ffc 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java @@ -16,6 +16,8 @@ package org.thingsboard.server.cache; import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; @@ -35,6 +37,12 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; import redis.clients.jedis.JedisPoolConfig; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; +import java.io.FileInputStream; +import java.security.KeyStore; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; @@ -44,6 +52,7 @@ import java.util.List; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @EnableCaching @Data +@Slf4j public abstract class TBRedisCacheConfiguration { private static final String COMMA = ","; @@ -90,6 +99,9 @@ public abstract class TBRedisCacheConfiguration { return loadFactory(); } + @Autowired + private TbRedisSslCredentialsConfiguration redisSslCredentials; + protected abstract JedisConnectionFactory loadFactory(); /** @@ -149,4 +161,33 @@ public abstract class TBRedisCacheConfiguration { } return result; } + + protected SSLSocketFactory createSslSocketFactory() { + try { + KeyStore trustStore = KeyStore.getInstance("jks"); + trustStore.load(new FileInputStream(redisSslCredentials.getTruststoreLocation()), redisSslCredentials.getTruststorePassword().toCharArray()); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509"); + trustManagerFactory.init(trustStore); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + + // client authentication is optional + if (redisSslCredentials.getKeystoreLocation() != null && redisSslCredentials.getKeystorePassword() != null) { + KeyStore keyStore = KeyStore.getInstance("pkcs12"); + keyStore.load(new FileInputStream(redisSslCredentials.getKeystoreLocation()), redisSslCredentials.getKeystorePassword().toCharArray()); + + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX"); + keyManagerFactory.init(keyStore, redisSslCredentials.getKeystorePassword().toCharArray()); + + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + } else { + sslContext.init(null, trustManagerFactory.getTrustManagers(), null); + } + return sslContext.getSocketFactory(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java index 0a378103b0..63517a98ac 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java @@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisClusterConfiguration; +import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; @Configuration @@ -39,15 +40,29 @@ public class TBRedisClusterConfiguration extends TBRedisCacheConfiguration { @Value("${redis.password:}") private String password; + @Value("${redis.ssl.enabled:}") + private boolean useSsl; + public JedisConnectionFactory loadFactory() { RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(); clusterConfiguration.setClusterNodes(getNodes(clusterNodes)); clusterConfiguration.setMaxRedirects(maxRedirects); clusterConfiguration.setPassword(password); - if (useDefaultPoolConfig) { - return new JedisConnectionFactory(clusterConfiguration); - } else { - return new JedisConnectionFactory(clusterConfiguration, buildPoolConfig()); + return new JedisConnectionFactory(clusterConfiguration, buildClientConfig()); + } + + private JedisClientConfiguration buildClientConfig() { + JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfigurationBuilder = JedisClientConfiguration.builder(); + if (!useDefaultPoolConfig) { + jedisClientConfigurationBuilder + .usePooling() + .poolConfig(buildPoolConfig()); + } + if (useSsl) { + jedisClientConfigurationBuilder + .useSsl() + .sslSocketFactory(createSslSocketFactory()); } + return jedisClientConfigurationBuilder.build(); } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisSentinelConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisSentinelConfiguration.java index 78cb445d82..dbfb3bce73 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisSentinelConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisSentinelConfiguration.java @@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisSentinelConfiguration; +import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; @Configuration @@ -42,6 +43,9 @@ public class TBRedisSentinelConfiguration extends TBRedisCacheConfiguration { @Value("${redis.db:}") private Integer database; + @Value("${redis.ssl.enabled:}") + private boolean useSsl; + @Value("${redis.password:}") private String password; @@ -52,11 +56,21 @@ public class TBRedisSentinelConfiguration extends TBRedisCacheConfiguration { redisSentinelConfiguration.setSentinelPassword(sentinelPassword); redisSentinelConfiguration.setPassword(password); redisSentinelConfiguration.setDatabase(database); - if (useDefaultPoolConfig) { - return new JedisConnectionFactory(redisSentinelConfiguration); - } else { - return new JedisConnectionFactory(redisSentinelConfiguration, buildPoolConfig()); - } + return new JedisConnectionFactory(redisSentinelConfiguration, buildClientConfig()); } + private JedisClientConfiguration buildClientConfig() { + JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfigurationBuilder = JedisClientConfiguration.builder(); + if (!useDefaultPoolConfig) { + jedisClientConfigurationBuilder + .usePooling() + .poolConfig(buildPoolConfig()); + } + if (useSsl) { + jedisClientConfigurationBuilder + .useSsl() + .sslSocketFactory(createSslSocketFactory()); + } + return jedisClientConfigurationBuilder.build(); + } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java index aae6ecd2a3..55065e443f 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java @@ -57,32 +57,36 @@ public class TBRedisStandaloneConfiguration extends TBRedisCacheConfiguration { @Value("${redis.password:}") private String password; + @Value("${redis.ssl.enabled:}") + private boolean useSsl; + public JedisConnectionFactory loadFactory() { RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration(); standaloneConfiguration.setHostName(host); standaloneConfiguration.setPort(port); standaloneConfiguration.setDatabase(db); standaloneConfiguration.setPassword(password); - if (useDefaultClientConfig) { - return new JedisConnectionFactory(standaloneConfiguration); - } else { - return new JedisConnectionFactory(standaloneConfiguration, buildClientConfig()); - } + return new JedisConnectionFactory(standaloneConfiguration, buildClientConfig()); } private JedisClientConfiguration buildClientConfig() { - if (usePoolConfig) { - return JedisClientConfiguration.builder() - .clientName(clientName) - .connectTimeout(Duration.ofMillis(connectTimeout)) - .readTimeout(Duration.ofMillis(readTimeout)) - .usePooling().poolConfig(buildPoolConfig()) - .build(); - } else { - return JedisClientConfiguration.builder() + JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfigurationBuilder = JedisClientConfiguration.builder(); + if (!useDefaultClientConfig) { + jedisClientConfigurationBuilder .clientName(clientName) .connectTimeout(Duration.ofMillis(connectTimeout)) - .readTimeout(Duration.ofMillis(readTimeout)).build(); + .readTimeout(Duration.ofMillis(readTimeout)); + } + if (useSsl) { + jedisClientConfigurationBuilder + .useSsl() + .sslSocketFactory(createSslSocketFactory()); + } + if (usePoolConfig) { + jedisClientConfigurationBuilder + .usePooling() + .poolConfig(buildPoolConfig()); } + return jedisClientConfigurationBuilder.build(); } } \ No newline at end of file diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbRedisSslCredentialsConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbRedisSslCredentialsConfiguration.java new file mode 100644 index 0000000000..fa0c09043d --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbRedisSslCredentialsConfiguration.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.cache; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "redis.ssl") +@Data +public class TbRedisSslCredentialsConfiguration { + + private boolean enabled; + + private String truststoreLocation; + + private String truststorePassword; + + private String keystoreLocation; + + private String keystorePassword; +} From 0c86f988e445494db769e0c9f33ef4edb1551f79 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 13 Sep 2023 12:22:11 +0300 Subject: [PATCH 024/209] Minor refactoring to fix compile error --- .../entitiy/EntityStateSourcingListener.java | 96 +++++++++++++------ .../DefaultTbTenantProfileService.java | 4 - .../type/DefaultWidgetTypeService.java | 8 +- .../rpc/processor/BaseEdgeProcessorTest.java | 4 +- 4 files changed, 72 insertions(+), 40 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java index 57d2d265ab..6ed5c01f12 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java @@ -69,22 +69,40 @@ public class EntityStateSourcingListener { boolean isCreated = event.getAdded() != null && event.getAdded(); ComponentLifecycleEvent lifecycleEvent = isCreated ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED; - if (isCommonEntityStateUpdated(entityId)) { - tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent); - } else if (EntityType.TENANT.equals(entityType)) { - onTenantUpdate((Tenant) event.getEntity(), isCreated); - } else if (EntityType.TENANT_PROFILE.equals(entityType)) { - onTenantProfileUpdate((TenantProfile) event.getEntity(), isCreated); - } else if (EntityType.DEVICE.equals(entityType)) { - onDeviceUpdate(event.getEntity(), event.getOldEntity()); - } else if (EntityType.DEVICE_PROFILE.equals(entityType)) { - onDeviceProfileUpdate((DeviceProfile) event.getEntity(), event.getOldEntity(), isCreated); - } else if (EntityType.EDGE.equals(entityType)) { - handleEdgeEvent(tenantId, entityId, event.getEntity(), lifecycleEvent); - } else if (EntityType.TB_RESOURCE.equals(entityType)) { - tbClusterService.onResourceChange((TbResource) event.getEntity(), null); - } else if (EntityType.API_USAGE_STATE.equals(entityType) && !event.getAdded()) { - tbClusterService.onApiStateChange((ApiUsageState) event.getEntity(), null); + switch (entityType) { + case ASSET: + case ASSET_PROFILE: + case ENTITY_VIEW: + tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent); + break; + case TENANT: + Tenant tenant = (Tenant) event.getEntity(); + onTenantUpdate(tenant, isCreated); + break; + case TENANT_PROFILE: + TenantProfile tenantProfile = (TenantProfile) event.getEntity(); + onTenantProfileUpdate(tenantProfile, isCreated); + break; + case DEVICE: + onDeviceUpdate(event.getEntity(), event.getOldEntity()); + break; + case DEVICE_PROFILE: + DeviceProfile deviceProfile = (DeviceProfile) event.getEntity(); + onDeviceProfileUpdate(deviceProfile, event.getOldEntity(), isCreated); + break; + case EDGE: + handleEdgeEvent(tenantId, entityId, event.getEntity(), lifecycleEvent); + break; + case TB_RESOURCE: + TbResource tbResource = (TbResource) event.getEntity(); + tbClusterService.onResourceChange(tbResource, null); + break; + case API_USAGE_STATE: + ApiUsageState apiUsageState = (ApiUsageState) event.getEntity(); + tbClusterService.onApiStateChange(apiUsageState, null); + break; + default: + break; } } @@ -95,20 +113,38 @@ public class EntityStateSourcingListener { EntityId entityId = event.getEntityId(); EntityType entityType = entityId.getEntityType(); - if (isCommonEntityStateUpdated(entityId) || EntityType.CUSTOMER.equals(entityType)) { - tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); - } else if (EntityType.TENANT.equals(event.getEntityId().getEntityType())) { - onTenantDeleted((Tenant) event.getEntity()); - } else if (EntityType.TENANT_PROFILE.equals(entityType)) { - tbClusterService.onTenantProfileDelete((TenantProfile) event.getEntity(), null); - } else if (EntityType.DEVICE.equals(entityType)) { - tbClusterService.onDeviceDeleted((Device) event.getEntity(), null); - } else if (EntityType.DEVICE_PROFILE.equals(entityType)) { - onDeviceProfileDelete(event.getTenantId(), event.getEntityId(), (DeviceProfile) event.getEntity()); - } else if (EntityType.EDGE.equals(entityType)) { - tbClusterService.broadcastEntityStateChangeEvent(event.getTenantId(), event.getEntityId(), ComponentLifecycleEvent.DELETED); - } else if (EntityType.TB_RESOURCE.equals(entityType)) { - tbClusterService.onResourceDeleted((TbResource) event.getEntity(), null); + switch (entityType) { + case ASSET: + case ASSET_PROFILE: + case ENTITY_VIEW: + case CUSTOMER: + tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); + break; + case TENANT: + Tenant tenant = (Tenant) event.getEntity(); + onTenantDeleted(tenant); + break; + case TENANT_PROFILE: + TenantProfile tenantProfile = (TenantProfile) event.getEntity(); + tbClusterService.onTenantProfileDelete(tenantProfile, null); + break; + case DEVICE: + Device device = (Device) event.getEntity(); + tbClusterService.onDeviceDeleted(device, null); + break; + case DEVICE_PROFILE: + DeviceProfile deviceProfile = (DeviceProfile) event.getEntity(); + onDeviceProfileDelete(event.getTenantId(), event.getEntityId(), deviceProfile); + break; + case EDGE: + tbClusterService.broadcastEntityStateChangeEvent(event.getTenantId(), event.getEntityId(), ComponentLifecycleEvent.DELETED); + break; + case TB_RESOURCE: + TbResource tbResource = (TbResource) event.getEntity(); + tbClusterService.onResourceDeleted(tbResource, null); + break; + default: + break; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java index 18878950d7..3c9a10a4ac 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java @@ -21,7 +21,6 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; @@ -45,9 +44,6 @@ public class DefaultTbTenantProfileService extends AbstractTbEntityService imple public TenantProfile save(TenantId tenantId, TenantProfile tenantProfile, TenantProfile oldTenantProfile) throws ThingsboardException { TenantProfile savedTenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(tenantId, tenantProfile)); tenantProfileCache.put(savedTenantProfile); - tbClusterService.onTenantProfileChange(savedTenantProfile, null); - tbClusterService.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, savedTenantProfile.getId(), - tenantProfile.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); List tenantIds = tenantService.findTenantIdsByTenantProfileId(savedTenantProfile.getId()); tbQueueService.updateQueuesByTenants(tenantIds, savedTenantProfile, oldTenantProfile); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java index 650f92ac20..c35b6db07a 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java @@ -52,11 +52,11 @@ public class DefaultWidgetTypeService extends AbstractTbEntityService implements try { WidgetTypeDetails savedWidgetTypeDetails = checkNotNull(widgetTypeService.saveWidgetType(widgetTypeDetails)); autoCommit(user, savedWidgetTypeDetails.getId()); - notificationEntityService.logEntityAction(tenantId, savedWidgetTypeDetails.getId(), savedWidgetTypeDetails, + logEntityActionService.logEntityAction(tenantId, savedWidgetTypeDetails.getId(), savedWidgetTypeDetails, null, actionType, user); return savedWidgetTypeDetails; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.WIDGET_TYPE), widgetTypeDetails, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.WIDGET_TYPE), widgetTypeDetails, actionType, user, e); throw e; } } @@ -67,9 +67,9 @@ public class DefaultWidgetTypeService extends AbstractTbEntityService implements TenantId tenantId = widgetTypeDetails.getTenantId(); try { widgetTypeService.deleteWidgetType(widgetTypeDetails.getTenantId(), widgetTypeDetails.getId()); - notificationEntityService.logEntityAction(tenantId, widgetTypeDetails.getId(), widgetTypeDetails, null, actionType, user); + logEntityActionService.logEntityAction(tenantId, widgetTypeDetails.getId(), widgetTypeDetails, null, actionType, user); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.WIDGET_TYPE), actionType, user, e, widgetTypeDetails.getId()); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.WIDGET_TYPE), actionType, user, e, widgetTypeDetails.getId()); throw e; } } diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java index 1c4b481302..4725c4e3f7 100644 --- a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java @@ -78,7 +78,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.TenantProfileMsgConst import org.thingsboard.server.service.edge.rpc.constructor.UserMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.WidgetTypeMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor; -import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.entitiy.TbLogEntityActionService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; @@ -94,7 +94,7 @@ public abstract class BaseEdgeProcessorTest { protected TelemetrySubscriptionService tsSubService; @MockBean - protected TbNotificationEntityService notificationEntityService; + protected TbLogEntityActionService logEntityActionService; @MockBean protected RuleChainService ruleChainService; From b92f5438330daafab999bd3e8adc85f741d756c3 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 19 Sep 2023 12:03:44 +0300 Subject: [PATCH 025/209] updated default value for redis ssl config --- application/src/main/resources/thingsboard.yml | 2 +- .../thingsboard/server/cache/TBRedisClusterConfiguration.java | 2 +- .../thingsboard/server/cache/TBRedisSentinelConfiguration.java | 2 +- .../server/cache/TBRedisStandaloneConfiguration.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 432caa4c27..afda163549 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -550,7 +550,7 @@ redis: password: "${REDIS_PASSWORD:}" # ssl config ssl: - enabled: "${TB_REDIS_SSL_ENABLED:true}" + enabled: "${TB_REDIS_SSL_ENABLED:false}" truststoreLocation: "${TB_REDIS_SSL_TRUSTSTORE_LOCATION:}" truststorePassword: "${TB_REDIS_SSL_TRUSTSTORE_PASSWORD:}" # client authentication could be optional and depends on redis server configuration diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java index 63517a98ac..14ffbedced 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java @@ -40,7 +40,7 @@ public class TBRedisClusterConfiguration extends TBRedisCacheConfiguration { @Value("${redis.password:}") private String password; - @Value("${redis.ssl.enabled:}") + @Value("${redis.ssl.enabled:false}") private boolean useSsl; public JedisConnectionFactory loadFactory() { diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisSentinelConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisSentinelConfiguration.java index dbfb3bce73..18aa315b1c 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisSentinelConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisSentinelConfiguration.java @@ -43,7 +43,7 @@ public class TBRedisSentinelConfiguration extends TBRedisCacheConfiguration { @Value("${redis.db:}") private Integer database; - @Value("${redis.ssl.enabled:}") + @Value("${redis.ssl.enabled:false}") private boolean useSsl; @Value("${redis.password:}") diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java index 55065e443f..b3bfda59cb 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java @@ -57,7 +57,7 @@ public class TBRedisStandaloneConfiguration extends TBRedisCacheConfiguration { @Value("${redis.password:}") private String password; - @Value("${redis.ssl.enabled:}") + @Value("${redis.ssl.enabled:false}") private boolean useSsl; public JedisConnectionFactory loadFactory() { From dd4b874b5f4328e6a7384a222e1a804fe2385646 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 27 Sep 2023 17:38:27 +0300 Subject: [PATCH 026/209] Version set to 3.6.1-SNAPSHOT --- application/pom.xml | 2 +- common/actor/pom.xml | 2 +- common/cache/pom.xml | 2 +- common/cluster-api/pom.xml | 2 +- common/coap-server/pom.xml | 2 +- common/dao-api/pom.xml | 2 +- common/data/pom.xml | 2 +- common/edge-api/pom.xml | 2 +- common/message/pom.xml | 2 +- common/pom.xml | 2 +- common/proto/pom.xml | 2 +- common/queue/pom.xml | 2 +- common/script/pom.xml | 2 +- common/script/remote-js-client/pom.xml | 2 +- common/script/script-api/pom.xml | 2 +- common/stats/pom.xml | 2 +- common/transport/coap/pom.xml | 2 +- common/transport/http/pom.xml | 2 +- common/transport/lwm2m/pom.xml | 2 +- common/transport/mqtt/pom.xml | 2 +- common/transport/pom.xml | 2 +- common/transport/snmp/pom.xml | 2 +- common/transport/transport-api/pom.xml | 2 +- common/util/pom.xml | 2 +- common/version-control/pom.xml | 2 +- dao/pom.xml | 2 +- monitoring/pom.xml | 2 +- msa/black-box-tests/pom.xml | 2 +- msa/js-executor/pom.xml | 2 +- msa/monitoring/pom.xml | 2 +- msa/pom.xml | 2 +- msa/tb-node/pom.xml | 2 +- msa/tb/pom.xml | 2 +- msa/transport/coap/pom.xml | 2 +- msa/transport/http/pom.xml | 2 +- msa/transport/lwm2m/pom.xml | 2 +- msa/transport/mqtt/pom.xml | 2 +- msa/transport/pom.xml | 2 +- msa/transport/snmp/pom.xml | 2 +- msa/vc-executor-docker/pom.xml | 2 +- msa/vc-executor/pom.xml | 2 +- msa/web-ui/pom.xml | 2 +- netty-mqtt/pom.xml | 4 ++-- pom.xml | 2 +- rest-client/pom.xml | 2 +- rule-engine/pom.xml | 2 +- rule-engine/rule-engine-api/pom.xml | 2 +- rule-engine/rule-engine-components/pom.xml | 2 +- tools/pom.xml | 2 +- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/lwm2m/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- transport/pom.xml | 2 +- transport/snmp/pom.xml | 2 +- ui-ngx/pom.xml | 2 +- 56 files changed, 57 insertions(+), 57 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index fe235cc85c..bcdd55e6bf 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT thingsboard application diff --git a/common/actor/pom.xml b/common/actor/pom.xml index 2de85be932..7fa770c811 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/cache/pom.xml b/common/cache/pom.xml index a14c84d3f4..47e2800538 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml index e940f246c8..cddc33e7fa 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml index 420fc4dae6..d4b54af0b5 100644 --- a/common/coap-server/pom.xml +++ b/common/coap-server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 6cfb7706a8..bde363c6dd 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/data/pom.xml b/common/data/pom.xml index 9aa6aed2c0..49623ccc26 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml index 60562efbf2..71d6a42819 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index e64d4676ce..2a8171ec72 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index 0c80189acf..4dcf1d737a 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT thingsboard common diff --git a/common/proto/pom.xml b/common/proto/pom.xml index ab2cdf91c9..cccac11145 100644 --- a/common/proto/pom.xml +++ b/common/proto/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 59e96d210d..a0d0f55559 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/script/pom.xml b/common/script/pom.xml index 26f60bd358..dcd5c6bc55 100644 --- a/common/script/pom.xml +++ b/common/script/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml index 8fa852d977..e0ad5ec3b1 100644 --- a/common/script/remote-js-client/pom.xml +++ b/common/script/remote-js-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT script org.thingsboard.common.script diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml index ff835d6335..b2ddd48f86 100644 --- a/common/script/script-api/pom.xml +++ b/common/script/script-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT script org.thingsboard.common.script diff --git a/common/stats/pom.xml b/common/stats/pom.xml index 0869243e7a..6514b65b2a 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 2ac15d3e42..52758820cf 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index 8cf3e0efe8..bcd05edba4 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index 4433eae2b9..c2a5d8bd4b 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index bb4767640c..0baf8be935 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index 6879a4924d..be175ae817 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index 1865f498fa..be95664251 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index b45cd28e52..619b65daf7 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/util/pom.xml b/common/util/pom.xml index 59669b11fd..275fbf3842 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml index 370ecc24a9..a5e1d437c8 100644 --- a/common/version-control/pom.xml +++ b/common/version-control/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index b63990109a..30bd6c9ba3 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT thingsboard dao diff --git a/monitoring/pom.xml b/monitoring/pom.xml index 399e34092d..989cd113af 100644 --- a/monitoring/pom.xml +++ b/monitoring/pom.xml @@ -21,7 +21,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT thingsboard diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 12d36272c0..341bdbb560 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 0bd7bf4d60..128479e5eb 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/monitoring/pom.xml b/msa/monitoring/pom.xml index ba96ada39c..092bcfccc8 100644 --- a/msa/monitoring/pom.xml +++ b/msa/monitoring/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT msa diff --git a/msa/pom.xml b/msa/pom.xml index bf4e15d9d4..fde5244ddf 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 8437cc35c3..61185f4eda 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index 527d96766e..8955d32a40 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 386b91ea76..404006d2fa 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 344219a660..2ba1a5cb73 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml index 8dfef617cd..3db99773c8 100644 --- a/msa/transport/lwm2m/pom.xml +++ b/msa/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index e1d740c877..3b67d74fee 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index e0bd5a9457..445ee31bf7 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index b6a4e15b04..af3dc31629 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT org.thingsboard.msa.transport diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml index 9b6fc493cd..3bca1a5c8d 100644 --- a/msa/vc-executor-docker/pom.xml +++ b/msa/vc-executor-docker/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml index 9bd2befa90..4e6b6c4e11 100644 --- a/msa/vc-executor/pom.xml +++ b/msa/vc-executor/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index e9500cf5e0..cbee67c12e 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 3e09ac6b98..213ed62a05 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT thingsboard netty-mqtt - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index 3167435c32..1782614c34 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT pom Thingsboard diff --git a/rest-client/pom.xml b/rest-client/pom.xml index 638dc42009..8651656744 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT thingsboard rest-client diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index fe8cb3fab5..39e395b756 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index eb57b59c3f..ae06117259 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index c449b590fe..9565c6566c 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/tools/pom.xml b/tools/pom.xml index c0b74bb0a9..5417f12119 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 3b1e0a9e62..c4b52361b7 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 3d4b806d03..4e3abea0d1 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index efe76111f3..5bac4f5973 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 75daa483a1..67f27f8ab4 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index 203ea6cbbb..4e0182928c 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT thingsboard transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index c04e7d1f1f..da4c8c23d3 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT transport diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index d6dc28490f..84b5f8b98c 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.0-SNAPSHOT + 3.6.1-SNAPSHOT thingsboard org.thingsboard From a61e9d94c5cb3a56e5783a45a71f75213fe529ea Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 28 Sep 2023 12:55:36 +0200 Subject: [PATCH 027/209] merge improvements --- .../ThingsboardSecurityConfiguration.java | 22 ++++-- .../DeviceConnectivityController.java | 77 ++++++++++++------- .../common/data/widget/WidgetTypeDetails.java | 2 +- .../dao/sql/widget/WidgetTypeRepository.java | 6 +- .../engine/rest/TbRestApiCallNodeTest.java | 1 - 5 files changed, 71 insertions(+), 37 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index 49f812944c..d07ef4e2d8 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -31,10 +31,9 @@ import org.springframework.security.config.annotation.authentication.builders.Au import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationFailureHandler; @@ -136,8 +135,8 @@ public class ThingsboardSecurityConfiguration { ShallowEtagHeaderFilter etagFilter = new ShallowEtagHeaderFilter(); etagFilter.setWriteWeakETag(true); FilterRegistrationBean filterRegistrationBean - = new FilterRegistrationBean<>( etagFilter); - filterRegistrationBean.addUrlPatterns("*.js","*.css","*.ico","/assets/*","/static/*"); + = new FilterRegistrationBean<>(etagFilter); + filterRegistrationBean.addUrlPatterns("*.js", "*.css", "*.ico", "/assets/*", "/static/*"); filterRegistrationBean.setName("etagFilter"); return filterRegistrationBean; } @@ -200,8 +199,19 @@ public class ThingsboardSecurityConfiguration { private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver; @Bean - public WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> web.ignoring().requestMatchers("/*.js", "/*.css", "/*.ico", "/assets/**", "/static/**"); + @Order(0) + SecurityFilterChain resources(HttpSecurity http) throws Exception { + http + .securityMatchers(matchers -> matchers + .requestMatchers("/*.js", "/*.css", "/*.ico", "/assets/**", "/static/**")) + .headers(header -> header + .defaultsDisabled() + .addHeaderWriter(new StaticHeadersWriter(HttpHeaders.CACHE_CONTROL, "max-age=0, public"))) + .authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll()) + .requestCache(RequestCacheConfigurer::disable) + .securityContext(AbstractHttpConfigurer::disable) + .sessionManagement(AbstractHttpConfigurer::disable); + return http.build(); } @Bean diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java index 7346cda9fc..0faa743bf2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java @@ -17,6 +17,10 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.JsonNode; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; @@ -36,10 +40,8 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.device.DeviceConnectivityService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.system.SystemSecurityService; -import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.net.URISyntaxException; @@ -64,16 +66,30 @@ public class DeviceConnectivityController extends BaseController { notes = "Fetch the list of commands to publish device telemetry based on device profile " + "If the user has the authority of 'Tenant Administrator', the server checks that the device is owned by the same tenant. " + "If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + - TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) -// @ApiResponses(value = { -// @ApiResponse(code = 200, message = "OK", -// examples = @io.swagger.annotations.Example( -// value = { -// @io.swagger.annotations.ExampleProperty( -// mediaType = "application/json", -// value = "{\"http\":\"curl -v -X POST http://localhost:8080/api/v1/0ySs4FTOn5WU15XLmal8/telemetry --header Content-Type:application/json --data {temperature:25}\"," + -// "\"mqtt\":\"mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -i myClient1 -u myUsername1 -P myPassword -m {temperature:25}\"," + -// "\"coap\":\"coap-client -m POST coap://localhost:5683/api/v1/0ySs4FTOn5WU15XLmal8/telemetry -t json -e {temperature:25}\"}")}))}) + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + responses = { + @ApiResponse( + responseCode = "200", + description = "OK", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + examples = { + @ExampleObject( + name = "http", + value = "curl -v -X POST http://localhost:8080/api/v1/0ySs4FTOn5WU15XLmal8/telemetry --header Content-Type:application/json --data {temperature:25}" + ), + @ExampleObject( + name = "mqtt", + value = "mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -i myClient1 -u myUsername1 -P myPassword -m {temperature:25}" + ), + @ExampleObject( + name = "coap", + value = "coap-client -m POST coap://localhost:5683/api/v1/0ySs4FTOn5WU15XLmal8/telemetry -t json -e {temperature:25}" + ) + } + ) + ) + }) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device-connectivity/{deviceId}", method = RequestMethod.GET) @ResponseBody @@ -81,7 +97,7 @@ public class DeviceConnectivityController extends BaseController { @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); + Device device = checkDeviceId(deviceId, org.thingsboard.server.service.security.permission.Operation.READ_CREDENTIALS); String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); return deviceConnectivityService.findDevicePublishTelemetryCommands(baseUrl, device); @@ -89,25 +105,34 @@ public class DeviceConnectivityController extends BaseController { @ApiOperation(value = "Get commands to launch gateway (getGatewayLaunchCommands)", notes = "Fetch the list of commands for different operation systems to launch a gateway using docker." + - TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) -// @ApiResponses(value = { -// @ApiResponse(code = 200, message = "OK", -// examples = @io.swagger.annotations.Example( -// value = { -// @io.swagger.annotations.ExampleProperty( -// mediaType = "application/json", -// value = "{\"mqtt\": {\n" + -// " \"linux\": \"docker run --rm -it -v ~/.tb-gateway/logs:/thingsboard_gateway/logs -v ~/.tb-gateway/extensions:/thingsboard_gateway/extensions -v ~/.tb-gateway/config:/thingsboard_gateway/config --name tbGateway127001 -e host=localhost -e port=1883 -e accessToken=qTe5oDBHPJf0KCSKO8J3 --restart always thingsboard/tb-gateway\",\n" + -// " \"windows\": \"docker run --rm -it -v %HOMEPATH%/tb-gateway/logs:/thingsboard_gateway/logs -v %HOMEPATH%/tb-gateway/extensions:/thingsboard_gateway/extensions -v %HOMEPATH%/tb-gateway/config:/thingsboard_gateway/config --name tbGateway127001 -e host=localhost -e port=1883 -e accessToken=qTe5oDBHPJf0KCSKO8J3 --restart always thingsboard/tb-gateway\"}\n" + -// "}")}))}) + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + responses = { + @ApiResponse( + responseCode = "200", + description = "OK", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + examples = { + @ExampleObject( + name = "mqtt-linux", + value = "docker run --rm -it -v ~/.tb-gateway/logs:/thingsboard_gateway/logs -v ~/.tb-gateway/extensions:/thingsboard_gateway/extensions -v ~/.tb-gateway/config:/thingsboard_gateway/config --name tbGateway127001 -e host=localhost -e port=1883 -e accessToken=qTe5oDBHPJf0KCSKO8J3 --restart always thingsboard/tb-gateway" + ), + @ExampleObject( + name = "mqtt-windows", + value = "docker run --rm -it -v %HOMEPATH%/tb-gateway/logs:/thingsboard_gateway/logs -v %HOMEPATH%/tb-gateway/extensions:/thingsboard_gateway/extensions -v %HOMEPATH%/tb-gateway/config:/thingsboard_gateway/config --name tbGateway127001 -e host=localhost -e port=1883 -e accessToken=qTe5oDBHPJf0KCSKO8J3 --restart always thingsboard/tb-gateway" + ) + } + ) + ) + }) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device-connectivity/gateway-launch/{deviceId}", method = RequestMethod.GET) @ResponseBody public JsonNode getGatewayLaunchCommands(@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION) - @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { + @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); + Device device = checkDeviceId(deviceId, org.thingsboard.server.service.security.permission.Operation.READ_CREDENTIALS); if (!checkIsGateway(device)) { throw new ThingsboardException("The device must be a gateway!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java index 88230aec94..2b80a3d0f3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java @@ -35,7 +35,7 @@ public class WidgetTypeDetails extends WidgetType implements HasName, HasTenantI @Schema(description = "Base64 encoded thumbnail") private String image; @NoXss - @Length(fieldName = "description") + @Length(fieldName = "description", max = 1024) @Schema(description = "Description of the widget") private String description; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java index 41fa293dd5..f85386b077 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java @@ -39,7 +39,7 @@ public interface WidgetTypeRepository extends JpaRepository findSystemWidgetTypes(@Param("systemTenantId") UUID systemTenantId, @Param("searchText") String searchText, @Param("fullSearch") boolean fullSearch, @@ -47,7 +47,7 @@ public interface WidgetTypeRepository extends JpaRepository findAllTenantWidgetTypesByTenantId(@Param("tenantId") UUID tenantId, @Param("nullTenantId") UUID nullTenantId, @Param("searchText") String searchText, @@ -56,7 +56,7 @@ public interface WidgetTypeRepository extends JpaRepository findTenantWidgetTypesByTenantId(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, @Param("fullSearch") boolean fullSearch, diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java index cb147020f1..de997ef45c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java @@ -203,7 +203,6 @@ public class TbRestApiCallNodeTest { ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - //verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); verify(ctx).transformMsg(msgCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); assertNotSame(metaData, metadataCaptor.getValue()); From 9970bffc2f0c69ccc4711856faf0387df5138be5 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 28 Sep 2023 16:11:25 +0300 Subject: [PATCH 028/209] TbDate test update according to Java 17 --- .../test/java/org/thingsboard/script/api/tbel/TbDateTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index 87dd4f0ebf..e31641a4fe 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -244,7 +244,7 @@ class TbDateTest { Assert.assertEquals("9/5/23, 9:04:05 PM", d.toLocaleString("en-US", "America/New_York")); Assert.assertEquals("23. 9. 5. 오후 9:04:05", d.toLocaleString("ko-KR", "America/New_York")); Assert.assertEquals("06.09.23, 04:04:05", d.toLocaleString( "uk-UA", "Europe/Kiev")); - Assert.assertEquals("5\u200F/9\u200F/2023 9:04:05 م", d.toLocaleString( "ar-EG", "America/New_York")); + Assert.assertEquals("5\u200F/9\u200F/2023, 9:04:05 م", d.toLocaleString( "ar-EG", "America/New_York")); Assert.assertEquals("Tuesday, September 5, 2023 at 9:04:05 PM Eastern Daylight Time", d.toLocaleString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") @@ -261,7 +261,7 @@ class TbDateTest { .put("dateStyle", "full") .put("timeStyle", "full") .toString())); - Assert.assertEquals("الثلاثاء، 5 سبتمبر 2023 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() + Assert.assertEquals("الثلاثاء، 5 سبتمبر 2023 في 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") .put("timeStyle", "full") From db4ce97621f26ebb630977e1243050d64a115263 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 29 Sep 2023 11:49:35 +0300 Subject: [PATCH 029/209] tbDate: TbDate implements InstantSource --- .../thingsboard/script/api/tbel/TbDate.java | 152 +++++++++++------- .../script/api/tbel/TbDateTest.java | 129 +++++++++++---- 2 files changed, 192 insertions(+), 89 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java index 3f79dc2cf7..64ab9a1c76 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java @@ -15,71 +15,91 @@ */ package org.thingsboard.script.api.tbel; +import org.mvel2.ConversionException; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.time.Instant; -import java.time.LocalDate; +import java.time.InstantSource; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; -import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalAccessor; +import java.time.format.DateTimeParseException; +import java.util.Arrays; import java.util.Date; -import java.util.GregorianCalendar; import java.util.Locale; import java.util.function.BiFunction; -public class TbDate extends Date { +public class TbDate implements InstantSource { - private static final DateTimeFormatter isoDateFormatter = DateTimeFormatter.ofPattern( - "yyyy-MM-dd[[ ]['T']HH:mm[:ss[.SSS]][ ][XXX][Z][z][VV][O]]").withZone(ZoneId.systemDefault()); + private static Instant instant; - private static final ThreadLocal isoDateFormat = ThreadLocal.withInitial(() -> - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")); + private static String patternDefault = "%s-%s-%sT%s:%s:%s.%d%s"; + +// private static final DateTimeFormatter isoDateFormatter = DateTimeFormatter.ofPattern( +// "yyyy-MM-dd[[ ]['T']HH:mm[:ss[.SSS]][ ][XXX][Z][z][VV][O]]").withZone(ZoneId.systemDefault()); +// +// private static final ThreadLocal isoDateFormat = ThreadLocal.withInitial(() -> +// new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")); public TbDate() { - super(); + instant = Instant.now(); } public TbDate(String s) { - super(parse(s)); + try{ + if (s.length() > 0 && Character.isDigit(s.charAt(0))) { + // assuming UTC instant a la "2007-12-03T10:15:30.00Z" + instant = Instant.parse(s); + } + else { + // assuming RFC-1123 value a la "Tue, 3 Jun 2008 11:05:30 GMT" + instant = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(s)); + } + } catch (final DateTimeParseException ex) { + final ConversionException exception = new ConversionException("Cannot parse value [" + s + "] as instant", ex); + throw exception; + } + } + + public TbDate(long dateMilliSecond) { + instant = Instant.ofEpochMilli(dateMilliSecond); } - public TbDate(long date) { - super(date); + public TbDate(int year, int month, int date, String... tz) { + this(year, month, date, 0, 0, 0, 0, tz); } - public TbDate(int year, int month, int date) { - this(year, month, date, 0, 0, 0); + public TbDate(int year, int month, int date, int hrs, int min, String... tz) { + this(year, month, date, hrs, min, 0, 0, tz); } - public TbDate(int year, int month, int date, int hrs, int min) { - this(year, month, date, hrs, min, 0); + public TbDate(int year, int month, int date, int hrs, int min, int second, String... tz) { + this(year, month, date, hrs, min, second, 0, tz); } - public TbDate(int year, int month, int date, - int hrs, int min, int second) { - super(new GregorianCalendar(year, month, date, hrs, min, second).getTimeInMillis()); + public TbDate(int year, int month, int date, int hrs, int min, int second, int secondMilli, String... tz) { + this(createDateTimeFromPattern (year, month, date, hrs, min, second, secondMilli, tz)); + } + + @Override + public Instant instant() { + return instant; } public String toDateString() { - DateFormat formatter = DateFormat.getDateInstance(); - return formatter.format(this); + return toLocaleDateString(); } public String toTimeString() { - DateFormat formatter = DateFormat.getTimeInstance(DateFormat.LONG); - return formatter.format(this); + return toLocaleTimeString(); } public String toISOString() { - return isoDateFormat.get().format(this); + return instant.toString(); } public String toLocaleDateString() { @@ -106,9 +126,13 @@ public class TbDate extends Date { return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(options.getTimeStyle()).withLocale(locale)); } - @Override public String toLocaleString() { - return toLocaleString(null, null); + LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + return localDateTime.toString(); + } + + public LocalDateTime getLocalDateTime() { + return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); } public String toLocaleString(String locale) { @@ -130,7 +154,7 @@ public class TbDate extends Date { public String toLocaleString(String localeStr, String optionsStr, BiFunction formatterBuilder) { Locale locale = StringUtils.isNotEmpty(localeStr) ? Locale.forLanguageTag(localeStr) : Locale.getDefault(); DateTimeFormatOptions options = getDateFormattingOptions(optionsStr); - ZonedDateTime zdt = this.toInstant().atZone(options.getTimeZone().toZoneId()); + ZonedDateTime zdt = this.instant().atZone(options.getTimeZone().toZoneId()); DateTimeFormatter formatter; if (StringUtils.isNotEmpty(options.getPattern())) { formatter = new DateTimeFormatterBuilder().appendPattern(options.getPattern()).toFormatter(locale); @@ -159,35 +183,53 @@ public class TbDate extends Date { return System.currentTimeMillis(); } - public static long parse(String value, String format) { - try { - DateFormat dateFormat = new SimpleDateFormat(format); - return dateFormat.parse(value).getTime(); - } catch (Exception e) { - return -1; - } - } - - public static long parse(String value) { - try { - TemporalAccessor accessor = isoDateFormatter.parseBest(value, - ZonedDateTime::from, - LocalDateTime::from, - LocalDate::from); - Instant instant = Instant.from(accessor); - return Instant.EPOCH.until(instant, ChronoUnit.MILLIS); - } catch (Exception e) { - try { - return Date.parse(value); - } catch (IllegalArgumentException e2) { - return -1; - } - } - } +// public static long parse(String value, String format) { +// try { +// DateFormat dateFormat = new SimpleDateFormat(format); +// return dateFormat.parse(value).getTime(); +// } catch (Exception e) { +// return -1; +// } +// } + public static long parseSecond() { + return instant.getEpochSecond(); + } + + public static long parseSecondMilli() { + return instant.toEpochMilli(); + } + +// public static long parse(String value) { +// try { +// TemporalAccessor accessor = isoDateFormatter.parseBest(value, +// ZonedDateTime::from, +// LocalDateTime::from, +// LocalDate::from); +// Instant instant = Instant.from(accessor); +// return Instant.EPOCH.until(instant, ChronoUnit.MILLIS); +// } catch (Exception e) { +// try { +// return Date.parse(value); +// } catch (IllegalArgumentException e2) { +// return -1; +// } +// } +// } public static long UTC(int year, int month, int date, int hrs, int min, int sec) { return Date.UTC(year - 1900, month, date, hrs, min, sec); } + private static String createDateTimeFromPattern (int year, int month, int date, int hrs, int min, int second, int secondMilli, String... tz) { + String yearStr = String.format("%04d", year); + if (yearStr.substring(0, 2).equals("00")) yearStr = "20" + yearStr.substring(2,4); + String monthStr = String.format("%02d", month); + String dateStr = String.format("%02d", date); + String hrsStr = String.format("%02d", hrs); + String minStr = String.format("%02d", min); + String secondStr = String.format("%02d", second); + String tzStr = tz.length > 0 ? Arrays.stream(tz).findFirst().get() : "Z"; + return String.format(patternDefault, yearStr, monthStr, dateStr, hrsStr, minStr, secondStr, secondMilli, tzStr); + } } diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index e31641a4fe..34643a2f86 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -24,10 +24,13 @@ import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.mvel2.ConversionException; import org.thingsboard.common.util.JacksonUtil; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.InstantSource; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.chrono.IsoChronology; @@ -46,6 +49,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; @Slf4j class TbDateTest { @@ -121,7 +126,7 @@ class TbDateTest { long ts = 1709217342987L; //Thu Feb 29 2024 14:35:42.987 GMT+0000 int offset = Calendar.getInstance().get(Calendar.ZONE_OFFSET); // for example 3600000 for GMT + 1 TbDate tbDate = new TbDate(ts - offset); - String datePrefix = "2024-02-29T14:35:42.987"; //without time zone + String datePrefix = "2024-02-29T12:35:42.987Z"; //without time zone assertThat(tbDate.toISOString()) .as("format in main thread") .startsWith(datePrefix); @@ -140,59 +145,63 @@ class TbDateTest { void testToLocaleDateString() { TbDate d = new TbDate(1693962245000L); + Assert.assertEquals("2023-09-06T01:04:05Z", d.instant().toString()); + Assert.assertEquals("06.09.23", d.toDateString()); + // Depends on time zone, so we just check it works; Assert.assertNotNull(d.toLocaleDateString()); Assert.assertNotNull(d.toLocaleDateString("en-US")); - Assert.assertEquals("9/5/23", d.toLocaleDateString("en-US", "America/New_York")); - Assert.assertEquals("23. 9. 5.", d.toLocaleDateString("ko-KR", "America/New_York")); - Assert.assertEquals("06.09.23", d.toLocaleDateString( "uk-UA", "Europe/Kiev")); - Assert.assertEquals("5\u200F/9\u200F/2023", d.toLocaleDateString( "ar-EG", "America/New_York")); + Assert.assertEquals("9/5/23", d.toLocaleDateString("en-US", "America/New_York")); + Assert.assertEquals("23. 9. 5.", d.toLocaleDateString("ko-KR", "America/New_York")); + Assert.assertEquals("06.09.23", d.toLocaleDateString( "uk-UA", "Europe/Kiev")); + Assert.assertEquals("5\u200F/9\u200F/2023", d.toLocaleDateString( "ar-EG", "America/New_York")); Assert.assertEquals("Tuesday, September 5, 2023", d.toLocaleDateString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") - .toString())); + .toString())); Assert.assertEquals("2023년 9월 5일 화요일", d.toLocaleDateString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") - .toString())); + .toString())); Assert.assertEquals("середа, 6 вересня 2023 р.", d.toLocaleDateString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("dateStyle", "full") - .toString())); + .toString())); Assert.assertEquals("الثلاثاء، 5 سبتمبر 2023", d.toLocaleDateString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") - .toString())); + .toString())); Assert.assertEquals("Tuesday 9/5/2023", d.toLocaleDateString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "EEEE M/d/yyyy") - .toString())); + .toString())); Assert.assertEquals("화요일 9/5/2023", d.toLocaleDateString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "EEEE M/d/yyyy") - .toString())); + .toString())); Assert.assertEquals("середа 9/6/2023", d.toLocaleDateString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("pattern", "EEEE M/d/yyyy") - .toString())); + .toString())); Assert.assertEquals("الثلاثاء 9/5/2023", d.toLocaleDateString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "EEEE M/d/yyyy") - .toString())); + .toString())); } @Test void testToLocaleTimeString() { TbDate d = new TbDate(1693962245000L); + Assert.assertEquals("04:04:05", d.toTimeString()); + // Depends on time zone, so we just check it works; Assert.assertNotNull(d.toLocaleTimeString()); Assert.assertNotNull(d.toLocaleTimeString("en-US")); - Assert.assertEquals("9:04:05 PM", d.toLocaleTimeString("en-US", "America/New_York")); Assert.assertEquals("오후 9:04:05", d.toLocaleTimeString("ko-KR", "America/New_York")); Assert.assertEquals("04:04:05", d.toLocaleTimeString( "uk-UA", "Europe/Kiev")); @@ -201,36 +210,36 @@ class TbDateTest { Assert.assertEquals("9:04:05 PM Eastern Daylight Time", d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("timeStyle", "full") - .toString())); + .toString())); Assert.assertEquals("오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleTimeString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("timeStyle", "full") - .toString())); + .toString())); Assert.assertEquals("04:04:05 за східноєвропейським літнім часом", d.toLocaleTimeString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("timeStyle", "full") - .toString())); + .toString())); Assert.assertEquals("9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleTimeString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("timeStyle", "full") - .toString())); + .toString())); Assert.assertEquals("9:04:05 PM", d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "h:mm:ss a") - .toString())); + .toString())); Assert.assertEquals("9:04:05 오후", d.toLocaleTimeString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "h:mm:ss a") - .toString())); + .toString())); Assert.assertEquals("4:04:05 дп", d.toLocaleTimeString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("pattern", "h:mm:ss a") - .toString())); + .toString())); Assert.assertEquals("9:04:05 م", d.toLocaleTimeString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "h:mm:ss a") - .toString())); + .toString())); } @Test @@ -241,52 +250,104 @@ class TbDateTest { Assert.assertNotNull(d.toLocaleString()); Assert.assertNotNull(d.toLocaleString("en-US")); - Assert.assertEquals("9/5/23, 9:04:05 PM", d.toLocaleString("en-US", "America/New_York")); - Assert.assertEquals("23. 9. 5. 오후 9:04:05", d.toLocaleString("ko-KR", "America/New_York")); - Assert.assertEquals("06.09.23, 04:04:05", d.toLocaleString( "uk-UA", "Europe/Kiev")); + Assert.assertEquals("9/5/23, 9:04:05 PM", d.toLocaleString("en-US", "America/New_York")); + Assert.assertEquals("23. 9. 5. 오후 9:04:05", d.toLocaleString("ko-KR", "America/New_York")); + Assert.assertEquals("06.09.23, 04:04:05", d.toLocaleString( "uk-UA", "Europe/Kiev")); Assert.assertEquals("5\u200F/9\u200F/2023, 9:04:05 م", d.toLocaleString( "ar-EG", "America/New_York")); Assert.assertEquals("Tuesday, September 5, 2023 at 9:04:05 PM Eastern Daylight Time", d.toLocaleString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") .put("timeStyle", "full") - .toString())); + .toString())); Assert.assertEquals("2023년 9월 5일 화요일 오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") .put("timeStyle", "full") - .toString())); + .toString())); Assert.assertEquals("середа, 6 вересня 2023 р. о 04:04:05 за східноєвропейським літнім часом", d.toLocaleString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("dateStyle", "full") .put("timeStyle", "full") - .toString())); + .toString())); Assert.assertEquals("الثلاثاء، 5 سبتمبر 2023 في 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") .put("timeStyle", "full") - .toString())); + .toString())); Assert.assertEquals("9/5/2023, 9:04:05 PM", d.toLocaleString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "M/d/yyyy, h:mm:ss a") - .toString())); + .toString())); Assert.assertEquals("9/5/2023, 9:04:05 오후", d.toLocaleString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "M/d/yyyy, h:mm:ss a") - .toString())); + .toString())); Assert.assertEquals("9/6/2023, 4:04:05 дп", d.toLocaleString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("pattern", "M/d/yyyy, h:mm:ss a") - .toString())); + .toString())); Assert.assertEquals("9/5/2023, 9:04:05 م", d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "M/d/yyyy, h:mm:ss a") - .toString())); + .toString())); + } + + @Test + void TestFromString () { + String stringDateUTC = "2023-09-06T01:04:05.00Z"; + TbDate d = new TbDate(stringDateUTC); + Assert.assertEquals("2023-09-06T01:04:05Z", d.instant().toString()); + String stringDateTZ = "2023-09-06T01:04:05.00+04:00"; + d = new TbDate(stringDateTZ); + Assert.assertEquals("2023-09-05T21:04:05Z", d.instant().toString()); + stringDateTZ = "2023-09-06T01:04:05.00-02:00"; + d = new TbDate(stringDateTZ); + Assert.assertEquals("2023-09-06T03:04:05Z", d.instant().toString()); + String stringDateRFC_1123 = "Sat, 3 Jun 2023 11:05:30 GMT"; + d = new TbDate(stringDateRFC_1123); + Assert.assertEquals("2023-06-03T11:05:30Z", d.instant().toString()); + + String stringDateRFC_1123_error = "Tue, 3 Jun 2023 11:05:30 GMT"; + Exception exception = assertThrows(ConversionException.class, () -> { + new TbDate(stringDateRFC_1123_error); + }); + String expectedMessage = "Cannot parse value [Tue, 3 Jun 2023 11:05:30 GMT] as instant"; + String actualMessage = exception.getMessage(); + assertTrue(actualMessage.contains(expectedMessage)); + } + + @Test + void TestParse () { + String stringDateUTC = "2023-09-06T01:04:05.345Z"; + TbDate d = new TbDate(stringDateUTC); + Assert.assertEquals(1693962245345L, d.parseSecondMilli()); + Assert.assertEquals(1693962245L, d.parseSecond()); + } + + @Test + void TestDate_Year_Moth_Date_Hs_Min_Sec () { + TbDate d = new TbDate(2023, 8, 18); + Assert.assertEquals("2023-08-18T00:00:00Z", d.instant().toString()); + d = new TbDate(2023, 9, 17, 17, 34); + Assert.assertEquals("2023-09-17T17:34:00Z", d.instant().toString()); + d = new TbDate(23, 9, 7, 8, 4); + Assert.assertEquals("2023-09-07T08:04:00Z", d.instant().toString()); + d = new TbDate(23, 9, 7, 8, 4, 5); + Assert.assertEquals("2023-09-07T08:04:05Z", d.instant().toString()); + d = new TbDate(23, 9, 7, 8, 4, 5, "+04:00"); + Assert.assertEquals("2023-09-07T04:04:05Z", d.instant().toString()); + d = new TbDate(23, 9, 7, 8, 4, 5, "-03:00"); + Assert.assertEquals("2023-09-07T11:04:05Z", d.instant().toString()); + d = new TbDate(23, 9, 7, 23, 4, 5, "-03:00"); + Assert.assertEquals("2023-09-08T02:04:05Z", d.instant().toString()); + d = new TbDate(23, 9, 7, 23, 4, 5, 567,"-03:00"); + Assert.assertEquals("2023-09-08T02:04:05.567Z", d.instant().toString()); } private static String toLocalString(TbDate d, Locale locale, ZoneId tz) { - LocalDateTime ldt = d.toInstant().atZone(tz).toLocalDateTime(); + LocalDateTime ldt = d.getLocalDateTime(); // new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale) From 47d1dc5fcf0d5b8a9866dfd6f08e635fed94f85f Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 29 Sep 2023 11:53:38 +0300 Subject: [PATCH 030/209] tbDate: class TbDate implements InstantSource --- .../test/java/org/thingsboard/script/api/tbel/TbDateTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index 34643a2f86..594047e1cc 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -29,8 +29,6 @@ import org.thingsboard.common.util.JacksonUtil; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.time.Instant; -import java.time.InstantSource; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.chrono.IsoChronology; From ab2976292b08c20b0b7f00338c4f2dc947b0c18e Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 29 Sep 2023 17:22:36 +0300 Subject: [PATCH 031/209] tbDate: fix bug tests --- .../script/api/tbel/TbDateTest.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index 594047e1cc..c415f59508 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -121,22 +121,29 @@ class TbDateTest { @Test void testToISOStringThreadLocalStaticFormatter() throws ExecutionException, InterruptedException, TimeoutException { executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1)); + int hrs = 14; + String pattern = "2024-02-29T%s:35:42.987Z"; + String tsStr = String.format(pattern, hrs); //Thu Feb 29 2024 14:35:42.987 GMT+0000 + TbDate tbDate = new TbDate(tsStr); long ts = 1709217342987L; //Thu Feb 29 2024 14:35:42.987 GMT+0000 + Assert.assertEquals(ts, tbDate.millis()); int offset = Calendar.getInstance().get(Calendar.ZONE_OFFSET); // for example 3600000 for GMT + 1 - TbDate tbDate = new TbDate(ts - offset); - String datePrefix = "2024-02-29T12:35:42.987Z"; //without time zone + + String datePrefix = tsStr; //without time zone assertThat(tbDate.toISOString()) .as("format in main thread") .startsWith(datePrefix); assertThat(executor.submit(tbDate::toISOString).get(30, TimeUnit.SECONDS)) .as("format in executor thread") .startsWith(datePrefix); + int offsetHrs = offset/1000/60/60; + String datePrefixLocal = String.format(pattern, (hrs - offsetHrs)); assertThat(new TbDate(ts - offset).toISOString()) .as("new instance format in main thread") - .startsWith(datePrefix); + .startsWith(datePrefixLocal); assertThat(executor.submit(() -> new TbDate(ts - offset).toISOString()).get(30, TimeUnit.SECONDS)) .as("new instance format in executor thread") - .startsWith(datePrefix); + .startsWith(datePrefixLocal); } @Test @@ -144,7 +151,6 @@ class TbDateTest { TbDate d = new TbDate(1693962245000L); Assert.assertEquals("2023-09-06T01:04:05Z", d.instant().toString()); - Assert.assertEquals("06.09.23", d.toDateString()); // Depends on time zone, so we just check it works; Assert.assertNotNull(d.toLocaleDateString()); @@ -194,8 +200,6 @@ class TbDateTest { void testToLocaleTimeString() { TbDate d = new TbDate(1693962245000L); - Assert.assertEquals("04:04:05", d.toTimeString()); - // Depends on time zone, so we just check it works; Assert.assertNotNull(d.toLocaleTimeString()); Assert.assertNotNull(d.toLocaleTimeString("en-US")); @@ -306,6 +310,9 @@ class TbDateTest { String stringDateRFC_1123 = "Sat, 3 Jun 2023 11:05:30 GMT"; d = new TbDate(stringDateRFC_1123); Assert.assertEquals("2023-06-03T11:05:30Z", d.instant().toString()); + stringDateRFC_1123 = "Thu, 29 Feb 2024 11:05:30 GMT"; + d = new TbDate(stringDateRFC_1123); + Assert.assertEquals("2024-02-29T11:05:30Z", d.instant().toString()); String stringDateRFC_1123_error = "Tue, 3 Jun 2023 11:05:30 GMT"; Exception exception = assertThrows(ConversionException.class, () -> { From f3d258e60a7c688db3bb16aa3c030d8dd8c0f4e6 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 2 Oct 2023 15:32:38 +0300 Subject: [PATCH 032/209] tbDate: tests comments1 --- .../script/api/tbel/TbDateTest.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index c415f59508..5115940a6b 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -150,7 +150,7 @@ class TbDateTest { void testToLocaleDateString() { TbDate d = new TbDate(1693962245000L); - Assert.assertEquals("2023-09-06T01:04:05Z", d.instant().toString()); + Assert.assertEquals("2023-09-06T01:04:05Z", d.toISOString()); // Depends on time zone, so we just check it works; Assert.assertNotNull(d.toLocaleDateString()); @@ -300,19 +300,19 @@ class TbDateTest { void TestFromString () { String stringDateUTC = "2023-09-06T01:04:05.00Z"; TbDate d = new TbDate(stringDateUTC); - Assert.assertEquals("2023-09-06T01:04:05Z", d.instant().toString()); + Assert.assertEquals("2023-09-06T01:04:05Z", d.toISOString()); String stringDateTZ = "2023-09-06T01:04:05.00+04:00"; d = new TbDate(stringDateTZ); - Assert.assertEquals("2023-09-05T21:04:05Z", d.instant().toString()); + Assert.assertEquals("2023-09-05T21:04:05Z", d.toISOString()); stringDateTZ = "2023-09-06T01:04:05.00-02:00"; d = new TbDate(stringDateTZ); - Assert.assertEquals("2023-09-06T03:04:05Z", d.instant().toString()); + Assert.assertEquals("2023-09-06T03:04:05Z", d.toISOString()); String stringDateRFC_1123 = "Sat, 3 Jun 2023 11:05:30 GMT"; d = new TbDate(stringDateRFC_1123); - Assert.assertEquals("2023-06-03T11:05:30Z", d.instant().toString()); + Assert.assertEquals("2023-06-03T11:05:30Z", d.toISOString()); stringDateRFC_1123 = "Thu, 29 Feb 2024 11:05:30 GMT"; d = new TbDate(stringDateRFC_1123); - Assert.assertEquals("2024-02-29T11:05:30Z", d.instant().toString()); + Assert.assertEquals("2024-02-29T11:05:30Z", d.toISOString()); String stringDateRFC_1123_error = "Tue, 3 Jun 2023 11:05:30 GMT"; Exception exception = assertThrows(ConversionException.class, () -> { @@ -334,21 +334,21 @@ class TbDateTest { @Test void TestDate_Year_Moth_Date_Hs_Min_Sec () { TbDate d = new TbDate(2023, 8, 18); - Assert.assertEquals("2023-08-18T00:00:00Z", d.instant().toString()); + Assert.assertEquals("2023-08-18T00:00:00Z", d.toISOString()); d = new TbDate(2023, 9, 17, 17, 34); - Assert.assertEquals("2023-09-17T17:34:00Z", d.instant().toString()); + Assert.assertEquals("2023-09-17T17:34:00Z", d.toISOString()); d = new TbDate(23, 9, 7, 8, 4); - Assert.assertEquals("2023-09-07T08:04:00Z", d.instant().toString()); + Assert.assertEquals("2023-09-07T08:04:00Z", d.toISOString()); d = new TbDate(23, 9, 7, 8, 4, 5); - Assert.assertEquals("2023-09-07T08:04:05Z", d.instant().toString()); + Assert.assertEquals("2023-09-07T08:04:05Z", d.toISOString()); d = new TbDate(23, 9, 7, 8, 4, 5, "+04:00"); - Assert.assertEquals("2023-09-07T04:04:05Z", d.instant().toString()); + Assert.assertEquals("2023-09-07T04:04:05Z", d.toISOString()); d = new TbDate(23, 9, 7, 8, 4, 5, "-03:00"); - Assert.assertEquals("2023-09-07T11:04:05Z", d.instant().toString()); + Assert.assertEquals("2023-09-07T11:04:05Z", d.toISOString()); d = new TbDate(23, 9, 7, 23, 4, 5, "-03:00"); - Assert.assertEquals("2023-09-08T02:04:05Z", d.instant().toString()); + Assert.assertEquals("2023-09-08T02:04:05Z", d.toISOString()); d = new TbDate(23, 9, 7, 23, 4, 5, 567,"-03:00"); - Assert.assertEquals("2023-09-08T02:04:05.567Z", d.instant().toString()); + Assert.assertEquals("2023-09-08T02:04:05.567Z", d.toISOString()); } private static String toLocalString(TbDate d, Locale locale, ZoneId tz) { From 73f8a7f537909e5d5d6912e61ee8792aa652ab49 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 2 Oct 2023 15:46:58 +0300 Subject: [PATCH 033/209] tbDate: tests comments12 --- .../thingsboard/script/api/tbel/TbDate.java | 31 ----------------- .../thingsboard/script/api/tbel/TbUtils.java | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java index 64ab9a1c76..42c5a228cf 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java @@ -39,12 +39,6 @@ public class TbDate implements InstantSource { private static String patternDefault = "%s-%s-%sT%s:%s:%s.%d%s"; -// private static final DateTimeFormatter isoDateFormatter = DateTimeFormatter.ofPattern( -// "yyyy-MM-dd[[ ]['T']HH:mm[:ss[.SSS]][ ][XXX][Z][z][VV][O]]").withZone(ZoneId.systemDefault()); -// -// private static final ThreadLocal isoDateFormat = ThreadLocal.withInitial(() -> -// new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")); - public TbDate() { instant = Instant.now(); } @@ -183,14 +177,6 @@ public class TbDate implements InstantSource { return System.currentTimeMillis(); } -// public static long parse(String value, String format) { -// try { -// DateFormat dateFormat = new SimpleDateFormat(format); -// return dateFormat.parse(value).getTime(); -// } catch (Exception e) { -// return -1; -// } -// } public static long parseSecond() { return instant.getEpochSecond(); } @@ -199,23 +185,6 @@ public class TbDate implements InstantSource { return instant.toEpochMilli(); } -// public static long parse(String value) { -// try { -// TemporalAccessor accessor = isoDateFormatter.parseBest(value, -// ZonedDateTime::from, -// LocalDateTime::from, -// LocalDate::from); -// Instant instant = Instant.from(accessor); -// return Instant.EPOCH.until(instant, ChronoUnit.MILLIS); -// } catch (Exception e) { -// try { -// return Date.parse(value); -// } catch (IllegalArgumentException e2) { -// return -1; -// } -// } -// } - public static long UTC(int year, int month, int date, int hrs, int min, int sec) { return Date.UTC(year - 1900, month, date, hrs, min, sec); diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java index bd14871fe2..1e71795f97 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java @@ -32,6 +32,16 @@ import java.math.RoundingMode; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; @@ -649,4 +659,27 @@ public class TbUtils { return true; } + public static long parse(String value, String format) { + try { + DateFormat dateFormat = new SimpleDateFormat(format); + return dateFormat.parse(value).getTime(); + } catch (Exception e) { + return -1; + } + } + public static long parse(String value) { + DateTimeFormatter isoDateFormatter = DateTimeFormatter.ofPattern( + "yyyy-MM-dd[[ ]['T']HH:mm[:ss[.SSS]][ ][XXX][Z][z][VV][O]]").withZone(ZoneId.systemDefault()); + try { + TemporalAccessor accessor = isoDateFormatter.parseBest(value, + ZonedDateTime::from, + LocalDateTime::from, + LocalDate::from); + Instant instant = Instant.from(accessor); + return Instant.EPOCH.until(instant, ChronoUnit.MILLIS); + } catch (Exception e) { + return -1; + } + } + } From 92b09b85144a3b756901a7ba369fb4068576c37f Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 5 Oct 2023 14:09:03 +0300 Subject: [PATCH 034/209] Minor refactoring: still not working as expected --- .../NotificationRuleController.java | 8 +---- .../DefaultTbApiUsageStateService.java | 6 ++-- .../edge/EdgeEventSourcingListener.java | 4 +-- .../entitiy/EntityStateSourcingListener.java | 36 ++++++++----------- .../device/DefaultTbDeviceService.java | 5 +-- .../entitiy/user/DefaultUserService.java | 4 +-- .../impl/NotificationRuleImportService.java | 6 +--- .../DefaultTbAlarmCommentServiceTest.java | 6 ++-- .../server/dao/device/DeviceService.java | 3 +- .../NotificationRequestService.java | 2 +- .../server/dao/device/DeviceServiceImpl.java | 7 +++- .../server/dao/edge/BaseEdgeEventService.java | 27 +++++++++++--- .../usagerecord/ApiUsageStateServiceImpl.java | 2 +- 13 files changed, 58 insertions(+), 58 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java index 2afb67a8bd..822b6b44ab 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java @@ -37,7 +37,6 @@ import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; @@ -125,11 +124,7 @@ public class NotificationRuleController extends BaseController { throw new IllegalArgumentException("Trigger type " + triggerType + " is not available"); } - boolean created = notificationRule.getId() == null; - notificationRule = doSaveAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::saveNotificationRule); - tbClusterService.broadcastEntityStateChangeEvent(user.getTenantId(), notificationRule.getId(), created ? - ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); - return notificationRule; + return doSaveAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::saveNotificationRule); } @ApiOperation(value = "Get notification rule by id (getNotificationRuleById)", @@ -177,7 +172,6 @@ public class NotificationRuleController extends BaseController { NotificationRuleId notificationRuleId = new NotificationRuleId(id); NotificationRule notificationRule = checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleById, Operation.DELETE); doDeleteAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::deleteNotificationRuleById); - tbClusterService.broadcastEntityStateChangeEvent(user.getTenantId(), notificationRuleId, ComponentLifecycleEvent.DELETED); } } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 2b1f7a0f6b..3f368f496d 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -25,7 +25,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.MailService; -import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordState; @@ -45,10 +44,11 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.notification.rule.trigger.ApiUsageLimitTrigger; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; -import org.thingsboard.server.common.data.notification.rule.trigger.ApiUsageLimitTrigger; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; @@ -61,7 +61,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; -import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.service.apiusage.BaseApiUsageState.StatsCalculationResult; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.mail.MailExecutorService; @@ -101,7 +100,6 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService public void onFailure(Throwable t) { } }; - private final TbClusterService clusterService; private final PartitionService partitionService; private final TenantService tenantService; private final TimeseriesService tsService; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 57dba7e86e..aacacb711e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -98,9 +98,7 @@ public class EdgeEventSourcingListener { } EntityType entityType = event.getEntityId().getEntityType(); try { - if (EntityType.EDGE.equals(entityType) || - EntityType.TENANT.equals(entityType) || - EntityType.TB_RESOURCE.equals(entityType)) { + if (EntityType.EDGE.equals(entityType) || EntityType.TENANT.equals(entityType)) { return; } log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java index 6ed5c01f12..60a9f28c9a 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.msg.TbMsg; @@ -73,15 +74,16 @@ public class EntityStateSourcingListener { case ASSET: case ASSET_PROFILE: case ENTITY_VIEW: + case NOTIFICATION_RULE: tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent); break; case TENANT: Tenant tenant = (Tenant) event.getEntity(); - onTenantUpdate(tenant, isCreated); + onTenantUpdate(tenant, lifecycleEvent); break; case TENANT_PROFILE: TenantProfile tenantProfile = (TenantProfile) event.getEntity(); - onTenantProfileUpdate(tenantProfile, isCreated); + onTenantProfileUpdate(tenantProfile, lifecycleEvent); break; case DEVICE: onDeviceUpdate(event.getEntity(), event.getOldEntity()); @@ -118,6 +120,8 @@ public class EntityStateSourcingListener { case ASSET_PROFILE: case ENTITY_VIEW: case CUSTOMER: + case EDGE: + case NOTIFICATION_RULE: tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); break; case TENANT: @@ -136,13 +140,15 @@ public class EntityStateSourcingListener { DeviceProfile deviceProfile = (DeviceProfile) event.getEntity(); onDeviceProfileDelete(event.getTenantId(), event.getEntityId(), deviceProfile); break; - case EDGE: - tbClusterService.broadcastEntityStateChangeEvent(event.getTenantId(), event.getEntityId(), ComponentLifecycleEvent.DELETED); - break; case TB_RESOURCE: TbResource tbResource = (TbResource) event.getEntity(); tbClusterService.onResourceDeleted(tbResource, null); break; + case NOTIFICATION_REQUEST: + NotificationRequest request = (NotificationRequest) event.getEntity(); + if (request.isScheduled()) { + tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); + } default: break; } @@ -167,26 +173,14 @@ public class EntityStateSourcingListener { } } - private void onTenantUpdate(Tenant tenant, boolean isCreated) { + private void onTenantUpdate(Tenant tenant, ComponentLifecycleEvent lifecycleEvent) { tbClusterService.onTenantChange(tenant, null); - tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), isCreated ? - ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), lifecycleEvent); } - private void onTenantProfileUpdate(TenantProfile tenantProfile, boolean isCreated) { + private void onTenantProfileUpdate(TenantProfile tenantProfile, ComponentLifecycleEvent lifecycleEvent) { tbClusterService.onTenantProfileChange(tenantProfile, null); - tbClusterService.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, tenantProfile.getId(), - isCreated ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); - } - - private boolean isCommonEntityStateUpdated(EntityId entityId) { - switch (entityId.getEntityType()) { - case ASSET: - case ASSET_PROFILE: - case ENTITY_VIEW: - return true; - } - return false; + tbClusterService.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, tenantProfile.getId(), lifecycleEvent); } private void onDeviceProfileUpdate(DeviceProfile deviceProfile, Object oldEntity, boolean isCreated) { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java index ab5b5088fc..b6785d41d6 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java @@ -40,7 +40,6 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.device.claim.ReclaimResult; -import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; @@ -53,7 +52,6 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T private final DeviceService deviceService; private final DeviceCredentialsService deviceCredentialsService; private final ClaimDevicesService claimDevicesService; - private final TenantService tenantService; @Override public Device save(Device device, String accessToken, User user) throws Exception { @@ -230,8 +228,7 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T TenantId newTenantId = newTenant.getId(); DeviceId deviceId = device.getId(); try { - Tenant tenant = tenantService.findTenantById(tenantId); - Device assignedDevice = deviceService.assignDeviceToTenant(tenant, newTenantId, device); + Device assignedDevice = deviceService.assignDeviceToTenant(newTenantId, device); logEntityActionService.logEntityAction(tenantId, deviceId, assignedDevice, assignedDevice.getCustomerId(), actionType, user, newTenantId.toString(), newTenant.getName()); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index ab4354a709..7ef110cb29 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -81,10 +81,10 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse try { userService.deleteUser(tenantId, user); - logEntityActionService.logEntityAction(tenantId, userId, user, customerId, actionType, user, customerId.toString()); + logEntityActionService.logEntityAction(tenantId, userId, user, customerId, actionType, responsibleUser, customerId.toString()); } catch (Exception e) { logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.USER), - actionType, user, e, userId.toString()); + actionType, responsibleUser, e, userId.toString()); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java index 9ace036ee6..0822640c4e 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java @@ -20,7 +20,6 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.NotificationRuleId; @@ -36,7 +35,6 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Devic import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.config.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.service.ConstraintValidator; @@ -130,11 +128,9 @@ public class NotificationRuleImportService extends BaseEntityImportService> findDeviceTypesByTenantId(TenantId tenantId); - Device assignDeviceToTenant(Tenant oldTenant, TenantId tenantId, Device device); + Device assignDeviceToTenant(TenantId tenantId, Device device); PageData findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType, PageLink pageLink); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java index af214dbf3d..22e7cfa530 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java @@ -45,7 +45,7 @@ public interface NotificationRequestService { List findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId); - void deleteNotificationRequest(TenantId tenantId, NotificationRequestId requestId); + void deleteNotificationRequest(TenantId tenantId, NotificationRequest request); PageData findScheduledNotificationRequests(PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 17975370ea..17da713dae 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -78,6 +78,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.tenant.TenantService; import java.util.ArrayList; import java.util.Comparator; @@ -114,6 +115,9 @@ public class DeviceServiceImpl extends AbstractCachedEntityService deviceValidator; @@ -497,9 +501,10 @@ public class DeviceServiceImpl extends AbstractCachedEntityService entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(oldTenantId, device.getId()); if (!CollectionUtils.isEmpty(entityViews)) { throw new DataValidationException("Can't assign device that has entity views to another tenant!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java index fbb03ebb54..46c5671002 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java @@ -18,12 +18,12 @@ package org.thingsboard.server.dao.edge; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; @@ -32,9 +32,14 @@ import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.service.DataValidator; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + @Service @Slf4j -@AllArgsConstructor +@RequiredArgsConstructor public class BaseEdgeEventService implements EdgeEventService { private final EdgeEventDao edgeEventDao; @@ -43,6 +48,20 @@ public class BaseEdgeEventService implements EdgeEventService { private final ApplicationEventPublisher eventPublisher; + private ExecutorService executor; + + @PostConstruct + public void initExecutor() { + executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("edge-event")); + } + + @PreDestroy + public void shutdownExecutor() { + if (executor != null) { + executor.shutdown(); + } + } + @Override public ListenableFuture saveAsync(EdgeEvent edgeEvent) { edgeEventValidator.validate(edgeEvent, EdgeEvent::getTenantId); @@ -58,7 +77,7 @@ public class BaseEdgeEventService implements EdgeEventService { @Override public void onFailure(@NotNull Throwable throwable) {} - }, MoreExecutors.directExecutor()); + }, executor); return saveFuture; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 70d6e2bec1..90f00d0ad6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -146,7 +146,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A validateId(apiUsageState.getId(), "Can't save new usage state. Only update is allowed!"); ApiUsageState savedState = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(apiUsageState.getTenantId()).entityId(savedState.getId()) - .entity(savedState).added(false).build()); + .entity(savedState).build()); return savedState; } From d1ba6935f40fcfa70d47722f8488043cc033c08a Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 5 Oct 2023 14:17:42 +0300 Subject: [PATCH 035/209] Rewrite NotificationCenter with new broadcast system --- .../service/notification/DefaultNotificationCenter.java | 6 +++--- .../notification/DefaultNotificationRequestService.java | 9 +++++++-- .../dao/notification/DefaultNotificationRuleService.java | 8 +++++++- .../thingsboard/server/dao/tenant/TenantServiceImpl.java | 4 ++-- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index f6c77691da..204ff89527 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -51,7 +51,6 @@ import org.thingsboard.server.common.data.notification.template.DeliveryMethodNo import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.page.PageDataIterable; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; @@ -391,7 +390,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) { log.debug("Deleting notification request {}", notificationRequestId); NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, notificationRequestId); - notificationRequestService.deleteNotificationRequest(tenantId, notificationRequestId); + notificationRequestService.deleteNotificationRequest(tenantId, notificationRequest); if (notificationRequest.isSent()) { // TODO: no need to send request update for other than PLATFORM_USERS target type @@ -401,7 +400,8 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple .build()); } else if (notificationRequest.isScheduled()) { // TODO: just forward to scheduler service - clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED); + // handling in EntityStateSourcingListener.class + // clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java index e951a75c7c..7f7e7e4f4c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.notification; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; @@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStatus import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.EntityDaoService; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.service.DataValidator; import java.util.List; @@ -43,6 +45,8 @@ public class DefaultNotificationRequestService implements NotificationRequestSer private final NotificationRequestDao notificationRequestDao; + private final ApplicationEventPublisher eventPublisher; + private final NotificationRequestValidator notificationRequestValidator = new NotificationRequestValidator(); @Override @@ -83,8 +87,9 @@ public class DefaultNotificationRequestService implements NotificationRequestSer // ON DELETE CASCADE is used: notifications for request are deleted as well @Override - public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId requestId) { - notificationRequestDao.removeById(tenantId, requestId.getId()); + public void deleteNotificationRequest(TenantId tenantId, NotificationRequest request) { + notificationRequestDao.removeById(tenantId, request.getUuidId()); + eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entity(request).entityId(request.getId()).build()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java index 41cea42875..90fcb3ccc4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java @@ -29,6 +29,8 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.EntityDaoService; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import java.util.List; import java.util.Map; @@ -49,7 +51,10 @@ public class DefaultNotificationRuleService extends AbstractEntityService implem } } try { - return notificationRuleDao.saveAndFlush(tenantId, notificationRule); + NotificationRule savedRule = notificationRuleDao.saveAndFlush(tenantId, notificationRule); + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entityId(savedRule.getId()) + .added(notificationRule.getId() == null).build()); + return savedRule; } catch (Exception e) { checkConstraintViolation(e, Map.of( "uq_notification_rule_name", "Notification rule with such name already exists" @@ -86,6 +91,7 @@ public class DefaultNotificationRuleService extends AbstractEntityService implem @Override public void deleteNotificationRuleById(TenantId tenantId, NotificationRuleId id) { notificationRuleDao.removeById(tenantId, id.getId()); + eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(id).build()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index ac85bfaa72..1aa90f7022 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -197,8 +197,6 @@ public class TenantServiceImpl extends AbstractCachedEntityService Date: Fri, 6 Oct 2023 10:52:36 +0300 Subject: [PATCH 036/209] tbDate: tests add method as Date JS --- .../thingsboard/script/api/tbel/TbDate.java | 374 ++++++++++++++++-- .../thingsboard/script/api/tbel/TbUtils.java | 25 +- .../script/api/tbel/TbDateTest.java | 371 +++++++++++++++-- 3 files changed, 665 insertions(+), 105 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java index 42c5a228cf..0dba460686 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java @@ -19,8 +19,11 @@ import org.mvel2.ConversionException; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; +import java.io.Serializable; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.time.Instant; -import java.time.InstantSource; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -28,39 +31,47 @@ import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; import java.util.Arrays; -import java.util.Date; import java.util.Locale; import java.util.function.BiFunction; -public class TbDate implements InstantSource { +public class TbDate implements Serializable, Cloneable { - private static Instant instant; + private Instant instant; + private ZonedDateTime zonedDateTime; + private LocalDateTime localDateTime; + private final ZoneId zoneIdUTC = ZoneId.of("UTC"); - private static String patternDefault = "%s-%s-%sT%s:%s:%s.%d%s"; + private static String patternDefault = "%s-%s-%sT%s:%s:%s.%s%s"; public TbDate() { - instant = Instant.now(); + this.instant = Instant.now(); + initZonedLocalDateTime(); } public TbDate(String s) { - try{ - if (s.length() > 0 && Character.isDigit(s.charAt(0))) { - // assuming UTC instant a la "2007-12-03T10:15:30.00Z" - instant = Instant.parse(s); - } - else { - // assuming RFC-1123 value a la "Tue, 3 Jun 2008 11:05:30 GMT" - instant = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(s)); - } - } catch (final DateTimeParseException ex) { - final ConversionException exception = new ConversionException("Cannot parse value [" + s + "] as instant", ex); - throw exception; - } + parseInstant(s); + } + + /** + * String s = "09:15:30 PM, Sun 10/09/2022"; + * String pattern = "hh:mm:ss a, EEE M/d/uuuu"; + * @param s + * @param pattern + */ + public TbDate(String s, String pattern, Locale locale) { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern, locale); + LocalDateTime localDateTime = LocalDateTime.parse(s, dateTimeFormatter); + ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault()); + instant = zonedDateTime.toInstant(); + initZonedLocalDateTime(); } public TbDate(long dateMilliSecond) { instant = Instant.ofEpochMilli(dateMilliSecond); + initZonedLocalDateTime(); } public TbDate(int year, int month, int date, String... tz) { @@ -76,25 +87,75 @@ public class TbDate implements InstantSource { } public TbDate(int year, int month, int date, int hrs, int min, int second, int secondMilli, String... tz) { - this(createDateTimeFromPattern (year, month, date, hrs, min, second, secondMilli, tz)); + this(createDateTimeFromPattern(year, month, date, hrs, min, second, secondMilli, tz)); } - @Override - public Instant instant() { + public Instant getInstant() { return instant; } - public String toDateString() { - return toLocaleDateString(); + public void setInstant(Instant instant) { + this.instant = instant; + } + public ZonedDateTime getZonedDateTime() { + return zonedDateTime; } - public String toTimeString() { - return toLocaleTimeString(); + private void initZonedLocalDateTime() { + setZonedDateTime(zoneIdUTC); + setLocalDateTime(); + } + public void setZonedDateTime(ZoneId z) { + this.zonedDateTime = instant.atZone(z);; + } + + public void setLocalDateTime() { + this.localDateTime = LocalDateTime.ofInstant(this.getInstant(), ZoneId.systemDefault()); + } + public LocalDateTime getLocalDateTime() { + return this.localDateTime ; } + public String toDateString() { + return toLocaleDateString("UTC", JacksonUtil.newObjectNode().put("dateStyle", "full").toString()); + } + public String toDateString(String locale) { + return toLocaleDateString(locale, JacksonUtil.newObjectNode().put("dateStyle", "full").toString()); + } + public String toTimeString() { + return toLocaleTimeString("UTC", JacksonUtil.newObjectNode().put("timeStyle", "full").toString()); + } + public String toTimeString(String locale) { + return toLocaleTimeString(locale, JacksonUtil.newObjectNode().put("timeStyle", "full").toString()); + } + /** + * "2011-10-05T14:48:00.000Z" + * @return + */ public String toISOString() { return instant.toString(); } + public String toJSON() { + return toISOString(); + } + public String toUTCString() { + return toLocaleString("UTC", JacksonUtil.newObjectNode().put("dateStyle", "full").put("timeStyle", "medium").toString()); + } + + public String toUTCString(String locale) { + return toLocaleString(locale, JacksonUtil.newObjectNode().put("dateStyle", "full").put("timeStyle", "medium").toString()); + } + + /** + * "Tue Aug 19 1975 23:15:30 GMT+0300 (за східноєвропейським стандартним часом)" + * @return + */ + public String toString() { + return toLocaleString(Locale.getDefault().toString(), JacksonUtil.newObjectNode().put("dateStyle", "full").put("timeStyle", "full").toString()); + } + public String toString(String locale) { + return toLocaleString(locale, JacksonUtil.newObjectNode().put("dateStyle", "full").put("timeStyle", "full").toString()); + } public String toLocaleDateString() { return toLocaleDateString(null, null); @@ -125,10 +186,6 @@ public class TbDate implements InstantSource { return localDateTime.toString(); } - public LocalDateTime getLocalDateTime() { - return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); - } - public String toLocaleString(String locale) { return toLocaleString(locale, null); } @@ -148,7 +205,7 @@ public class TbDate implements InstantSource { public String toLocaleString(String localeStr, String optionsStr, BiFunction formatterBuilder) { Locale locale = StringUtils.isNotEmpty(localeStr) ? Locale.forLanguageTag(localeStr) : Locale.getDefault(); DateTimeFormatOptions options = getDateFormattingOptions(optionsStr); - ZonedDateTime zdt = this.instant().atZone(options.getTimeZone().toZoneId()); + ZonedDateTime zdt = this.getInstant().atZone(options.getTimeZone().toZoneId()); DateTimeFormatter formatter; if (StringUtils.isNotEmpty(options.getPattern())) { formatter = new DateTimeFormatterBuilder().appendPattern(options.getPattern()).toFormatter(locale); @@ -158,7 +215,7 @@ public class TbDate implements InstantSource { return formatter.format(zdt); } - private static DateTimeFormatOptions getDateFormattingOptions(String options) { + private DateTimeFormatOptions getDateFormattingOptions(String options) { DateTimeFormatOptions opt = null; if (StringUtils.isNotEmpty(options)) { try { @@ -173,32 +230,265 @@ public class TbDate implements InstantSource { return opt; } - public static long now() { - return System.currentTimeMillis(); + public long now() { + return Instant.now().toEpochMilli(); } - public static long parseSecond() { + public long parseSecond() { return instant.getEpochSecond(); } - public static long parseSecondMilli() { + public long parseSecondMilli() { + return instant.toEpochMilli(); + } + + public static long UTC(int year) { + return UTC(year, 0, 0, 0, 0, 0, 0); + } + public static long UTC(int year, int month) { + return UTC(year, month, 0, 0, 0, 0, 0); + } + public static long UTC(int year, int month, int date) { + return UTC(year, month, date, 0, 0, 0, 0); + } + public static long UTC(int year, int month, int date, int hrs) { + return UTC(year, month, date, hrs, 0, 0, 0); + } + public static long UTC(int year, int month, int date, int hrs, int min) { + return UTC(year, month, date, hrs, min, 0, 0); + } + public static long UTC(int year, int month, int date, int hrs, int min, int sec) { + return UTC(year, month, date, hrs, min, sec, 0); + } + public static long UTC(int year, int month, int date, int hrs, int min, int sec, int ms) { + year = year == 0 ? year = 1899 : year; + month = month == 0 ? month = 12 : month; + date = date == 0 ? date = 31 : date; + return Instant.parse(createDateTimeFromPattern (year, month, date, hrs, min, sec, ms)).toEpochMilli(); + } + public int getUTCFullYear() { + return zonedDateTime.getYear(); + } + + public int getUTCMonth() { + return zonedDateTime.getMonthValue(); + } + // day in month + public int getUTCDate() { + return zonedDateTime.getDayOfMonth(); + } + // day in week + public int getUTCDay() { + return zonedDateTime.getDayOfWeek().getValue(); + } + + public int getUTCHours() { + return zonedDateTime.getHour(); + } + + public int getUTCMinutes() { + return zonedDateTime.getMinute(); + } + + public int getUTCSeconds() { + return zonedDateTime.getSecond(); + } + public int getUTCMilliseconds() { + return zonedDateTime.getNano()/1000000; + } + + public void setUTCFullYear(int year) { + parseInstant(createDateTimeFromPattern(year, getUTCMonth(), getUTCDate(), getUTCHours(), getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + } + public void setUTCFullYear(int year, int month) { + parseInstant(createDateTimeFromPattern(year, month, getUTCDate(), getUTCHours(), getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + } + public void setUTCFullYear(int year, int month, int date) { + parseInstant(createDateTimeFromPattern(year, month, date, getUTCHours(), getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + } + public void setUTCMonth(int month) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), month, getUTCDate(), getUTCHours(), getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + } + public void setUTCMonth(int month, int date) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), month, date, getUTCHours(), getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + } + public void setUTCDate(int date) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), date, getUTCHours(), getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + } + public void setUTCHours(int hrs) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), hrs, getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + } + public void setUTCHours(int hrs, int minutes) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), hrs, minutes, getUTCSeconds(), getUTCMilliseconds())); + } + public void setUTCHours(int hrs, int minutes, int seconds) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), hrs, minutes, seconds, getUTCMilliseconds())); + } + public void setUTCHours(int hrs, int minutes, int seconds, int ms) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), hrs, minutes, seconds, ms)); + } + public void setUTCMinutes(int minutes) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), minutes, getUTCSeconds(), getUTCMilliseconds())); + } + public void setUTCMinutes(int minutes, int seconds) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), minutes, seconds, getUTCMilliseconds())); + } + public void setUTCMinutes(int minutes, int seconds, int ms) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), minutes, seconds, ms)); + } + public void setUTCSeconds(int seconds) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), getUTCMinutes(), seconds, getUTCMilliseconds())); + } + public void setUTCSeconds(int seconds, int ms) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), getUTCMinutes(), seconds, ms)); + } + public void setUTCMilliseconds(int ms) { + parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), getUTCMinutes(), getUTCSeconds(), ms)); + } + public int getFullYear() { + return localDateTime.getYear(); + } + + public int getMonth() { + return localDateTime.getMonthValue(); + } + // day in month + public int getDate() { + return localDateTime.getDayOfMonth(); + } + // day in week + public int getDay() { + return localDateTime.getDayOfWeek().getValue(); + } + + public int getHours() { + return localDateTime.getHour(); + } + + public int getMinutes() { + return localDateTime.getMinute(); + } + + public int getSeconds() { + return localDateTime.getSecond(); + } + public int getMilliseconds() { + return localDateTime.getNano()/1000000; + } + // Milliseconds since Jan 1, 1970, 00:00:00.000 GMT + public long getTime() { return instant.toEpochMilli(); } + public long valueOf(){ + return getTime() ; + } + public void setFullYear(int year) { + setUTCFullYear(year); + } + public void setFullYear(int year, int month) { + setUTCFullYear(year, month); + } public void setFullYear(int year, int month, int date) { + setUTCFullYear(year, month, date); + } + public void setMonth(int month) { + setUTCMonth(month); + } + public void setMonth(int month, int date) { + setUTCMonth(month, date) ; + } + public void setDate(int date) { + setUTCDate(date); + } + public void setHours(int hrs) { + setUTCHours(hrs); + } + public void setHours(int hrs, int minutes) { + setUTCHours(hrs, minutes); + } + public void setHours(int hrs, int minutes, int seconds) { + setUTCHours(hrs, minutes, seconds); + } + public void setHours(int hrs, int minutes, int seconds, int ms) { + setUTCHours(hrs, minutes, seconds, ms); + } + public void setMinutes(int minutes) { + setUTCMinutes(minutes); + } + public void setMinutes(int minutes, int seconds) { + setUTCMinutes(minutes, seconds); + } + public void setMinutes(int minutes, int seconds, int ms) { + setUTCMinutes(minutes, seconds, ms); + } + public void setSeconds(int seconds) { + setUTCSeconds(seconds); + } + public void setSeconds(int seconds, int ms) { + setUTCSeconds(seconds, ms); + } + public void setMilliseconds(int ms) { + setUTCMilliseconds(ms); + } - public static long UTC(int year, int month, int date, - int hrs, int min, int sec) { - return Date.UTC(year - 1900, month, date, hrs, min, sec); + // Milliseconds since Jan 1, 1970, 00:00:00.000 GMT + public void setTime(long dateMilliSecond) { + instant = Instant.ofEpochMilli(dateMilliSecond); + initZonedLocalDateTime(); + } + public int getTimezoneOffset() { + int seconds = ZoneId.systemDefault().getRules().getOffset(instant).getTotalSeconds(); + return -seconds/60; } - private static String createDateTimeFromPattern (int year, int month, int date, int hrs, int min, int second, int secondMilli, String... tz) { + public static long parse(String value, String format) { + try { + DateFormat dateFormat = new SimpleDateFormat(format); + return dateFormat.parse(value).getTime(); + } catch (Exception e) { + return -1; + } + } + public static long parse(String value) { + DateTimeFormatter isoDateFormatter = DateTimeFormatter.ofPattern( + "yyyy-MM-dd[[ ]['T']HH:mm[:ss[.SSS]][ ][XXX][Z][z][VV][O]]").withZone(ZoneId.systemDefault()); + try { + TemporalAccessor accessor = isoDateFormatter.parseBest(value, + ZonedDateTime::from, + LocalDateTime::from, + LocalDate::from); + Instant instant = Instant.from(accessor); + return Instant.EPOCH.until(instant, ChronoUnit.MILLIS); + } catch (Exception e) { + return -1; + } + } + private static String createDateTimeFromPattern(int year, int month, int date, int hrs, int min, int second, int secondMilli, String... tz) { String yearStr = String.format("%04d", year); - if (yearStr.substring(0, 2).equals("00")) yearStr = "20" + yearStr.substring(2,4); + yearStr = yearStr.substring(0, 2).equals("00") ? year < 70 ? "20" + yearStr.substring(2,4) : "19" + yearStr.substring(2,4) : yearStr; String monthStr = String.format("%02d", month); String dateStr = String.format("%02d", date); String hrsStr = String.format("%02d", hrs); String minStr = String.format("%02d", min); String secondStr = String.format("%02d", second); + String secondMilliStr = String.format("%03d", secondMilli); String tzStr = tz.length > 0 ? Arrays.stream(tz).findFirst().get() : "Z"; - return String.format(patternDefault, yearStr, monthStr, dateStr, hrsStr, minStr, secondStr, secondMilli, tzStr); + return String.format(patternDefault, yearStr, monthStr, dateStr, hrsStr, minStr, secondStr, secondMilliStr, tzStr); + } + + private void parseInstant(String s) { + try{ + if (s.length() > 0 && Character.isDigit(s.charAt(0))) { + // assuming UTC instant "2007-12-03T10:15:30.00Z" + instant = Instant.parse(s); + } + else { + // assuming RFC-1123 value "Tue, 3 Jun 2008 11:05:30 GMT-02:00" + instant = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(s)); + } + initZonedLocalDateTime(); + } catch (final DateTimeParseException ex) { + final ConversionException exception = new ConversionException("Cannot parse value [" + s + "] as instant", ex); + throw exception; + } } } diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java index 1e71795f97..3c803f1eff 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java @@ -658,28 +658,5 @@ public class TbUtils { } return true; } - - public static long parse(String value, String format) { - try { - DateFormat dateFormat = new SimpleDateFormat(format); - return dateFormat.parse(value).getTime(); - } catch (Exception e) { - return -1; - } - } - public static long parse(String value) { - DateTimeFormatter isoDateFormatter = DateTimeFormatter.ofPattern( - "yyyy-MM-dd[[ ]['T']HH:mm[:ss[.SSS]][ ][XXX][Z][z][VV][O]]").withZone(ZoneId.systemDefault()); - try { - TemporalAccessor accessor = isoDateFormatter.parseBest(value, - ZonedDateTime::from, - LocalDateTime::from, - LocalDate::from); - Instant instant = Instant.from(accessor); - return Instant.EPOCH.until(instant, ChronoUnit.MILLIS); - } catch (Exception e) { - return -1; - } - } - } + diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index 5115940a6b..75027247b1 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -27,19 +27,10 @@ import org.junit.jupiter.api.Test; import org.mvel2.ConversionException; import org.thingsboard.common.util.JacksonUtil; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.chrono.IsoChronology; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.format.FormatStyle; +import java.time.format.DateTimeParseException; import java.util.ArrayList; -import java.util.Calendar; import java.util.List; import java.util.Locale; -import java.util.TimeZone; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; @@ -126,8 +117,8 @@ class TbDateTest { String tsStr = String.format(pattern, hrs); //Thu Feb 29 2024 14:35:42.987 GMT+0000 TbDate tbDate = new TbDate(tsStr); long ts = 1709217342987L; //Thu Feb 29 2024 14:35:42.987 GMT+0000 - Assert.assertEquals(ts, tbDate.millis()); - int offset = Calendar.getInstance().get(Calendar.ZONE_OFFSET); // for example 3600000 for GMT + 1 + Assert.assertEquals(ts, tbDate.parseSecondMilli()); + int offsetMin = tbDate.getTimezoneOffset(); // for example 3600000 for GMT + 1 String datePrefix = tsStr; //without time zone assertThat(tbDate.toISOString()) @@ -136,20 +127,29 @@ class TbDateTest { assertThat(executor.submit(tbDate::toISOString).get(30, TimeUnit.SECONDS)) .as("format in executor thread") .startsWith(datePrefix); - int offsetHrs = offset/1000/60/60; + int offsetHrs = offsetMin/60; String datePrefixLocal = String.format(pattern, (hrs - offsetHrs)); - assertThat(new TbDate(ts - offset).toISOString()) + long offsetMilli = offsetMin*60*1000; + assertThat(new TbDate(ts - offsetMilli).toISOString()) .as("new instance format in main thread") .startsWith(datePrefixLocal); - assertThat(executor.submit(() -> new TbDate(ts - offset).toISOString()).get(30, TimeUnit.SECONDS)) + assertThat(executor.submit(() -> new TbDate(ts - offsetMilli).toISOString()).get(30, TimeUnit.SECONDS)) .as("new instance format in executor thread") .startsWith(datePrefixLocal); } @Test void testToLocaleDateString() { - TbDate d = new TbDate(1693962245000L); - + String s = "09:15:30 PM, Sun 10/09/2022"; + String pattern = "hh:mm:ss a, EEE M/d/uuuu"; + TbDate d = new TbDate(s, pattern, Locale.US); + Assert.assertEquals("2022-10-09T18:15:30Z", d.toISOString()); + s = "09:15:30 пп, середа, 4 жовтня 2023 р."; + pattern = "hh:mm:ss a, EEEE, d MMMM y 'р.'"; + d = new TbDate(s, pattern, Locale.forLanguageTag("uk-UA")); + Assert.assertEquals("2023-10-04T18:15:30Z", d.toISOString()); + + d = new TbDate(1693962245000L); Assert.assertEquals("2023-09-06T01:04:05Z", d.toISOString()); // Depends on time zone, so we just check it works; @@ -309,18 +309,19 @@ class TbDateTest { Assert.assertEquals("2023-09-06T03:04:05Z", d.toISOString()); String stringDateRFC_1123 = "Sat, 3 Jun 2023 11:05:30 GMT"; d = new TbDate(stringDateRFC_1123); - Assert.assertEquals("2023-06-03T11:05:30Z", d.toISOString()); - stringDateRFC_1123 = "Thu, 29 Feb 2024 11:05:30 GMT"; + stringDateRFC_1123 = "Sat, 3 Jun 2023 11:05:30 +0400"; + d = new TbDate(stringDateRFC_1123); + Assert.assertEquals("2023-06-03T07:05:30Z", d.toISOString()); + stringDateRFC_1123 = "Thu, 29 Feb 2024 11:05:30 -03"; d = new TbDate(stringDateRFC_1123); - Assert.assertEquals("2024-02-29T11:05:30Z", d.toISOString()); + Assert.assertEquals("2024-02-29T14:05:30Z", d.toISOString()); String stringDateRFC_1123_error = "Tue, 3 Jun 2023 11:05:30 GMT"; - Exception exception = assertThrows(ConversionException.class, () -> { + Exception actual = assertThrows(ConversionException.class, () -> { new TbDate(stringDateRFC_1123_error); }); - String expectedMessage = "Cannot parse value [Tue, 3 Jun 2023 11:05:30 GMT] as instant"; - String actualMessage = exception.getMessage(); - assertTrue(actualMessage.contains(expectedMessage)); + String expectedMessage = "Cannot parse value"; + assertTrue(actual.getMessage().contains(expectedMessage)); } @Test @@ -329,6 +330,13 @@ class TbDateTest { TbDate d = new TbDate(stringDateUTC); Assert.assertEquals(1693962245345L, d.parseSecondMilli()); Assert.assertEquals(1693962245L, d.parseSecond()); + String stringDateStart = "1970-01-01T00:00:00Z"; + d = new TbDate(stringDateStart); + long actualMillis = TbDate.parse("1970-01-01 T00:00:00"); + Assert.assertEquals(d.getTimezoneOffset(), actualMillis/60/1000); + String pattern = "yyyy-MM-dd HH:mm:ss.SSS"; + String stringDate = "1995-12-04 00:12:00.000"; + Assert.assertNotEquals(-1L, TbDate.parse(stringDate, pattern)); } @Test @@ -351,25 +359,310 @@ class TbDateTest { Assert.assertEquals("2023-09-08T02:04:05.567Z", d.toISOString()); } - private static String toLocalString(TbDate d, Locale locale, ZoneId tz) { - LocalDateTime ldt = d.getLocalDateTime(); + @Test + void TestMethodGetAsDateUTC () { + TbDate dd = new TbDate(TbDate.UTC(1996, 2, 2, 3, 4, 5)); + Assert.assertEquals(823230245000L, dd.valueOf()); + dd = new TbDate(1996, 2, 2, 3, 4, 5); + Assert.assertEquals(823230245000L, dd.valueOf()); + TbDate beforeStartUTC = new TbDate(1969, 7, 20, 20, 17, 40); + Assert.assertEquals(-14182940000L, beforeStartUTC.getTime()); + + TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"+02:00"); + TbDate d2 = new TbDate(1975, 12, 31, 23,15,30, 567,"-02:00"); + + Assert.assertEquals(189292530567L, d1.getTime()); + Assert.assertEquals(189306930567L, d2.getTime()); + Assert.assertEquals(d1.getTimezoneOffset(), d2.getTimezoneOffset()); + + Assert.assertEquals(1975, d1.getUTCFullYear()); + Assert.assertEquals(1976, d2.getUTCFullYear()); + + Assert.assertEquals(12, d1.getUTCMonth()); + Assert.assertEquals(1, d2.getUTCMonth()); + + Assert.assertEquals(31, d1.getUTCDate()); + Assert.assertEquals(1, d2.getUTCDate()); + + Assert.assertEquals(3, d1.getUTCDay()); + Assert.assertEquals(4, d2.getUTCDay()); + + Assert.assertEquals(21, d1.getUTCHours()); + Assert.assertEquals(1, d2.getUTCHours()); + + Assert.assertEquals(15, d1.getUTCMinutes()); + Assert.assertEquals(15, d2.getUTCMinutes()); + + Assert.assertEquals(30, d1.getUTCSeconds()); + Assert.assertEquals(30, d2.getUTCSeconds()); + + Assert.assertEquals(567, d1.getUTCMilliseconds()); + Assert.assertEquals(567, d2.getUTCMilliseconds()); + } @Test + void TestMethodGetAsDateLocal () { + TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"+02:00"); + TbDate d2 = new TbDate(1975, 12, 31, 23,15,30, 567,"-02:00"); + TbDate dLocal1 = new TbDate(d1.parseSecondMilli()-d1.getTimezoneOffset()*60*1000); + TbDate dLocal2 = new TbDate(d2.parseSecondMilli()-d2.getTimezoneOffset()*60*1000); + + Assert.assertEquals(dLocal1.getUTCFullYear(), d1.getFullYear()); + Assert.assertEquals(dLocal2.getUTCFullYear(), d2.getFullYear()); + + Assert.assertEquals(dLocal1.getUTCMonth(), d1.getMonth()); + Assert.assertEquals(dLocal2.getUTCMonth(), d2.getMonth()); + + Assert.assertEquals(dLocal1.getUTCDate(), d1.getDate()); + Assert.assertEquals(dLocal2.getUTCDate(), d2.getDate()); + + Assert.assertEquals(dLocal1.getUTCDay(), d1.getDay()); + Assert.assertEquals(dLocal1.getUTCDay(), d2.getDay()); + + Assert.assertEquals(dLocal1.getUTCHours(), d1.getHours()); + Assert.assertEquals(dLocal2.getUTCHours(), d2.getHours()); + + Assert.assertEquals(dLocal1.getUTCMinutes(), d1.getMinutes()); + Assert.assertEquals(dLocal2.getUTCMinutes(), d2.getMinutes()); -// new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale) + Assert.assertEquals(dLocal1.getUTCSeconds(), d1.getSeconds()); + Assert.assertEquals(dLocal2.getUTCSeconds(), d2.getSeconds()); - String formatPattern = - DateTimeFormatterBuilder.getLocalizedDateTimePattern( - FormatStyle.SHORT, - FormatStyle.MEDIUM, - IsoChronology.INSTANCE, - locale); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatPattern, locale); - return formatter.format(ldt); + Assert.assertEquals(dLocal1.getUTCMilliseconds(), d1.getMilliseconds()); + Assert.assertEquals(dLocal2.getUTCMilliseconds(), d2.getMilliseconds()); } - private static String toLocalString2(TbDate d, Locale locale, ZoneId tz) { - DateFormat dateFormat = SimpleDateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, locale); - dateFormat.setTimeZone(TimeZone.getTimeZone(tz)); - return dateFormat.format(d); + @Test + void TestMethodSetUTCFullYearMonthDate() { + TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"-03:00"); + Assert.assertEquals(1976, d1.getUTCFullYear()); + Assert.assertEquals(4, d1.getUTCDay()); + d1.setUTCFullYear(1969); + Assert.assertEquals(1969, d1.getUTCFullYear()); + d1.setUTCFullYear(1975); + Assert.assertEquals(1975, d1.getUTCFullYear()); + Assert.assertEquals(3, d1.getUTCDay()); + d1.setUTCFullYear(1977, 4); + Assert.assertEquals(1977, d1.getUTCFullYear()); + Assert.assertEquals(4, d1.getUTCMonth()); + Assert.assertEquals(5, d1.getUTCDay()); + d1.setUTCFullYear(2023, 2, 24); + Assert.assertEquals(2023, d1.getUTCFullYear()); + Assert.assertEquals(2, d1.getUTCMonth()); + Assert.assertEquals(24, d1.getUTCDate()); + Assert.assertEquals(5, d1.getUTCDay()); + + d1.setUTCMonth(11); + Assert.assertEquals(11, d1.getUTCMonth()); + Assert.assertEquals(5, d1.getUTCDay()); + d1.setUTCMonth(2, 28); + Assert.assertEquals(2, d1.getUTCMonth()); + Assert.assertEquals(28, d1.getUTCDate()); + Assert.assertEquals(2, d1.getUTCDay()); + + d1.setUTCDate(11); + Assert.assertEquals(11, d1.getUTCDate()); + Assert.assertEquals(6, d1.getUTCDay()); + } + + @Test + void TestMethodSetUTCHoursMinutesSecondsMilliSec() { + TbDate d1 = new TbDate(1975, 12, 31, 23, 15, 30, 567, "-03:00"); + Assert.assertEquals(2, d1.getUTCHours()); + Assert.assertEquals(4, d1.getUTCDay()); + + d1.setUTCHours(5); + Assert.assertEquals(5, d1.getUTCHours()); + Assert.assertEquals(4, d1.getUTCDay()); + d1.setUTCHours(12, 45); + Assert.assertEquals(12, d1.getUTCHours()); + Assert.assertEquals(45, d1.getUTCMinutes()); + Assert.assertEquals(4, d1.getUTCDay()); + d1.setUTCHours(0, 12, 59); + Assert.assertEquals(0, d1.getUTCHours()); + Assert.assertEquals(12, d1.getUTCMinutes()); + Assert.assertEquals(59, d1.getUTCSeconds()); + Assert.assertEquals(4, d1.getUTCDay()); + d1.setUTCHours(4, 58, 2, 456); + Assert.assertEquals(4, d1.getUTCHours()); + Assert.assertEquals(58, d1.getUTCMinutes()); + Assert.assertEquals(2, d1.getUTCSeconds()); + Assert.assertEquals(456, d1.getUTCMilliseconds()); + Assert.assertEquals(4, d1.getUTCDay()); + + d1.setUTCMinutes(5); + Assert.assertEquals(5, d1.getUTCMinutes()); + Assert.assertEquals(4, d1.getUTCDay()); + d1.setUTCMinutes(15, 32); + Assert.assertEquals(15, d1.getUTCMinutes()); + Assert.assertEquals(32, d1.getUTCSeconds()); + Assert.assertEquals(4, d1.getUTCDay()); + d1.setUTCMinutes(10, 42, 321); + Assert.assertEquals(10, d1.getUTCMinutes()); + Assert.assertEquals(42, d1.getUTCSeconds()); + Assert.assertEquals(321, d1.getUTCMilliseconds()); + Assert.assertEquals(4, d1.getUTCDay()); + + d1.setUTCSeconds(5); + Assert.assertEquals(5, d1.getUTCSeconds()); + Assert.assertEquals(4, d1.getUTCDay()); + d1.setUTCSeconds(15, 32); + Assert.assertEquals(15, d1.getUTCSeconds()); + Assert.assertEquals(32, d1.getUTCMilliseconds()); + Assert.assertEquals(4, d1.getUTCDay()); + + d1.setUTCMilliseconds(5); + Assert.assertEquals(5, d1.getUTCMilliseconds()); + Assert.assertEquals(4, d1.getUTCDay()); + } + @Test + void TestMethodSetTome() { + TbDate d1 = new TbDate(1975, 12, 31, 23, 15, 30, 567, "-03:00"); + long dateMilliSecond = d1.getTime(); + int fiveMinutesInMillis = 5 * 60 * 1000; + Assert.assertEquals(15, d1.getUTCMinutes()); + Assert.assertEquals(4, d1.getUTCDay()); + d1.setTime(dateMilliSecond + fiveMinutesInMillis); + Assert.assertEquals(20, d1.getUTCMinutes()); + Assert.assertEquals(4, d1.getUTCDay()); + d1.setTime(-378682769433L); + Assert.assertEquals(1958, d1.getUTCFullYear()); + Assert.assertEquals(1, d1.getUTCMonth()); + Assert.assertEquals(1, d1.getUTCDate()); + Assert.assertEquals(2, d1.getUTCHours()); + Assert.assertEquals(20, d1.getUTCMinutes()); + Assert.assertEquals(30, d1.getUTCSeconds()); + Assert.assertEquals(567, d1.getUTCMilliseconds()); + Assert.assertEquals(3, d1.getUTCDay()); } + @Test + void TestMethodSeFullYearMonthDate() { + TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"-03:00"); + Assert.assertEquals(1976, d1.getFullYear()); + Assert.assertEquals(1, d1.getMonth()); + Assert.assertEquals(1, d1.getDate()); + Assert.assertEquals(4, d1.getUTCDay()); + d1.setFullYear(1969); + Assert.assertEquals(1969, d1.getFullYear()); + d1.setUTCFullYear(1975); + Assert.assertEquals(1975, d1.getFullYear()); + Assert.assertEquals(3, d1.getUTCDay()); + d1.setFullYear(1977, 4); + Assert.assertEquals(1977, d1.getFullYear()); + Assert.assertEquals(4, d1.getMonth()); + Assert.assertEquals(5, d1.getDay()); + d1.setFullYear(2023, 2, 24); + Assert.assertEquals(2023, d1.getFullYear()); + Assert.assertEquals(2, d1.getMonth()); + Assert.assertEquals(24, d1.getDate()); + Assert.assertEquals(5, d1.getDay()); + + d1.setMonth(11); + Assert.assertEquals(11, d1.getMonth()); + Assert.assertEquals(5, d1.getDay()); + d1.setMonth(2, 28); + Assert.assertEquals(2, d1.getMonth()); + Assert.assertEquals(28, d1.getDate()); + Assert.assertEquals(2, d1.getDay()); + + d1.setDate(11); + Assert.assertEquals(11, d1.getDate()); + Assert.assertEquals(6, d1.getDay()); + } + @Test + void TestMethodSeHoursMinutesSecondsMilliSec() { + TbDate d1 = new TbDate(1975, 12, 31, 23, 15, 30, 567, "-03:00"); + Assert.assertEquals(5, d1.getHours()); + Assert.assertEquals(4, d1.getDay()); + + d1.setHours(5); + Assert.assertEquals(8, d1.getHours()); + Assert.assertEquals(4, d1.getDay()); + d1.setHours(23, 45); + Assert.assertEquals(1, d1.getMonth()); + Assert.assertEquals(2, d1.getDate()); + Assert.assertEquals(2, d1.getHours()); + Assert.assertEquals(45, d1.getMinutes()); + Assert.assertEquals(5, d1.getDay()); + d1.setUTCHours(0, 12, 59); + Assert.assertEquals(3, d1.getHours()); + Assert.assertEquals(12, d1.getMinutes()); + Assert.assertEquals(59, d1.getSeconds()); + Assert.assertEquals(4, d1.getDay()); + d1.setUTCHours(4, 58, 2, 456); + Assert.assertEquals(7, d1.getHours()); + Assert.assertEquals(58, d1.getMinutes()); + Assert.assertEquals(2, d1.getSeconds()); + Assert.assertEquals(456, d1.getMilliseconds()); + Assert.assertEquals(4, d1.getDay()); + + d1.setMinutes(5); + Assert.assertEquals(5, d1.getMinutes()); + Assert.assertEquals(4, d1.getUTCDay()); + d1.setMinutes(15, 32); + Assert.assertEquals(15, d1.getMinutes()); + Assert.assertEquals(32, d1.getSeconds()); + Assert.assertEquals(4, d1.getDay()); + d1.setMinutes(10, 42, 321); + Assert.assertEquals(10, d1.getMinutes()); + Assert.assertEquals(42, d1.getSeconds()); + Assert.assertEquals(321, d1.getMilliseconds()); + Assert.assertEquals(4, d1.getDay()); + + d1.setSeconds(5); + Assert.assertEquals(5, d1.getSeconds()); + Assert.assertEquals(4, d1.getDay()); + d1.setSeconds(15, 32); + Assert.assertEquals(15, d1.getSeconds()); + Assert.assertEquals(32, d1.getMilliseconds()); + Assert.assertEquals(4, d1.getDay()); + + d1.setMilliseconds(5); + Assert.assertEquals(5, d1.getMilliseconds()); + Assert.assertEquals(4, d1.getDay()); + } + @Test + public void toStringAsJs() { + TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"-14:00"); + Assert.assertEquals("1976 Jan 1, Thu 16:15:30 Eastern European Time", d1.toString()); + Assert.assertEquals("1976 Jan 1, Thu 16:15:30 Eastern European Time", d1.toString("GMT")); + Assert.assertEquals("1976 Jan 1, Thu 16:15:30 Eastern European Time", d1.toString("UTC")); + Assert.assertEquals("Thursday, January 1, 1976 at 4:15:30 PM Eastern European Standard Time", d1.toString("en-US")); + Assert.assertEquals("1976 Jan 1, Thu 16:15:30", d1.toUTCString()); + Assert.assertEquals("четвер, 1 січня 1976 р., 16:15:30", d1.toUTCString("uk-UA")); + Assert.assertEquals("Thursday, January 1, 1976, 4:15:30 PM", d1.toUTCString("en-US")); + Assert.assertEquals("четвер, 1 січня 1976 р., 16:15:30", d1.toUTCString("uk-UA")); + Assert.assertEquals("Thursday, January 1, 1976, 4:15:30 PM", d1.toUTCString("en-US")); + Assert.assertEquals("1976 Jan 1, Thu", d1.toDateString()); + Assert.assertEquals("четвер, 1 січня 1976 р.", d1.toDateString("uk-UA")); + Assert.assertEquals("Thursday, January 1, 1976", d1.toDateString("en-US")); + Assert.assertEquals("16:15:30 Eastern European Time", d1.toTimeString()); + Assert.assertEquals("16:15:30 за східноєвропейським стандартним часом", d1.toTimeString("uk-UA")); + Assert.assertEquals("4:15:30 PM Eastern European Standard Time", d1.toTimeString("en-US")); + Assert.assertEquals("1976-01-01T13:15:30.567Z", d1.toJSON()); + } + + /** + * Date.UTC(0) + * -2208988800000 + * > "Mon, 01 Jan 1900 00:00:00 GMT" + * new Date(Date.UTC(0, 0, 0, 0, 0, 0)); + * "Sun, 31 Dec 1899 00:00:00 GMT" + */ + @Test + public void toUTC() { + Assert.assertEquals(-2209075200000L, TbDate.UTC(0)); + Assert.assertEquals("1899-12-31T00:00:00Z", new TbDate(TbDate.UTC(0)).toJSON()); + Assert.assertEquals("1996-02-02T03:04:05Z", new TbDate(TbDate.UTC(96, 2, 2, 3, 4, 5)).toJSON()); + Assert.assertEquals("2022-12-31T03:04:05.678Z", new TbDate(TbDate.UTC(22, 0, 0, 3, 4, 5, 678)).toJSON()); + Assert.assertEquals("0903-12-31T03:04:05.678Z", new TbDate(TbDate.UTC(903, 0, 0, 3, 4, 5, 678)).toJSON()); + Assert.assertEquals("1958-12-31T03:04:05.678Z", new TbDate(TbDate.UTC(1958, 0, 0, 3, 4, 5, 678)).toJSON()); + Assert.assertEquals("2032-04-05T03:04:05.678Z", new TbDate(TbDate.UTC(2032, 4, 5, 3, 4, 5, 678)).toJSON()); + Assert.assertEquals("2024-02-29T03:04:05.678Z", new TbDate(TbDate.UTC(2024, 2, 29, 3, 4, 5, 678)).toJSON()); + Exception actual = assertThrows(DateTimeParseException.class, () -> { + TbDate.UTC(2023, 2, 29, 3, 4, 5, 678); + }); + String expectedMessage = "could not be parsed"; + assertTrue(actual.getMessage().contains(expectedMessage)); + } } + From 987bcf476c9fe73c855a46880ef90947de3baeb9 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 11 Oct 2023 10:37:03 +0300 Subject: [PATCH 037/209] Add syncManager for EntityStateListener because of actor creation problem --- .../controller/DashboardController.java | 8 ++- .../edge/DefaultEdgeNotificationService.java | 3 -- .../edge/EdgeEventSourcingListener.java | 47 +++++++++--------- .../entitiy/EntityStateSourcingListener.java | 49 +++++++++++++++---- .../tenant/DefaultTbTenantService.java | 14 ++++++ .../service/entitiy/user/TbUserService.java | 1 + .../service/install/InstallScripts.java | 2 +- .../rule/DefaultTbRuleChainService.java | 24 +-------- .../oauth2/AbstractOAuth2ClientMapper.java | 3 +- .../dao/entity/EntityStateSyncManager.java | 23 +++++++++ .../dao/asset/AssetProfileServiceImpl.java | 10 ++-- .../server/dao/asset/BaseAssetService.java | 10 ++-- .../dao/dashboard/DashboardServiceImpl.java | 12 ++--- .../dao/device/DeviceProfileServiceImpl.java | 10 ++-- .../entity/DefaultEntityStateSyncManager.java | 34 +++++++++++++ .../dao/entityview/EntityViewServiceImpl.java | 10 ++-- .../dao/eventsourcing/DeleteEntityEvent.java | 1 + .../server/dao/rule/BaseRuleChainService.java | 48 +++++++++--------- 18 files changed, 185 insertions(+), 124 deletions(-) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityStateSyncManager.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityStateSyncManager.java diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index afc737ab88..7a1bb977ec 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -527,7 +527,7 @@ public class DashboardController extends BaseController { } Tenant tenant = tenantService.findTenantById(getTenantId()); JsonNode additionalInfo = tenant.getAdditionalInfo(); - if (additionalInfo == null || !(additionalInfo instanceof ObjectNode)) { + if (!(additionalInfo instanceof ObjectNode)) { additionalInfo = JacksonUtil.newObjectNode(); } if (homeDashboardInfo.getDashboardId() != null) { @@ -553,8 +553,7 @@ public class DashboardController extends BaseController { } return new HomeDashboardInfo(dashboardId, hideDashboardToolbar); } - } catch (Exception e) { - } + } catch (Exception ignored) {} return null; } @@ -570,8 +569,7 @@ public class DashboardController extends BaseController { } return new HomeDashboard(dashboard, hideDashboardToolbar); } - } catch (Exception e) { - } + } catch (Exception ignored) {} return null; } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java index 69852e6c05..7106b8c94c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java @@ -239,7 +239,4 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { log.error("[{}] Can't push to edge updates, edgeNotificationMsg [{}]", tenantId, edgeNotificationMsg, throwable); callback.onFailure(throwable); } - } - - diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index aacacb711e..b077908bba 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -21,16 +21,12 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackageInfo; -import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -79,7 +75,7 @@ public class EdgeEventSourcingListener { return; } try { - if (!isValidEdgeEventEntity(event.getEntity())) { + if (!isValidEdgeEventEntity(event)) { return; } log.trace("[{}] SaveEntityEvent called: {}", event.getTenantId(), event); @@ -150,24 +146,29 @@ public class EdgeEventSourcingListener { } } - private boolean isValidEdgeEventEntity(Object entity) { - if (entity instanceof OtaPackageInfo) { - OtaPackageInfo otaPackageInfo = (OtaPackageInfo) entity; - return otaPackageInfo.hasUrl() || otaPackageInfo.isHasData(); - } else if (entity instanceof RuleChain) { - RuleChain ruleChain = (RuleChain) entity; - return RuleChainType.EDGE.equals(ruleChain.getType()); - } else if (entity instanceof User) { - User user = (User) entity; - return !Authority.SYS_ADMIN.equals(user.getAuthority()); - } else if (entity instanceof AlarmApiCallResult) { - AlarmApiCallResult alarmApiCallResult = (AlarmApiCallResult) entity; - return alarmApiCallResult.isModified(); - } else if (entity instanceof Edge || - entity instanceof ApiUsageState || - entity instanceof TbResource || - entity instanceof EdgeEvent) { - return false; + private boolean isValidEdgeEventEntity(SaveEntityEvent event) { + switch (event.getEntityId().getEntityType()) { + case RULE_CHAIN: + RuleChain ruleChain = (RuleChain) event.getEntity(); + return RuleChainType.EDGE.equals(ruleChain.getType()); + case USER: + User user = (User) event.getEntity(); + return !Authority.SYS_ADMIN.equals(user.getAuthority()); + case OTA_PACKAGE: + OtaPackageInfo otaPackageInfo = (OtaPackageInfo) event.getEntity(); + return otaPackageInfo.hasUrl() || otaPackageInfo.isHasData(); + case ALARM: + if (event.getEntity() instanceof AlarmApiCallResult) { + AlarmApiCallResult alarmApiCallResult = (AlarmApiCallResult) event.getEntity(); + return alarmApiCallResult.isModified(); + } + break; + case TENANT: + return !event.getAdded(); + case API_USAGE_STATE: + case TB_RESOURCE: + case EDGE: + return false; } // Default: If the entity doesn't match any of the conditions, consider it as valid. return true; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java index 60a9f28c9a..250d88757a 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.entitiy; +import com.fasterxml.jackson.core.type.TypeReference; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -35,19 +36,25 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.entity.EntityStateSyncManager; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; +import org.thingsboard.server.dao.tenant.TenantService; import javax.annotation.PostConstruct; +import java.util.Set; @Component @RequiredArgsConstructor @@ -55,6 +62,8 @@ import javax.annotation.PostConstruct; public class EntityStateSourcingListener { private final TbClusterService tbClusterService; + private final TenantService tenantService; + private final EntityStateSyncManager entityStateSyncManager; @PostConstruct public void init() { @@ -63,6 +72,9 @@ public class EntityStateSourcingListener { @TransactionalEventListener(fallbackExecution = true) public void handleEvent(SaveEntityEvent event) { + if (entityStateSyncManager.isSync()) { + return; + } log.trace("[{}] SaveEntityEvent called: {}", event.getTenantId(), event); TenantId tenantId = event.getTenantId(); EntityId entityId = event.getEntityId(); @@ -77,6 +89,12 @@ public class EntityStateSourcingListener { case NOTIFICATION_RULE: tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent); break; + case RULE_CHAIN: + RuleChain ruleChain = (RuleChain) event.getEntity(); + if (RuleChainType.CORE.equals(ruleChain.getType())) { + tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), lifecycleEvent); + } + break; case TENANT: Tenant tenant = (Tenant) event.getEntity(); onTenantUpdate(tenant, lifecycleEvent); @@ -124,6 +142,17 @@ public class EntityStateSourcingListener { case NOTIFICATION_RULE: tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); break; + case RULE_CHAIN: + RuleChain ruleChain = (RuleChain) event.getEntity(); + Set referencingRuleChainIds = JacksonUtil.fromString(event.getBody(), new TypeReference<>() {}); + if (RuleChainType.CORE.equals(ruleChain.getType())) { + if (referencingRuleChainIds != null) { + referencingRuleChainIds.forEach(referencingRuleChainId -> + tbClusterService.broadcastEntityStateChangeEvent(tenantId, referencingRuleChainId, ComponentLifecycleEvent.UPDATED)); + } + tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChain.getId(), ComponentLifecycleEvent.DELETED); + } + break; case TENANT: Tenant tenant = (Tenant) event.getEntity(); onTenantDeleted(tenant); @@ -154,11 +183,6 @@ public class EntityStateSourcingListener { } } - private void onDeviceProfileDelete(TenantId tenantId, EntityId entityId, DeviceProfile deviceProfile) { - tbClusterService.onDeviceProfileDelete(deviceProfile, null); - tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); - } - @TransactionalEventListener(fallbackExecution = true) public void handleEvent(ActionEntityEvent event) { log.trace("[{}] ActionEntityEvent called: {}", event.getTenantId(), event); @@ -178,6 +202,11 @@ public class EntityStateSourcingListener { tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), lifecycleEvent); } + private void onTenantDeleted(Tenant tenant) { + tbClusterService.onTenantDelete(tenant, null); + tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), ComponentLifecycleEvent.DELETED); + } + private void onTenantProfileUpdate(TenantProfile tenantProfile, ComponentLifecycleEvent lifecycleEvent) { tbClusterService.onTenantProfileChange(tenantProfile, null); tbClusterService.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, tenantProfile.getId(), lifecycleEvent); @@ -198,6 +227,11 @@ public class EntityStateSourcingListener { return null; } + private void onDeviceProfileDelete(TenantId tenantId, EntityId entityId, DeviceProfile deviceProfile) { + tbClusterService.onDeviceProfileDelete(deviceProfile, null); + tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); + } + private void onDeviceUpdate(Object entity, Object oldEntity) { Device device = (Device) entity; Device oldDevice = null; @@ -207,11 +241,6 @@ public class EntityStateSourcingListener { tbClusterService.onDeviceUpdated(device, oldDevice); } - private void onTenantDeleted(Tenant tenant) { - tbClusterService.onTenantDelete(tenant, null); - tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), ComponentLifecycleEvent.DELETED); - } - private void handleEdgeEvent(TenantId tenantId, EntityId entityId, Object entity, ComponentLifecycleEvent lifecycleEvent) { if (entity instanceof Edge) { tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java index 8b1ffcfcf0..b9c8f29f82 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java @@ -16,10 +16,13 @@ package org.thingsboard.server.service.entitiy.tenant; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.entity.EntityStateSyncManager; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; @@ -43,12 +46,18 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T private final TbQueueService tbQueueService; private final TenantProfileService tenantProfileService; private final EntitiesVersionControlService versionControlService; + private final ApplicationEventPublisher eventPublisher; + private final EntityStateSyncManager entityStateSyncManager; @Override public Tenant save(Tenant tenant) throws Exception { boolean created = tenant.getId() == null; Tenant oldTenant = !created ? tenantService.findTenantById(tenant.getId()) : null; + if (created) { + entityStateSyncManager.getSync().set(true); + } + Tenant savedTenant = checkNotNull(tenantService.saveTenant(tenant)); if (created) { installScripts.createDefaultRuleChains(savedTenant.getId()); @@ -56,6 +65,11 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T } tenantProfileCache.evict(savedTenant.getId()); + if (created) { + entityStateSyncManager.getSync().remove(); + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(TenantId.SYS_TENANT_ID).entityId(savedTenant.getId()).entity(savedTenant).added(true).build()); + } + TenantProfile oldTenantProfile = oldTenant != null ? tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, oldTenant.getTenantProfileId()) : null; TenantProfile newTenantProfile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, savedTenant.getTenantProfileId()); tbQueueService.updateQueuesByTenants(Collections.singletonList(savedTenant.getTenantId()), newTenantProfile, oldTenantProfile); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java index 0764425116..08929d9ac9 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId; import javax.servlet.http.HttpServletRequest; public interface TbUserService { + User save(TenantId tenantId, CustomerId customerId, User tbUser, boolean sendActivationMail, HttpServletRequest request, User user) throws ThingsboardException; void delete(TenantId tenantId, CustomerId customerId, User user, User responsibleUser) throws ThingsboardException; diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 7f0b70cb27..ec0d6fd268 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -171,7 +171,7 @@ public class InstallScripts { return createRuleChainFromFile(tenantId, getDeviceProfileDefaultRuleChainTemplateFilePath(), ruleChainName); } - public RuleChain createRuleChainFromFile(TenantId tenantId, Path templateFilePath, String newRuleChainName) throws IOException { + public RuleChain createRuleChainFromFile(TenantId tenantId, Path templateFilePath, String newRuleChainName) { JsonNode ruleChainJson = JacksonUtil.toJsonNode(templateFilePath.toFile()); RuleChain ruleChain = JacksonUtil.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class); RuleChainMetaData ruleChainMetaData = JacksonUtil.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class); diff --git a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java index b5d303a952..830c223d07 100644 --- a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java +++ b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java @@ -179,10 +179,6 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement try { RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain)); autoCommit(user, savedRuleChain.getId()); - if (RuleChainType.CORE.equals(savedRuleChain.getType())) { - tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedRuleChain.getId(), - actionType.equals(ActionType.ADDED) ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); - } logEntityActionService.logEntityAction(tenantId, savedRuleChain.getId(), savedRuleChain, null, actionType, user); return savedRuleChain; } catch (Exception e) { @@ -204,13 +200,6 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement referencingRuleChainIds.remove(ruleChain.getId()); - if (RuleChainType.CORE.equals(ruleChain.getType())) { - referencingRuleChainIds.forEach(referencingRuleChainId -> - tbClusterService.broadcastEntityStateChangeEvent(tenantId, referencingRuleChainId, ComponentLifecycleEvent.UPDATED)); - - tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChain.getId(), ComponentLifecycleEvent.DELETED); - } - logEntityActionService.logEntityAction(tenantId, ruleChainId, ruleChain, null, ActionType.DELETED, user, ruleChainId.toString()); } catch (Exception e) { logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.DELETED, @@ -224,7 +213,6 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement try { RuleChain savedRuleChain = installScripts.createDefaultRuleChain(tenantId, request.getName()); autoCommit(user, savedRuleChain.getId()); - tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedRuleChain.getId(), ComponentLifecycleEvent.CREATED); logEntityActionService.logEntityAction(tenantId, savedRuleChain.getId(), savedRuleChain, ActionType.ADDED, user); return savedRuleChain; } catch (Exception e) { @@ -243,18 +231,11 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement RuleChain previousRootRuleChain = ruleChainService.getRootTenantRuleChain(tenantId); if (ruleChainService.setRootRuleChain(tenantId, ruleChainId)) { if (previousRootRuleChain != null) { - RuleChainId previousRootRuleChainId = previousRootRuleChain.getId(); - previousRootRuleChain = ruleChainService.findRuleChainById(tenantId, previousRootRuleChainId); - - tbClusterService.broadcastEntityStateChangeEvent(tenantId, previousRootRuleChainId, - ComponentLifecycleEvent.UPDATED); - logEntityActionService.logEntityAction(tenantId, previousRootRuleChainId, previousRootRuleChain, + previousRootRuleChain = ruleChainService.findRuleChainById(tenantId, previousRootRuleChain.getId()); + logEntityActionService.logEntityAction(tenantId, previousRootRuleChain.getId(), previousRootRuleChain, ActionType.UPDATED, user); } ruleChain = ruleChainService.findRuleChainById(tenantId, ruleChainId); - - tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChainId, - ComponentLifecycleEvent.UPDATED); logEntityActionService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); } return ruleChain; @@ -293,7 +274,6 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaDataId)); if (RuleChainType.CORE.equals(ruleChain.getType())) { - tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChainId, ComponentLifecycleEvent.UPDATED); updatedRuleChains.forEach(updatedRuleChain -> tbClusterService.broadcastEntityStateChangeEvent(tenantId, updatedRuleChain.getId(), ComponentLifecycleEvent.UPDATED)); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index c2288a6f62..3732f13b08 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -24,7 +24,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.StringUtils; @@ -88,7 +87,7 @@ public abstract class AbstractOAuth2ClientMapper { @Value("${edges.enabled}") @Getter private boolean edgesEnabled; - + private final Lock userCreationLock = new ReentrantLock(); protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2Registration registration) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityStateSyncManager.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityStateSyncManager.java new file mode 100644 index 0000000000..69a4004498 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityStateSyncManager.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.entity; + +public interface EntityStateSyncManager { + + ThreadLocal getSync(); + + boolean isSync(); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java index 210810f68c..c785f88078 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java @@ -111,17 +111,13 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService sync = new ThreadLocal<>(); + + @Override + public boolean isSync() { + Boolean sync = this.sync.get(); + return sync != null && sync; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java index 4335fa5f1c..ceecce8b1d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java @@ -101,17 +101,13 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService { private final EntityId entityId; private final EdgeId edgeId; private final T entity; + private final String body; @Builder.Default private final long ts = System.currentTimeMillis(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 5e6b1e8009..c8024d31a2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentClusteringMode; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -73,6 +74,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -135,6 +137,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } else if (!previousRootRuleChain.getId().equals(ruleChain.getId())) { previousRootRuleChain.setRoot(false); ruleChainDao.save(tenantId, previousRootRuleChain); + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId) + .entityId(previousRootRuleChain.getId()).entity(previousRootRuleChain).added(false).build()); setRootAndSave(tenantId, ruleChain); return true; } @@ -145,6 +149,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC private void setRootAndSave(TenantId tenantId, RuleChain ruleChain) { ruleChain.setRoot(true); ruleChainDao.save(tenantId, ruleChain); + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entityId(ruleChain.getId()).entity(ruleChain).added(false).build()); } @Override @@ -290,11 +295,11 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC List nodeRelations = getRuleNodeRelations(tenantId, node.getId()); for (EntityRelation nodeRelation : nodeRelations) { String type = nodeRelation.getType(); - if (nodeRelation.getTo().getEntityType() == EntityType.RULE_NODE) { + if (EntityType.RULE_NODE.equals(nodeRelation.getTo().getEntityType())) { RuleNodeId toNodeId = new RuleNodeId(nodeRelation.getTo().getId()); int toIndex = ruleNodeIndexMap.get(toNodeId); ruleChainMetaData.addConnectionInfo(fromIndex, toIndex, type); - } else if (nodeRelation.getTo().getEntityType() == EntityType.RULE_CHAIN) { + } else if (EntityType.RULE_CHAIN.equals(nodeRelation.getTo().getEntityType())) { log.warn("[{}][{}] Unsupported node relation: {}", tenantId, ruleChainId, nodeRelation.getTo()); } } @@ -410,29 +415,24 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC public void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId) { Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request."); RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId()); + + List referencingRuleNodes = getReferencingRuleChainNodes(tenantId, ruleChainId); + Set referencingRuleChainIds = referencingRuleNodes.stream().map(RuleNode::getRuleChainId).collect(Collectors.toSet()); + if (ruleChain != null) { if (ruleChain.isRoot()) { throw new DataValidationException("Deletion of Root Tenant Rule Chain is prohibited!"); } if (RuleChainType.EDGE.equals(ruleChain.getType())) { - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - do { - pageData = edgeService.findEdgesByTenantIdAndEntityId(tenantId, ruleChainId, pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - for (Edge edge : pageData.getData()) { - if (edge.getRootRuleChainId() != null && edge.getRootRuleChainId().equals(ruleChainId)) { - throw new DataValidationException("Can't delete rule chain that is root for edge [" + edge.getName() + "]. Please assign another root rule chain first to the edge!"); - } - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } + for (Edge edge : new PageDataIterable<>(link -> edgeService.findEdgesByTenantIdAndEntityId(tenantId, ruleChainId, link), DEFAULT_PAGE_SIZE)) { + if (edge.getRootRuleChainId() != null && edge.getRootRuleChainId().equals(ruleChainId)) { + throw new DataValidationException("Can't delete rule chain that is root for edge [" + edge.getName() + "]. Please assign another root rule chain first to the edge!"); } - } while (pageData != null && pageData.hasNext()); + } } } - checkRuleNodesAndDelete(tenantId, ruleChainId); + checkRuleNodesAndDelete(tenantId, ruleChain, referencingRuleChainIds); + referencingRuleChainIds.remove(ruleChainId); } @Override @@ -731,11 +731,15 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return ruleNodeDao.save(tenantId, ruleNode); } - private void checkRuleNodesAndDelete(TenantId tenantId, RuleChainId ruleChainId) { + private void checkRuleNodesAndDelete(TenantId tenantId, RuleChain ruleChain, Set referencingRuleChainIds) { try { entityCountService.publishCountEntityEvictEvent(tenantId, EntityType.RULE_CHAIN); - ruleChainDao.removeById(tenantId, ruleChainId.getId()); - eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(ruleChainId).build()); + ruleChainDao.removeById(tenantId, ruleChain.getUuidId()); + + if (referencingRuleChainIds != null) { + referencingRuleChainIds.remove(ruleChain.getId()); + } + eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(ruleChain.getId()).entity(ruleChain).body(JacksonUtil.toString(referencingRuleChainIds)).build()); } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_default_rule_chain_device_profile")) { @@ -746,7 +750,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC throw t; } } - deleteRuleNodes(tenantId, ruleChainId); + deleteRuleNodes(tenantId, ruleChain.getId()); } private void deleteRuleNodes(TenantId tenantId, List ruleNodes) { @@ -815,7 +819,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Override protected void removeEntity(TenantId tenantId, RuleChain entity) { - checkRuleNodesAndDelete(tenantId, entity.getId()); + checkRuleNodesAndDelete(tenantId, entity, null); } }; From 5ace34bccf5d4d86b008dabbd8d2c1cc620df331 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 11 Oct 2023 12:37:48 +0300 Subject: [PATCH 038/209] Improve code, fix failing tests --- .../service/edge/EdgeEventSourcingListener.java | 9 ++++++--- .../entitiy/EntityStateSourcingListener.java | 2 -- .../server/service/install/InstallScripts.java | 2 +- .../service/rule/DefaultTbRuleChainService.java | 7 ------- .../auth/oauth2/AbstractOAuth2ClientMapper.java | 14 ++++++++++++++ .../ie/importing/impl/RuleChainImportService.java | 4 ---- .../sync/ie/ExportImportServiceSqlTest.java | 1 + .../server/dao/device/DeviceService.java | 1 + .../server/dao/asset/AssetProfileServiceImpl.java | 2 +- .../server/dao/device/DeviceServiceImpl.java | 2 +- .../server/dao/edge/BaseEdgeEventService.java | 10 +++++----- .../server/dao/edge/EdgeServiceImpl.java | 2 +- .../server/dao/rule/BaseRuleChainService.java | 1 - .../dao/usagerecord/ApiUsageStateServiceImpl.java | 2 +- 14 files changed, 32 insertions(+), 27 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index b077908bba..18f616ddfc 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -94,7 +94,7 @@ public class EdgeEventSourcingListener { } EntityType entityType = event.getEntityId().getEntityType(); try { - if (EntityType.EDGE.equals(entityType) || EntityType.TENANT.equals(entityType)) { + if (EntityType.EDGE.equals(entityType) || EntityType.TENANT.equals(entityType) || EntityType.TB_RESOURCE.equals(entityType)) { return; } log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event); @@ -155,8 +155,11 @@ public class EdgeEventSourcingListener { User user = (User) event.getEntity(); return !Authority.SYS_ADMIN.equals(user.getAuthority()); case OTA_PACKAGE: - OtaPackageInfo otaPackageInfo = (OtaPackageInfo) event.getEntity(); - return otaPackageInfo.hasUrl() || otaPackageInfo.isHasData(); + if (event.getEntity() instanceof OtaPackageInfo) { + OtaPackageInfo otaPackageInfo = (OtaPackageInfo) event.getEntity(); + return otaPackageInfo.hasUrl() || otaPackageInfo.isHasData(); + } + break; case ALARM: if (event.getEntity() instanceof AlarmApiCallResult) { AlarmApiCallResult alarmApiCallResult = (AlarmApiCallResult) event.getEntity(); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java index 250d88757a..7b92d8c659 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java @@ -51,7 +51,6 @@ import org.thingsboard.server.dao.entity.EntityStateSyncManager; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.tenant.TenantService; import javax.annotation.PostConstruct; import java.util.Set; @@ -62,7 +61,6 @@ import java.util.Set; public class EntityStateSourcingListener { private final TbClusterService tbClusterService; - private final TenantService tenantService; private final EntityStateSyncManager entityStateSyncManager; @PostConstruct diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index ec0d6fd268..888b9b3343 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -167,7 +167,7 @@ public class InstallScripts { return paths; } - public RuleChain createDefaultRuleChain(TenantId tenantId, String ruleChainName) throws IOException { + public RuleChain createDefaultRuleChain(TenantId tenantId, String ruleChainName) { return createRuleChainFromFile(tenantId, getDeviceProfileDefaultRuleChainTemplateFilePath(), ruleChainName); } diff --git a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java index 830c223d07..7391641598 100644 --- a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java +++ b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java @@ -192,14 +192,7 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement TenantId tenantId = ruleChain.getTenantId(); RuleChainId ruleChainId = ruleChain.getId(); try { - List referencingRuleNodes = ruleChainService.getReferencingRuleChainNodes(tenantId, ruleChainId); - - Set referencingRuleChainIds = referencingRuleNodes.stream().map(RuleNode::getRuleChainId).collect(Collectors.toSet()); - ruleChainService.deleteRuleChainById(tenantId, ruleChainId); - - referencingRuleChainIds.remove(ruleChain.getId()); - logEntityActionService.logEntityAction(tenantId, ruleChainId, ruleChain, null, ActionType.DELETED, user, ruleChainId.toString()); } catch (Exception e) { logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.DELETED, diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index 3732f13b08..49d33122ef 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -20,6 +20,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -41,6 +42,8 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.entity.EntityStateSyncManager; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; @@ -84,6 +87,12 @@ public abstract class AbstractOAuth2ClientMapper { @Autowired protected TbTenantProfileCache tenantProfileCache; + @Autowired + private ApplicationEventPublisher eventPublisher; + + @Autowired + private EntityStateSyncManager entityStateSyncManager; + @Value("${edges.enabled}") @Getter private boolean edgesEnabled; @@ -170,12 +179,17 @@ public abstract class AbstractOAuth2ClientMapper { List tenants = tenantService.findTenants(new PageLink(1, 0, tenantName)).getData(); Tenant tenant; if (tenants == null || tenants.isEmpty()) { + entityStateSyncManager.getSync().set(true); + tenant = new Tenant(); tenant.setTitle(tenantName); tenant = tenantService.saveTenant(tenant); installScripts.createDefaultRuleChains(tenant.getId()); installScripts.createDefaultEdgeRuleChains(tenant.getId()); tenantProfileCache.evict(tenant.getId()); + + entityStateSyncManager.getSync().remove(); + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(TenantId.SYS_TENANT_ID).entityId(tenant.getId()).entity(tenant).added(true).build()); } else { tenant = tenants.get(0); } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java index bb930f9634..63072dfe2b 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java @@ -132,10 +132,6 @@ public class RuleChainImportService extends BaseEntityImportService customerDeviceUnasigner = new PaginatedRemover() { + private PaginatedRemover customerDeviceUnasigner = new PaginatedRemover<>() { @Override protected PageData findEntities(TenantId tenantId, CustomerId id, PageLink pageLink) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java index 46c5671002..40a968d39b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java @@ -48,17 +48,17 @@ public class BaseEdgeEventService implements EdgeEventService { private final ApplicationEventPublisher eventPublisher; - private ExecutorService executor; + private ExecutorService edgeEventExecutor; @PostConstruct public void initExecutor() { - executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("edge-event")); + edgeEventExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("edge-event-service")); } @PreDestroy public void shutdownExecutor() { - if (executor != null) { - executor.shutdown(); + if (edgeEventExecutor != null) { + edgeEventExecutor.shutdown(); } } @@ -77,7 +77,7 @@ public class BaseEdgeEventService implements EdgeEventService { @Override public void onFailure(@NotNull Throwable throwable) {} - }, executor); + }, edgeEventExecutor); return saveFuture; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index d4e45961d9..72af88ab4e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -166,7 +166,7 @@ public class EdgeServiceImpl extends AbstractCachedEntityService Date: Wed, 11 Oct 2023 12:49:57 +0300 Subject: [PATCH 039/209] Improve notification center broadcast logic --- .../server/service/entitiy/EntityStateSourcingListener.java | 6 +----- .../service/notification/DefaultNotificationCenter.java | 4 ---- .../dao/notification/DefaultNotificationRequestService.java | 4 +++- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java index 7b92d8c659..5804b6d8ce 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java @@ -138,6 +138,7 @@ public class EntityStateSourcingListener { case CUSTOMER: case EDGE: case NOTIFICATION_RULE: + case NOTIFICATION_REQUEST: tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); break; case RULE_CHAIN: @@ -171,11 +172,6 @@ public class EntityStateSourcingListener { TbResource tbResource = (TbResource) event.getEntity(); tbClusterService.onResourceDeleted(tbResource, null); break; - case NOTIFICATION_REQUEST: - NotificationRequest request = (NotificationRequest) event.getEntity(); - if (request.isScheduled()) { - tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); - } default: break; } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index 204ff89527..9d3c04bcf0 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -398,10 +398,6 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple .notificationRequestId(notificationRequestId) .deleted(true) .build()); - } else if (notificationRequest.isScheduled()) { - // TODO: just forward to scheduler service - // handling in EntityStateSourcingListener.class - // clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java index 7f7e7e4f4c..9b87bd1070 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java @@ -89,7 +89,9 @@ public class DefaultNotificationRequestService implements NotificationRequestSer @Override public void deleteNotificationRequest(TenantId tenantId, NotificationRequest request) { notificationRequestDao.removeById(tenantId, request.getUuidId()); - eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entity(request).entityId(request.getId()).build()); + if (request.isScheduled()) { + eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(request.getId()).build()); + } } @Override From bbdd0fd6b86f4cbc9d7fcac3f2e8dd49a39c650c Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 12 Oct 2023 13:31:47 +0300 Subject: [PATCH 040/209] Minor refactoring --- .../server/controller/RuleChainController.java | 9 +-------- .../server/common/data/edge/EdgeEventActionType.java | 8 ++++---- .../server/dao/device/DeviceCredentialsServiceImpl.java | 8 ++++---- .../thingsboard/server/dao/tenant/TenantServiceImpl.java | 4 ++-- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index e43a365cf8..f46a1f0117 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -460,14 +460,7 @@ public class RuleChainController extends BaseController { @ApiParam(value = "Enables overwrite for existing rule chains with the same name.") @RequestParam(required = false, defaultValue = "false") boolean overwrite) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); - List importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, overwrite, tbRuleChainService::updateRuleNodeConfiguration); - for (RuleChainImportResult importResult : importResults) { - if (importResult.getError() == null) { - tbClusterService.broadcastEntityStateChangeEvent(importResult.getTenantId(), importResult.getRuleChainId(), - importResult.isUpdated() ? ComponentLifecycleEvent.UPDATED : ComponentLifecycleEvent.CREATED); - } - } - return importResults; + return ruleChainService.importTenantRuleChains(tenantId, ruleChainData, overwrite, tbRuleChainService::updateRuleNodeConfiguration); } private String msgToOutput(TbMsg msg) throws Exception { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java index 2cf0393a98..fbb8c73f0c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java @@ -24,15 +24,15 @@ public enum EdgeEventActionType { UPDATED(ActionType.UPDATED), DELETED(ActionType.DELETED), POST_ATTRIBUTES(null), - ATTRIBUTES_UPDATED(null), - ATTRIBUTES_DELETED(null), - TIMESERIES_UPDATED(null), + ATTRIBUTES_UPDATED(ActionType.ATTRIBUTES_UPDATED), + ATTRIBUTES_DELETED(ActionType.ATTRIBUTES_DELETED), + TIMESERIES_UPDATED(ActionType.TIMESERIES_UPDATED), CREDENTIALS_UPDATED(ActionType.CREDENTIALS_UPDATED), ASSIGNED_TO_CUSTOMER(ActionType.ASSIGNED_TO_CUSTOMER), UNASSIGNED_FROM_CUSTOMER(ActionType.UNASSIGNED_FROM_CUSTOMER), RELATION_ADD_OR_UPDATE(ActionType.RELATION_ADD_OR_UPDATE), RELATION_DELETED(ActionType.RELATION_DELETED), - RPC_CALL(null), + RPC_CALL(ActionType.RPC_CALL), ALARM_ACK(ActionType.ALARM_ACK), ALARM_CLEAR(ActionType.ALARM_CLEAR), ALARM_ASSIGNED(ActionType.ALARM_ASSIGNED), diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index 325fadd0df..b89b0143ec 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -107,12 +107,12 @@ public class DeviceCredentialsServiceImpl extends AbstractCachedEntityService Date: Tue, 17 Oct 2023 16:20:01 +0300 Subject: [PATCH 041/209] tbDate: delete timeZneId --- .../thingsboard/script/api/tbel/TbDate.java | 309 ++++---- .../script/api/tbel/TbDateTest.java | 673 +++++++++++------- .../script/api/tbel/TbDateTestEntity.java | 90 +++ 3 files changed, 663 insertions(+), 409 deletions(-) create mode 100644 common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTestEntity.java diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java index 0dba460686..146de4570f 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java @@ -26,11 +26,13 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; +import java.time.format.FormatStyle; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAccessor; import java.util.Arrays; @@ -40,38 +42,31 @@ import java.util.function.BiFunction; public class TbDate implements Serializable, Cloneable { private Instant instant; - private ZonedDateTime zonedDateTime; - private LocalDateTime localDateTime; - private final ZoneId zoneIdUTC = ZoneId.of("UTC"); - private static String patternDefault = "%s-%s-%sT%s:%s:%s.%s%s"; + private static final ZoneId zoneIdUTC = ZoneId.of("UTC"); + private static final Locale localeUTC = Locale.forLanguageTag("UTC"); public TbDate() { this.instant = Instant.now(); - initZonedLocalDateTime(); } public TbDate(String s) { - parseInstant(s); + this.instant = parseInstant(s); } - /** - * String s = "09:15:30 PM, Sun 10/09/2022"; - * String pattern = "hh:mm:ss a, EEE M/d/uuuu"; - * @param s - * @param pattern - */ public TbDate(String s, String pattern, Locale locale) { - DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern, locale); - LocalDateTime localDateTime = LocalDateTime.parse(s, dateTimeFormatter); - ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault()); - instant = zonedDateTime.toInstant(); - initZonedLocalDateTime(); + instant = parseInstant(s, pattern, locale, zoneIdUTC); + } + public TbDate(String s, String pattern, Locale locale, String zoneIdStr) { + ZoneId zoneId = ZoneId.of(zoneIdStr); + instant = parseInstant(s, pattern, locale, zoneId); + } + public TbDate(String s, String pattern, Locale locale, ZoneId zoneId) { + instant = parseInstant(s, pattern, locale, zoneId); } public TbDate(long dateMilliSecond) { instant = Instant.ofEpochMilli(dateMilliSecond); - initZonedLocalDateTime(); } public TbDate(int year, int month, int date, String... tz) { @@ -87,51 +82,45 @@ public class TbDate implements Serializable, Cloneable { } public TbDate(int year, int month, int date, int hrs, int min, int second, int secondMilli, String... tz) { - this(createDateTimeFromPattern(year, month, date, hrs, min, second, secondMilli, tz)); + ZoneId zoneId = tz.length > 0 ? ZoneId.of(Arrays.stream(tz).findFirst().get()) : ZoneId.systemDefault(); + instant = parseInstant(year, month, date, hrs, min, second, secondMilli, zoneId); } public Instant getInstant() { return instant; } - public void setInstant(Instant instant) { - this.instant = instant; - } public ZonedDateTime getZonedDateTime() { - return zonedDateTime; + return instant.atZone(zoneIdUTC); } - private void initZonedLocalDateTime() { - setZonedDateTime(zoneIdUTC); - setLocalDateTime(); - } - public void setZonedDateTime(ZoneId z) { - this.zonedDateTime = instant.atZone(z);; + public LocalDateTime getLocalDateTime() { + return LocalDateTime.ofInstant(this.instant, ZoneId.systemDefault()); } - public void setLocalDateTime() { - this.localDateTime = LocalDateTime.ofInstant(this.getInstant(), ZoneId.systemDefault()); - } - public LocalDateTime getLocalDateTime() { - return this.localDateTime ; + public LocalDateTime getUTCDateTime() { + return LocalDateTime.ofInstant(this.instant, zoneIdUTC); } public String toDateString() { - return toLocaleDateString("UTC", JacksonUtil.newObjectNode().put("dateStyle", "full").toString()); + return toDateString(localeUTC.getLanguage()); } public String toDateString(String locale) { - return toLocaleDateString(locale, JacksonUtil.newObjectNode().put("dateStyle", "full").toString()); + return toDateString(locale, ZoneId.systemDefault().toString()); + } + public String toDateString(String locale, String zoneStr) { + return toLocaleDateString(locale, JacksonUtil.newObjectNode().put("dateStyle", FormatStyle.FULL.name()).put("timeZone", zoneStr).toString()); } public String toTimeString() { - return toLocaleTimeString("UTC", JacksonUtil.newObjectNode().put("timeStyle", "full").toString()); + return toTimeString(Locale.getDefault().getLanguage()); } public String toTimeString(String locale) { - return toLocaleTimeString(locale, JacksonUtil.newObjectNode().put("timeStyle", "full").toString()); + return toTimeString(locale, ZoneId.systemDefault().toString()); + } + public String toTimeString(String locale, String zoneStr) { + return toLocaleTbTimeString(locale, JacksonUtil.newObjectNode().put("timeStyle", FormatStyle.FULL.name()).put("timeZone", zoneStr).toString()); } - /** - * "2011-10-05T14:48:00.000Z" - * @return - */ + public String toISOString() { return instant.toString(); } @@ -139,59 +128,73 @@ public class TbDate implements Serializable, Cloneable { return toISOString(); } public String toUTCString() { - return toLocaleString("UTC", JacksonUtil.newObjectNode().put("dateStyle", "full").put("timeStyle", "medium").toString()); + return toUTCString(localeUTC.getLanguage()); } public String toUTCString(String locale) { - return toLocaleString(locale, JacksonUtil.newObjectNode().put("dateStyle", "full").put("timeStyle", "medium").toString()); + return toLocaleTbString(locale, JacksonUtil.newObjectNode().put("dateStyle", FormatStyle.FULL.name()).put("timeStyle", FormatStyle.MEDIUM.name()).put("timeZone", zoneIdUTC.getId()).toString()); } - /** - * "Tue Aug 19 1975 23:15:30 GMT+0300 (за східноєвропейським стандартним часом)" - * @return - */ public String toString() { - return toLocaleString(Locale.getDefault().toString(), JacksonUtil.newObjectNode().put("dateStyle", "full").put("timeStyle", "full").toString()); + return toString(Locale.getDefault().getLanguage()); } + public String toString(String locale) { - return toLocaleString(locale, JacksonUtil.newObjectNode().put("dateStyle", "full").put("timeStyle", "full").toString()); + return toString(locale, ZoneId.systemDefault().toString()); + } + + public String toString(String locale, String zoneStr) { + return toLocaleTbString(locale, JacksonUtil.newObjectNode().put("dateStyle", FormatStyle.FULL.name()).put("timeStyle", FormatStyle.FULL.name()).put("timeZone", zoneStr).toString()); + } + public String toISOZonedDateTimeString() { + return getZonedDateTime().toString(); + } + public String toISOZonedDateTimeString(DateTimeFormatter formatter) { + return getZonedDateTime().format(formatter); } public String toLocaleDateString() { - return toLocaleDateString(null, null); + return toLocaleDateString(localeUTC.getLanguage()); } public String toLocaleDateString(String locale) { - return toLocaleDateString(locale, null); + return toLocaleDateString(locale, JacksonUtil.newObjectNode().put("dateStyle", FormatStyle.FULL.name()).put("timeZone", ZoneId.systemDefault().toString()).toString()); } public String toLocaleDateString(String localeStr, String optionsStr) { - return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedDate(options.getDateStyle()).withLocale(locale)); + return toLocaleTbString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedDate(options.getDateStyle()).withLocale(locale)); } public String toLocaleTimeString() { - return toLocaleTimeString(null, null); + return toLocaleTimeString(Locale.getDefault().getLanguage()); } - public String toLocaleTimeString(String locale) { - return toLocaleTimeString(locale, null); + public String toLocaleTimeString(String localeStr) { + return toLocaleTimeString(localeStr, ZoneId.systemDefault().toString()); } - public String toLocaleTimeString(String localeStr, String optionsStr) { - return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(options.getTimeStyle()).withLocale(locale)); + public String toLocaleTimeString(String localeStr, String zoneStr) { + return toLocaleTbTimeString(localeStr, JacksonUtil.newObjectNode().put("timeStyle", FormatStyle.MEDIUM.name()).put("timeZone", zoneStr).toString()); + } + + public String toLocaleTbTimeString(String localeStr, String optionsStr) { + return toLocaleTbString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(options.getTimeStyle()).withLocale(locale)); } public String toLocaleString() { - LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); - return localDateTime.toString(); + return toLocaleString(localeUTC.getLanguage(), ZoneId.systemDefault().toString()); } public String toLocaleString(String locale) { - return toLocaleString(locale, null); + return toLocaleString(locale, ZoneId.systemDefault().toString()); } - public String toLocaleString(String localeStr, String optionsStr) { - return toLocaleString(localeStr, optionsStr, (locale, options) -> { + public String toLocaleString(String locale, String zoneStr) { + return toLocaleTbString(locale, JacksonUtil.newObjectNode().put("dateStyle", FormatStyle.SHORT.name()).put("timeStyle", FormatStyle.MEDIUM.name()).put("timeZone", zoneStr).toString()); + } + + public String toLocaleTbString(String localeStr, String optionsStr) { + return toLocaleTbString(localeStr, optionsStr, (locale, options) -> { String formatPattern = DateTimeFormatterBuilder.getLocalizedDateTimePattern( options.getDateStyle(), @@ -202,7 +205,7 @@ public class TbDate implements Serializable, Cloneable { }); } - public String toLocaleString(String localeStr, String optionsStr, BiFunction formatterBuilder) { + public String toLocaleTbString(String localeStr, String optionsStr, BiFunction formatterBuilder) { Locale locale = StringUtils.isNotEmpty(localeStr) ? Locale.forLanguageTag(localeStr) : Locale.getDefault(); DateTimeFormatOptions options = getDateFormattingOptions(optionsStr); ZonedDateTime zdt = this.getInstant().atZone(options.getTimeZone().toZoneId()); @@ -264,116 +267,130 @@ public class TbDate implements Serializable, Cloneable { year = year == 0 ? year = 1899 : year; month = month == 0 ? month = 12 : month; date = date == 0 ? date = 31 : date; - return Instant.parse(createDateTimeFromPattern (year, month, date, hrs, min, sec, ms)).toEpochMilli(); + return parseInstant(year, month, date, hrs, min, sec, ms, zoneIdUTC).toEpochMilli(); } public int getUTCFullYear() { - return zonedDateTime.getYear(); + return getUTCDateTime().getYear(); } public int getUTCMonth() { - return zonedDateTime.getMonthValue(); + return getUTCDateTime().getMonthValue(); } // day in month public int getUTCDate() { - return zonedDateTime.getDayOfMonth(); + return getUTCDateTime().getDayOfMonth(); } // day in week public int getUTCDay() { - return zonedDateTime.getDayOfWeek().getValue(); + return getUTCDateTime().getDayOfWeek().getValue(); } public int getUTCHours() { - return zonedDateTime.getHour(); + return getUTCDateTime().getHour(); } public int getUTCMinutes() { - return zonedDateTime.getMinute(); + return getZonedDateTime().getMinute(); } public int getUTCSeconds() { - return zonedDateTime.getSecond(); + return getUTCDateTime().getSecond(); } public int getUTCMilliseconds() { - return zonedDateTime.getNano()/1000000; + return getUTCDateTime().getNano()/1000000; } public void setUTCFullYear(int year) { - parseInstant(createDateTimeFromPattern(year, getUTCMonth(), getUTCDate(), getUTCHours(), getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + if (getUTCDate() > 28) { + long time = getZonedDateTime().withYear(year).withDayOfMonth(1).toInstant().toEpochMilli() + (getUTCDate() - 1) * 24 * 60 * 60 * 1000L; + this.instant = Instant.ofEpochMilli(time); + } else { + this.instant = getZonedDateTime().withYear(year).toInstant(); + } } public void setUTCFullYear(int year, int month) { - parseInstant(createDateTimeFromPattern(year, month, getUTCDate(), getUTCHours(), getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + if (getUTCDate() > 28) { + long time = getZonedDateTime().withYear(year).withMonth(month).withDayOfMonth(1).toInstant().toEpochMilli() + (getUTCDate() - 1) * 24 * 60 * 60 * 1000L; + this.instant = Instant.ofEpochMilli(time); + } else { + this.instant = getZonedDateTime().withYear(year).withMonth(month).toInstant(); + } } public void setUTCFullYear(int year, int month, int date) { - parseInstant(createDateTimeFromPattern(year, month, date, getUTCHours(), getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + this.instant = getZonedDateTime().withYear(year).withMonth(month).withDayOfMonth(date).toInstant(); } public void setUTCMonth(int month) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), month, getUTCDate(), getUTCHours(), getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + if (getUTCDate() > 28) { + long time = getZonedDateTime().withMonth(month).withDayOfMonth(1).toInstant().toEpochMilli() + (getUTCDate() - 1) * 24 * 60 * 60 * 1000L; + this.instant = Instant.ofEpochMilli(time); + } else { + this.instant = getZonedDateTime().withMonth(month).toInstant(); + } } public void setUTCMonth(int month, int date) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), month, date, getUTCHours(), getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + this.instant = getZonedDateTime().withMonth(month).withDayOfMonth(date).toInstant(); } public void setUTCDate(int date) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), date, getUTCHours(), getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + this.instant = getZonedDateTime().withDayOfMonth(date).toInstant(); } public void setUTCHours(int hrs) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), hrs, getUTCMinutes(), getUTCSeconds(), getUTCMilliseconds())); + this.instant = getZonedDateTime().withHour(hrs).toInstant(); } public void setUTCHours(int hrs, int minutes) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), hrs, minutes, getUTCSeconds(), getUTCMilliseconds())); + this.instant = getZonedDateTime().withHour(hrs).withMinute(minutes).toInstant(); } public void setUTCHours(int hrs, int minutes, int seconds) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), hrs, minutes, seconds, getUTCMilliseconds())); + this.instant = getZonedDateTime().withHour(hrs).withMinute(minutes).withSecond(seconds).toInstant(); } public void setUTCHours(int hrs, int minutes, int seconds, int ms) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), hrs, minutes, seconds, ms)); + this.instant = getZonedDateTime().withHour(hrs).withMinute(minutes).withSecond(seconds).withNano(ms*1000000).toInstant(); } public void setUTCMinutes(int minutes) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), minutes, getUTCSeconds(), getUTCMilliseconds())); + this.instant = getZonedDateTime().withMinute(minutes).toInstant(); } public void setUTCMinutes(int minutes, int seconds) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), minutes, seconds, getUTCMilliseconds())); - } + this.instant = getZonedDateTime().withMinute(minutes).withSecond(seconds).toInstant(); } public void setUTCMinutes(int minutes, int seconds, int ms) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), minutes, seconds, ms)); + this.instant = parseInstant(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), minutes, seconds, ms, zoneIdUTC); } public void setUTCSeconds(int seconds) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), getUTCMinutes(), seconds, getUTCMilliseconds())); + this.instant = getZonedDateTime().withSecond(seconds).toInstant(); } public void setUTCSeconds(int seconds, int ms) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), getUTCMinutes(), seconds, ms)); + this.instant = getZonedDateTime().withSecond(seconds).withNano(ms*1000000).toInstant(); } public void setUTCMilliseconds(int ms) { - parseInstant(createDateTimeFromPattern(getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours(), getUTCMinutes(), getUTCSeconds(), ms)); + this.instant = getZonedDateTime().withNano(ms*1000000).toInstant(); } public int getFullYear() { - return localDateTime.getYear(); + return getLocalDateTime().getYear(); } public int getMonth() { - return localDateTime.getMonthValue(); + return getLocalDateTime().getMonthValue(); } // day in month public int getDate() { - return localDateTime.getDayOfMonth(); + return getLocalDateTime().getDayOfMonth(); } // day in week public int getDay() { - return localDateTime.getDayOfWeek().getValue(); + return getLocalDateTime().getDayOfWeek().getValue(); } public int getHours() { - return localDateTime.getHour(); + return getLocalDateTime().getHour(); } public int getMinutes() { - return localDateTime.getMinute(); + return getLocalDateTime().getMinute(); } public int getSeconds() { - return localDateTime.getSecond(); + return getLocalDateTime().getSecond(); } public int getMilliseconds() { - return localDateTime.getNano()/1000000; + return getLocalDateTime().getNano()/1000000; } // Milliseconds since Jan 1, 1970, 00:00:00.000 GMT public long getTime() { @@ -383,61 +400,84 @@ public class TbDate implements Serializable, Cloneable { return getTime() ; } public void setFullYear(int year) { - setUTCFullYear(year); + Instant instantEpochWithYear = getZonedDateTime().withYear(year).toInstant(); + if (getDate() > 28) { + long time = getLocalDateTime().withYear(year).withDayOfMonth(1).toInstant(getLocaleZoneOffset(instantEpochWithYear)).toEpochMilli() + (getDate() - 1) * 24 * 60 * 60 * 1000L; + this.instant = Instant.ofEpochMilli(time); + } else { + this.instant = getLocalDateTime().withYear(year).toInstant(getLocaleZoneOffset(instantEpochWithYear)); + } } public void setFullYear(int year, int month) { - setUTCFullYear(year, month); - } public void setFullYear(int year, int month, int date) { - setUTCFullYear(year, month, date); + Instant instantEpochWithYear = getZonedDateTime().withYear(year).withMonth(month).toInstant(); + if (getDate() > 28) { + long time = getLocalDateTime().withYear(year).withMonth(month).withDayOfMonth(1).toInstant(getLocaleZoneOffset(instantEpochWithYear)).toEpochMilli() + (getDate() - 1) * 24 * 60 * 60 * 1000L; + this.instant = Instant.ofEpochMilli(time); + } else { + this.instant = getLocalDateTime().withYear(year).withMonth(month).toInstant(getLocaleZoneOffset(instantEpochWithYear)); + } + } + public void setFullYear(int year, int month, int date) { + Instant instantEpochWithYearMonthDate = getZonedDateTime().withYear(year).withMonth(month).withDayOfMonth(date).toInstant(); + this.instant = getLocalDateTime().withYear(year).withMonth(month).withDayOfMonth(date).toInstant(getLocaleZoneOffset(instantEpochWithYearMonthDate)); } public void setMonth(int month) { - setUTCMonth(month); + Instant instantEpochWithYear = getZonedDateTime().withMonth(month).toInstant(); + if (getDate() > 28) { + long time = getLocalDateTime().withMonth(month).withDayOfMonth(1).toInstant(getLocaleZoneOffset(instantEpochWithYear)).toEpochMilli() + (getDate() - 1) * 24 * 60 * 60 * 1000L; + this.instant = Instant.ofEpochMilli(time); + } else { + this.instant = getLocalDateTime().withMonth(month).toInstant(getLocaleZoneOffset(instantEpochWithYear)); + } + } public void setMonth(int month, int date) { - setUTCMonth(month, date) ; + Instant instantEpochWithMonthDate = getZonedDateTime().withMonth(month).withDayOfMonth(date).toInstant(); + this.instant = getLocalDateTime().withMonth(month).withDayOfMonth(date).toInstant(getLocaleZoneOffset(instantEpochWithMonthDate)); } public void setDate(int date) { - setUTCDate(date); + Instant instantEpochWithDate = getZonedDateTime().withDayOfMonth(date).toInstant(); + this.instant = getLocalDateTime().withDayOfMonth(date).toInstant(getLocaleZoneOffset(instantEpochWithDate)); } public void setHours(int hrs) { - setUTCHours(hrs); + this.instant = getLocalDateTime().withHour(hrs).toInstant(getLocaleZoneOffset(this.instant)); } public void setHours(int hrs, int minutes) { - setUTCHours(hrs, minutes); + this.instant = getLocalDateTime().withHour(hrs).withMinute(minutes).toInstant(getLocaleZoneOffset(this.instant)); } public void setHours(int hrs, int minutes, int seconds) { - setUTCHours(hrs, minutes, seconds); + this.instant = getLocalDateTime().withHour(hrs).withMinute(minutes).withSecond(seconds).toInstant(getLocaleZoneOffset(this.instant)); } public void setHours(int hrs, int minutes, int seconds, int ms) { - setUTCHours(hrs, minutes, seconds, ms); + this.instant = getLocalDateTime().withHour(hrs).withMinute(minutes).withSecond(seconds).withNano(ms*1000000).toInstant(getLocaleZoneOffset(this.instant)); } public void setMinutes(int minutes) { - setUTCMinutes(minutes); + this.instant = getLocalDateTime().withMinute(minutes).toInstant(getLocaleZoneOffset(this.instant)); } public void setMinutes(int minutes, int seconds) { - setUTCMinutes(minutes, seconds); + this.instant = getLocalDateTime().withMinute(minutes).withSecond(seconds).toInstant(getLocaleZoneOffset(this.instant)); + } public void setMinutes(int minutes, int seconds, int ms) { - setUTCMinutes(minutes, seconds, ms); + this.instant = getLocalDateTime().withMinute(minutes).withSecond(seconds).withNano(ms*1000000).toInstant(getLocaleZoneOffset(this.instant)); } public void setSeconds(int seconds) { - setUTCSeconds(seconds); + this.instant = getLocalDateTime().withSecond(seconds).toInstant(getLocaleZoneOffset(this.instant)); } public void setSeconds(int seconds, int ms) { - setUTCSeconds(seconds, ms); + this.instant = getLocalDateTime().withSecond(seconds).withNano(ms*1000000).toInstant(getLocaleZoneOffset(this.instant)); } public void setMilliseconds(int ms) { - setUTCMilliseconds(ms); + this.instant = getLocalDateTime().withNano(ms*1000000).toInstant(getLocaleZoneOffset(this.instant)); } // Milliseconds since Jan 1, 1970, 00:00:00.000 GMT public void setTime(long dateMilliSecond) { instant = Instant.ofEpochMilli(dateMilliSecond); - initZonedLocalDateTime(); } - public int getTimezoneOffset() { - int seconds = ZoneId.systemDefault().getRules().getOffset(instant).getTotalSeconds(); - return -seconds/60; + + public ZoneOffset getLocaleZoneOffset(Instant... instants){ + return ZoneId.systemDefault().getRules().getOffset(instants.length > 0 ? instants[0] : this.instant); } public static long parse(String value, String format) { @@ -462,33 +502,32 @@ public class TbDate implements Serializable, Cloneable { return -1; } } - private static String createDateTimeFromPattern(int year, int month, int date, int hrs, int min, int second, int secondMilli, String... tz) { - String yearStr = String.format("%04d", year); - yearStr = yearStr.substring(0, 2).equals("00") ? year < 70 ? "20" + yearStr.substring(2,4) : "19" + yearStr.substring(2,4) : yearStr; - String monthStr = String.format("%02d", month); - String dateStr = String.format("%02d", date); - String hrsStr = String.format("%02d", hrs); - String minStr = String.format("%02d", min); - String secondStr = String.format("%02d", second); - String secondMilliStr = String.format("%03d", secondMilli); - String tzStr = tz.length > 0 ? Arrays.stream(tz).findFirst().get() : "Z"; - return String.format(patternDefault, yearStr, monthStr, dateStr, hrsStr, minStr, secondStr, secondMilliStr, tzStr); - } - private void parseInstant(String s) { + private static Instant parseInstant(String s) { try{ if (s.length() > 0 && Character.isDigit(s.charAt(0))) { // assuming UTC instant "2007-12-03T10:15:30.00Z" - instant = Instant.parse(s); + return Instant.parse(s); } else { // assuming RFC-1123 value "Tue, 3 Jun 2008 11:05:30 GMT-02:00" - instant = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(s)); + return Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(s)); } - initZonedLocalDateTime(); } catch (final DateTimeParseException ex) { final ConversionException exception = new ConversionException("Cannot parse value [" + s + "] as instant", ex); throw exception; } } + + private static Instant parseInstant(int year, int month, int date, int hrs, int min, int second, int secondMilli, ZoneId zoneId) { + year = year < 70 ? 2000 + year : year <= 99 ? 1900 + year : year; + ZonedDateTime zonedDateTime = ZonedDateTime.of(year, month, date, hrs, min, second, secondMilli*1000000, zoneId); + return zonedDateTime.toInstant(); + } + private static Instant parseInstant(String s, String pattern, Locale locale, ZoneId zoneId) { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern, locale); + LocalDateTime localDateTime = LocalDateTime.parse(s, dateTimeFormatter); + ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId); + return zonedDateTime.toInstant(); + } } diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index 75027247b1..f020d3e092 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -27,7 +27,9 @@ import org.junit.jupiter.api.Test; import org.mvel2.ConversionException; import org.thingsboard.common.util.JacksonUtil; -import java.time.format.DateTimeParseException; +import java.time.DateTimeException; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -113,12 +115,14 @@ class TbDateTest { void testToISOStringThreadLocalStaticFormatter() throws ExecutionException, InterruptedException, TimeoutException { executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1)); int hrs = 14; - String pattern = "2024-02-29T%s:35:42.987Z"; - String tsStr = String.format(pattern, hrs); //Thu Feb 29 2024 14:35:42.987 GMT+0000 + int date = 29; + int month = 2; + String pattern = "2024-%s-%sT%s:35:42.987Z"; + String tsStr = String.format(pattern, String.format("%02d", month), date, hrs); //Thu Feb 29 2024 14:35:42.987 GMT+0000 TbDate tbDate = new TbDate(tsStr); long ts = 1709217342987L; //Thu Feb 29 2024 14:35:42.987 GMT+0000 Assert.assertEquals(ts, tbDate.parseSecondMilli()); - int offsetMin = tbDate.getTimezoneOffset(); // for example 3600000 for GMT + 1 + int offsetLocalSecond = tbDate.getLocaleZoneOffset().getTotalSeconds(); // for example 7200 for GMT + 2 String datePrefix = tsStr; //without time zone assertThat(tbDate.toISOString()) @@ -127,13 +131,13 @@ class TbDateTest { assertThat(executor.submit(tbDate::toISOString).get(30, TimeUnit.SECONDS)) .as("format in executor thread") .startsWith(datePrefix); - int offsetHrs = offsetMin/60; - String datePrefixLocal = String.format(pattern, (hrs - offsetHrs)); - long offsetMilli = offsetMin*60*1000; - assertThat(new TbDate(ts - offsetMilli).toISOString()) + TbDateTestEntity tbDateTest = new TbDateTestEntity(tbDate.getFullYear(), month, date,hrs + offsetLocalSecond/60/60); + String datePrefixLocal = String.format(pattern, tbDateTest.geMonthStr(), tbDateTest.geDateStr(), tbDateTest.geHoursStr()); + long offsetLocalMilli = offsetLocalSecond*1000; + assertThat(new TbDate(ts + offsetLocalMilli).toISOString()) .as("new instance format in main thread") .startsWith(datePrefixLocal); - assertThat(executor.submit(() -> new TbDate(ts - offsetMilli).toISOString()).get(30, TimeUnit.SECONDS)) + assertThat(executor.submit(() -> new TbDate(ts + offsetLocalMilli).toISOString()).get(30, TimeUnit.SECONDS)) .as("new instance format in executor thread") .startsWith(datePrefixLocal); } @@ -143,11 +147,21 @@ class TbDateTest { String s = "09:15:30 PM, Sun 10/09/2022"; String pattern = "hh:mm:ss a, EEE M/d/uuuu"; TbDate d = new TbDate(s, pattern, Locale.US); - Assert.assertEquals("2022-10-09T18:15:30Z", d.toISOString()); + Assert.assertEquals("2022-10-09T21:15:30Z", d.toISOString()); + // tz = "-04:00" + d = new TbDate(s, pattern, Locale.US, "-04:00"); + Assert.assertEquals("2022-10-10T01:15:30Z", d.toISOString()); + d = new TbDate(s, pattern, Locale.US, "America/New_York"); + Assert.assertEquals("2022-10-10T01:15:30Z", d.toISOString()); + // tz = "+02:00" + s = "09:15:30 PM, So. 10/09/2022"; + d = new TbDate(s, pattern, Locale.GERMAN, ZoneId.of("Europe/Berlin")); + Assert.assertEquals("2022-10-09T19:15:30Z", d.toISOString()); + s = "09:15:30 пп, середа, 4 жовтня 2023 р."; pattern = "hh:mm:ss a, EEEE, d MMMM y 'р.'"; d = new TbDate(s, pattern, Locale.forLanguageTag("uk-UA")); - Assert.assertEquals("2023-10-04T18:15:30Z", d.toISOString()); + Assert.assertEquals("2023-10-04T21:15:30Z", d.toISOString()); d = new TbDate(1693962245000L); Assert.assertEquals("2023-09-06T01:04:05Z", d.toISOString()); @@ -204,41 +218,41 @@ class TbDateTest { Assert.assertNotNull(d.toLocaleTimeString()); Assert.assertNotNull(d.toLocaleTimeString("en-US")); - Assert.assertEquals("9:04:05 PM", d.toLocaleTimeString("en-US", "America/New_York")); - Assert.assertEquals("오후 9:04:05", d.toLocaleTimeString("ko-KR", "America/New_York")); - Assert.assertEquals("04:04:05", d.toLocaleTimeString( "uk-UA", "Europe/Kiev")); - Assert.assertEquals("9:04:05 م", d.toLocaleTimeString( "ar-EG", "America/New_York")); + Assert.assertEquals("9:04:05 PM", d.toLocaleTbTimeString("en-US", "America/New_York")); + Assert.assertEquals("오후 9:04:05", d.toLocaleTbTimeString("ko-KR", "America/New_York")); + Assert.assertEquals("04:04:05", d.toLocaleTbTimeString( "uk-UA", "Europe/Kiev")); + Assert.assertEquals("9:04:05 م", d.toLocaleTbTimeString( "ar-EG", "America/New_York")); - Assert.assertEquals("9:04:05 PM Eastern Daylight Time", d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode() + Assert.assertEquals("9:04:05 PM Eastern Daylight Time", d.toLocaleTbTimeString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("timeStyle", "full") .toString())); - Assert.assertEquals("오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleTimeString("ko-KR", JacksonUtil.newObjectNode() + Assert.assertEquals("오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleTbTimeString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("timeStyle", "full") .toString())); - Assert.assertEquals("04:04:05 за східноєвропейським літнім часом", d.toLocaleTimeString("uk-UA", JacksonUtil.newObjectNode() + Assert.assertEquals("04:04:05 за східноєвропейським літнім часом", d.toLocaleTbTimeString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("timeStyle", "full") .toString())); - Assert.assertEquals("9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleTimeString("ar-EG", JacksonUtil.newObjectNode() + Assert.assertEquals("9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleTbTimeString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("timeStyle", "full") .toString())); - Assert.assertEquals("9:04:05 PM", d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode() + Assert.assertEquals("9:04:05 PM", d.toLocaleTbTimeString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "h:mm:ss a") .toString())); - Assert.assertEquals("9:04:05 오후", d.toLocaleTimeString("ko-KR", JacksonUtil.newObjectNode() + Assert.assertEquals("9:04:05 오후", d.toLocaleTbTimeString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "h:mm:ss a") .toString())); - Assert.assertEquals("4:04:05 дп", d.toLocaleTimeString("uk-UA", JacksonUtil.newObjectNode() + Assert.assertEquals("4:04:05 дп", d.toLocaleTbTimeString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("pattern", "h:mm:ss a") .toString())); - Assert.assertEquals("9:04:05 م", d.toLocaleTimeString("ar-EG", JacksonUtil.newObjectNode() + Assert.assertEquals("9:04:05 م", d.toLocaleTbTimeString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "h:mm:ss a") .toString())); @@ -252,45 +266,45 @@ class TbDateTest { Assert.assertNotNull(d.toLocaleString()); Assert.assertNotNull(d.toLocaleString("en-US")); - Assert.assertEquals("9/5/23, 9:04:05 PM", d.toLocaleString("en-US", "America/New_York")); - Assert.assertEquals("23. 9. 5. 오후 9:04:05", d.toLocaleString("ko-KR", "America/New_York")); - Assert.assertEquals("06.09.23, 04:04:05", d.toLocaleString( "uk-UA", "Europe/Kiev")); - Assert.assertEquals("5\u200F/9\u200F/2023, 9:04:05 م", d.toLocaleString( "ar-EG", "America/New_York")); + Assert.assertEquals("9/5/23, 9:04:05 PM", d.toLocaleTbString("en-US", "America/New_York")); + Assert.assertEquals("23. 9. 5. 오후 9:04:05", d.toLocaleTbString("ko-KR", "America/New_York")); + Assert.assertEquals("06.09.23, 04:04:05", d.toLocaleTbString( "uk-UA", "Europe/Kiev")); + Assert.assertEquals("5\u200F/9\u200F/2023, 9:04:05 م", d.toLocaleTbString( "ar-EG", "America/New_York")); - Assert.assertEquals("Tuesday, September 5, 2023 at 9:04:05 PM Eastern Daylight Time", d.toLocaleString("en-US", JacksonUtil.newObjectNode() + Assert.assertEquals("Tuesday, September 5, 2023 at 9:04:05 PM Eastern Daylight Time", d.toLocaleTbString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") .put("timeStyle", "full") .toString())); - Assert.assertEquals("2023년 9월 5일 화요일 오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleString("ko-KR", JacksonUtil.newObjectNode() + Assert.assertEquals("2023년 9월 5일 화요일 오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleTbString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") .put("timeStyle", "full") .toString())); - Assert.assertEquals("середа, 6 вересня 2023 р. о 04:04:05 за східноєвропейським літнім часом", d.toLocaleString("uk-UA", JacksonUtil.newObjectNode() + Assert.assertEquals("середа, 6 вересня 2023 р. о 04:04:05 за східноєвропейським літнім часом", d.toLocaleTbString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("dateStyle", "full") .put("timeStyle", "full") .toString())); - Assert.assertEquals("الثلاثاء، 5 سبتمبر 2023 في 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() + Assert.assertEquals("الثلاثاء، 5 سبتمبر 2023 في 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleTbString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") .put("timeStyle", "full") .toString())); - Assert.assertEquals("9/5/2023, 9:04:05 PM", d.toLocaleString("en-US", JacksonUtil.newObjectNode() + Assert.assertEquals("9/5/2023, 9:04:05 PM", d.toLocaleTbString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "M/d/yyyy, h:mm:ss a") .toString())); - Assert.assertEquals("9/5/2023, 9:04:05 오후", d.toLocaleString("ko-KR", JacksonUtil.newObjectNode() + Assert.assertEquals("9/5/2023, 9:04:05 오후", d.toLocaleTbString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "M/d/yyyy, h:mm:ss a") .toString())); - Assert.assertEquals("9/6/2023, 4:04:05 дп", d.toLocaleString("uk-UA", JacksonUtil.newObjectNode() + Assert.assertEquals("9/6/2023, 4:04:05 дп", d.toLocaleTbString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("pattern", "M/d/yyyy, h:mm:ss a") .toString())); - Assert.assertEquals("9/5/2023, 9:04:05 م", d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() + Assert.assertEquals("9/5/2023, 9:04:05 م", d.toLocaleTbString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "M/d/yyyy, h:mm:ss a") .toString())); @@ -301,27 +315,63 @@ class TbDateTest { String stringDateUTC = "2023-09-06T01:04:05.00Z"; TbDate d = new TbDate(stringDateUTC); Assert.assertEquals("2023-09-06T01:04:05Z", d.toISOString()); - String stringDateTZ = "2023-09-06T01:04:05.00+04:00"; + String stringDateTZ = "2023-09-06T01:04:05.00+04:00:00"; d = new TbDate(stringDateTZ); Assert.assertEquals("2023-09-05T21:04:05Z", d.toISOString()); + stringDateTZ = "2023-09-06T01:04:05.00-04:00"; + d = new TbDate(stringDateTZ); + Assert.assertEquals("2023-09-06T05:04:05Z", d.toISOString()); + stringDateTZ = "2023-09-06T01:04:05.00+04:30:56"; + d = new TbDate(stringDateTZ); + Assert.assertEquals("2023-09-05T20:33:09Z", d.toISOString()); stringDateTZ = "2023-09-06T01:04:05.00-02:00"; d = new TbDate(stringDateTZ); Assert.assertEquals("2023-09-06T03:04:05Z", d.toISOString()); String stringDateRFC_1123 = "Sat, 3 Jun 2023 11:05:30 GMT"; d = new TbDate(stringDateRFC_1123); - stringDateRFC_1123 = "Sat, 3 Jun 2023 11:05:30 +0400"; + Assert.assertEquals("2023-06-03T11:05:30Z", d.toISOString()); + stringDateRFC_1123 = "Sat, 3 Jun 2023 01:04:05 +043056"; + d = new TbDate(stringDateRFC_1123); + Assert.assertEquals("2023-06-02T20:33:09Z", d.toISOString()); + stringDateRFC_1123 = "Sat, 3 Jun 2023 11:05:30 +0400"; d = new TbDate(stringDateRFC_1123); Assert.assertEquals("2023-06-03T07:05:30Z", d.toISOString()); stringDateRFC_1123 = "Thu, 29 Feb 2024 11:05:30 -03"; d = new TbDate(stringDateRFC_1123); Assert.assertEquals("2024-02-29T14:05:30Z", d.toISOString()); - String stringDateRFC_1123_error = "Tue, 3 Jun 2023 11:05:30 GMT"; + String stringDateZ_error = "2023-09-06T01:04:05.00+04"; Exception actual = assertThrows(ConversionException.class, () -> { - new TbDate(stringDateRFC_1123_error); + new TbDate(stringDateZ_error); }); String expectedMessage = "Cannot parse value"; assertTrue(actual.getMessage().contains(expectedMessage)); + + String stringDateRFC_1123_error = "Tue, 3 Jun 2023 11:05:30 GMT"; + actual = assertThrows(ConversionException.class, () -> { + new TbDate(stringDateRFC_1123_error); + }); + assertTrue(actual.getMessage().contains(expectedMessage)); + } + + @Test + void TestDateTimeFormatterToString () { + TbDate d1 = new TbDate(23, 9, 7, 8, 4, 5, "+04:00"); + TbDate d2 = new TbDate(23, 9, 7, 8, 4, 5, "-03:00"); + + Assert.assertEquals("2023-09-07T04:04:05Z[UTC]", d1.toISOZonedDateTimeString()); + Assert.assertEquals("2023-09-07T04:04:05Z", d1.toISOZonedDateTimeString(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + Assert.assertEquals("2023-09-07T04:04:05Z[UTC]", d1.toISOZonedDateTimeString(DateTimeFormatter.ISO_ZONED_DATE_TIME)); + Assert.assertEquals("2023-09-07T04:04:05", d1.toISOZonedDateTimeString(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + Assert.assertEquals("Thu, 7 Sep 2023 04:04:05 GMT", d1.toISOZonedDateTimeString(DateTimeFormatter.RFC_1123_DATE_TIME)); + Assert.assertEquals("2023-09-07T04:04:05Z", d1.toISOZonedDateTimeString(DateTimeFormatter.ISO_INSTANT)); + + Assert.assertEquals("2023-09-07T11:04:05Z[UTC]", d2.toISOZonedDateTimeString()); + Assert.assertEquals("2023-09-07T11:04:05Z", d2.toISOZonedDateTimeString(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + Assert.assertEquals("2023-09-07T11:04:05Z[UTC]", d2.toISOZonedDateTimeString(DateTimeFormatter.ISO_ZONED_DATE_TIME)); + Assert.assertEquals("2023-09-07T11:04:05", d2.toISOZonedDateTimeString(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + Assert.assertEquals("Thu, 7 Sep 2023 11:04:05 GMT", d2.toISOZonedDateTimeString(DateTimeFormatter.RFC_1123_DATE_TIME)); + Assert.assertEquals("2023-09-07T11:04:05Z", d2.toISOZonedDateTimeString(DateTimeFormatter.ISO_INSTANT)); } @Test @@ -333,188 +383,231 @@ class TbDateTest { String stringDateStart = "1970-01-01T00:00:00Z"; d = new TbDate(stringDateStart); long actualMillis = TbDate.parse("1970-01-01 T00:00:00"); - Assert.assertEquals(d.getTimezoneOffset(), actualMillis/60/1000); + Assert.assertEquals(-d.getLocaleZoneOffset().getTotalSeconds() * 1000, actualMillis); String pattern = "yyyy-MM-dd HH:mm:ss.SSS"; String stringDate = "1995-12-04 00:12:00.000"; Assert.assertNotEquals(-1L, TbDate.parse(stringDate, pattern)); } @Test - void TestDate_Year_Moth_Date_Hs_Min_Sec () { - TbDate d = new TbDate(2023, 8, 18); - Assert.assertEquals("2023-08-18T00:00:00Z", d.toISOString()); - d = new TbDate(2023, 9, 17, 17, 34); - Assert.assertEquals("2023-09-17T17:34:00Z", d.toISOString()); - d = new TbDate(23, 9, 7, 8, 4); - Assert.assertEquals("2023-09-07T08:04:00Z", d.toISOString()); - d = new TbDate(23, 9, 7, 8, 4, 5); - Assert.assertEquals("2023-09-07T08:04:05Z", d.toISOString()); - d = new TbDate(23, 9, 7, 8, 4, 5, "+04:00"); - Assert.assertEquals("2023-09-07T04:04:05Z", d.toISOString()); - d = new TbDate(23, 9, 7, 8, 4, 5, "-03:00"); - Assert.assertEquals("2023-09-07T11:04:05Z", d.toISOString()); - d = new TbDate(23, 9, 7, 23, 4, 5, "-03:00"); - Assert.assertEquals("2023-09-08T02:04:05Z", d.toISOString()); - d = new TbDate(23, 9, 7, 23, 4, 5, 567,"-03:00"); - Assert.assertEquals("2023-09-08T02:04:05.567Z", d.toISOString()); - } - - @Test - void TestMethodGetAsDateUTC () { - TbDate dd = new TbDate(TbDate.UTC(1996, 2, 2, 3, 4, 5)); - Assert.assertEquals(823230245000L, dd.valueOf()); - dd = new TbDate(1996, 2, 2, 3, 4, 5); - Assert.assertEquals(823230245000L, dd.valueOf()); - TbDate beforeStartUTC = new TbDate(1969, 7, 20, 20, 17, 40); - Assert.assertEquals(-14182940000L, beforeStartUTC.getTime()); - - TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"+02:00"); - TbDate d2 = new TbDate(1975, 12, 31, 23,15,30, 567,"-02:00"); - - Assert.assertEquals(189292530567L, d1.getTime()); - Assert.assertEquals(189306930567L, d2.getTime()); - Assert.assertEquals(d1.getTimezoneOffset(), d2.getTimezoneOffset()); - - Assert.assertEquals(1975, d1.getUTCFullYear()); - Assert.assertEquals(1976, d2.getUTCFullYear()); - - Assert.assertEquals(12, d1.getUTCMonth()); - Assert.assertEquals(1, d2.getUTCMonth()); - - Assert.assertEquals(31, d1.getUTCDate()); - Assert.assertEquals(1, d2.getUTCDate()); - - Assert.assertEquals(3, d1.getUTCDay()); - Assert.assertEquals(4, d2.getUTCDay()); - - Assert.assertEquals(21, d1.getUTCHours()); - Assert.assertEquals(1, d2.getUTCHours()); - - Assert.assertEquals(15, d1.getUTCMinutes()); - Assert.assertEquals(15, d2.getUTCMinutes()); - - Assert.assertEquals(30, d1.getUTCSeconds()); - Assert.assertEquals(30, d2.getUTCSeconds()); - - Assert.assertEquals(567, d1.getUTCMilliseconds()); - Assert.assertEquals(567, d2.getUTCMilliseconds()); - } @Test - void TestMethodGetAsDateLocal () { - TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"+02:00"); - TbDate d2 = new TbDate(1975, 12, 31, 23,15,30, 567,"-02:00"); - TbDate dLocal1 = new TbDate(d1.parseSecondMilli()-d1.getTimezoneOffset()*60*1000); - TbDate dLocal2 = new TbDate(d2.parseSecondMilli()-d2.getTimezoneOffset()*60*1000); - + void TestMethodGetAsDateTimeLocal() { + TbDate d = new TbDate(1975, 12, 31, 23,15,30, 560); + TbDate d0 = new TbDate(1975, 12, 31, 23,15,30, 560,"UTC"); + TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 561,"+03:00"); + TbDate d2 = new TbDate(1975, 12, 31, 23,15,30, 562,"-02:00"); + TbDate dLocal = new TbDate(d.parseSecondMilli() + d.getLocaleZoneOffset().getTotalSeconds()*1000); + TbDate dLocal0 = new TbDate(d0.parseSecondMilli() + d0.getLocaleZoneOffset().getTotalSeconds()*1000); + TbDate dLocal1 = new TbDate(d1.parseSecondMilli() + d1.getLocaleZoneOffset().getTotalSeconds()*1000); + TbDate dLocal2 = new TbDate(d2.parseSecondMilli() + d2.getLocaleZoneOffset().getTotalSeconds()*1000); + + Assert.assertEquals(dLocal.getUTCFullYear(), d.getFullYear()); + Assert.assertEquals(dLocal0.getUTCFullYear(), d0.getFullYear()); Assert.assertEquals(dLocal1.getUTCFullYear(), d1.getFullYear()); Assert.assertEquals(dLocal2.getUTCFullYear(), d2.getFullYear()); + Assert.assertEquals(dLocal.getUTCMonth(), d.getMonth()); + Assert.assertEquals(dLocal0.getUTCMonth(), d0.getMonth()); Assert.assertEquals(dLocal1.getUTCMonth(), d1.getMonth()); Assert.assertEquals(dLocal2.getUTCMonth(), d2.getMonth()); + Assert.assertEquals(dLocal.getUTCDate(), d.getDate()); + Assert.assertEquals(dLocal0.getUTCDate(), d0.getDate()); Assert.assertEquals(dLocal1.getUTCDate(), d1.getDate()); Assert.assertEquals(dLocal2.getUTCDate(), d2.getDate()); + Assert.assertEquals(dLocal.getUTCDay(), d.getDay()); + Assert.assertEquals(dLocal0.getUTCDay(), d0.getDay()); Assert.assertEquals(dLocal1.getUTCDay(), d1.getDay()); - Assert.assertEquals(dLocal1.getUTCDay(), d2.getDay()); + Assert.assertEquals(dLocal2.getUTCDay(), d2.getDay()); + Assert.assertEquals(dLocal.getUTCHours(), d.getHours()); + Assert.assertEquals(dLocal0.getUTCHours(), d0.getHours()); Assert.assertEquals(dLocal1.getUTCHours(), d1.getHours()); Assert.assertEquals(dLocal2.getUTCHours(), d2.getHours()); + Assert.assertEquals(dLocal.getUTCMinutes(), d.getMinutes()); + Assert.assertEquals(dLocal0.getUTCMinutes(), d0.getMinutes()); Assert.assertEquals(dLocal1.getUTCMinutes(), d1.getMinutes()); Assert.assertEquals(dLocal2.getUTCMinutes(), d2.getMinutes()); + Assert.assertEquals(dLocal.getUTCSeconds(), d.getSeconds()); + Assert.assertEquals(dLocal0.getUTCSeconds(), d0.getSeconds()); Assert.assertEquals(dLocal1.getUTCSeconds(), d1.getSeconds()); Assert.assertEquals(dLocal2.getUTCSeconds(), d2.getSeconds()); + Assert.assertEquals(dLocal.getUTCMilliseconds(), d.getMilliseconds()); + Assert.assertEquals(dLocal0.getUTCMilliseconds(), d0.getMilliseconds()); Assert.assertEquals(dLocal1.getUTCMilliseconds(), d1.getMilliseconds()); Assert.assertEquals(dLocal2.getUTCMilliseconds(), d2.getMilliseconds()); } + @Test + void Test_Year_Moth_Date_Hours_Min_Sec_Without_TZ() { + TbDate d = new TbDate(2023, 8, 18); + Assert.assertEquals("2023-08-18 00:00:00", d.toLocaleString()); + d = new TbDate(2023, 9, 17, 17, 34); + Assert.assertEquals("2023-09-17 17:34:00", d.toLocaleString()); + d = new TbDate(23, 9, 7, 8, 4); + Assert.assertEquals("2023-09-07 08:04:00", d.toLocaleString()); + d = new TbDate(23, 9, 7, 8, 4, 5); + Assert.assertEquals("2023-09-07 08:04:05", d.toLocaleString()); + d = new TbDate(23, 9, 7, 8, 4, 5, 567); + Assert.assertEquals("2023-09-07 08:04:05", d.toLocaleString()); + } + + /** + * Tests for: + * min TZ = "Etc/GMT+12" = '-12' + * max TZ = "Etc/GMT-14" = '+14' + */ + @Test + void Test_Get_LocalDateTime_With_TZ() { + int hrs = 8; + int date = 7; + int tz = 0; + String pattern = "2023-09-%s %s:04:05"; + String tzStr = "+00:00"; + TbDate d = new TbDate(23, 9, date, hrs, 4, 5, tzStr); + int localOffsetHrs = ZoneId.systemDefault().getRules().getOffset(d.getInstant()).getTotalSeconds()/60/60; + TbDateTestEntity tbDateTest = new TbDateTestEntity(23, 9, date, hrs + localOffsetHrs - tz); + String expected = String.format(pattern, tbDateTest.geDateStr(), tbDateTest.geHoursStr()); + Assert.assertEquals(expected, d.toLocaleString()); + + tz = 3; + tzStr = "+03:00"; + d = new TbDate(23, 9, date, hrs, 4, 5, tzStr); + localOffsetHrs = ZoneId.systemDefault().getRules().getOffset(d.getInstant()).getTotalSeconds()/60/60; + tbDateTest = new TbDateTestEntity(23, 9, date, hrs + localOffsetHrs - tz); + expected = String.format(pattern, tbDateTest.geDateStr(), tbDateTest.geHoursStr()); + Assert.assertEquals(expected, d.toLocaleString()); + + tz = -4; + tzStr = "-04:00"; + d = new TbDate(23, 9, date, hrs, 4, 5, tzStr); + localOffsetHrs = ZoneId.systemDefault().getRules().getOffset(d.getInstant()).getTotalSeconds()/60/60; + tbDateTest = new TbDateTestEntity(23, 9, date, hrs + localOffsetHrs - tz); + expected = String.format(pattern, tbDateTest.geDateStr(), tbDateTest.geHoursStr()); + Assert.assertEquals(expected, d.toLocaleString()); + } + + @Test + void TestMethodGetTimeUTC() { + TbDate dd = new TbDate(TbDate.UTC(1996, 1, 2, 3, 4, 5)); + Assert.assertEquals(820551845000L, dd.valueOf()); + dd = new TbDate(1996, 1, 2, 3, 4, 5); + Assert.assertEquals(820544645000L, dd.valueOf()); + TbDate beforeStartUTC = new TbDate(1969, 7, 20, 20, 17, 40); + Assert.assertEquals(-14193740000L, beforeStartUTC.getTime()); + + TbDate d = new TbDate(1975, 12, 31, 23,15,30, 560); + TbDate d0 = new TbDate(1975, 12, 31, 23,15,30, 560,"UTC"); + TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 561,"+03:00"); + TbDate d2 = new TbDate(1975, 12, 31, 23,15,30, 562,"-04:00"); + + Assert.assertEquals(189288930560L, d.valueOf()); + Assert.assertEquals(189299730560L, d0.valueOf()); + Assert.assertEquals(189288930561L, d1.valueOf()); + Assert.assertEquals(189314130562L, d2.getTime()); + } + @Test void TestMethodSetUTCFullYearMonthDate() { - TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"-03:00"); - Assert.assertEquals(1976, d1.getUTCFullYear()); - Assert.assertEquals(4, d1.getUTCDay()); + TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"-04:00"); + TbDate d2 = new TbDate(1975, 12, 31, 23,15,30, 567,"+04:00"); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + d1.setUTCFullYear(1969); - Assert.assertEquals(1969, d1.getUTCFullYear()); - d1.setUTCFullYear(1975); - Assert.assertEquals(1975, d1.getUTCFullYear()); - Assert.assertEquals(3, d1.getUTCDay()); - d1.setUTCFullYear(1977, 4); - Assert.assertEquals(1977, d1.getUTCFullYear()); - Assert.assertEquals(4, d1.getUTCMonth()); - Assert.assertEquals(5, d1.getUTCDay()); - d1.setUTCFullYear(2023, 2, 24); - Assert.assertEquals(2023, d1.getUTCFullYear()); - Assert.assertEquals(2, d1.getUTCMonth()); - Assert.assertEquals(24, d1.getUTCDate()); - Assert.assertEquals(5, d1.getUTCDay()); + d2.setUTCFullYear(1969); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + + d1.setUTCFullYear(1975, 5); + d2.setUTCFullYear(2023, 11); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + + d1.setUTCFullYear(2023, 2, 28); + d2.setUTCFullYear(2023, 12, 31); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); d1.setUTCMonth(11); - Assert.assertEquals(11, d1.getUTCMonth()); - Assert.assertEquals(5, d1.getUTCDay()); - d1.setUTCMonth(2, 28); - Assert.assertEquals(2, d1.getUTCMonth()); - Assert.assertEquals(28, d1.getUTCDate()); - Assert.assertEquals(2, d1.getUTCDay()); - - d1.setUTCDate(11); - Assert.assertEquals(11, d1.getUTCDate()); - Assert.assertEquals(6, d1.getUTCDay()); + d2.setUTCMonth(2); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + + d1.setUTCMonth(5, 20); + d2.setUTCMonth(2, 8); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + + d1.setUTCDate(6); + d2.setUTCDate(15); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); } @Test void TestMethodSetUTCHoursMinutesSecondsMilliSec() { - TbDate d1 = new TbDate(1975, 12, 31, 23, 15, 30, 567, "-03:00"); - Assert.assertEquals(2, d1.getUTCHours()); - Assert.assertEquals(4, d1.getUTCDay()); + TbDate d1 = new TbDate(1975, 12, 31, 23, 15, 30, 567, "+02:00"); + TbDate d2 = new TbDate(1975, 12, 31, 23, 15, 30, 567, "-02:00"); + + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); d1.setUTCHours(5); - Assert.assertEquals(5, d1.getUTCHours()); - Assert.assertEquals(4, d1.getUTCDay()); - d1.setUTCHours(12, 45); - Assert.assertEquals(12, d1.getUTCHours()); - Assert.assertEquals(45, d1.getUTCMinutes()); - Assert.assertEquals(4, d1.getUTCDay()); + d2.setUTCHours(23); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + + d1.setUTCHours(23, 45); + d2.setUTCHours(1, 5); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + d1.setUTCHours(0, 12, 59); - Assert.assertEquals(0, d1.getUTCHours()); - Assert.assertEquals(12, d1.getUTCMinutes()); - Assert.assertEquals(59, d1.getUTCSeconds()); - Assert.assertEquals(4, d1.getUTCDay()); - d1.setUTCHours(4, 58, 2, 456); - Assert.assertEquals(4, d1.getUTCHours()); - Assert.assertEquals(58, d1.getUTCMinutes()); - Assert.assertEquals(2, d1.getUTCSeconds()); - Assert.assertEquals(456, d1.getUTCMilliseconds()); - Assert.assertEquals(4, d1.getUTCDay()); + d2.setUTCHours(4, 45, 01); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + + d1.setUTCHours(2, 32, 49, 123); + d2.setUTCHours(8, 45, 12, 234); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); d1.setUTCMinutes(5); - Assert.assertEquals(5, d1.getUTCMinutes()); - Assert.assertEquals(4, d1.getUTCDay()); - d1.setUTCMinutes(15, 32); - Assert.assertEquals(15, d1.getUTCMinutes()); - Assert.assertEquals(32, d1.getUTCSeconds()); - Assert.assertEquals(4, d1.getUTCDay()); - d1.setUTCMinutes(10, 42, 321); - Assert.assertEquals(10, d1.getUTCMinutes()); - Assert.assertEquals(42, d1.getUTCSeconds()); - Assert.assertEquals(321, d1.getUTCMilliseconds()); - Assert.assertEquals(4, d1.getUTCDay()); + d2.setUTCMinutes(15); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); - d1.setUTCSeconds(5); - Assert.assertEquals(5, d1.getUTCSeconds()); - Assert.assertEquals(4, d1.getUTCDay()); - d1.setUTCSeconds(15, 32); - Assert.assertEquals(15, d1.getUTCSeconds()); - Assert.assertEquals(32, d1.getUTCMilliseconds()); - Assert.assertEquals(4, d1.getUTCDay()); + d1.setUTCMinutes(5, 34); + d2.setUTCMinutes(25, 43); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); - d1.setUTCMilliseconds(5); - Assert.assertEquals(5, d1.getUTCMilliseconds()); - Assert.assertEquals(4, d1.getUTCDay()); + d1.setUTCMinutes(25, 14, 567); + d2.setUTCMinutes(45, 3, 876); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + + d1.setUTCSeconds(5); + d2.setUTCSeconds(23); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + + d1.setUTCSeconds(45, 987); + d2.setUTCSeconds(54, 842); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + + d1.setUTCMilliseconds(675); + d1.setUTCMilliseconds(923); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); } @Test - void TestMethodSetTome() { + void TestMethodSetTime() { TbDate d1 = new TbDate(1975, 12, 31, 23, 15, 30, 567, "-03:00"); long dateMilliSecond = d1.getTime(); int fiveMinutesInMillis = 5 * 60 * 1000; @@ -533,121 +626,141 @@ class TbDateTest { Assert.assertEquals(567, d1.getUTCMilliseconds()); Assert.assertEquals(3, d1.getUTCDay()); } - - @Test + @Test void TestMethodSeFullYearMonthDate() { - TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"-03:00"); - Assert.assertEquals(1976, d1.getFullYear()); - Assert.assertEquals(1, d1.getMonth()); - Assert.assertEquals(1, d1.getDate()); - Assert.assertEquals(4, d1.getUTCDay()); - d1.setFullYear(1969); - Assert.assertEquals(1969, d1.getFullYear()); - d1.setUTCFullYear(1975); - Assert.assertEquals(1975, d1.getFullYear()); - Assert.assertEquals(3, d1.getUTCDay()); - d1.setFullYear(1977, 4); - Assert.assertEquals(1977, d1.getFullYear()); - Assert.assertEquals(4, d1.getMonth()); - Assert.assertEquals(5, d1.getDay()); - d1.setFullYear(2023, 2, 24); - Assert.assertEquals(2023, d1.getFullYear()); - Assert.assertEquals(2, d1.getMonth()); - Assert.assertEquals(24, d1.getDate()); - Assert.assertEquals(5, d1.getDay()); - - d1.setMonth(11); - Assert.assertEquals(11, d1.getMonth()); - Assert.assertEquals(5, d1.getDay()); - d1.setMonth(2, 28); - Assert.assertEquals(2, d1.getMonth()); - Assert.assertEquals(28, d1.getDate()); - Assert.assertEquals(2, d1.getDay()); - - d1.setDate(11); - Assert.assertEquals(11, d1.getDate()); - Assert.assertEquals(6, d1.getDay()); - } - @Test + TbDate d = new TbDate(2024, 1, 1, 1, 15, 30, 567); + testResultChangeDateTime(d); + + d = new TbDate(2023, 12, 31, 22, 15, 30, 567); + testResultChangeDateTime(d); + + d = new TbDate(1975, 12, 31, 1, 15, 30, 567); + testResultChangeDateTime(d); + + d.setFullYear(1969); + testResultChangeDateTime(d); + + d.setFullYear(1975, 2); + testResultChangeDateTime(d); + + d.setFullYear(2023, 6, 30); + testResultChangeDateTime(d); + + d.setMonth(2); + testResultChangeDateTime(d); + + d.setMonth(1, 24); + testResultChangeDateTime(d); + + d.setDate(6); + testResultChangeDateTime(d); + } + @Test void TestMethodSeHoursMinutesSecondsMilliSec() { - TbDate d1 = new TbDate(1975, 12, 31, 23, 15, 30, 567, "-03:00"); - Assert.assertEquals(5, d1.getHours()); - Assert.assertEquals(4, d1.getDay()); + TbDate d1 = new TbDate(1975, 12, 31, 23, 15, 30, 567, "+02:00"); + TbDate d2 = new TbDate(1975, 12, 31, 23, 15, 30, 567, "-02:00"); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); d1.setHours(5); - Assert.assertEquals(8, d1.getHours()); - Assert.assertEquals(4, d1.getDay()); + d2.setHours(23); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + d1.setHours(23, 45); - Assert.assertEquals(1, d1.getMonth()); - Assert.assertEquals(2, d1.getDate()); - Assert.assertEquals(2, d1.getHours()); - Assert.assertEquals(45, d1.getMinutes()); - Assert.assertEquals(5, d1.getDay()); - d1.setUTCHours(0, 12, 59); - Assert.assertEquals(3, d1.getHours()); - Assert.assertEquals(12, d1.getMinutes()); - Assert.assertEquals(59, d1.getSeconds()); - Assert.assertEquals(4, d1.getDay()); - d1.setUTCHours(4, 58, 2, 456); - Assert.assertEquals(7, d1.getHours()); - Assert.assertEquals(58, d1.getMinutes()); - Assert.assertEquals(2, d1.getSeconds()); - Assert.assertEquals(456, d1.getMilliseconds()); - Assert.assertEquals(4, d1.getDay()); + d2.setHours(1, 5); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + + d1.setHours(0, 12, 59); + d2.setHours(4, 45, 1); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); + + d1.setHours(2, 32, 49, 123); + d2.setHours(8, 45, 12, 234); + testResultChangeDateTime(d1); + testResultChangeDateTime(d2); d1.setMinutes(5); + d2.setMinutes(15); Assert.assertEquals(5, d1.getMinutes()); - Assert.assertEquals(4, d1.getUTCDay()); - d1.setMinutes(15, 32); - Assert.assertEquals(15, d1.getMinutes()); - Assert.assertEquals(32, d1.getSeconds()); - Assert.assertEquals(4, d1.getDay()); - d1.setMinutes(10, 42, 321); - Assert.assertEquals(10, d1.getMinutes()); - Assert.assertEquals(42, d1.getSeconds()); - Assert.assertEquals(321, d1.getMilliseconds()); - Assert.assertEquals(4, d1.getDay()); + Assert.assertEquals(15, d2.getMinutes()); + + d1.setMinutes(5, 34); + d2.setMinutes(25, 43); + Assert.assertEquals(5, d1.getMinutes()); + Assert.assertEquals(34, d1.getSeconds()); + Assert.assertEquals(25, d2.getMinutes()); + Assert.assertEquals(43, d2.getSeconds()); + + d1.setMinutes(25, 14, 567); + d2.setMinutes(45, 3, 876); + Assert.assertEquals(25, d1.getMinutes()); + Assert.assertEquals(14, d1.getSeconds()); + Assert.assertEquals(567, d1.getMilliseconds()); + Assert.assertEquals(45, d2.getMinutes()); + Assert.assertEquals(3, d2.getSeconds()); + Assert.assertEquals(876, d2.getMilliseconds()); d1.setSeconds(5); + d2.setSeconds(23); Assert.assertEquals(5, d1.getSeconds()); - Assert.assertEquals(4, d1.getDay()); - d1.setSeconds(15, 32); - Assert.assertEquals(15, d1.getSeconds()); - Assert.assertEquals(32, d1.getMilliseconds()); - Assert.assertEquals(4, d1.getDay()); - - d1.setMilliseconds(5); - Assert.assertEquals(5, d1.getMilliseconds()); - Assert.assertEquals(4, d1.getDay()); + Assert.assertEquals(23, d2.getSeconds()); + + d1.setSeconds(45, 987); + d2.setSeconds(54, 842); + Assert.assertEquals(45, d1.getSeconds()); + Assert.assertEquals(987, d1.getMilliseconds()); + Assert.assertEquals(54, d2.getSeconds()); + Assert.assertEquals(842, d2.getMilliseconds()); + + d1.setMilliseconds(675); + d2.setMilliseconds(923); + Assert.assertEquals(675, d1.getMilliseconds()); + Assert.assertEquals(923, d2.getMilliseconds()); } @Test public void toStringAsJs() { - TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"-14:00"); - Assert.assertEquals("1976 Jan 1, Thu 16:15:30 Eastern European Time", d1.toString()); - Assert.assertEquals("1976 Jan 1, Thu 16:15:30 Eastern European Time", d1.toString("GMT")); - Assert.assertEquals("1976 Jan 1, Thu 16:15:30 Eastern European Time", d1.toString("UTC")); - Assert.assertEquals("Thursday, January 1, 1976 at 4:15:30 PM Eastern European Standard Time", d1.toString("en-US")); - Assert.assertEquals("1976 Jan 1, Thu 16:15:30", d1.toUTCString()); - Assert.assertEquals("четвер, 1 січня 1976 р., 16:15:30", d1.toUTCString("uk-UA")); - Assert.assertEquals("Thursday, January 1, 1976, 4:15:30 PM", d1.toUTCString("en-US")); - Assert.assertEquals("четвер, 1 січня 1976 р., 16:15:30", d1.toUTCString("uk-UA")); - Assert.assertEquals("Thursday, January 1, 1976, 4:15:30 PM", d1.toUTCString("en-US")); - Assert.assertEquals("1976 Jan 1, Thu", d1.toDateString()); - Assert.assertEquals("четвер, 1 січня 1976 р.", d1.toDateString("uk-UA")); - Assert.assertEquals("Thursday, January 1, 1976", d1.toDateString("en-US")); - Assert.assertEquals("16:15:30 Eastern European Time", d1.toTimeString()); - Assert.assertEquals("16:15:30 за східноєвропейським стандартним часом", d1.toTimeString("uk-UA")); - Assert.assertEquals("4:15:30 PM Eastern European Standard Time", d1.toTimeString("en-US")); - Assert.assertEquals("1976-01-01T13:15:30.567Z", d1.toJSON()); + TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"-04:00"); + Assert.assertEquals("четвер, 1 січня 1976 р. о 06:15:30 за східноєвропейським стандартним часом", d1.toString("uk-UA", "Europe/Kyiv")); + Assert.assertEquals("Thursday, January 1, 1976 at 6:15:30 AM Eastern European Standard Time", d1.toString("en-US", "Europe/Kyiv")); + Assert.assertEquals("1976 Jan 1, Thu 06:15:30 Eastern European Time", d1.toString("UTC", "Europe/Kyiv")); + Assert.assertEquals("Wednesday, December 31, 1975 at 10:15:30 PM Eastern Standard Time", d1.toString("en-US", "America/New_York")); + Assert.assertEquals("1975 Dec 31, Wed 22:15:30 Eastern Standard Time", d1.toString("GMT", "America/New_York")); + Assert.assertEquals("1975 Dec 31, Wed 22:15:30 Eastern Standard Time", d1.toString("UTC", "America/New_York")); + + Assert.assertEquals(d1.toUTCString("UTC"), d1.toUTCString()); + Assert.assertEquals("четвер, 1 січня 1976 р., 03:15:30", d1.toUTCString("uk-UA")); + Assert.assertEquals("Thursday, January 1, 1976, 3:15:30 AM", d1.toUTCString("en-US")); + + Assert.assertEquals("1976-01-01T03:15:30.567Z", d1.toJSON()); + Assert.assertEquals("1976-01-01T03:15:30.567Z", d1.toISOString()); + + Assert.assertEquals("1976-01-01 06:15:30", d1.toLocaleString("UTC", "Europe/Kyiv")); + Assert.assertEquals("01.01.76, 06:15:30", d1.toLocaleString("uk-UA", "Europe/Kyiv")); + Assert.assertEquals("1975-12-31 22:15:30", d1.toLocaleString("UTC", "America/New_York")); + Assert.assertEquals("12/31/75, 10:15:30 PM", d1.toLocaleString("en-US", "America/New_York")); + + Assert.assertEquals("1976 Jan 1, Thu", d1.toDateString("UTC", "Europe/Kyiv")); + Assert.assertEquals("четвер, 1 січня 1976 р.", d1.toDateString("uk-UA", "Europe/Kyiv")); + Assert.assertEquals("1975 Dec 31, Wed", d1.toDateString("UTC", "America/New_York")); + Assert.assertEquals("Wednesday, December 31, 1975", d1.toDateString("en-US", "America/New_York")); + + + Assert.assertEquals("06:15:30", d1.toLocaleTimeString("uk-UA", "Europe/Kyiv")); + Assert.assertEquals("06:15:30", d1.toLocaleTimeString("UTC", "Europe/Kyiv")); + Assert.assertEquals("10:15:30 PM", d1.toLocaleTimeString("en-US", "America/New_York")); + Assert.assertEquals("22:15:30", d1.toLocaleTimeString("UTC", "America/New_York")); + + Assert.assertEquals("06:15:30 за східноєвропейським стандартним часом", d1.toTimeString("uk-UA", "Europe/Kyiv")); + Assert.assertEquals("06:15:30 Eastern European Time", d1.toTimeString("UTC", "Europe/Kyiv")); + Assert.assertEquals("10:15:30 PM Eastern Standard Time", d1.toTimeString("en-US", "America/New_York")); + Assert.assertEquals("22:15:30 Eastern Standard Time", d1.toTimeString("UTC", "America/New_York")); + + } - /** - * Date.UTC(0) - * -2208988800000 - * > "Mon, 01 Jan 1900 00:00:00 GMT" - * new Date(Date.UTC(0, 0, 0, 0, 0, 0)); - * "Sun, 31 Dec 1899 00:00:00 GMT" - */ @Test public void toUTC() { Assert.assertEquals(-2209075200000L, TbDate.UTC(0)); @@ -658,11 +771,23 @@ class TbDateTest { Assert.assertEquals("1958-12-31T03:04:05.678Z", new TbDate(TbDate.UTC(1958, 0, 0, 3, 4, 5, 678)).toJSON()); Assert.assertEquals("2032-04-05T03:04:05.678Z", new TbDate(TbDate.UTC(2032, 4, 5, 3, 4, 5, 678)).toJSON()); Assert.assertEquals("2024-02-29T03:04:05.678Z", new TbDate(TbDate.UTC(2024, 2, 29, 3, 4, 5, 678)).toJSON()); - Exception actual = assertThrows(DateTimeParseException.class, () -> { + Exception actual = assertThrows(DateTimeException.class, () -> { TbDate.UTC(2023, 2, 29, 3, 4, 5, 678); }); - String expectedMessage = "could not be parsed"; + String expectedMessage = "Invalid date 'February 29' as '2023' is not a leap year"; assertTrue(actual.getMessage().contains(expectedMessage)); } + + private void testResultChangeDateTime(TbDate d) { + int localOffset = ZoneId.systemDefault().getRules().getOffset(d.getInstant()).getTotalSeconds(); + TbDateTestEntity tbDateTestEntity = new TbDateTestEntity(d.getFullYear(), d.getMonth(), d.getDate(), (d.getHours() - (localOffset/60/60))); + Assert.assertEquals(tbDateTestEntity.getYear(), d.getUTCFullYear()); + Assert.assertEquals(tbDateTestEntity.getMonth(), d.getUTCMonth()); + Assert.assertEquals(tbDateTestEntity.getDate(), d.getUTCDate()); + Assert.assertEquals(tbDateTestEntity.getHours(), d.getUTCHours()); + Assert.assertEquals(d.getMinutes(), d.getUTCMinutes()); + Assert.assertEquals(d.getSeconds(), d.getUTCSeconds()); + Assert.assertEquals(d.getMilliseconds(), d.getUTCMilliseconds()); + } } diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTestEntity.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTestEntity.java new file mode 100644 index 0000000000..771eba896b --- /dev/null +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTestEntity.java @@ -0,0 +1,90 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api.tbel; + +import lombok.Data; +import java.time.chrono.IsoChronology; +@Data +public class TbDateTestEntity { + private int year; + private int month; + private int date; + private int hours; + public TbDateTestEntity(int year, int month, int date, int hours) { + this.year = year; + this.month = month; + this.date = date; + this.hours = hours; + if (hours > 23) { + if (date == 31) { + this.year++; + this.month = 1; + this.date = 1; + } else { + this.date++; + } + this.hours = hours - 24; + } else if (hours < 0) { + if (month== 1 && date == 1) { + this.year--; + this.month = 12; + this.date = 31; + } else { + this.date--; + } + this.hours = hours + 24; + } + + if (this.date > 28) { + int dom = 31; + switch (month) { + case 2: + dom = IsoChronology.INSTANCE.isLeapYear((long) year) ? 29 : 28; + case 3: + case 5: + case 7: + case 8: + case 10: + default: + break; + case 4: + case 6: + case 9: + case 11: + dom = 30; + } + if (this.date > dom) { + this.date = this.date - dom; + this.month++; + } + } + } + public int getYear(){ + return year < 70 ? 2000 + year : year <= 99 ? 1900 + year : year; + } + + public String geMonthStr(){ + return String.format("%02d", month); + } + + public String geDateStr(){ + return String.format("%02d", date); + } + + public String geHoursStr(){ + return String.format("%02d", hours); + } +} From 69bcd8426e35d85f4cdb1b2b5c19e0698563182a Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 17 Oct 2023 18:42:28 +0300 Subject: [PATCH 042/209] tbDate: Tests were checked with: * -timezone Etc/GMT-14 * -timezone Etc/GMT+12 * -timezone America/New_York * -timezone Europe/Kyiv --- .../script/api/tbel/TbDateTest.java | 86 +++++++++---------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index f020d3e092..af9ff4c5e1 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -71,6 +71,11 @@ class TbDateTest { * at java.base/java.util.Calendar.setTimeInMillis(Calendar.java:1834) * at java.base/java.util.Calendar.setTime(Calendar.java:1800) * at java.base/java.text.SimpleDateFormat.format(SimpleDateFormat.java:974) + * Tests were checked with: + * -timezone Etc/GMT-14 + * -timezone Etc/GMT+12 + * -timezone America/New_York + * -timezone Europe/Kyiv */ @Test void testToISOStringConcurrently() throws ExecutionException, InterruptedException, TimeoutException { @@ -455,24 +460,25 @@ class TbDateTest { Assert.assertEquals("2023-09-07 08:04:05", d.toLocaleString()); } - /** - * Tests for: - * min TZ = "Etc/GMT+12" = '-12' - * max TZ = "Etc/GMT-14" = '+14' - */ @Test void Test_Get_LocalDateTime_With_TZ() { int hrs = 8; int date = 7; int tz = 0; String pattern = "2023-09-%s %s:04:05"; - String tzStr = "+00:00"; - TbDate d = new TbDate(23, 9, date, hrs, 4, 5, tzStr); - int localOffsetHrs = ZoneId.systemDefault().getRules().getOffset(d.getInstant()).getTotalSeconds()/60/60; + String tzStr = "UTC"; + TbDate d = new TbDate(23, 9, date, hrs, 4, 5); + int localOffsetHrs = 0; TbDateTestEntity tbDateTest = new TbDateTestEntity(23, 9, date, hrs + localOffsetHrs - tz); String expected = String.format(pattern, tbDateTest.geDateStr(), tbDateTest.geHoursStr()); Assert.assertEquals(expected, d.toLocaleString()); + d = new TbDate(23, 9, date, hrs, 4, 5, tzStr); + localOffsetHrs = ZoneId.systemDefault().getRules().getOffset(d.getInstant()).getTotalSeconds()/60/60; + tbDateTest = new TbDateTestEntity(23, 9, date, hrs + localOffsetHrs - tz); + expected = String.format(pattern, tbDateTest.geDateStr(), tbDateTest.geHoursStr()); + Assert.assertEquals(expected, d.toLocaleString()); + tz = 3; tzStr = "+03:00"; d = new TbDate(23, 9, date, hrs, 4, 5, tzStr); @@ -490,24 +496,36 @@ class TbDateTest { Assert.assertEquals(expected, d.toLocaleString()); } + @Test + public void TestToUTC() { + Assert.assertEquals(-2209075200000L, TbDate.UTC(0)); + Assert.assertEquals("1899-12-31T00:00:00Z", new TbDate(TbDate.UTC(0)).toJSON()); + Assert.assertEquals("1996-02-02T03:04:05Z", new TbDate(TbDate.UTC(96, 2, 2, 3, 4, 5)).toJSON()); + Assert.assertEquals("2022-12-31T03:04:05.678Z", new TbDate(TbDate.UTC(22, 0, 0, 3, 4, 5, 678)).toJSON()); + Assert.assertEquals("0903-12-31T03:04:05.678Z", new TbDate(TbDate.UTC(903, 0, 0, 3, 4, 5, 678)).toJSON()); + Assert.assertEquals("1958-12-31T03:04:05.678Z", new TbDate(TbDate.UTC(1958, 0, 0, 3, 4, 5, 678)).toJSON()); + Assert.assertEquals("2032-04-05T03:04:05.678Z", new TbDate(TbDate.UTC(2032, 4, 5, 3, 4, 5, 678)).toJSON()); + Assert.assertEquals("2024-02-29T03:04:05.678Z", new TbDate(TbDate.UTC(2024, 2, 29, 3, 4, 5, 678)).toJSON()); + Exception actual = assertThrows(DateTimeException.class, () -> { + TbDate.UTC(2023, 2, 29, 3, 4, 5, 678); + }); + String expectedMessage = "Invalid date 'February 29' as '2023' is not a leap year"; + assertTrue(actual.getMessage().contains(expectedMessage)); + } + @Test void TestMethodGetTimeUTC() { - TbDate dd = new TbDate(TbDate.UTC(1996, 1, 2, 3, 4, 5)); - Assert.assertEquals(820551845000L, dd.valueOf()); - dd = new TbDate(1996, 1, 2, 3, 4, 5); - Assert.assertEquals(820544645000L, dd.valueOf()); + TbDate ddUTC = new TbDate(TbDate.UTC(1996, 1, 2, 3, 4, 5)); + Assert.assertEquals(820551845000L, ddUTC.valueOf()); + TbDate dd = new TbDate(1996, 1, 2, 3, 4, 5); + int localOffsetMilli = ZoneId.systemDefault().getRules().getOffset(dd.getInstant()).getTotalSeconds()*1000; + Assert.assertEquals((dd.valueOf() + localOffsetMilli), ddUTC.valueOf()); + + ddUTC = new TbDate(TbDate.UTC(1969, 7, 20, 20, 17, 40)); + Assert.assertEquals(-14182940000L, ddUTC.valueOf()); TbDate beforeStartUTC = new TbDate(1969, 7, 20, 20, 17, 40); - Assert.assertEquals(-14193740000L, beforeStartUTC.getTime()); - - TbDate d = new TbDate(1975, 12, 31, 23,15,30, 560); - TbDate d0 = new TbDate(1975, 12, 31, 23,15,30, 560,"UTC"); - TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 561,"+03:00"); - TbDate d2 = new TbDate(1975, 12, 31, 23,15,30, 562,"-04:00"); - - Assert.assertEquals(189288930560L, d.valueOf()); - Assert.assertEquals(189299730560L, d0.valueOf()); - Assert.assertEquals(189288930561L, d1.valueOf()); - Assert.assertEquals(189314130562L, d2.getTime()); + localOffsetMilli = ZoneId.systemDefault().getRules().getOffset(beforeStartUTC.getInstant()).getTotalSeconds()*1000; + Assert.assertEquals((beforeStartUTC.valueOf() + localOffsetMilli), ddUTC.valueOf()); } @Test @@ -747,35 +765,15 @@ class TbDateTest { Assert.assertEquals("1975 Dec 31, Wed", d1.toDateString("UTC", "America/New_York")); Assert.assertEquals("Wednesday, December 31, 1975", d1.toDateString("en-US", "America/New_York")); - Assert.assertEquals("06:15:30", d1.toLocaleTimeString("uk-UA", "Europe/Kyiv")); Assert.assertEquals("06:15:30", d1.toLocaleTimeString("UTC", "Europe/Kyiv")); Assert.assertEquals("10:15:30 PM", d1.toLocaleTimeString("en-US", "America/New_York")); Assert.assertEquals("22:15:30", d1.toLocaleTimeString("UTC", "America/New_York")); - Assert.assertEquals("06:15:30 за східноєвропейським стандартним часом", d1.toTimeString("uk-UA", "Europe/Kyiv")); + Assert.assertEquals("06:15:30 за східноєвропейським стандартним часом", d1.toTimeString("uk-UA", "Europe/Kyiv")); Assert.assertEquals("06:15:30 Eastern European Time", d1.toTimeString("UTC", "Europe/Kyiv")); Assert.assertEquals("10:15:30 PM Eastern Standard Time", d1.toTimeString("en-US", "America/New_York")); Assert.assertEquals("22:15:30 Eastern Standard Time", d1.toTimeString("UTC", "America/New_York")); - - - } - - @Test - public void toUTC() { - Assert.assertEquals(-2209075200000L, TbDate.UTC(0)); - Assert.assertEquals("1899-12-31T00:00:00Z", new TbDate(TbDate.UTC(0)).toJSON()); - Assert.assertEquals("1996-02-02T03:04:05Z", new TbDate(TbDate.UTC(96, 2, 2, 3, 4, 5)).toJSON()); - Assert.assertEquals("2022-12-31T03:04:05.678Z", new TbDate(TbDate.UTC(22, 0, 0, 3, 4, 5, 678)).toJSON()); - Assert.assertEquals("0903-12-31T03:04:05.678Z", new TbDate(TbDate.UTC(903, 0, 0, 3, 4, 5, 678)).toJSON()); - Assert.assertEquals("1958-12-31T03:04:05.678Z", new TbDate(TbDate.UTC(1958, 0, 0, 3, 4, 5, 678)).toJSON()); - Assert.assertEquals("2032-04-05T03:04:05.678Z", new TbDate(TbDate.UTC(2032, 4, 5, 3, 4, 5, 678)).toJSON()); - Assert.assertEquals("2024-02-29T03:04:05.678Z", new TbDate(TbDate.UTC(2024, 2, 29, 3, 4, 5, 678)).toJSON()); - Exception actual = assertThrows(DateTimeException.class, () -> { - TbDate.UTC(2023, 2, 29, 3, 4, 5, 678); - }); - String expectedMessage = "Invalid date 'February 29' as '2023' is not a leap year"; - assertTrue(actual.getMessage().contains(expectedMessage)); } private void testResultChangeDateTime(TbDate d) { From 76ff8f4d93ecf90b2f0017fb0bf2463d1aa47eee Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 24 Oct 2023 13:56:15 +0200 Subject: [PATCH 043/209] docker FROM thingsboard/openjdk17:bookworm-slim --- msa/monitoring/docker/Dockerfile | 2 +- msa/tb-node/docker/Dockerfile | 2 +- msa/tb/docker-cassandra/Dockerfile | 2 +- msa/tb/docker-postgres/Dockerfile | 2 +- msa/transport/coap/docker/Dockerfile | 2 +- msa/transport/http/docker/Dockerfile | 2 +- msa/transport/lwm2m/docker/Dockerfile | 2 +- msa/transport/mqtt/docker/Dockerfile | 2 +- msa/transport/snmp/docker/Dockerfile | 2 +- msa/vc-executor-docker/docker/Dockerfile | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/msa/monitoring/docker/Dockerfile b/msa/monitoring/docker/Dockerfile index 14c6246280..ce27eb175b 100644 --- a/msa/monitoring/docker/Dockerfile +++ b/msa/monitoring/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bullseye-slim +FROM thingsboard/openjdk17:bookworm-slim COPY start-tb-monitoring.sh ${pkg.name}.deb /tmp/ diff --git a/msa/tb-node/docker/Dockerfile b/msa/tb-node/docker/Dockerfile index 2c8af18f9a..8dcba61255 100644 --- a/msa/tb-node/docker/Dockerfile +++ b/msa/tb-node/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bullseye-slim +FROM thingsboard/openjdk17:bookworm-slim COPY start-tb-node.sh ${pkg.name}.deb /tmp/ diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index f871d57ff8..0db1f47331 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bullseye-slim +FROM thingsboard/openjdk17:bookworm-slim ENV PG_MAJOR=12 diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile index 2520bee62b..be9cc2799b 100644 --- a/msa/tb/docker-postgres/Dockerfile +++ b/msa/tb/docker-postgres/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bullseye-slim +FROM thingsboard/openjdk17:bookworm-slim ENV PG_MAJOR 12 diff --git a/msa/transport/coap/docker/Dockerfile b/msa/transport/coap/docker/Dockerfile index 526a8620a8..9ed00eeaf8 100644 --- a/msa/transport/coap/docker/Dockerfile +++ b/msa/transport/coap/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bullseye-slim +FROM thingsboard/openjdk17:bookworm-slim COPY start-tb-coap-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/transport/http/docker/Dockerfile b/msa/transport/http/docker/Dockerfile index 399df84000..17e1ed3dbb 100644 --- a/msa/transport/http/docker/Dockerfile +++ b/msa/transport/http/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bullseye-slim +FROM thingsboard/openjdk17:bookworm-slim COPY start-tb-http-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/transport/lwm2m/docker/Dockerfile b/msa/transport/lwm2m/docker/Dockerfile index d17f368c8f..2d41c9a0fe 100644 --- a/msa/transport/lwm2m/docker/Dockerfile +++ b/msa/transport/lwm2m/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bullseye-slim +FROM thingsboard/openjdk17:bookworm-slim COPY start-tb-lwm2m-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/transport/mqtt/docker/Dockerfile b/msa/transport/mqtt/docker/Dockerfile index 769164b86e..de89c64a58 100644 --- a/msa/transport/mqtt/docker/Dockerfile +++ b/msa/transport/mqtt/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bullseye-slim +FROM thingsboard/openjdk17:bookworm-slim COPY start-tb-mqtt-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/transport/snmp/docker/Dockerfile b/msa/transport/snmp/docker/Dockerfile index 2835c56bc7..755a17f495 100644 --- a/msa/transport/snmp/docker/Dockerfile +++ b/msa/transport/snmp/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bullseye-slim +FROM thingsboard/openjdk17:bookworm-slim COPY start-tb-snmp-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/vc-executor-docker/docker/Dockerfile b/msa/vc-executor-docker/docker/Dockerfile index 70a3d3e18f..68f9d938f9 100644 --- a/msa/vc-executor-docker/docker/Dockerfile +++ b/msa/vc-executor-docker/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bullseye-slim +FROM thingsboard/openjdk17:bookworm-slim COPY start-tb-vc-executor.sh ${pkg.name}.deb /tmp/ From f4a4782d83593821d3cb67b47a76c5599b3312f3 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 24 Oct 2023 13:57:22 +0200 Subject: [PATCH 044/209] docker: removed as it exists in base image: echo 'networkaddress.cache.ttl=60' >> /etc/java-17-openjdk/security/java.security --- msa/tb-node/docker/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/msa/tb-node/docker/Dockerfile b/msa/tb-node/docker/Dockerfile index 8dcba61255..506d41f8ec 100644 --- a/msa/tb-node/docker/Dockerfile +++ b/msa/tb-node/docker/Dockerfile @@ -18,8 +18,7 @@ FROM thingsboard/openjdk17:bookworm-slim COPY start-tb-node.sh ${pkg.name}.deb /tmp/ -RUN echo 'networkaddress.cache.ttl=60' >> /etc/java-17-openjdk/security/java.security \ - && chmod a+x /tmp/*.sh \ +RUN chmod a+x /tmp/*.sh \ && mv /tmp/start-tb-node.sh /usr/bin && \ (yes | dpkg -i /tmp/${pkg.name}.deb) && \ rm /tmp/${pkg.name}.deb && \ From 939d672b07f158e3a3620500753c1dc8233ec497 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 24 Oct 2023 14:01:22 +0200 Subject: [PATCH 045/209] docker: bump minor version FROM thingsboard/node:16.20.2-bookworm-slim (previous was 16.17.0-bullseye-slim) --- msa/js-executor/docker/Dockerfile | 2 +- msa/web-ui/docker/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/msa/js-executor/docker/Dockerfile b/msa/js-executor/docker/Dockerfile index f934628a96..ef22343442 100644 --- a/msa/js-executor/docker/Dockerfile +++ b/msa/js-executor/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/node:16.17.0-bullseye-slim +FROM thingsboard/node:16.20.2-bookworm-slim ENV NODE_ENV production ENV DOCKER_MODE true diff --git a/msa/web-ui/docker/Dockerfile b/msa/web-ui/docker/Dockerfile index cf5df5ee2c..18a19e0ead 100644 --- a/msa/web-ui/docker/Dockerfile +++ b/msa/web-ui/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/node:16.17.0-bullseye-slim +FROM thingsboard/node:16.20.2-bookworm-slim ENV NODE_ENV production ENV DOCKER_MODE true From e5eff1f8c7f12fd9113cad8d16f06c18431a1e4d Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 24 Oct 2023 14:05:58 +0200 Subject: [PATCH 046/209] docker: bump minor version thingsboard/haproxy-certbot:2.2.31-alpine3.18 --- docker/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 78ca337e0a..942be3782a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -300,7 +300,7 @@ services: haproxy: restart: always container_name: "${LOAD_BALANCER_NAME}" - image: thingsboard/haproxy-certbot:1.3.0 + image: thingsboard/haproxy-certbot:2.2.31-alpine3.18 volumes: - ./haproxy/config:/config - ./haproxy/letsencrypt:/etc/letsencrypt From dfb1200e958c9d1e575db737604d510184549a9f Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 24 Oct 2023 16:11:00 +0300 Subject: [PATCH 047/209] tbDate: comments2 --- .../thingsboard/script/api/tbel/TbDate.java | 37 ++++++++++--------- .../script/api/tbel/TbDateTest.java | 33 ++++++++--------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java index 146de4570f..591084f986 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java @@ -46,6 +46,9 @@ public class TbDate implements Serializable, Cloneable { private static final ZoneId zoneIdUTC = ZoneId.of("UTC"); private static final Locale localeUTC = Locale.forLanguageTag("UTC"); + private static final DateTimeFormatter isoDateFormatter = DateTimeFormatter.ofPattern( + "yyyy-MM-dd[[ ]['T']HH:mm[:ss[.SSS]][ ][XXX][Z][z][VV][O]]").withZone(ZoneId.systemDefault()); + public TbDate() { this.instant = Instant.now(); } @@ -108,8 +111,8 @@ public class TbDate implements Serializable, Cloneable { public String toDateString(String locale) { return toDateString(locale, ZoneId.systemDefault().toString()); } - public String toDateString(String locale, String zoneStr) { - return toLocaleDateString(locale, JacksonUtil.newObjectNode().put("dateStyle", FormatStyle.FULL.name()).put("timeZone", zoneStr).toString()); + public String toDateString(String localeStr, String zoneStr) { + return toLocaleTbString(localeStr, zoneStr, (locale, options) -> DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(locale)); } public String toTimeString() { return toTimeString(Locale.getDefault().getLanguage()); @@ -117,8 +120,8 @@ public class TbDate implements Serializable, Cloneable { public String toTimeString(String locale) { return toTimeString(locale, ZoneId.systemDefault().toString()); } - public String toTimeString(String locale, String zoneStr) { - return toLocaleTbTimeString(locale, JacksonUtil.newObjectNode().put("timeStyle", FormatStyle.FULL.name()).put("timeZone", zoneStr).toString()); + public String toTimeString(String localeStr, String zoneStr) { + return toLocaleTbString(localeStr, zoneStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL).withLocale(locale)); } public String toISOString() { @@ -131,8 +134,8 @@ public class TbDate implements Serializable, Cloneable { return toUTCString(localeUTC.getLanguage()); } - public String toUTCString(String locale) { - return toLocaleTbString(locale, JacksonUtil.newObjectNode().put("dateStyle", FormatStyle.FULL.name()).put("timeStyle", FormatStyle.MEDIUM.name()).put("timeZone", zoneIdUTC.getId()).toString()); + public String toUTCString(String localeStr) { + return toLocaleTbString(localeStr, zoneIdUTC.getId(), (locale, options) -> DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.MEDIUM).withLocale(locale)); } public String toString() { @@ -143,8 +146,8 @@ public class TbDate implements Serializable, Cloneable { return toString(locale, ZoneId.systemDefault().toString()); } - public String toString(String locale, String zoneStr) { - return toLocaleTbString(locale, JacksonUtil.newObjectNode().put("dateStyle", FormatStyle.FULL.name()).put("timeStyle", FormatStyle.FULL.name()).put("timeZone", zoneStr).toString()); + public String toString(String localeStr, String zoneStr) { + return toLocaleTbString(localeStr, zoneStr, (locale, options) -> DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.FULL).withLocale(locale)); } public String toISOZonedDateTimeString() { return getZonedDateTime().toString(); @@ -157,8 +160,8 @@ public class TbDate implements Serializable, Cloneable { return toLocaleDateString(localeUTC.getLanguage()); } - public String toLocaleDateString(String locale) { - return toLocaleDateString(locale, JacksonUtil.newObjectNode().put("dateStyle", FormatStyle.FULL.name()).put("timeZone", ZoneId.systemDefault().toString()).toString()); + public String toLocaleDateString(String localeStr) { + return toLocaleTbString(localeStr, ZoneId.systemDefault().toString(), (locale, options) -> DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(locale)); } public String toLocaleDateString(String localeStr, String optionsStr) { @@ -170,14 +173,14 @@ public class TbDate implements Serializable, Cloneable { } public String toLocaleTimeString(String localeStr) { - return toLocaleTimeString(localeStr, ZoneId.systemDefault().toString()); + return toLocaleTimeStringWithZoneId(localeStr, ZoneId.systemDefault().toString()); } - public String toLocaleTimeString(String localeStr, String zoneStr) { - return toLocaleTbTimeString(localeStr, JacksonUtil.newObjectNode().put("timeStyle", FormatStyle.MEDIUM.name()).put("timeZone", zoneStr).toString()); + public String toLocaleTimeStringWithZoneId(String localeStr, String zoneStr) { + return toLocaleTbString(localeStr, zoneStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(options.getTimeStyle()).withLocale(locale)); } - public String toLocaleTbTimeString(String localeStr, String optionsStr) { + public String toLocaleTimeString(String localeStr, String optionsStr) { return toLocaleTbString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(options.getTimeStyle()).withLocale(locale)); } @@ -189,8 +192,8 @@ public class TbDate implements Serializable, Cloneable { return toLocaleString(locale, ZoneId.systemDefault().toString()); } - public String toLocaleString(String locale, String zoneStr) { - return toLocaleTbString(locale, JacksonUtil.newObjectNode().put("dateStyle", FormatStyle.SHORT.name()).put("timeStyle", FormatStyle.MEDIUM.name()).put("timeZone", zoneStr).toString()); + public String toLocaleString(String localeStr, String zoneStr) { + return toLocaleTbString(localeStr, zoneStr, (locale, options) -> DateTimeFormatter.ofLocalizedDateTime(options.getDateStyle(), options.getTimeStyle()).withLocale(locale)); } public String toLocaleTbString(String localeStr, String optionsStr) { @@ -489,8 +492,6 @@ public class TbDate implements Serializable, Cloneable { } } public static long parse(String value) { - DateTimeFormatter isoDateFormatter = DateTimeFormatter.ofPattern( - "yyyy-MM-dd[[ ]['T']HH:mm[:ss[.SSS]][ ][XXX][Z][z][VV][O]]").withZone(ZoneId.systemDefault()); try { TemporalAccessor accessor = isoDateFormatter.parseBest(value, ZonedDateTime::from, diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index af9ff4c5e1..cf74ff3cb2 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -223,41 +223,41 @@ class TbDateTest { Assert.assertNotNull(d.toLocaleTimeString()); Assert.assertNotNull(d.toLocaleTimeString("en-US")); - Assert.assertEquals("9:04:05 PM", d.toLocaleTbTimeString("en-US", "America/New_York")); - Assert.assertEquals("오후 9:04:05", d.toLocaleTbTimeString("ko-KR", "America/New_York")); - Assert.assertEquals("04:04:05", d.toLocaleTbTimeString( "uk-UA", "Europe/Kiev")); - Assert.assertEquals("9:04:05 م", d.toLocaleTbTimeString( "ar-EG", "America/New_York")); + Assert.assertEquals("9:04:05 PM", d.toLocaleTimeString("en-US", "America/New_York")); + Assert.assertEquals("오후 9:04:05", d.toLocaleTimeString("ko-KR", "America/New_York")); + Assert.assertEquals("04:04:05", d.toLocaleTimeString( "uk-UA", "Europe/Kiev")); + Assert.assertEquals("9:04:05 م", d.toLocaleTimeString( "ar-EG", "America/New_York")); - Assert.assertEquals("9:04:05 PM Eastern Daylight Time", d.toLocaleTbTimeString("en-US", JacksonUtil.newObjectNode() + Assert.assertEquals("9:04:05 PM Eastern Daylight Time", d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("timeStyle", "full") .toString())); - Assert.assertEquals("오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleTbTimeString("ko-KR", JacksonUtil.newObjectNode() + Assert.assertEquals("오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleTimeString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("timeStyle", "full") .toString())); - Assert.assertEquals("04:04:05 за східноєвропейським літнім часом", d.toLocaleTbTimeString("uk-UA", JacksonUtil.newObjectNode() + Assert.assertEquals("04:04:05 за східноєвропейським літнім часом", d.toLocaleTimeString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("timeStyle", "full") .toString())); - Assert.assertEquals("9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleTbTimeString("ar-EG", JacksonUtil.newObjectNode() + Assert.assertEquals("9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleTimeString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("timeStyle", "full") .toString())); - Assert.assertEquals("9:04:05 PM", d.toLocaleTbTimeString("en-US", JacksonUtil.newObjectNode() + Assert.assertEquals("9:04:05 PM", d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "h:mm:ss a") .toString())); - Assert.assertEquals("9:04:05 오후", d.toLocaleTbTimeString("ko-KR", JacksonUtil.newObjectNode() + Assert.assertEquals("9:04:05 오후", d.toLocaleTimeString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "h:mm:ss a") .toString())); - Assert.assertEquals("4:04:05 дп", d.toLocaleTbTimeString("uk-UA", JacksonUtil.newObjectNode() + Assert.assertEquals("4:04:05 дп", d.toLocaleTimeString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("pattern", "h:mm:ss a") .toString())); - Assert.assertEquals("9:04:05 م", d.toLocaleTbTimeString("ar-EG", JacksonUtil.newObjectNode() + Assert.assertEquals("9:04:05 م", d.toLocaleTimeString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "h:mm:ss a") .toString())); @@ -765,10 +765,10 @@ class TbDateTest { Assert.assertEquals("1975 Dec 31, Wed", d1.toDateString("UTC", "America/New_York")); Assert.assertEquals("Wednesday, December 31, 1975", d1.toDateString("en-US", "America/New_York")); - Assert.assertEquals("06:15:30", d1.toLocaleTimeString("uk-UA", "Europe/Kyiv")); - Assert.assertEquals("06:15:30", d1.toLocaleTimeString("UTC", "Europe/Kyiv")); - Assert.assertEquals("10:15:30 PM", d1.toLocaleTimeString("en-US", "America/New_York")); - Assert.assertEquals("22:15:30", d1.toLocaleTimeString("UTC", "America/New_York")); + Assert.assertEquals("06:15:30", d1.toLocaleTimeStringWithZoneId("uk-UA", "Europe/Kyiv")); + Assert.assertEquals("06:15:30", d1.toLocaleTimeStringWithZoneId("UTC", "Europe/Kyiv")); + Assert.assertEquals("10:15:30 PM", d1.toLocaleTimeStringWithZoneId("en-US", "America/New_York")); + Assert.assertEquals("22:15:30", d1.toLocaleTimeStringWithZoneId("UTC", "America/New_York")); Assert.assertEquals("06:15:30 за східноєвропейським стандартним часом", d1.toTimeString("uk-UA", "Europe/Kyiv")); Assert.assertEquals("06:15:30 Eastern European Time", d1.toTimeString("UTC", "Europe/Kyiv")); @@ -788,4 +788,3 @@ class TbDateTest { Assert.assertEquals(d.getMilliseconds(), d.getUTCMilliseconds()); } } - From 2046c4863f5c2ebf9a2927856d3889a034c2f510 Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 24 Oct 2023 16:48:08 +0300 Subject: [PATCH 048/209] tbDate: comments3 --- .../thingsboard/script/api/tbel/TbDate.java | 42 +++++++++---------- .../script/api/tbel/TbDateTest.java | 8 ++-- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java index 591084f986..0e406cf6dd 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java @@ -111,8 +111,8 @@ public class TbDate implements Serializable, Cloneable { public String toDateString(String locale) { return toDateString(locale, ZoneId.systemDefault().toString()); } - public String toDateString(String localeStr, String zoneStr) { - return toLocaleTbString(localeStr, zoneStr, (locale, options) -> DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(locale)); + public String toDateString(String localeStr, String optionsStr) { + return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(locale)); } public String toTimeString() { return toTimeString(Locale.getDefault().getLanguage()); @@ -120,8 +120,8 @@ public class TbDate implements Serializable, Cloneable { public String toTimeString(String locale) { return toTimeString(locale, ZoneId.systemDefault().toString()); } - public String toTimeString(String localeStr, String zoneStr) { - return toLocaleTbString(localeStr, zoneStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL).withLocale(locale)); + public String toTimeString(String localeStr, String optionsStr) { + return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL).withLocale(locale)); } public String toISOString() { @@ -135,7 +135,7 @@ public class TbDate implements Serializable, Cloneable { } public String toUTCString(String localeStr) { - return toLocaleTbString(localeStr, zoneIdUTC.getId(), (locale, options) -> DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.MEDIUM).withLocale(locale)); + return toLocaleString(localeStr, zoneIdUTC.getId(), (locale, options) -> DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.MEDIUM).withLocale(locale)); } public String toString() { @@ -146,8 +146,8 @@ public class TbDate implements Serializable, Cloneable { return toString(locale, ZoneId.systemDefault().toString()); } - public String toString(String localeStr, String zoneStr) { - return toLocaleTbString(localeStr, zoneStr, (locale, options) -> DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.FULL).withLocale(locale)); + public String toString(String localeStr, String optionsStr) { + return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.FULL).withLocale(locale)); } public String toISOZonedDateTimeString() { return getZonedDateTime().toString(); @@ -161,11 +161,11 @@ public class TbDate implements Serializable, Cloneable { } public String toLocaleDateString(String localeStr) { - return toLocaleTbString(localeStr, ZoneId.systemDefault().toString(), (locale, options) -> DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(locale)); + return toLocaleString(localeStr, ZoneId.systemDefault().toString(), (locale, options) -> DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(locale)); } public String toLocaleDateString(String localeStr, String optionsStr) { - return toLocaleTbString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedDate(options.getDateStyle()).withLocale(locale)); + return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedDate(options.getDateStyle()).withLocale(locale)); } public String toLocaleTimeString() { @@ -173,15 +173,11 @@ public class TbDate implements Serializable, Cloneable { } public String toLocaleTimeString(String localeStr) { - return toLocaleTimeStringWithZoneId(localeStr, ZoneId.systemDefault().toString()); - } - - public String toLocaleTimeStringWithZoneId(String localeStr, String zoneStr) { - return toLocaleTbString(localeStr, zoneStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(options.getTimeStyle()).withLocale(locale)); + return toLocaleTimeString(localeStr, ZoneId.systemDefault().toString()); } public String toLocaleTimeString(String localeStr, String optionsStr) { - return toLocaleTbString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(options.getTimeStyle()).withLocale(locale)); + return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(options.getTimeStyle()).withLocale(locale)); } public String toLocaleString() { @@ -192,12 +188,12 @@ public class TbDate implements Serializable, Cloneable { return toLocaleString(locale, ZoneId.systemDefault().toString()); } - public String toLocaleString(String localeStr, String zoneStr) { - return toLocaleTbString(localeStr, zoneStr, (locale, options) -> DateTimeFormatter.ofLocalizedDateTime(options.getDateStyle(), options.getTimeStyle()).withLocale(locale)); + public String toLocaleString(String localeStr, String optionsStr) { + return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedDateTime(options.getDateStyle(), options.getTimeStyle()).withLocale(locale)); } public String toLocaleTbString(String localeStr, String optionsStr) { - return toLocaleTbString(localeStr, optionsStr, (locale, options) -> { + return toLocaleString(localeStr, optionsStr, (locale, options) -> { String formatPattern = DateTimeFormatterBuilder.getLocalizedDateTimePattern( options.getDateStyle(), @@ -208,7 +204,7 @@ public class TbDate implements Serializable, Cloneable { }); } - public String toLocaleTbString(String localeStr, String optionsStr, BiFunction formatterBuilder) { + public String toLocaleString(String localeStr, String optionsStr, BiFunction formatterBuilder) { Locale locale = StringUtils.isNotEmpty(localeStr) ? Locale.forLanguageTag(localeStr) : Locale.getDefault(); DateTimeFormatOptions options = getDateFormattingOptions(optionsStr); ZonedDateTime zdt = this.getInstant().atZone(options.getTimeZone().toZoneId()); @@ -221,13 +217,13 @@ public class TbDate implements Serializable, Cloneable { return formatter.format(zdt); } - private DateTimeFormatOptions getDateFormattingOptions(String options) { + private DateTimeFormatOptions getDateFormattingOptions(String optionsStr) { DateTimeFormatOptions opt = null; - if (StringUtils.isNotEmpty(options)) { + if (StringUtils.isNotEmpty(optionsStr)) { try { - opt = JacksonUtil.fromString(options, DateTimeFormatOptions.class); + opt = JacksonUtil.fromString(optionsStr, DateTimeFormatOptions.class); } catch (IllegalArgumentException iae) { - opt = new DateTimeFormatOptions(options); + opt = new DateTimeFormatOptions(optionsStr); } } if (opt == null) { diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index cf74ff3cb2..e6c9b3b140 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -765,10 +765,10 @@ class TbDateTest { Assert.assertEquals("1975 Dec 31, Wed", d1.toDateString("UTC", "America/New_York")); Assert.assertEquals("Wednesday, December 31, 1975", d1.toDateString("en-US", "America/New_York")); - Assert.assertEquals("06:15:30", d1.toLocaleTimeStringWithZoneId("uk-UA", "Europe/Kyiv")); - Assert.assertEquals("06:15:30", d1.toLocaleTimeStringWithZoneId("UTC", "Europe/Kyiv")); - Assert.assertEquals("10:15:30 PM", d1.toLocaleTimeStringWithZoneId("en-US", "America/New_York")); - Assert.assertEquals("22:15:30", d1.toLocaleTimeStringWithZoneId("UTC", "America/New_York")); + Assert.assertEquals("06:15:30", d1.toLocaleTimeString("uk-UA", "Europe/Kyiv")); + Assert.assertEquals("06:15:30", d1.toLocaleTimeString("UTC", "Europe/Kyiv")); + Assert.assertEquals("10:15:30 PM", d1.toLocaleTimeString("en-US", "America/New_York")); + Assert.assertEquals("22:15:30", d1.toLocaleTimeString("UTC", "America/New_York")); Assert.assertEquals("06:15:30 за східноєвропейським стандартним часом", d1.toTimeString("uk-UA", "Europe/Kyiv")); Assert.assertEquals("06:15:30 Eastern European Time", d1.toTimeString("UTC", "Europe/Kyiv")); From b1ebb9f27973dd374d2e8e72aaa3342633cfa6ea Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 24 Oct 2023 16:21:12 +0200 Subject: [PATCH 049/209] msa v16.20.2 --- msa/js-executor/pom.xml | 2 +- msa/web-ui/pom.xml | 2 +- ui-ngx/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 128479e5eb..1cf55d9795 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -71,7 +71,7 @@ install-node-and-yarn - v16.15.1 + v16.20.2 v1.22.17 diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index cbee67c12e..02772a68e3 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -80,7 +80,7 @@ install-node-and-yarn - v16.15.1 + v16.20.2 v1.22.17 diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index 84b5f8b98c..2c09d29af2 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -56,7 +56,7 @@ install-node-and-yarn - v16.15.1 + v16.20.2 v1.22.17 From 5a0b942b7547888de0d465d53df26b6373d24235 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 25 Oct 2023 15:57:42 +0300 Subject: [PATCH 050/209] Fix backward compatibility --- .../thingsboard/script/api/tbel/TbDate.java | 15 ++--------- .../script/api/tbel/TbDateTest.java | 25 ++++++++++--------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java index 0e406cf6dd..34c4669f1e 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java @@ -189,19 +189,8 @@ public class TbDate implements Serializable, Cloneable { } public String toLocaleString(String localeStr, String optionsStr) { - return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedDateTime(options.getDateStyle(), options.getTimeStyle()).withLocale(locale)); - } - - public String toLocaleTbString(String localeStr, String optionsStr) { - return toLocaleString(localeStr, optionsStr, (locale, options) -> { - String formatPattern = - DateTimeFormatterBuilder.getLocalizedDateTimePattern( - options.getDateStyle(), - options.getTimeStyle(), - IsoChronology.INSTANCE, - locale); - return DateTimeFormatter.ofPattern(formatPattern, locale); - }); + return toLocaleString(localeStr, optionsStr, (locale, options) -> + DateTimeFormatter.ofLocalizedDateTime(options.getDateStyle(), options.getTimeStyle()).withLocale(locale)); } public String toLocaleString(String localeStr, String optionsStr, BiFunction formatterBuilder) { diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index e6c9b3b140..7f09219df9 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -271,45 +271,46 @@ class TbDateTest { Assert.assertNotNull(d.toLocaleString()); Assert.assertNotNull(d.toLocaleString("en-US")); - Assert.assertEquals("9/5/23, 9:04:05 PM", d.toLocaleTbString("en-US", "America/New_York")); - Assert.assertEquals("23. 9. 5. 오후 9:04:05", d.toLocaleTbString("ko-KR", "America/New_York")); - Assert.assertEquals("06.09.23, 04:04:05", d.toLocaleTbString( "uk-UA", "Europe/Kiev")); - Assert.assertEquals("5\u200F/9\u200F/2023, 9:04:05 م", d.toLocaleTbString( "ar-EG", "America/New_York")); + Assert.assertEquals("9/5/23, 9:04:05 PM", d.toLocaleString("en-US", "America/New_York")); + Assert.assertEquals("23. 9. 5. 오후 9:04:05", d.toLocaleString("ko-KR", "America/New_York")); + Assert.assertEquals("06.09.23, 04:04:05", d.toLocaleString( "uk-UA", "Europe/Kiev")); + Assert.assertEquals("5\u200F/9\u200F/2023, 9:04:05 م", d.toLocaleString( "ar-EG", "America/New_York")); - Assert.assertEquals("Tuesday, September 5, 2023 at 9:04:05 PM Eastern Daylight Time", d.toLocaleTbString("en-US", JacksonUtil.newObjectNode() + + Assert.assertEquals("Tuesday, September 5, 2023 at 9:04:05 PM Eastern Daylight Time", d.toLocaleString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") .put("timeStyle", "full") .toString())); - Assert.assertEquals("2023년 9월 5일 화요일 오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleTbString("ko-KR", JacksonUtil.newObjectNode() + Assert.assertEquals("2023년 9월 5일 화요일 오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") .put("timeStyle", "full") .toString())); - Assert.assertEquals("середа, 6 вересня 2023 р. о 04:04:05 за східноєвропейським літнім часом", d.toLocaleTbString("uk-UA", JacksonUtil.newObjectNode() + Assert.assertEquals("середа, 6 вересня 2023 р. о 04:04:05 за східноєвропейським літнім часом", d.toLocaleString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("dateStyle", "full") .put("timeStyle", "full") .toString())); - Assert.assertEquals("الثلاثاء، 5 سبتمبر 2023 في 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleTbString("ar-EG", JacksonUtil.newObjectNode() + Assert.assertEquals("الثلاثاء، 5 سبتمبر 2023 في 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") .put("timeStyle", "full") .toString())); - Assert.assertEquals("9/5/2023, 9:04:05 PM", d.toLocaleTbString("en-US", JacksonUtil.newObjectNode() + Assert.assertEquals("9/5/2023, 9:04:05 PM", d.toLocaleString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "M/d/yyyy, h:mm:ss a") .toString())); - Assert.assertEquals("9/5/2023, 9:04:05 오후", d.toLocaleTbString("ko-KR", JacksonUtil.newObjectNode() + Assert.assertEquals("9/5/2023, 9:04:05 오후", d.toLocaleString("ko-KR", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "M/d/yyyy, h:mm:ss a") .toString())); - Assert.assertEquals("9/6/2023, 4:04:05 дп", d.toLocaleTbString("uk-UA", JacksonUtil.newObjectNode() + Assert.assertEquals("9/6/2023, 4:04:05 дп", d.toLocaleString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("pattern", "M/d/yyyy, h:mm:ss a") .toString())); - Assert.assertEquals("9/5/2023, 9:04:05 م", d.toLocaleTbString("ar-EG", JacksonUtil.newObjectNode() + Assert.assertEquals("9/5/2023, 9:04:05 م", d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("pattern", "M/d/yyyy, h:mm:ss a") .toString())); From f1a751db5a835209fbfe35ca66c2028e86a9dde7 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 27 Oct 2023 16:33:42 +0300 Subject: [PATCH 051/209] tbDate: add DateTimeFormatter --- .../thingsboard/script/api/tbel/TbDate.java | 18 +++++++-- .../script/api/tbel/TbDateTest.java | 40 +++++++++++++------ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java index 34c4669f1e..a662aa1ad9 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java @@ -28,7 +28,6 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; @@ -94,7 +93,10 @@ public class TbDate implements Serializable, Cloneable { } public ZonedDateTime getZonedDateTime() { - return instant.atZone(zoneIdUTC); + return getZonedDateTime(zoneIdUTC); + } + public ZonedDateTime getZonedDateTime(ZoneId zoneId) { + return instant.atZone(zoneId); } public LocalDateTime getLocalDateTime() { @@ -149,13 +151,21 @@ public class TbDate implements Serializable, Cloneable { public String toString(String localeStr, String optionsStr) { return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.FULL).withLocale(locale)); } - public String toISOZonedDateTimeString() { + public String toZonedDateTimeString() { return getZonedDateTime().toString(); } - public String toISOZonedDateTimeString(DateTimeFormatter formatter) { + public String toZonedDateTimeString(DateTimeFormatter formatter) { return getZonedDateTime().format(formatter); } + public String toZonedDateTimeString(String zoneIdStr) { + return getZonedDateTime(ZoneId.of(zoneIdStr)).toString(); + } + public String toZonedDateTimeString(DateTimeFormatter formatter, String zoneIdStr) { + return getZonedDateTime(ZoneId.of(zoneIdStr)).format(formatter); + } + + public String toLocaleDateString() { return toLocaleDateString(localeUTC.getLanguage()); } diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index 7f09219df9..3f6730de51 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -365,19 +365,33 @@ class TbDateTest { TbDate d1 = new TbDate(23, 9, 7, 8, 4, 5, "+04:00"); TbDate d2 = new TbDate(23, 9, 7, 8, 4, 5, "-03:00"); - Assert.assertEquals("2023-09-07T04:04:05Z[UTC]", d1.toISOZonedDateTimeString()); - Assert.assertEquals("2023-09-07T04:04:05Z", d1.toISOZonedDateTimeString(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - Assert.assertEquals("2023-09-07T04:04:05Z[UTC]", d1.toISOZonedDateTimeString(DateTimeFormatter.ISO_ZONED_DATE_TIME)); - Assert.assertEquals("2023-09-07T04:04:05", d1.toISOZonedDateTimeString(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); - Assert.assertEquals("Thu, 7 Sep 2023 04:04:05 GMT", d1.toISOZonedDateTimeString(DateTimeFormatter.RFC_1123_DATE_TIME)); - Assert.assertEquals("2023-09-07T04:04:05Z", d1.toISOZonedDateTimeString(DateTimeFormatter.ISO_INSTANT)); - - Assert.assertEquals("2023-09-07T11:04:05Z[UTC]", d2.toISOZonedDateTimeString()); - Assert.assertEquals("2023-09-07T11:04:05Z", d2.toISOZonedDateTimeString(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - Assert.assertEquals("2023-09-07T11:04:05Z[UTC]", d2.toISOZonedDateTimeString(DateTimeFormatter.ISO_ZONED_DATE_TIME)); - Assert.assertEquals("2023-09-07T11:04:05", d2.toISOZonedDateTimeString(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); - Assert.assertEquals("Thu, 7 Sep 2023 11:04:05 GMT", d2.toISOZonedDateTimeString(DateTimeFormatter.RFC_1123_DATE_TIME)); - Assert.assertEquals("2023-09-07T11:04:05Z", d2.toISOZonedDateTimeString(DateTimeFormatter.ISO_INSTANT)); + Assert.assertEquals("2023-09-07T04:04:05Z[UTC]", d1.toZonedDateTimeString()); + Assert.assertEquals("2023-09-07T04:04:05Z", d1.toZonedDateTimeString(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + Assert.assertEquals("2023-09-07T04:04:05Z[UTC]", d1.toZonedDateTimeString(DateTimeFormatter.ISO_ZONED_DATE_TIME)); + Assert.assertEquals("2023-09-07T04:04:05", d1.toZonedDateTimeString(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + Assert.assertEquals("Thu, 7 Sep 2023 04:04:05 GMT", d1.toZonedDateTimeString(DateTimeFormatter.RFC_1123_DATE_TIME)); + Assert.assertEquals("2023-09-07T04:04:05Z", d1.toZonedDateTimeString(DateTimeFormatter.ISO_INSTANT)); + + Assert.assertEquals("2023-09-07T11:04:05Z[UTC]", d2.toZonedDateTimeString()); + Assert.assertEquals("2023-09-07T11:04:05Z", d2.toZonedDateTimeString(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + Assert.assertEquals("2023-09-07T11:04:05Z[UTC]", d2.toZonedDateTimeString(DateTimeFormatter.ISO_ZONED_DATE_TIME)); + Assert.assertEquals("2023-09-07T11:04:05", d2.toZonedDateTimeString(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + Assert.assertEquals("Thu, 7 Sep 2023 11:04:05 GMT", d2.toZonedDateTimeString(DateTimeFormatter.RFC_1123_DATE_TIME)); + Assert.assertEquals("2023-09-07T11:04:05Z", d2.toZonedDateTimeString(DateTimeFormatter.ISO_INSTANT)); + } + @Test + void TestZoneDateTimeFormatterToString () { + TbDate d1 = new TbDate(23, 9, 7, 8, 4, 5, "-04:00"); + Assert.assertEquals("2023-09-07T12:04:05Z[UTC]", d1.toZonedDateTimeString()); + Assert.assertEquals("2023-09-07T12:04:05Z", d1.toZonedDateTimeString(DateTimeFormatter.ISO_INSTANT,"America/New_York")); + Assert.assertEquals("2023-09-07T08:04:05-04:00", d1.toZonedDateTimeString(DateTimeFormatter.ISO_OFFSET_DATE_TIME,"America/New_York")); + Assert.assertEquals("2023-09-07T08:04:05-04:00[America/New_York]", d1.toZonedDateTimeString("America/New_York")); + Assert.assertEquals("Thu, 7 Sep 2023 08:04:05 -0400", d1.toZonedDateTimeString(DateTimeFormatter.RFC_1123_DATE_TIME,"America/New_York")); + + Assert.assertEquals("2023-09-07T14:04:05+02:00[Europe/Berlin]", d1.toZonedDateTimeString("Europe/Berlin")); + Assert.assertEquals("2023-09-07T14:04:05+02:00", d1.toZonedDateTimeString(DateTimeFormatter.ISO_OFFSET_DATE_TIME,"Europe/Berlin")); + Assert.assertEquals("2023-09-08T02:04:05+14:00[Etc/GMT-14]", d1.toZonedDateTimeString("Etc/GMT-14")); + Assert.assertEquals("2023-09-07T00:04:05-12:00[Etc/GMT+12]", d1.toZonedDateTimeString("Etc/GMT+12")); } @Test From edfc72ad2a1246f0cdbad36b865dece8b6a0041c Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 30 Oct 2023 14:14:40 +0200 Subject: [PATCH 052/209] tbel Release 1.1.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a393dd74cf..a81c4d38f9 100755 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,7 @@ 3.8.1 3.21.9 1.42.1 - 1.1.1 + 1.1.3 1.18.26 1.2.4 1.2.5 From 3bfde753f037abd3a4b76fca1785225fcbec0981 Mon Sep 17 00:00:00 2001 From: nick Date: Sat, 2 Dec 2023 10:59:45 +0200 Subject: [PATCH 053/209] tbel Release 1.1.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a81c4d38f9..64ed92bbc6 100755 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,7 @@ 3.8.1 3.21.9 1.42.1 - 1.1.3 + 1.1.5 1.18.26 1.2.4 1.2.5 From 767169640ede2df09e339555ddea1dea056b9141 Mon Sep 17 00:00:00 2001 From: Arkadiusz Drabczyk Date: Wed, 29 Nov 2023 11:26:53 +0100 Subject: [PATCH 054/209] Expand all tags in Swagger UI API is very big and it's hard to find something on a page without manually expanding all tags. Fixes #9721 --- .../org/thingsboard/server/config/SwaggerConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 946d2cf2a2..78bb923c75 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -213,7 +213,7 @@ public class SwaggerConfiguration { uiProperties.setDefaultModelExpandDepth(1); uiProperties.setDefaultModelRendering("example"); uiProperties.setDisplayRequestDuration(false); - uiProperties.setDocExpansion("none"); + uiProperties.setDocExpansion("list"); uiProperties.setFilter("false"); uiProperties.setMaxDisplayedTags(null); uiProperties.setOperationsSorter("alpha"); From bccb2e7e8c58e7c9c64bcf78f8790ace411dcce6 Mon Sep 17 00:00:00 2001 From: dudnikmaksim Date: Wed, 6 Dec 2023 18:51:03 +0200 Subject: [PATCH 055/209] RPC connector templates --- .../gateway/gateway-connectors.component.ts | 7 +- ...ice-rpc-connector-templates.component.html | 60 ++ ...ice-rpc-connector-templates.component.scss | 79 +++ ...rvice-rpc-connector-templates.component.ts | 81 +++ ...teway-service-rpc-connector.component.html | 531 ++++++++++++++++++ ...teway-service-rpc-connector.component.scss | 85 +++ ...gateway-service-rpc-connector.component.ts | 272 +++++++++ .../gateway-service-rpc.component.html | 78 ++- .../gateway-service-rpc.component.scss | 23 +- .../gateway/gateway-service-rpc.component.ts | 21 +- .../lib/gateway/gateway-widget.models.ts | 175 +++++- .../widget/widget-components.module.ts | 92 +-- .../assets/locale/locale.constant-en_US.json | 74 +++ 13 files changed, 1460 insertions(+), 118 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index 9fa1f7b5f2..18b2c2d862 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -40,6 +40,7 @@ import { DatasourceType, widgetType } from '@shared/models/widget.models'; import { UtilsService } from '@core/services/utils.service'; import { EntityType } from '@shared/models/entity-type.models'; import { + ConnectorType, GatewayConnector, GatewayConnectorDefaultTypesTranslates, GatewayLogLevel @@ -198,10 +199,10 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie saveConnector(): void { const value = this.connectorForm.value; value.configuration = camelCase(value.name) + '.json'; - if (value.type !== 'grpc') { + if (value.type !== ConnectorType.GRPC) { delete value.key; } - if (value.type !== 'custom') { + if (value.type !== ConnectorType.CUSTOM) { delete value.class; } value.ts = new Date().getTime(); @@ -309,7 +310,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie private clearOutConnectorForm(): void { this.connectorForm.setValue({ name: '', - type: 'mqtt', + type: ConnectorType.MQTT, logLevel: GatewayLogLevel.INFO, key: 'auto', class: '', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html new file mode 100644 index 0000000000..3c6b410ac2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html @@ -0,0 +1,60 @@ + +
{{ 'gateway.rpc.templates-title' | translate }}
+ + + + {{template.name}} + + + + + + + + + + + +
+
{{config.key}}
+
+ {{config.value}}
+ + + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss new file mode 100644 index 0000000000..33ce5fa6d8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss @@ -0,0 +1,79 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + padding: 0; + + .title { + font-weight: 500; + } + + .template-key { + color: rgba(0, 0, 0, 0.38); + height: 32px; + line-height: 32px; + } + + .boolean-true, .boolean-false { + border-radius: 3px; + height: 32px; + line-height: 32px; + padding: 0 12px; + width: fit-content; + font-size: 14px; + text-transform: capitalize; + } + + .boolean-false { + color: #d12730; + background-color: rgba(209, 39, 48, 0.08); + } + + .boolean-true { + color: #198038; + background-color: rgba(25, 128, 56, 0.08); + } + + .mat-expansion-panel-header-description { + flex-direction: row-reverse; + align-items: center; + margin-right: 0; + + & > mat-icon { + margin-left: 15px; + color: rgba(0, 0, 0, 0.38); + } + } + + .mat-expansion-panel-header { + padding: 0 0 0 12px; + + &.mat-expansion-panel-header.mat-expanded { + height: 48px; + } + + .mat-content.mat-content-hide-toggle { + margin-right: 0; + } + } + + +} + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts new file mode 100644 index 0000000000..26cdf24dd1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts @@ -0,0 +1,81 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { ConnectorType, RPCTemplate } from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { TranslateService } from '@ngx-translate/core'; +import { KeyValue } from '@angular/common'; + +@Component({ + selector: 'tb-gateway-service-rpc-connector-templates', + templateUrl: './gateway-service-rpc-connector-templates.component.html', + styleUrls: ['./gateway-service-rpc-connector-templates.component.scss'] +}) +export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit { + + @Input() + connectorType: ConnectorType; + + @Output() + saveTemplate: EventEmitter = new EventEmitter(); + + rpcTemplates: Array = []; + + constructor(private translate: TranslateService) { + this.rpcTemplates.push( + { + name: 'Test Template', + config: { + fieldString: 'string', + fieldNumber: 666, + fieldBool: true, + fieldArray: [111, 222, 333, "String", "444"], + fieldObj: { + subKey1: 'dasd', + subKey2: 666, + } + } + } + ) + } + + + ngOnInit() { + + } + + public useTemplate($event: Event): void { + $event.stopPropagation(); + console.log("useTemplate") + } + + public copyTemplate($event: Event): void { + $event.stopPropagation(); + console.log("copyTemplate") + } + + public deleteTemplate($event: Event): void { + $event.stopPropagation(); + console.log("deleteTemplate") + } + + public originalOrder = (a: KeyValue, b: KeyValue): number => { + return 0; + } + public isObject(value: any) { + return value !== null && typeof value === 'object' && !Array.isArray(value); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html new file mode 100644 index 0000000000..9bab9508a5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html @@ -0,0 +1,531 @@ + +
+
{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}
+ + + + + {{ 'gateway.rpc.method-filter' | translate }} + + + + {{ 'gateway.rpc.request-topic-expression' | translate }} + + + + {{ 'gateway.rpc.response-topic-expression' | translate }} + + + + {{ 'gateway.rpc.response-timeout' | translate }} + + + + {{ 'gateway.rpc.value-expression' | translate }} + + + + + + + {{ 'gateway.rpc.tag' | translate }} + + +
+ + {{ 'gateway.rpc.type' | translate }} + + + {{ type }} + + + + + {{ 'gateway.rpc.function-code' | translate }} + + + {{ code }} + + + +
+
+ + {{ 'gateway.rpc.address' | translate }} + + + + {{ 'gateway.rpc.objects-count' | translate }} + + +
+
+ + + + {{ 'gateway.rpc.method-rpc' | translate }} + + + + {{ 'gateway.rpc.request-type' | translate }} + + + {{bACnetRequestTypesTranslates.get(type) | translate}} + + + + + {{ 'gateway.rpc.request-timeout' | translate }} + + +
+ + {{ 'gateway.rpc.object-type' | translate }} + + + {{bACnetObjectTypesTranslates.get(type) | translate}} + + + + + {{ 'gateway.rpc.identifier' | translate }} + + +
+ + {{ 'gateway.rpc.property-id' | translate }} + + +
+ + + + {{ 'gateway.rpc.method-rpc' | translate }} + + + + {{ 'gateway.rpc.characteristic-uuid' | translate }} + + + + {{ 'gateway.rpc.method-processing' | translate }} + + + {{bLEMethodsTranslates.get(type) | translate}} + + + + + {{ 'gateway.rpc.with-response' | translate }} + + + + + + {{ 'gateway.rpc.method-rpc' | translate }} + + + + {{ 'gateway.rpc.node-id' | translate }} + + + + {{ 'gateway.rpc.is-extended-id' | translate }} + + + {{ 'gateway.rpc.is-fd' | translate }} + + + {{ 'gateway.rpc.bitrate-switch' | translate }} + +
+ + {{ 'gateway.rpc.data-length' | translate }} + + + + {{ 'gateway.rpc.data-byte-order' | translate }} + + + {{ order | translate }} + + + +
+
+ + {{ 'gateway.rpc.data-before' | translate }} + + + + {{ 'gateway.rpc.data-after' | translate }} + + +
+ + {{ 'gateway.rpc.data-in-hex' | translate }} + + + + {{ 'gateway.rpc.data-expression' | translate }} + + +
+ + + + + {{ 'gateway.rpc.method-filter' | translate }} + + + + {{ 'gateway.rpc.value-expression' | translate }} + + + + + + + {{ 'gateway.rpc.method-rpc' | translate }} + + + + {{ 'gateway.rpc.value-expression' | translate }} + + + + {{ 'gateway.rpc.with-response' | translate }} + + + + + + {{ 'gateway.rpc.method-rpc' | translate }} + + + + {{ 'gateway.rpc.method-processing' | translate }} + + + {{ SocketMethodProcessingsTranslates.get(method) | translate }} + + + + + {{ 'gateway.rpc.encoding' | translate }} + + + {{ encoding }} + + + + + {{ 'gateway.rpc.with-response' | translate }} + + + + + + {{ 'gateway.rpc.method-rpc' | translate }} + + + + {{ 'gateway.rpc.value-expression' | translate }} + + + + {{ 'gateway.rpc.with-response' | translate }} + + + + + + {{ 'gateway.rpc.request-filter' | translate }} + + + + {{ 'gateway.rpc.method' | translate }} + + + {{ SNMPMethodsTranslations.get(method) | translate }} + + + +
+ {{ 'gateway.rpc.oids' | translate }}* +
+ + + + delete + +
+ +
+
+ + + + {{ 'gateway.rpc.method-filter' | translate }} + + +
+ + {{ 'gateway.rpc.http-method' | translate }} + + + {{ method }} + + + + + {{ 'gateway.rpc.request-url' | translate }} + + +
+
+ + {{ 'gateway.rpc.response-timeout' | translate }} + + + + {{ 'gateway.rpc.timeout' | translate }} + + + + {{ 'gateway.rpc.tries' | translate }} + + +
+ + {{ 'gateway.rpc.value-expression' | translate }} + + +
+ {{ 'gateway.rpc.http-headers' | translate }} +
+
+ {{ 'gateway.rpc.header-name' | translate }} + {{ 'gateway.rpc.value' | translate }} + +
+ +
+ + + + + + + + delete + + +
+
+ +
+
+ {{ 'gateway.rpc.security' | translate }} +
+
+ {{ 'gateway.rpc.security-name' | translate }} + {{ 'gateway.rpc.value' | translate }} + +
+ +
+ + + + + + + + delete + + +
+
+ +
+
+ + + + {{ 'gateway.rpc.method-filter' | translate }} + + +
+ + {{ 'gateway.rpc.method' | translate }} + + + {{ method }} + + + + + {{ 'gateway.rpc.request-url' | translate }} + + +
+
+ + {{ 'gateway.rpc.response-timeout' | translate }} + + + + {{ 'gateway.rpc.timeout' | translate }} + + + + {{ 'gateway.rpc.tries' | translate }} + + +
+ + {{ 'gateway.rpc.request-value-expression' | translate }} + + + + {{ 'gateway.rpc.response-value-expression' | translate }} + + +
+ {{ 'gateway.rpc.http-headers' | translate }} +
+
+ {{ 'gateway.rpc.header-name' | translate }} + {{ 'gateway.rpc.value' | translate }} + +
+ +
+ + + + + + + + delete + + +
+
+ +
+
+ + + + + + + {{ 'gateway.rpc.method' | translate }} + + +
+ {{ 'gateway.rpc.arguments' | translate }} +
+ + + + + delete + +
+ +
+
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss new file mode 100644 index 0000000000..65abdb50b3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss @@ -0,0 +1,85 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + padding: 0; + + .title { + font-weight: 500; + } + + .command-form { + flex-wrap: nowrap; + + & > button { + margin-top: 10px; + } + } + + .mat-mdc-slide-toggle.margin { + margin-bottom: 10px; + margin-left: 10px; + } + + .fields { + + ::ng-deep .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mat-mdc-form-field-infix { + padding-top: 6px; + padding-bottom: 6px; + min-height: auto; + } + + ::ng-deep .mat-mdc-form-field-subscript-wrapper { + display: none; + } + ::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__leading, + ::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__notch, + ::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__trailing { + border-color: #e0e0e0; + } + + .fields-label { + font-weight: 500; + } + } + + .border { + padding: 16px; + margin-bottom: 10px; + box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%); + border: solid 1px #e0e0e0; + border-radius: 4px; + + .title { + color: rgba(0, 0, 0, .54); + } + + .mat-icon { + color: rgba(0, 0, 0, .38); + } + + .mat-divider { + margin-left: -16px; + margin-right: -16px; + margin-bottom: 16px; + } + } + +} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts new file mode 100644 index 0000000000..ce2c850741 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts @@ -0,0 +1,272 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { + BACnetObjectTypes, BACnetObjectTypesTranslates, + BACnetRequestTypes, BACnetRequestTypesTranslates, BLEMethods, BLEMethodsTranslates, + CANByteOrders, + ConnectorType, GatewayConnectorDefaultTypesTranslates, HTTPMethods, + ModbusCommandTypes, + RPCCommand, + SNMPMethods, SNMPMethodsTranslations, + SocketEncodings, + SocketMethodProcessings, SocketMethodProcessingsTranslates +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { TranslateService } from "@ngx-translate/core"; + +@Component({ + selector: 'tb-gateway-service-rpc-connector', + templateUrl: './gateway-service-rpc-connector.component.html', + styleUrls: ['./gateway-service-rpc-connector.component.scss'] +}) +export class GatewayServiceRPCConnectorComponent implements OnInit { + + @Input() + connectorType: ConnectorType; + + @Output() + sendCommand: EventEmitter = new EventEmitter(); + + commandForm: FormGroup; + + codesArray: Array = [1, 2, 3, 4, 5, 6, 15, 16]; + + readonly ConnectorType = ConnectorType; + + modbusCommandTypes = Object.values(ModbusCommandTypes) as ModbusCommandTypes[]; + bACnetRequestTypes = Object.values(BACnetRequestTypes) as BACnetRequestTypes[]; + bACnetObjectTypes = Object.values(BACnetObjectTypes) as BACnetObjectTypes[]; + bLEMethods = Object.values(BLEMethods) as BLEMethods[]; + cANByteOrders = Object.values(CANByteOrders) as CANByteOrders[]; + socketMethodProcessings = Object.values(SocketMethodProcessings) as SocketMethodProcessings[]; + socketEncodings = Object.values(SocketEncodings) as SocketEncodings[]; + sNMPMethods = Object.values(SNMPMethods) as SNMPMethods[]; + hTTPMethods = Object.values(HTTPMethods) as HTTPMethods[]; + + bACnetRequestTypesTranslates = BACnetRequestTypesTranslates; + bACnetObjectTypesTranslates = BACnetObjectTypesTranslates; + bLEMethodsTranslates = BLEMethodsTranslates; + SocketMethodProcessingsTranslates = SocketMethodProcessingsTranslates; + SNMPMethodsTranslations = SNMPMethodsTranslations; + gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslates; + + urlPattern = new RegExp( + '^(https?:\\/\\/)?' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR IP (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', // fragment locator + 'i' + ); + + constructor(private fb: FormBuilder, + private translate: TranslateService) { + } + + + ngOnInit() { + this.commandForm = this.connectorParamsFormGroupByType(this.connectorType) + } + + connectorParamsFormGroupByType(type: ConnectorType): FormGroup { + let formGroup: FormGroup; + + switch (type) { + case ConnectorType.MQTT: + formGroup = this.fb.group({ + methodFilter: [null, [Validators.required]], + requestTopicExpression: [null, [Validators.required]], + responseTopicExpression: [null, []], + responseTimeout: [null, [Validators.min(10), Validators.pattern("^[0-9]*$")]], + valueExpression: [null, [Validators.required]], + }) + break; + case ConnectorType.MODBUS: + formGroup = this.fb.group({ + tag: [null, [Validators.required]], + type: [null, [Validators.required]], + functionCode: [null, [Validators.required]], + address: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]], + objectsCount: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]] + }) + break; + case ConnectorType.BACNET: + formGroup = this.fb.group({ + method: [null, [Validators.required]], + requestType: [null, [Validators.required]], + requestTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], + objectType: [null, []], + identifier: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]], + propertyId: [null, [Validators.required]] + }) + break; + case ConnectorType.BLE: + formGroup = this.fb.group({ + methodRPC: [null, [Validators.required]], + characteristicUUID: [null, [Validators.required]], + methodProcessing: [null, [Validators.required]], + withResponse: [false, []] + }) + break; + case ConnectorType.CAN: + formGroup = this.fb.group({ + method: [null, [Validators.required]], + nodeID: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]], + isExtendedID: [false, []], + isFD: [false, []], + bitrateSwitch: [false, []], + dataLength: [null, [Validators.min(1), Validators.pattern("^[0-9]*$")]], + dataByteorder: [null, []], + dataBefore: [null, []], + dataAfter: [null, []], + dataInHEX: [null, []], + dataExpression: [null, []] + }) + break; + case ConnectorType.FTP: + formGroup = this.fb.group({ + methodFilter: [null, [Validators.required]], + valueExpression: [null, [Validators.required]] + }) + break; + case ConnectorType.OCPP: + formGroup = this.fb.group({ + methodRPC: [null, [Validators.required]], + valueExpression: [null, [Validators.required]], + withResponse: [false, []] + }) + break; + case ConnectorType.SOCKET: + formGroup = this.fb.group({ + methodRPC: [null, [Validators.required]], + methodProcessing: [null, [Validators.required]], + encoding: [null, [Validators.required]], + withResponse: [false, []] + }) + break; + case ConnectorType.XMPP: + formGroup = this.fb.group({ + methodRPC: [null, [Validators.required]], + valueExpression: [null, [Validators.required]], + withResponse: [false, []] + }) + break; + case ConnectorType.SNMP: + formGroup = this.fb.group({ + requestFilter: [null, [Validators.required]], + method: [null, [Validators.required]], + oid: this.fb.array([], [Validators.required]) + }) + break; + case ConnectorType.REST: + formGroup = this.fb.group({ + methodFilter: [null, [Validators.required]], + HTTPMethod: [null, [Validators.required]], + requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]], + responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], + timeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], + tries: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]], + valueExpression: [null, [Validators.required]], + httpHeaders: this.fb.array([]), + security: this.fb.array([]) + }) + break; + case ConnectorType.REQUEST: + formGroup = this.fb.group({ + methodFilter: [null, [Validators.required]], + httpMethod: [null, [Validators.required]], + requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]], + responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], + timeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], + tries: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]], + requestValueExpression: [null, [Validators.required]], + responseValueExpression: [null, []], + httpHeaders: this.fb.array([]), + }) + break; + case ConnectorType.OPCUA: + case ConnectorType.OPCUA_ASYNCIO: + formGroup = this.fb.group({ + method: [null, [Validators.required]], + arguments: this.fb.array([]), + }) + } + return formGroup; + } + + addSNMPoid(value: string = null) { + const oidsFA = this.commandForm.get('oid') as FormArray; + if (oidsFA) { + oidsFA.push(this.fb.control(value, [Validators.required])); + } + } + + removeSNMPoid(index: number) { + const oidsFA = this.commandForm.get('oid') as FormArray; + oidsFA.removeAt(index); + } + + addHTTPHeader(value: { headerName: string, value: string } = {headerName: null, value: null}) { + const headerFA = this.commandForm.get('httpHeaders') as FormArray; + const formGroup = this.fb.group({ + headerName: [value.headerName, [Validators.required]], + value: [value.value, [Validators.required]] + }) + if (headerFA) { + headerFA.push(formGroup); + } + } + + removeHTTPHeader(index: number) { + const oidsFA = this.commandForm.get('httpHeaders') as FormArray; + oidsFA.removeAt(index); + } + + addHTTPSecurity(value: { securityName: string, value: string } = {securityName: null, value: null}) { + const securityFA = this.commandForm.get('security') as FormArray; + const formGroup = this.fb.group({ + securityName: [value.securityName, [Validators.required]], + value: [value.value, [Validators.required]] + }) + if (securityFA) { + securityFA.push(formGroup); + } + } + + removeHTTPSecurity(index: number) { + const oidsFA = this.commandForm.get('security') as FormArray; + oidsFA.removeAt(index); + } + + getFormArrayControls(path: string) { + return (this.commandForm.get(path) as FormArray).controls as FormControl[]; + } + + addOCPUAArguments(value: string = null) { + const oidsFA = this.commandForm.get('arguments') as FormArray; + if (oidsFA) { + oidsFA.push(this.fb.control(value)); + } + } + + removeOCPUAArguments(index: number) { + const oidsFA = this.commandForm.get('arguments') as FormArray; + oidsFA.removeAt(index); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html index 8a96f1849b..2397d1bcdd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html @@ -15,51 +15,41 @@ limitations under the License. --> -
- - - {{ 'gateway.statistics.command' | translate }} - - - {{ command }} - - - - - {{ 'gateway.statistics.timeout-ms' | translate }} - - - {{ 'gateway.statistics.timeout-min' | translate }} - - - - - - {{ 'gateway.statistics.command' | translate }} - - - - {{ 'widget-config.datasource-parameters' | translate }} - - edit - - - - -
-
+
+
+ + + {{ 'gateway.statistics.command' | translate }} + + + {{ command }} + + + + + {{ 'gateway.statistics.timeout-ms' | translate }} + + + {{ 'gateway.statistics.timeout-min' | translate }} + + + + + + + +
+
{{ 'gateway.rpc-command-result' | translate }}
schedule {{ resultTime | date: 'yyyy/MM/dd HH:mm:ss' }}
- -
- + +
+ + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss index 5dab1d91c1..00e9e8df63 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss @@ -16,10 +16,20 @@ :host { width: 100%; height: 100%; - overflow-x: auto; + overflow: auto; display: flex; - flex-direction: column; - padding: 0; + flex-direction: row; + padding: 0 5px; + + & > * { + height: 100%; + overflow: auto; + } + + & > tb-gateway-service-rpc-connector-templates:last-child { + margin-left: 10px; + } + .command-form { flex-wrap: nowrap; @@ -63,6 +73,13 @@ flex: 1; } } + + .border { + padding: 16px; + box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%); + border: solid 1px #e0e0e0; + border-radius: 4px; + } } :host ::ng-deep { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts index d19624e257..a3c96f7d49 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { AfterViewInit, Component, Input } from '@angular/core'; +import { OnInit, Component, Input } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { WidgetContext } from '@home/models/widget-component.models'; @@ -24,13 +24,14 @@ import { JsonObjectEditDialogData } from '@shared/components/dialog/json-object-edit-dialog.component'; import { jsonRequired } from '@shared/components/json-object-edit.component'; +import { ConnectorType, RPCCommand } from '@home/components/widget/lib/gateway/gateway-widget.models'; @Component({ selector: 'tb-gateway-service-rpc', templateUrl: './gateway-service-rpc.component.html', styleUrls: ['./gateway-service-rpc.component.scss'] }) -export class GatewayServiceRPCComponent implements AfterViewInit { +export class GatewayServiceRPCComponent implements OnInit { @Input() ctx: WidgetContext; @@ -56,19 +57,19 @@ export class GatewayServiceRPCComponent implements AfterViewInit { 'Reboot' ]; - private connectorType: string; + public connectorType: ConnectorType; constructor(private fb: FormBuilder, private dialog: MatDialog) { this.commandForm = this.fb.group({ - command: [null,[Validators.required]], + command: [null, [Validators.required]], time: [60, [Validators.required, Validators.min(1)]], params: ['{}', [jsonRequired]], result: [null] }); } - ngAfterViewInit() { + ngOnInit() { this.isConnector = this.ctx.settings.isConnector; if (!this.isConnector) { this.commandForm.get('command').setValue(this.RPCCommands[0]); @@ -77,17 +78,17 @@ export class GatewayServiceRPCComponent implements AfterViewInit { } } - sendCommand() { + sendCommand(value?: RPCCommand) { this.resultTime = null; - const formValues = this.commandForm.value; + const formValues = value || this.commandForm.value; const commandPrefix = this.isConnector ? `${this.connectorType}_` : 'gateway_'; - this.ctx.controlApi.sendTwoWayCommand(commandPrefix+formValues.command.toLowerCase(), formValues.params,formValues.time).subscribe({ + this.ctx.controlApi.sendTwoWayCommand(commandPrefix + formValues.command.toLowerCase(), formValues.params, formValues.time).subscribe({ next: resp => { - this.resultTime = new Date().getTime(); + this.resultTime = new Date().getTime(); this.commandForm.get('result').setValue(JSON.stringify(resp)) }, error: error => { - this.resultTime = new Date().getTime(); + this.resultTime = new Date().getTime(); console.error(error); this.commandForm.get('result').setValue(JSON.stringify(error.error)); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index d58c33079a..4b09667a6b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -107,27 +107,166 @@ export interface GatewayConnector { key?: string; } +export enum ConnectorType { + MQTT = 'mqtt', + MODBUS = 'modbus', + GRPC = 'grpc', + OPCUA = 'opcua', + OPCUA_ASYNCIO = 'opcua_asyncio', + BLE = 'ble', + REQUEST = 'request', + CAN = 'can', + BACNET = 'bacnet', + ODBC = 'odbc', + REST = 'rest', + SNMP = 'snmp', + FTP = 'ftp', + SOCKET = 'socket', + XMPP = 'xmpp', + OCPP = 'ocpp', + CUSTOM = 'custom' +} -export const GatewayConnectorDefaultTypesTranslates = new Map([ - ['mqtt', 'MQTT'], - ['modbus', 'MODBUS'], - ['grpc', 'GRPC'], - ['opcua', 'OPCUA'], - ['opcua_asyncio', 'OPCUA ASYNCIO'], - ['ble', 'BLE'], - ['request', 'REQUEST'], - ['can', 'CAN'], - ['bacnet', 'BACNET'], - ['odbc', 'ODBC'], - ['rest', 'REST'], - ['snmp', 'SNMP'], - ['ftp', 'FTP'], - ['socket', 'SOCKET'], - ['xmpp', 'XMPP'], - ['ocpp', 'OCPP'], - ['custom', 'CUSTOM'] +export const GatewayConnectorDefaultTypesTranslates = new Map([ + [ConnectorType.MQTT, 'MQTT'], + [ConnectorType.MODBUS, 'MODBUS'], + [ConnectorType.GRPC, 'GRPC'], + [ConnectorType.OPCUA, 'OPCUA'], + [ConnectorType.OPCUA_ASYNCIO, 'OPCUA ASYNCIO'], + [ConnectorType.BLE, 'BLE'], + [ConnectorType.REQUEST, 'REQUEST'], + [ConnectorType.CAN, 'CAN'], + [ConnectorType.BACNET, 'BACNET'], + [ConnectorType.ODBC, 'ODBC'], + [ConnectorType.REST, 'REST'], + [ConnectorType.SNMP, 'SNMP'], + [ConnectorType.FTP, 'FTP'], + [ConnectorType.SOCKET, 'SOCKET'], + [ConnectorType.XMPP, 'XMPP'], + [ConnectorType.OCPP, 'OCPP'], + [ConnectorType.CUSTOM, 'CUSTOM'] ]); +export interface RPCCommand { + command: string, + params: any, + time: number +} + + +export enum ModbusCommandTypes { + Bits = 'bits', + Bit = 'bit', + String = 'string', + Bytes = 'bytes', + Int8 = '8int', + Uint8 = '8uint', + Int16 = '16int', + Uint16 = '16uint', + Float16 = '16float', + Int32 = '32int', + Uint32 = '32uint', + Float32 = '32float', + Int64 = '64int', + Uint64 = '64uint', + Float64 = '64float' +} + +export enum BACnetRequestTypes { + WriteProperty = 'writeProperty', + ReadProperty = 'readProperty' +} + +export const BACnetRequestTypesTranslates = new Map([ + [BACnetRequestTypes.WriteProperty, 'gateway.rpc.write-property'], + [BACnetRequestTypes.ReadProperty, "gateway.rpc.read-property"] +]) + +export enum BACnetObjectTypes { + BinaryOutput = 'binaryOutput', + AnalogOutput = 'analogOutput', + BinaryValue = 'binaryValue' +} + +export const BACnetObjectTypesTranslates = new Map([ + [BACnetObjectTypes.AnalogOutput, 'gateway.rpc.analog-output'], + [BACnetObjectTypes.BinaryOutput, 'gateway.rpc.binary-output'], + [BACnetObjectTypes.BinaryValue, 'gateway.rpc.binary-value'], +]) + +export enum BLEMethods { + WRITE = 'write', + READ = 'read', + SCAN = 'scan' +} + +export const BLEMethodsTranslates = new Map([ + [BLEMethods.WRITE, 'gateway.rpc.write'], + [BLEMethods.READ, 'gateway.rpc.read'], + [BLEMethods.SCAN, 'gateway.rpc.scan'], +]) + +export enum CANByteOrders { + LITTLE = 'LITTLE', + BIG = 'BIG' +} + +export enum SocketMethodProcessings { + WRITE = 'write' +} + +export const SocketMethodProcessingsTranslates = new Map([ + [SocketMethodProcessings.WRITE, 'gateway.rpc.write'] +]) + +export enum SNMPMethods { + SET = 'set', + MULTISET = "multiset", + GET = "get", + BULKWALK = "bulkwalk", + TABLE = "table", + MULTIGET = "multiget", + GETNEXT = "getnext", + BULKGET = "bulkget", + WALKS = "walk" +} + +export const SNMPMethodsTranslations = new Map([ + [SNMPMethods.SET, 'gateway.rpc.set'], + [SNMPMethods.MULTISET, 'gateway.rpc.multiset'], + [SNMPMethods.GET, 'gateway.rpc.get'], + [SNMPMethods.BULKWALK, 'gateway.rpc.bulk-walk'], + [SNMPMethods.TABLE, 'gateway.rpc.table'], + [SNMPMethods.MULTIGET, 'gateway.rpc.multi-get'], + [SNMPMethods.GETNEXT, 'gateway.rpc.get-next'], + [SNMPMethods.BULKGET, 'gateway.rpc.bul-kget'], + [SNMPMethods.WALKS, 'gateway.rpc.walk'] +]) + +export enum HTTPMethods { + CONNECT = 'CONNECT', + DELETE = 'DELETE', + GET = 'GET', + HEAD = 'HEAD', + OPTIONS = 'OPTIONS', + PATCH = 'PATCH', + POST = 'POST', + PUT = 'PUT', + TRACE = 'TRACE' + +} + +export enum SocketEncodings { + UTF_8 = 'utf-8' +} + +export interface RPCTemplate { + name: string; + config: { + [key: string]: any; + }; +} + export interface LogLink { name: string; key: string; diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 858856fb4a..b4b688848e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -22,7 +22,9 @@ import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/displa import { AlarmsTableWidgetComponent } from '@home/components/widget/lib/alarm/alarms-table-widget.component'; import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module'; import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/timeseries-table-widget.component'; -import { EntitiesHierarchyWidgetComponent } from '@home/components/widget/lib/entity/entities-hierarchy-widget.component'; +import { + EntitiesHierarchyWidgetComponent +} from '@home/components/widget/lib/entity/entities-hierarchy-widget.component'; import { RpcWidgetsModule } from '@home/components/widget/lib/rpc/rpc-widgets.module'; import { DateRangeNavigatorPanelComponent, @@ -47,6 +49,12 @@ import { GatewayConnectorComponent } from '@home/components/widget/lib/gateway/g import { GatewayLogsComponent } from '@home/components/widget/lib/gateway/gateway-logs.component'; import { GatewayStatisticsComponent } from '@home/components/widget/lib/gateway/gateway-statistics.component'; import { GatewayServiceRPCComponent } from '@home/components/widget/lib/gateway/gateway-service-rpc.component'; +import { + GatewayServiceRPCConnectorComponent +} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector.component'; +import { + GatewayServiceRPCConnectorTemplatesComponent +} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component'; import { DeviceGatewayCommandComponent } from '@home/components/widget/lib/gateway/device-gateway-command.component'; import { GatewayConfigurationComponent } from '@home/components/widget/lib/gateway/gateway-configuration.component'; import { @@ -94,6 +102,8 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough GatewayLogsComponent, GatewayStatisticsComponent, GatewayServiceRPCComponent, + GatewayServiceRPCConnectorComponent, + GatewayServiceRPCConnectorTemplatesComponent, DeviceGatewayCommandComponent, GatewayConfigurationComponent, GatewayRemoteConfigurationDialogComponent, @@ -115,46 +125,48 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough HomePageWidgetsModule, SharedHomeComponentsModule ], - exports: [ - EntitiesTableWidgetComponent, - AlarmsTableWidgetComponent, - TimeseriesTableWidgetComponent, - EntitiesHierarchyWidgetComponent, - EdgesOverviewWidgetComponent, - RpcWidgetsModule, - HomePageWidgetsModule, - DateRangeNavigatorWidgetComponent, - JsonInputWidgetComponent, - MultipleInputWidgetComponent, - TripAnimationComponent, - PhotoCameraInputWidgetComponent, - GatewayFormComponent, - NavigationCardsWidgetComponent, - NavigationCardWidgetComponent, - QrCodeWidgetComponent, - MarkdownWidgetComponent, - LegendComponent, - FlotWidgetComponent, - GatewayConnectorComponent, - GatewayLogsComponent, - GatewayStatisticsComponent, - GatewayServiceRPCComponent, - DeviceGatewayCommandComponent, - GatewayConfigurationComponent, - GatewayRemoteConfigurationDialogComponent, - ValueCardWidgetComponent, - AggregatedValueCardWidgetComponent, - CountWidgetComponent, - BatteryLevelWidgetComponent, - WindSpeedDirectionWidgetComponent, - SignalStrengthWidgetComponent, - ValueChartCardWidgetComponent, - ProgressBarWidgetComponent, - LiquidLevelWidgetComponent, - DoughnutWidgetComponent - ], + exports: [ + EntitiesTableWidgetComponent, + AlarmsTableWidgetComponent, + TimeseriesTableWidgetComponent, + EntitiesHierarchyWidgetComponent, + EdgesOverviewWidgetComponent, + RpcWidgetsModule, + HomePageWidgetsModule, + DateRangeNavigatorWidgetComponent, + JsonInputWidgetComponent, + MultipleInputWidgetComponent, + TripAnimationComponent, + PhotoCameraInputWidgetComponent, + GatewayFormComponent, + NavigationCardsWidgetComponent, + NavigationCardWidgetComponent, + QrCodeWidgetComponent, + MarkdownWidgetComponent, + LegendComponent, + FlotWidgetComponent, + GatewayConnectorComponent, + GatewayLogsComponent, + GatewayServiceRPCConnectorComponent, + GatewayServiceRPCConnectorTemplatesComponent, + GatewayStatisticsComponent, + GatewayServiceRPCComponent, + DeviceGatewayCommandComponent, + GatewayConfigurationComponent, + GatewayRemoteConfigurationDialogComponent, + ValueCardWidgetComponent, + AggregatedValueCardWidgetComponent, + CountWidgetComponent, + BatteryLevelWidgetComponent, + WindSpeedDirectionWidgetComponent, + SignalStrengthWidgetComponent, + ValueChartCardWidgetComponent, + ProgressBarWidgetComponent, + LiquidLevelWidgetComponent, + DoughnutWidgetComponent + ], providers: [ - {provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule } + {provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule} ] }) export class WidgetComponentsModule { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index fcf42dc0e2..294173e231 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2681,6 +2681,7 @@ "connectors-table-actions": "Actions", "connectors-table-key": "Key", "connectors-table-class": "Class", + "rpc-command-save-template": "Save Template", "rpc-command-send": "Send", "rpc-command-result": "Response", "rpc-command-edit-params": "Edit parameters", @@ -2772,6 +2773,79 @@ "remove-entry": "Remove configuration", "remote-shell": "Remote shell", "remote-configuration": "Remote Configuration", + "rpc": { + "title": "{{type}} Connector RPC parameters", + "templates-title": "Connector RPC Templates", + "method-filter": "Method filter", + "request-topic-expression": "Request topic expression", + "response-topic-expression": "Response topic expression", + "response-timeout": "Response Time", + "value-expression": "Value Expression", + "tag": "Tag", + "type": "Type", + "function-code": "Function Code", + "objects-count": "Objects Count", + "address": "Address", + "method": "Method", + "request-type": "Request Type", + "request-timeout": "Request Timeout", + "object-type": "Object type", + "identifier": "Identifier", + "property-id": "Property ID", + "method-rpc": "Method RPC name", + "with-response": "With Response", + "characteristic-uuid": "Characteristic UUID", + "method-processing": "Method Processing", + "node-id": "Node ID", + "is-extended-id": "Is Extended ID", + "is-fd": "Is FD", + "bitrate-switch": "Bitrate Switch", + "data-in-hex": "Data In HEX", + "data-length": "Data Length", + "data-byte-order": "Data Byte Order", + "data-before": "Data Before", + "data-after": "Data After", + "data-expression": "Data Expression", + "encoding": "Encoding", + "oid": "OID", + "add-oid": "Add OID", + "add-header": "Add header", + "add-security": "Add security", + "remove": "Remove", + "request-filter": "Request Filter", + "request-url": "Request URL Expression", + "http-method": "HTTP Method", + "timeout": "Timeout", + "tries": "Tries", + "http-headers": "HTTP Headers", + "header-name": "Header name", + "security-name": "Security name", + "value": "Value", + "security": "Security", + "response-value-expression": "Response Value Expression", + "request-value-expression": "Request Value Expression", + "arguments": "Arguments", + "add-argument": "Add argument", + "write-property": "Write property", + "read-property": "Read property", + "analog-output": "Analog output", + "binary-output": "Binary output", + "binary-value": "Binary value", + "write": "Write", + "read": "Read", + "scan": "Scan", + "oids": "OIDS", + "set": "Set", + "multiset": "Multiset", + "get": "Get", + "bulk-walk": "Bulk walk", + "table": "Table", + "multi-get": "Multiget", + "get-next": "Get next", + "bulk-get": "Bulk get", + "walk": "Walk" + + }, "other": "Other", "save-tip": "Save configuration file", "security-type": "Security type", From 6ee6ccaa368ad27ddc1ac216ffed80c1a01f00f5 Mon Sep 17 00:00:00 2001 From: Maksym Dudnik Date: Thu, 14 Dec 2023 13:21:57 +0200 Subject: [PATCH 056/209] connectors templates --- ...service-rpc-connector-template-dialog.html | 54 ++++++ ...y-service-rpc-connector-template-dialog.ts | 62 ++++++ ...ice-rpc-connector-templates.component.html | 19 +- ...ice-rpc-connector-templates.component.scss | 5 + ...rvice-rpc-connector-templates.component.ts | 69 ++++--- ...teway-service-rpc-connector.component.html | 136 ++++++------- ...gateway-service-rpc-connector.component.ts | 181 ++++++++++++++++-- .../gateway-service-rpc.component.html | 7 +- .../gateway/gateway-service-rpc.component.ts | 141 ++++++++++++-- .../lib/gateway/gateway-widget.models.ts | 15 +- .../widget/widget-components.module.ts | 5 + .../assets/locale/locale.constant-en_US.json | 69 +++---- 12 files changed, 584 insertions(+), 179 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.html new file mode 100644 index 0000000000..6227ee1049 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.html @@ -0,0 +1,54 @@ + + +

gateway.rpc.save-template

+ + +
+
+ + gateway.rpc.template-name + + + {{ 'gateway.rpc.template-name-required' | translate }} + + +
+ {{ 'gateway.rpc.template-name-duplicate' | translate }} +
+
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts new file mode 100644 index 0000000000..63f492519c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts @@ -0,0 +1,62 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { FormBuilder, FormControl, UntypedFormControl, Validators } from '@angular/forms'; +import { RPCTemplate, SaveRPCTemplateData } from '@home/components/widget/lib/gateway/gateway-widget.models'; + +@Component({ + selector: 'gateway-service-rpc-connector-template-dialog', + templateUrl: './gateway-service-rpc-connector-template-dialog.html' +}) + +export class GatewayServiceRPCConnectorTemplateDialogComponent extends DialogComponent { + + config: { + [key: string]: any; + }; + templates: Array; + templateNameCtrl: FormControl; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: SaveRPCTemplateData, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store, router, dialogRef); + this.config = this.data.config; + this.templates = this.data.templates; + this.templateNameCtrl = this.fb.control('', [Validators.required]) + } + + validateDuplicateName(c: UntypedFormControl) { + const name = c.value.trim(); + return !!this.templates.find((template) => template.name === name); + }; + + close(): void { + this.dialogRef.close(); + } + + save(): void { + this.dialogRef.close(this.templateNameCtrl.value); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html index 3c6b410ac2..0554ca0b57 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html @@ -22,13 +22,10 @@ {{template.name}} - - - @@ -37,13 +34,15 @@ + [ngTemplateOutletContext]="{ $implicit: config, innerValue: false }"> - +
-
{{config.key}}
+ [ngStyle]="{'padding-left': innerValue ? '16px': '0'}"> +
+ {{!innerValue ? ('gateway.rpc.' + config.key | translate) : config.key}} +
@@ -52,7 +51,7 @@ + [ngTemplateOutletContext]="{ $implicit: subConfig, innerValue: true }">
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss index 33ce5fa6d8..4c28959be2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss @@ -50,6 +50,11 @@ background-color: rgba(25, 128, 56, 0.08); } + mat-expansion-panel { + margin-top: 10px; + overflow: visible; + } + .mat-expansion-panel-header-description { flex-direction: row-reverse; align-items: center; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts index 26cdf24dd1..357dd0504b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts @@ -15,9 +15,16 @@ /// import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { ConnectorType, RPCTemplate } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { TranslateService } from '@ngx-translate/core'; +import { + ConnectorType, + RPCTemplate +} from '@home/components/widget/lib/gateway/gateway-widget.models'; import { KeyValue } from '@angular/common'; +import { EntityType } from '@shared/models/entity-type.models'; +import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { AttributeService } from '@core/http/attribute.service'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { MatDialog } from '@angular/material/dialog'; @Component({ selector: 'tb-gateway-service-rpc-connector-templates', @@ -29,52 +36,52 @@ export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit { @Input() connectorType: ConnectorType; + @Input() + ctx: WidgetContext; + @Output() saveTemplate: EventEmitter = new EventEmitter(); - rpcTemplates: Array = []; - - constructor(private translate: TranslateService) { - this.rpcTemplates.push( - { - name: 'Test Template', - config: { - fieldString: 'string', - fieldNumber: 666, - fieldBool: true, - fieldArray: [111, 222, 333, "String", "444"], - fieldObj: { - subKey1: 'dasd', - subKey2: 666, - } - } - } - ) - } - + @Output() + useTemplate: EventEmitter = new EventEmitter(); - ngOnInit() { + @Input() + rpcTemplates: Array; + constructor(private attributeService: AttributeService) { } - public useTemplate($event: Event): void { - $event.stopPropagation(); - console.log("useTemplate") + ngOnInit() { } - public copyTemplate($event: Event): void { + public applyTemplate($event: Event, template: RPCTemplate): void { $event.stopPropagation(); - console.log("copyTemplate") + this.useTemplate.emit(template); } - public deleteTemplate($event: Event): void { + public deleteTemplate($event: Event, template: RPCTemplate): void { $event.stopPropagation(); - console.log("deleteTemplate") + const index = this.rpcTemplates.findIndex(data => { + return data.name == template.name; + }) + this.rpcTemplates.splice(index, 1); + const key = `${this.connectorType}_template`; + this.attributeService.saveEntityAttributes( + { + id: this.ctx.defaultSubscription.targetDeviceId, + entityType: EntityType.DEVICE + } + , AttributeScope.SERVER_SCOPE, [{ + key, + value: this.rpcTemplates + }]).subscribe(() => { + }) } - public originalOrder = (a: KeyValue, b: KeyValue): number => { + public originalOrder = (a, b): number => { return 0; } + public isObject(value: any) { return value !== null && typeof value === 'object' && !Array.isArray(value); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html index 9bab9508a5..c706244c31 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html @@ -16,7 +16,8 @@ -->
-
{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}
+
{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}
@@ -26,27 +27,26 @@ placeholder="echo"/> - {{ 'gateway.rpc.request-topic-expression' | translate }} + {{ 'gateway.rpc.requestTopicExpression' | translate }} - {{ 'gateway.rpc.response-topic-expression' | translate }} + {{ 'gateway.rpc.responseTopicExpression' | translate }} - {{ 'gateway.rpc.response-timeout' | translate }} + {{ 'gateway.rpc.responseTimeout' | translate }} - {{ 'gateway.rpc.value-expression' | translate }} + {{ 'gateway.rpc.valueExpression' | translate }} - {{ 'gateway.rpc.tag' | translate }} @@ -62,7 +62,7 @@ - {{ 'gateway.rpc.function-code' | translate }} + {{ 'gateway.rpc.functionCode' | translate }} {{ code }} @@ -77,20 +77,19 @@ placeholder="1" step="1"/> - {{ 'gateway.rpc.objects-count' | translate }} + {{ 'gateway.rpc.objectsCount' | translate }}
- - {{ 'gateway.rpc.method-rpc' | translate }} + {{ 'gateway.rpc.methodRPC' | translate }} - {{ 'gateway.rpc.request-type' | translate }} + {{ 'gateway.rpc.requestType' | translate }} {{bACnetRequestTypesTranslates.get(type) | translate}} @@ -98,13 +97,13 @@ - {{ 'gateway.rpc.request-timeout' | translate }} + {{ 'gateway.rpc.requestTimeout' | translate }}
- {{ 'gateway.rpc.object-type' | translate }} + {{ 'gateway.rpc.objectType' | translate }} {{bACnetObjectTypesTranslates.get(type) | translate}} @@ -118,22 +117,21 @@
- {{ 'gateway.rpc.property-id' | translate }} + {{ 'gateway.rpc.propertyId' | translate }}
- - {{ 'gateway.rpc.method-rpc' | translate }} + {{ 'gateway.rpc.methodRPC' | translate }} - {{ 'gateway.rpc.characteristic-uuid' | translate }} + {{ 'gateway.rpc.characteristicUUID' | translate }} - {{ 'gateway.rpc.method-processing' | translate }} + {{ 'gateway.rpc.methodProcessing' | translate }} {{bLEMethodsTranslates.get(type) | translate}} @@ -141,35 +139,34 @@ - {{ 'gateway.rpc.with-response' | translate }} + {{ 'gateway.rpc.withResponse' | translate }} - - {{ 'gateway.rpc.method-rpc' | translate }} + {{ 'gateway.rpc.methodRPC' | translate }} - {{ 'gateway.rpc.node-id' | translate }} + {{ 'gateway.rpc.nodeID' | translate }} - {{ 'gateway.rpc.is-extended-id' | translate }} + {{ 'gateway.rpc.isExtendedID' | translate }} - {{ 'gateway.rpc.is-fd' | translate }} + {{ 'gateway.rpc.isFD' | translate }} - {{ 'gateway.rpc.bitrate-switch' | translate }} + {{ 'gateway.rpc.bitrateSwitch' | translate }}
- {{ 'gateway.rpc.data-length' | translate }} + {{ 'gateway.rpc.dataLength' | translate }} - {{ 'gateway.rpc.data-byte-order' | translate }} + {{ 'gateway.rpc.dataByteorder' | translate }} {{ order | translate }} @@ -179,59 +176,55 @@
- {{ 'gateway.rpc.data-before' | translate }} + {{ 'gateway.rpc.dataBefore' | translate }} - {{ 'gateway.rpc.data-after' | translate }} + {{ 'gateway.rpc.dataAfter' | translate }}
- {{ 'gateway.rpc.data-in-hex' | translate }} + {{ 'gateway.rpc.dataInHEX' | translate }} - {{ 'gateway.rpc.data-expression' | translate }} + {{ 'gateway.rpc.dataExpression' | translate }}
- - - {{ 'gateway.rpc.method-filter' | translate }} + {{ 'gateway.rpc.methodFilter' | translate }} - {{ 'gateway.rpc.value-expression' | translate }} + {{ 'gateway.rpc.valueExpression' | translate }} - - {{ 'gateway.rpc.method-rpc' | translate }} + {{ 'gateway.rpc.methodRPC' | translate }} - {{ 'gateway.rpc.value-expression' | translate }} + {{ 'gateway.rpc.valueExpression' | translate }} - {{ 'gateway.rpc.with-response' | translate }} + {{ 'gateway.rpc.withResponse' | translate }} - - {{ 'gateway.rpc.method-rpc' | translate }} + {{ 'gateway.rpc.methodRPC' | translate }} - {{ 'gateway.rpc.method-processing' | translate }} + {{ 'gateway.rpc.methodProcessing' | translate }} {{ SocketMethodProcessingsTranslates.get(method) | translate }} @@ -247,27 +240,25 @@ - {{ 'gateway.rpc.with-response' | translate }} + {{ 'gateway.rpc.withResponse' | translate }} - - {{ 'gateway.rpc.method-rpc' | translate }} + {{ 'gateway.rpc.methodRPC' | translate }} - {{ 'gateway.rpc.value-expression' | translate }} + {{ 'gateway.rpc.valueExpression' | translate }} - {{ 'gateway.rpc.with-response' | translate }} + {{ 'gateway.rpc.withResponse' | translate }} - - {{ 'gateway.rpc.request-filter' | translate }} + {{ 'gateway.rpc.requestFilter' | translate }} @@ -298,15 +289,14 @@ - - {{ 'gateway.rpc.method-filter' | translate }} + {{ 'gateway.rpc.methodFilter' | translate }}
- {{ 'gateway.rpc.http-method' | translate }} + {{ 'gateway.rpc.HTTPMethod' | translate }} {{ method }} @@ -314,14 +304,14 @@ - {{ 'gateway.rpc.request-url' | translate }} + {{ 'gateway.rpc.requestUrlExpression' | translate }}
- {{ 'gateway.rpc.response-timeout' | translate }} + {{ 'gateway.rpc.responseTimeout' | translate }} @@ -337,11 +327,11 @@
- {{ 'gateway.rpc.value-expression' | translate }} + {{ 'gateway.rpc.valueExpression' | translate }}
- {{ 'gateway.rpc.http-headers' | translate }} + {{ 'gateway.rpc.httpHeaders' | translate }}
{{ 'gateway.rpc.header-name' | translate }} @@ -405,15 +395,14 @@
- - {{ 'gateway.rpc.method-filter' | translate }} + {{ 'gateway.rpc.methodFilter' | translate }}
- {{ 'gateway.rpc.method' | translate }} + {{ 'gateway.rpc.httpMethod' | translate }} {{ method }} @@ -421,13 +410,13 @@ - {{ 'gateway.rpc.request-url' | translate }} + {{ 'gateway.rpc.requestUrlExpression' | translate }}
- {{ 'gateway.rpc.response-timeout' | translate }} + {{ 'gateway.rpc.responseTimeout' | translate }} @@ -443,15 +432,15 @@
- {{ 'gateway.rpc.request-value-expression' | translate }} + {{ 'gateway.rpc.requestValueExpression' | translate }} - {{ 'gateway.rpc.response-value-expression' | translate }} + {{ 'gateway.rpc.responseValueExpression' | translate }}
- {{ 'gateway.rpc.http-headers' | translate }} + {{ 'gateway.rpc.httpHeaders' | translate }}
{{ 'gateway.rpc.header-name' | translate }} @@ -483,7 +472,6 @@
- @@ -513,11 +501,27 @@ + + {{'dsadas'}} + + {{ 'gateway.statistics.command' | translate }} + + + + {{ 'widget-config.datasource-parameters' | translate }} + + edit + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts index ce2c850741..7a2f91907e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts @@ -14,27 +14,57 @@ /// limitations under the License. /// -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; import { - BACnetObjectTypes, BACnetObjectTypesTranslates, - BACnetRequestTypes, BACnetRequestTypesTranslates, BLEMethods, BLEMethodsTranslates, + ControlValueAccessor, + FormArray, + FormBuilder, + FormControl, + FormGroup, + NG_VALUE_ACCESSOR, + Validators +} from '@angular/forms'; +import { + BACnetObjectTypes, + BACnetObjectTypesTranslates, + BACnetRequestTypes, + BACnetRequestTypesTranslates, + BLEMethods, + BLEMethodsTranslates, CANByteOrders, - ConnectorType, GatewayConnectorDefaultTypesTranslates, HTTPMethods, + ConnectorType, + GatewayConnectorDefaultTypesTranslates, + HTTPMethods, ModbusCommandTypes, RPCCommand, - SNMPMethods, SNMPMethodsTranslations, + RPCTemplateConfig, + SNMPMethods, + SNMPMethodsTranslations, SocketEncodings, - SocketMethodProcessings, SocketMethodProcessingsTranslates + SocketMethodProcessings, + SocketMethodProcessingsTranslates } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { TranslateService } from "@ngx-translate/core"; +import { MatDialog } from '@angular/material/dialog'; +import { + JsonObjectEditDialogComponent, + JsonObjectEditDialogData +} from '@shared/components/dialog/json-object-edit-dialog.component'; +import { jsonRequired } from '@shared/components/json-object-edit.component'; +import { deepClone } from '@core/utils'; @Component({ selector: 'tb-gateway-service-rpc-connector', templateUrl: './gateway-service-rpc-connector.component.html', - styleUrls: ['./gateway-service-rpc-connector.component.scss'] + styleUrls: ['./gateway-service-rpc-connector.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => GatewayServiceRPCConnectorComponent), + multi: true + } + ] }) -export class GatewayServiceRPCConnectorComponent implements OnInit { +export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValueAccessor { @Input() connectorType: ConnectorType; @@ -42,6 +72,9 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { @Output() sendCommand: EventEmitter = new EventEmitter(); + @Output() + saveTemplate: EventEmitter = new EventEmitter(); + commandForm: FormGroup; codesArray: Array = [1, 2, 3, 4, 5, 6, 15, 16]; @@ -74,14 +107,41 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { '(\\#[-a-z\\d_]*)?$', // fragment locator 'i' ); + private propagateChange = (v: any) => { + } constructor(private fb: FormBuilder, - private translate: TranslateService) { + private dialog: MatDialog,) { } ngOnInit() { - this.commandForm = this.connectorParamsFormGroupByType(this.connectorType) + this.commandForm = this.connectorParamsFormGroupByType(this.connectorType); + this.commandForm.valueChanges.subscribe(value => { + const httpHeaders = {}; + const security = {}; + switch (this.connectorType) { + case ConnectorType.REST: + value.httpHeaders.forEach(data => { + httpHeaders[data.headerName] = data.value; + }) + value.httpHeaders = httpHeaders; + value.security.forEach(data => { + security[data.securityName] = data.value; + }) + value.security = security; + break; + case ConnectorType.REQUEST: + value.httpHeaders.forEach(data => { + httpHeaders[data.headerName] = data.value; + }) + value.httpHeaders = httpHeaders; + break; + } + if (this.commandForm.valid) { + this.propagateChange({...this.commandForm.value,...value}); + } + }) } connectorParamsFormGroupByType(type: ConnectorType): FormGroup { @@ -206,6 +266,13 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { method: [null, [Validators.required]], arguments: this.fb.array([]), }) + break; + default: + formGroup = this.fb.group({ + command: [null, [Validators.required]], + params: ['{}', [jsonRequired]], + }) + } return formGroup; } @@ -213,7 +280,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { addSNMPoid(value: string = null) { const oidsFA = this.commandForm.get('oid') as FormArray; if (oidsFA) { - oidsFA.push(this.fb.control(value, [Validators.required])); + oidsFA.push(this.fb.control(value, [Validators.required]), {emitEvent: false}); } } @@ -229,7 +296,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { value: [value.value, [Validators.required]] }) if (headerFA) { - headerFA.push(formGroup); + headerFA.push(formGroup, {emitEvent: false}); } } @@ -245,7 +312,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { value: [value.value, [Validators.required]] }) if (securityFA) { - securityFA.push(formGroup); + securityFA.push(formGroup, {emitEvent: false}); } } @@ -261,7 +328,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { addOCPUAArguments(value: string = null) { const oidsFA = this.commandForm.get('arguments') as FormArray; if (oidsFA) { - oidsFA.push(this.fb.control(value)); + oidsFA.push(this.fb.control(value), {emitEvent: false}); } } @@ -269,4 +336,86 @@ export class GatewayServiceRPCConnectorComponent implements OnInit { const oidsFA = this.commandForm.get('arguments') as FormArray; oidsFA.removeAt(index); } + + openEditJSONDialog($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(JsonObjectEditDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + jsonValue: JSON.parse(this.commandForm.get('params').value), + required: true + } + }).afterClosed().subscribe( + (res) => { + if (res) { + this.commandForm.get('params').setValue(JSON.stringify(res)); + } + } + ); + } + + save() { + this.saveTemplate.emit(); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + clearFromArrayByName(name: string) { + const formArray = this.commandForm.get(name) as FormArray; + while (formArray.length !== 0) { + formArray.removeAt(0) + } + } + + writeValue(value: RPCTemplateConfig): void { + if (typeof value == "object") { + value = deepClone(value); + switch (this.connectorType) { + case ConnectorType.SNMP: + this.clearFromArrayByName("oids"); + value.oids.forEach(value => { + this.addSNMPoid(value) + }) + delete value.oids; + break; + case ConnectorType.REQUEST: + this.clearFromArrayByName("httpHeaders"); + value.httpHeaders && Object.entries(value.httpHeaders).forEach(httpHeader => { + this.addHTTPHeader({headerName: httpHeader[0], value: httpHeader[1] as string}) + }) + delete value.httpHeaders; + break; + case ConnectorType.REST: + this.clearFromArrayByName("httpHeaders"); + this.clearFromArrayByName("security"); + value.security && Object.entries(value.security).forEach(securityHeader => { + this.addHTTPSecurity({securityName: securityHeader[0], value: securityHeader[1] as string}) + }) + delete value.security; + value.httpHeaders && Object.entries(value.httpHeaders).forEach(httpHeader => { + this.addHTTPHeader({headerName: httpHeader[0], value: httpHeader[1] as string}) + }) + delete value.httpHeaders; + break; + case ConnectorType.OPCUA: + case ConnectorType.OPCUA_ASYNCIO: + this.clearFromArrayByName("arguments"); + value.arguments.forEach(value => { + this.addOCPUAArguments(value) + }) + delete value.arguments; + break; + } + this.commandForm.patchValue(value, {onlySelf: false}); + } + } + } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html index 2397d1bcdd..da9ea07ebd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html @@ -41,7 +41,8 @@ - +
@@ -52,4 +53,6 @@
- + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts index a3c96f7d49..484f8b16b5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts @@ -14,17 +14,28 @@ /// limitations under the License. /// -import { OnInit, Component, Input } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { WidgetContext } from '@home/models/widget-component.models'; import { ContentType } from '@shared/models/constants'; -import { - JsonObjectEditDialogComponent, - JsonObjectEditDialogData -} from '@shared/components/dialog/json-object-edit-dialog.component'; import { jsonRequired } from '@shared/components/json-object-edit.component'; -import { ConnectorType, RPCCommand } from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { + ConnectorType, + RPCCommand, + RPCTemplate, + RPCTemplateConfig, + SaveRPCTemplateData +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { + GatewayServiceRPCConnectorTemplateDialogComponent +} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog'; +import { AttributeService } from '@core/http/attribute.service'; +import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { DatasourceType, widgetType } from '@shared/models/widget.models'; +import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; +import { UtilsService } from '@core/services/utils.service'; @Component({ selector: 'tb-gateway-service-rpc', @@ -58,9 +69,27 @@ export class GatewayServiceRPCComponent implements OnInit { ]; public connectorType: ConnectorType; + public templates: Array = []; + + private subscription: IWidgetSubscription; + private subscriptionOptions: WidgetSubscriptionOptions = { + callbacks: { + onDataUpdated: () => this.ctx.ngZone.run(() => { + this.updateTemplates() + }), + onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => { + this.onDataUpdateError(e); + }), + dataLoading: () => { + } + } + }; constructor(private fb: FormBuilder, - private dialog: MatDialog) { + private dialog: MatDialog, + private utils: UtilsService, + private cd: ChangeDetectorRef, + private attributeService: AttributeService) { this.commandForm = this.fb.group({ command: [null, [Validators.required]], time: [60, [Validators.required, Validators.min(1)]], @@ -75,6 +104,17 @@ export class GatewayServiceRPCComponent implements OnInit { this.commandForm.get('command').setValue(this.RPCCommands[0]); } else { this.connectorType = this.ctx.stateController.getStateParams().connector_rpc.value.type; + const subscriptionInfo = [{ + type: DatasourceType.entity, + entityType: EntityType.DEVICE, + entityId: this.ctx.defaultSubscription.targetDeviceId, + entityName: 'Connector', + attributes: [{name: `${this.connectorType}_template`}] + }]; + this.ctx.subscriptionApi.createSubscriptionFromInfo(widgetType.latest, subscriptionInfo, + this.subscriptionOptions, false, true).subscribe(subscription => { + this.subscription = subscription; + }) } } @@ -82,7 +122,9 @@ export class GatewayServiceRPCComponent implements OnInit { this.resultTime = null; const formValues = value || this.commandForm.value; const commandPrefix = this.isConnector ? `${this.connectorType}_` : 'gateway_'; - this.ctx.controlApi.sendTwoWayCommand(commandPrefix + formValues.command.toLowerCase(), formValues.params, formValues.time).subscribe({ + const command = !this.isConnector ? formValues.command.toLowerCase() : this.getCommandFromParamsByType(formValues.params); + const params = formValues.params; + this.ctx.controlApi.sendTwoWayCommand(commandPrefix + command, params, formValues.time).subscribe({ next: resp => { this.resultTime = new Date().getTime(); this.commandForm.get('result').setValue(JSON.stringify(resp)) @@ -95,23 +137,86 @@ export class GatewayServiceRPCComponent implements OnInit { }); } - openEditJSONDialog($event: Event) { - if ($event) { - $event.stopPropagation(); + getCommandFromParamsByType(params: RPCTemplateConfig) { + switch (this.connectorType) { + case ConnectorType.MQTT: + case ConnectorType.FTP: + case ConnectorType.SNMP: + case ConnectorType.REST: + case ConnectorType.REQUEST: + return params.methodFilter; + case ConnectorType.MODBUS: + return params.tag; + case ConnectorType.BACNET: + case ConnectorType.CAN: + case ConnectorType.OPCUA: + case ConnectorType.OPCUA_ASYNCIO: + return params.method; + case ConnectorType.BLE: + case ConnectorType.OCPP: + case ConnectorType.SOCKET: + case ConnectorType.XMPP: + return params.methodRPC; + default: + return params.command; } - this.dialog.open(JsonObjectEditDialogComponent, { + } + + saveTemplate() { + this.dialog.open + (GatewayServiceRPCConnectorTemplateDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - jsonValue: JSON.parse(this.commandForm.get('params').value), - required: true - } + data: {config: this.commandForm.value.params, templates: this.templates} }).afterClosed().subscribe( (res) => { - if (res) { - this.commandForm.get('params').setValue(JSON.stringify(res)); + const templateAttribute: RPCTemplate = { + name: res, + config: this.commandForm.value.params + } + const templatesArray = this.templates; + const existingIndex = templatesArray.findIndex(template=>{ + return template.name == templateAttribute.name; + }) + if (existingIndex > -1 ){ + templatesArray.splice(existingIndex, 1) } + templatesArray.push(templateAttribute) + const key = `${this.connectorType}_template`; + this.attributeService.saveEntityAttributes( + { + id: this.ctx.defaultSubscription.targetDeviceId, + entityType: EntityType.DEVICE + } + , AttributeScope.SERVER_SCOPE, [{ + key, + value: templatesArray + }]).subscribe(() => { + }) } ); } + + useTemplate($event) { + this.commandForm.get('params').patchValue($event.config); + } + + private updateTemplates() { + this.templates = this.subscription.data[0].data[0][1].length ? + JSON.parse(this.subscription.data[0].data[0][1]) : []; + if (this.templates.length && this.commandForm.get('params').value == "{}") { + this.commandForm.get('params').patchValue(this.templates[0].config); + } + this.cd.detectChanges(); + } + + private onDataUpdateError(e: any) { + const exceptionData = this.utils.parseException(e); + let errorText = exceptionData.name; + if (exceptionData.message) { + errorText += ': ' + exceptionData.message; + } + console.error(errorText); + } + } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index 4b09667a6b..b8f8c996b3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -261,10 +261,17 @@ export enum SocketEncodings { } export interface RPCTemplate { - name: string; - config: { - [key: string]: any; - }; + name?: string; + config: RPCTemplateConfig; +} + +export interface RPCTemplateConfig { + [key: string]: any; +} + +export interface SaveRPCTemplateData { + config: RPCTemplateConfig, + templates: Array } export interface LogLink { diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index b4b688848e..d4828a0190 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -74,6 +74,9 @@ import { ValueChartCardWidgetComponent } from '@home/components/widget/lib/cards import { ProgressBarWidgetComponent } from '@home/components/widget/lib/cards/progress-bar-widget.component'; import { LiquidLevelWidgetComponent } from '@home/components/widget/lib/indicator/liquid-level-widget.component'; import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/doughnut-widget.component'; +import { + GatewayServiceRPCConnectorTemplateDialogComponent +} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog'; @NgModule({ declarations: @@ -107,6 +110,7 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough DeviceGatewayCommandComponent, GatewayConfigurationComponent, GatewayRemoteConfigurationDialogComponent, + GatewayServiceRPCConnectorTemplateDialogComponent, ValueCardWidgetComponent, AggregatedValueCardWidgetComponent, CountWidgetComponent, @@ -154,6 +158,7 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough DeviceGatewayCommandComponent, GatewayConfigurationComponent, GatewayRemoteConfigurationDialogComponent, + GatewayServiceRPCConnectorTemplateDialogComponent, ValueCardWidgetComponent, AggregatedValueCardWidgetComponent, CountWidgetComponent, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 294173e231..da5fbf824a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2776,54 +2776,54 @@ "rpc": { "title": "{{type}} Connector RPC parameters", "templates-title": "Connector RPC Templates", - "method-filter": "Method filter", - "request-topic-expression": "Request topic expression", - "response-topic-expression": "Response topic expression", - "response-timeout": "Response Time", - "value-expression": "Value Expression", + "methodFilter": "Method filter", + "requestTopicExpression": "Request topic expression", + "responseTopicExpression": "Response topic expression", + "responseTimeout": "Response Time", + "valueExpression": "Value Expression", "tag": "Tag", "type": "Type", - "function-code": "Function Code", - "objects-count": "Objects Count", + "functionCode": "Function Code", + "objectsCount": "Objects Count", "address": "Address", "method": "Method", - "request-type": "Request Type", - "request-timeout": "Request Timeout", - "object-type": "Object type", + "requestType": "Request Type", + "requestTimeout": "Request Timeout", + "objectType": "Object type", "identifier": "Identifier", - "property-id": "Property ID", - "method-rpc": "Method RPC name", - "with-response": "With Response", - "characteristic-uuid": "Characteristic UUID", - "method-processing": "Method Processing", - "node-id": "Node ID", - "is-extended-id": "Is Extended ID", - "is-fd": "Is FD", - "bitrate-switch": "Bitrate Switch", - "data-in-hex": "Data In HEX", - "data-length": "Data Length", - "data-byte-order": "Data Byte Order", - "data-before": "Data Before", - "data-after": "Data After", - "data-expression": "Data Expression", + "propertyId": "Property ID", + "methodRPC": "Method RPC name", + "withResponse": "With Response", + "characteristicUUID": "Characteristic UUID", + "methodProcessing": "Method Processing", + "nodeID": "Node ID", + "isExtendedID": "Is Extended ID", + "isFD": "Is FD", + "bitrateSwitch": "Bitrate Switch", + "dataInHEX": "Data In HEX", + "dataLength": "Data Length", + "dataByteorder": "Data Byte Order", + "dataBefore": "Data Before", + "dataAfter": "Data After", + "dataExpression": "Data Expression", "encoding": "Encoding", "oid": "OID", "add-oid": "Add OID", "add-header": "Add header", "add-security": "Add security", "remove": "Remove", - "request-filter": "Request Filter", - "request-url": "Request URL Expression", - "http-method": "HTTP Method", + "requestFilter": "Request Filter", + "requestUrlExpression": "Request URL Expression", + "HTTPMethod": "HTTP Method", "timeout": "Timeout", "tries": "Tries", - "http-headers": "HTTP Headers", + "httpHeaders": "HTTP Headers", "header-name": "Header name", "security-name": "Security name", "value": "Value", "security": "Security", - "response-value-expression": "Response Value Expression", - "request-value-expression": "Request Value Expression", + "responseValueExpression": "Response Value Expression", + "requestValueExpression": "Request Value Expression", "arguments": "Arguments", "add-argument": "Add argument", "write-property": "Write property", @@ -2843,7 +2843,12 @@ "multi-get": "Multiget", "get-next": "Get next", "bulk-get": "Bulk get", - "walk": "Walk" + "walk": "Walk", + "save-template": "Save template", + "template-name": "Template name", + "template-name-required": "Template name is required.", + "template-name-duplicate": "The name is already used." + }, "other": "Other", From b30bbf9d80183c3839ffcca8bf6df2c0dc609098 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 14 Dec 2023 19:05:54 +0200 Subject: [PATCH 057/209] attribute_kv optimization: deleted entity_type column, updated attribute_type and attribute_key columns --- .../3.6.2/schema_update_attribute_kv.sql | 181 ++++++++++++++++++ .../data/upgrade/3.6.2/schema_update_ttl.sql | 87 +++++++++ .../install/ThingsboardInstallService.java | 6 + .../CassandraTsDatabaseUpgradeService.java | 1 + .../install/SqlDatabaseUpgradeService.java | 97 ++++++++++ .../install/SqlTsDatabaseUpgradeService.java | 2 + .../TimescaleTsDatabaseUpgradeService.java | 5 + .../query/DefaultEntityQueryService.java | 11 +- .../dao/attributes/AttributesService.java | 3 +- .../server/common/data/AttributeScope.java | 32 ++++ .../server/dao/attributes/AttributeUtils.java | 3 +- .../server/dao/attributes/AttributesDao.java | 14 +- .../dao/attributes/BaseAttributesService.java | 18 +- .../attributes/CachedAttributesService.java | 18 +- .../model/sql/AttributeKvCompositeKey.java | 11 +- .../dao/model/sql/AttributeKvDictionary.java | 41 ++++ .../dao/model/sql/AttributeKvEntity.java | 14 +- .../server/dao/service/Validator.java | 14 ++ .../AttributeKvDictionaryRepository.java | 29 +++ .../AttributeKvInsertRepository.java | 58 +++--- .../sql/attributes/AttributeKvRepository.java | 29 ++- .../dao/sql/attributes/JpaAttributeDao.java | 113 ++++++++--- .../dao/sql/query/EntityKeyMapping.java | 20 +- .../main/resources/sql/schema-entities.sql | 13 +- .../main/resources/sql/schema-timescale.sql | 4 +- dao/src/main/resources/sql/schema-ts-psql.sql | 4 +- .../sql/schema-views-and-functions.sql | 2 +- .../profile/TbDeviceProfileNodeTest.java | 58 ++++-- 28 files changed, 729 insertions(+), 159 deletions(-) create mode 100644 application/src/main/data/upgrade/3.6.2/schema_update_attribute_kv.sql create mode 100644 application/src/main/data/upgrade/3.6.2/schema_update_ttl.sql create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/AttributeScope.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionary.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvDictionaryRepository.java diff --git a/application/src/main/data/upgrade/3.6.2/schema_update_attribute_kv.sql b/application/src/main/data/upgrade/3.6.2/schema_update_attribute_kv.sql new file mode 100644 index 0000000000..142b3c1844 --- /dev/null +++ b/application/src/main/data/upgrade/3.6.2/schema_update_attribute_kv.sql @@ -0,0 +1,181 @@ +-- +-- Copyright © 2016-2023 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + + +-- create new attribute_kv table schema +DO +$$ + BEGIN + -- in case of running the upgrade script a second time: + IF EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name = 'attribute_kv' and column_name='entity_type') THEN + IF EXISTS(SELECT 1 FROM pg_indexes WHERE indexname = 'idx_attribute_kv_by_key_and_last_update_ts') THEN + ALTER INDEX idx_attribute_kv_by_key_and_last_update_ts RENAME TO idx_attribute_kv_by_key_and_last_update_ts_old; + END IF; + IF EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'attribute_kv_pkey') THEN + ALTER TABLE attribute_kv RENAME CONSTRAINT attribute_kv_pkey TO attribute_kv_pkey_old; + END IF; + ALTER TABLE attribute_kv + RENAME TO attribute_kv_old; + CREATE TABLE IF NOT EXISTS attribute_kv + ( + entity_id uuid, + attribute_type int, + attribute_key int, + bool_v boolean, + str_v varchar(10000000), + long_v bigint, + dbl_v double precision, + json_v json, + last_update_ts bigint, + CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_id, attribute_type, attribute_key) + ); + END IF; + END; +$$; + +-- create attribute_kv_dictionary table +CREATE TABLE IF NOT EXISTS attribute_kv_dictionary +( + key varchar(255) NOT NULL, + key_id serial UNIQUE, + CONSTRAINT attribute_key_id_pkey PRIMARY KEY (key) +); + +-- create to_attribute_type_id +CREATE OR REPLACE FUNCTION to_attribute_type_id(IN attribute_type varchar, OUT attribute_type_id int) AS +$$ +BEGIN + CASE + WHEN attribute_type = 'CLIENT_SCOPE' THEN + attribute_type_id := 1; + WHEN attribute_type = 'SERVER_SCOPE' THEN + attribute_type_id := 2; + WHEN attribute_type = 'SHARED_SCOPE' THEN + attribute_type_id := 3; + END CASE; +END; +$$ LANGUAGE plpgsql; + + +-- insert keys into attribute_kv_dictionary +DO +$$ +DECLARE + insert_record RECORD; + key_cursor refcursor; +BEGIN + IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'attribute_kv_old') THEN + OPEN key_cursor FOR SELECT DISTINCT attribute_key + FROM attribute_kv_old + ORDER BY attribute_key; + LOOP + FETCH key_cursor INTO insert_record; + EXIT WHEN NOT FOUND; + IF NOT EXISTS(SELECT key FROM attribute_kv_dictionary WHERE key = insert_record.attribute_key) THEN + INSERT INTO attribute_kv_dictionary(key) VALUES (insert_record.attribute_key); + END IF; + END LOOP; + CLOSE key_cursor; + END IF; +END; +$$; + +-- create procedure to migrate all rows from attribute_kv_old to attribute_kv +CREATE OR REPLACE PROCEDURE insert_into_attribute_kv(IN path_to_file varchar) + LANGUAGE plpgsql AS +$$ +DECLARE + row_num_old integer; + row_num integer; + attribute_scope_array text[]; +BEGIN + attribute_scope_array := ARRAY['SERVER_SCOPE', 'CLIENT_SCOPE', 'SHARED_SCOPE']; + IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'attribute_kv_old') THEN + EXECUTE format('COPY (SELECT records.entity_id AS entity_id, + to_attribute_type_id(records.attribute_type) AS attribute_type, + records.attribute_key AS attribute_key, + records.bool_v AS bool_v, + records.str_v AS str_v, + records.long_v AS long_v, + records.dbl_v AS dbl_v, + records.json_v AS json_v, + records.last_update_ts AS last_update_ts + FROM (SELECT entity_id, + attribute_type, + key_id AS attribute_key, + bool_v, + str_v, + long_v, + dbl_v, + json_v, + last_update_ts + FROM attribute_kv_old INNER JOIN attribute_kv_dictionary ON (attribute_kv_old.attribute_key = attribute_kv_dictionary.key) + WHERE attribute_type= ANY(%L)) AS records) TO %L;', attribute_scope_array, path_to_file); + EXECUTE format('COPY attribute_kv FROM %L', path_to_file); + SELECT COUNT(*) INTO row_num_old FROM attribute_kv_old; + SELECT COUNT(*) INTO row_num FROM attribute_kv; + RAISE NOTICE 'Migrated % of % rows', row_num, row_num_old; + END IF; +EXCEPTION + WHEN others THEN + ROLLBACK; + RAISE EXCEPTION 'Error during COPY: %', SQLERRM; +END +$$; + +CREATE OR REPLACE PROCEDURE recreate_device_info_active_attribute_view() + LANGUAGE plpgsql AS +$$ +BEGIN + DROP VIEW IF EXISTS device_info_active_attribute_view CASCADE; + CREATE OR REPLACE VIEW device_info_active_attribute_view AS + SELECT d.* + , c.title as customer_title + , COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public + , d.type as device_profile_name + , COALESCE(da.bool_v, FALSE) as active + FROM device d + LEFT JOIN customer c ON c.id = d.customer_id + LEFT JOIN attribute_kv da ON da.entity_id = d.id AND da.attribute_type = 2 AND da.attribute_key = (select key_id from attribute_kv_dictionary where key = 'active'); +END; +$$; + +CREATE OR REPLACE PROCEDURE recreate_device_info_view() + LANGUAGE plpgsql AS +$$ +BEGIN + DROP VIEW IF EXISTS device_info_view CASCADE; + CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_attribute_view; +END; +$$; + +CREATE OR REPLACE PROCEDURE drop_attribute_kv_old_table() + LANGUAGE plpgsql AS +$$ +DECLARE + row_num integer; +BEGIN + SELECT COUNT(*) INTO row_num FROM attribute_kv; + IF row_num != 0 then + DROP TABLE IF EXISTS attribute_kv_old; + DROP PROCEDURE IF EXISTS insert_into_attribute_kv(IN path_to_file varchar); + ELSE + RAISE EXCEPTION 'Table attribute_kv is empty'; + END IF; + RETURN; +END; +$$; + diff --git a/application/src/main/data/upgrade/3.6.2/schema_update_ttl.sql b/application/src/main/data/upgrade/3.6.2/schema_update_ttl.sql new file mode 100644 index 0000000000..67ec25656e --- /dev/null +++ b/application/src/main/data/upgrade/3.6.2/schema_update_ttl.sql @@ -0,0 +1,87 @@ +-- +-- Copyright © 2016-2023 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid, + IN system_ttl bigint, INOUT deleted bigint) + LANGUAGE plpgsql AS +$$ +DECLARE +tenant_cursor CURSOR FOR select tenant.id as tenant_id + from tenant; +tenant_id_record uuid; + customer_id_record uuid; + tenant_ttl bigint; + customer_ttl bigint; + deleted_for_entities bigint; + tenant_ttl_ts bigint; + customer_ttl_ts bigint; +BEGIN +OPEN tenant_cursor; +FETCH tenant_cursor INTO tenant_id_record; +WHILE FOUND + LOOP + EXECUTE format( + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', + tenant_id_record, 'TTL') INTO tenant_ttl; + if tenant_ttl IS NULL THEN + tenant_ttl := system_ttl; +END IF; + IF tenant_ttl > 0 THEN + tenant_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - tenant_ttl::bigint * 1000)::bigint; + deleted_for_entities := delete_device_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for devices where tenant_id = %', deleted_for_entities, tenant_id_record; + deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for assets where tenant_id = %', deleted_for_entities, tenant_id_record; +END IF; +FOR customer_id_record IN +SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record + LOOP + EXECUTE format( + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', + customer_id_record, 'TTL') INTO customer_ttl; +IF customer_ttl IS NULL THEN + customer_ttl_ts := tenant_ttl_ts; +ELSE + IF customer_ttl > 0 THEN + customer_ttl_ts := + (EXTRACT(EPOCH FROM current_timestamp) * 1000 - + customer_ttl::bigint * 1000)::bigint; +END IF; +END IF; + IF customer_ttl_ts IS NOT NULL AND customer_ttl_ts > 0 THEN + deleted_for_entities := + delete_customer_records_from_ts_kv(tenant_id_record, customer_id_record, + customer_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for customer with id = % where tenant_id = %', deleted_for_entities, customer_id_record, tenant_id_record; + deleted_for_entities := + delete_device_records_from_ts_kv(tenant_id_record, customer_id_record, + customer_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for devices where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; + deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, + customer_id_record, + customer_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for assets where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; +END IF; +END LOOP; +FETCH tenant_cursor INTO tenant_id_record; +END LOOP; +END +$$; \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 3829b3cf45..ac348c1249 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -265,6 +265,12 @@ public class ThingsboardInstallService { case "3.6.0": log.info("Upgrading ThingsBoard from version 3.6.0 to 3.6.1 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.6.0"); + case "3.6.2": + log.info("Upgrading ThingsBoard from version 3.6.2 to 3.7.0 ..."); + if (databaseTsUpgradeService != null) { + databaseTsUpgradeService.upgradeDatabase("3.6.2"); + } + databaseEntitiesUpgradeService.upgradeDatabase("3.6.2"); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache break; default: diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java index 5c7c51eb2c..3e2f29440c 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java @@ -52,6 +52,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase case "3.1.1": case "3.2.1": case "3.2.2": + case "3.6.2": break; default: throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index c02b7e9cc1..2983dea649 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -19,12 +19,14 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.SystemUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -48,6 +50,8 @@ import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; import org.thingsboard.server.service.install.sql.SqlDbHelper; import org.thingsboard.server.service.install.update.DefaultDataUpdateService; +import java.io.File; +import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; @@ -64,6 +68,8 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; +import static org.thingsboard.server.service.install.AbstractSqlTsDatabaseUpgradeService.PATH_TO_USERS_PUBLIC_FOLDER; +import static org.thingsboard.server.service.install.AbstractSqlTsDatabaseUpgradeService.THINGSBOARD_WINDOWS_UPGRADE_DIR; import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO; import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS; import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION; @@ -89,6 +95,7 @@ import static org.thingsboard.server.service.install.DatabaseHelper.TYPE; public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService { private static final String SCHEMA_UPDATE_SQL = "schema_update.sql"; + private static final String LOAD_ATTRIBUTE_KV_FUNCTIONS_SQL = "schema_update_attribute_kv.sql"; @Value("${spring.datasource.url}") private String dbUrl; @@ -792,11 +799,88 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.error("Failed updating schema!!!", e); } break; + case "3.6.2": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + if (isOldSchema(conn, 3006002)) { + log.info("Updating schema ..."); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.6.2", LOAD_ATTRIBUTE_KV_FUNCTIONS_SQL); + loadSql(schemaUpdateFile, conn); + + Path pathToTempAttributeKvFile; + if (SystemUtils.IS_OS_WINDOWS) { + pathToTempAttributeKvFile = createTempFileWindows("attribute_kv_temp",".sql"); + executeQuery(conn, "call insert_into_attribute_kv('" + pathToTempAttributeKvFile + "')"); + } else { + pathToTempAttributeKvFile = createTempFile("attribute_kv", "attribute_kv_temp.sql"); + executeQuery(conn, "call insert_into_attribute_kv('" + pathToTempAttributeKvFile + "')"); + } + + //recreate device_info_active_attribute_view + executeQuery(conn, "call recreate_device_info_active_attribute_view()"); + executeQuery(conn, "call recreate_device_info_view()"); + + // remove attribute_kv_old + executeQuery(conn, "call drop_attribute_kv_old_table()"); + + //create index for new table attribute_kv + executeQuery(conn, "CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribute_kv(entity_id, attribute_key, last_update_ts desc);"); + + // remove temp files + if (pathToTempAttributeKvFile.toFile().exists()) { + boolean deleteTsKvFile = pathToTempAttributeKvFile.toFile().delete(); + if (deleteTsKvFile) { + log.info("Successfully deleted the temp file for attribute_kv table upgrade!"); + } + } + + executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 3007000;"); + log.info("Schema updated to version 3.7.0."); + } else { + log.info("Skip schema re-update to version 3.7.0. Use env flag 'SKIP_SCHEMA_VERSION_CHECK' to force the re-update."); + } + } catch (Exception e) { + log.error("Failed updating schema!!!", e); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } } + private static Path createTempFile(String tempDirectoryName, String tempFileName) throws IOException { + Path pathToTempAttributeKvFile; + Path tempDirPath = Files.createTempDirectory(tempDirectoryName); + File tempDirAsFile = tempDirPath.toFile(); + boolean writable = tempDirAsFile.setWritable(true, false); + boolean readable = tempDirAsFile.setReadable(true, false); + boolean executable = tempDirAsFile.setExecutable(true, false); + pathToTempAttributeKvFile = tempDirPath.resolve(tempFileName).toAbsolutePath(); + + if (!(writable && readable && executable)) { + throw new RuntimeException("Failed to grant write permissions for the: " + tempDirPath + "folder!"); + } + return pathToTempAttributeKvFile; + } + + private static Path createTempFileWindows(String prefix, String suffix) throws IOException { + Path pathToTempAttributeKvFile; + log.info("Lookup for environment variable: {} ...", THINGSBOARD_WINDOWS_UPGRADE_DIR); + Path pathToDir; + String thingsboardWindowsUpgradeDir = System.getenv("THINGSBOARD_WINDOWS_UPGRADE_DIR"); + if (StringUtils.isNotEmpty(thingsboardWindowsUpgradeDir)) { + log.info("Environment variable: {} was found!", THINGSBOARD_WINDOWS_UPGRADE_DIR); + pathToDir = Paths.get(thingsboardWindowsUpgradeDir); + } else { + log.info("Failed to lookup environment variable: {}", THINGSBOARD_WINDOWS_UPGRADE_DIR); + pathToDir = Paths.get(PATH_TO_USERS_PUBLIC_FOLDER); + } + log.info("Directory: {} will be used for creation temporary upgrade files!", pathToDir); + + Path attributeKvFile = Files.createTempFile(pathToDir, prefix, suffix); + pathToTempAttributeKvFile = attributeKvFile.toAbsolutePath(); + return pathToTempAttributeKvFile; + } + private void runSchemaUpdateScript(Connection connection, String version) throws Exception { Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, SCHEMA_UPDATE_SQL); loadSql(schemaUpdateFile, connection); @@ -811,6 +895,19 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService Thread.sleep(5000); } + private void executeQuery(Connection conn, String query) { + try { + Statement statement = conn.createStatement(); + statement.execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + printWarnings(statement); + Thread.sleep(2000); + log.info("Successfully executed query: {}", query); + } catch (InterruptedException | SQLException e) { + log.error("Failed to execute query: {} due to: {}", query, e.getMessage()); + throw new RuntimeException("Failed to execute query:" + query + " due to: ", e); + } + } + protected void printWarnings(Statement statement) throws SQLException { SQLWarning warnings = statement.getWarnings(); if (warnings != null) { diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java index 6400463e11..b61da45631 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java @@ -213,6 +213,8 @@ public class SqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSer loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3"); } break; + case "3.6.2": + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java index 205ec14591..4477bef1c4 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java @@ -184,6 +184,11 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr break; case "3.2.2": break; + case "3.6.2": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "3.6.2"); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java index 89b4002223..296c992ccf 100644 --- a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java +++ b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java @@ -251,14 +251,13 @@ public class DefaultEntityQueryService implements EntityQueryService { } if (isAttributes) { - Map> typesMap = ids.stream().collect(Collectors.groupingBy(EntityId::getEntityType)); - List>> futures = new ArrayList<>(typesMap.size()); - typesMap.forEach((type, entityIds) -> futures.add(dbCallbackExecutor.submit(() -> attributesService.findAllKeysByEntityIds(tenantId, type, entityIds)))); - attributesKeysFuture = Futures.transform(Futures.allAsList(futures), lists -> { - if (CollectionUtils.isEmpty(lists)) { + ListenableFuture> future; + future = dbCallbackExecutor.submit(() -> attributesService.findAllKeysByEntityIds(tenantId, ids)); + attributesKeysFuture = Futures.transform(future, list -> { + if (CollectionUtils.isEmpty(list)) { return Collections.emptyList(); } - return lists.stream().flatMap(List::stream).distinct().sorted().collect(Collectors.toList()); + return list.stream().distinct().sorted().collect(Collectors.toList()); }, dbCallbackExecutor); } else { attributesKeysFuture = null; diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java index 8fab864cc5..de1386d0c6 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java @@ -16,7 +16,6 @@ package org.thingsboard.server.dao.attributes; import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -45,6 +44,6 @@ public interface AttributesService { List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); - List findAllKeysByEntityIds(TenantId tenantId, EntityType entityType, List entityIds); + List findAllKeysByEntityIds(TenantId tenantId, List entityIds); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/AttributeScope.java b/common/data/src/main/java/org/thingsboard/server/common/data/AttributeScope.java new file mode 100644 index 0000000000..83f5210427 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/AttributeScope.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import lombok.Getter; + +public enum AttributeScope { + + CLIENT_SCOPE(1), + SERVER_SCOPE(2), + SHARED_SCOPE(3); + @Getter + private final int id; + + AttributeScope(int id) { + this.id = id; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java index 192d56334d..29c44edd68 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.attributes; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.dao.exception.IncorrectParameterException; @@ -27,7 +28,7 @@ public class AttributeUtils { public static void validate(EntityId id, String scope) { Validator.validateId(id.getId(), "Incorrect id " + id); - Validator.validateString(scope, "Incorrect scope " + scope); + Validator.validateEnum(AttributeScope.class, scope, "Incorrect scope " + scope); } public static void validate(List kvEntries, boolean valueNoXssValidation) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java index ffb3b9c229..2f53b0afa7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.attributes; import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -31,17 +31,17 @@ import java.util.Optional; */ public interface AttributesDao { - Optional find(TenantId tenantId, EntityId entityId, String attributeType, String attributeKey); + Optional find(TenantId tenantId, EntityId entityId, AttributeScope attributeScope, String attributeKey); - List find(TenantId tenantId, EntityId entityId, String attributeType, Collection attributeKey); + List find(TenantId tenantId, EntityId entityId, AttributeScope attributeScope, Collection attributeKey); - List findAll(TenantId tenantId, EntityId entityId, String attributeType); + List findAll(TenantId tenantId, EntityId entityId, AttributeScope attributeScope); - ListenableFuture save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute); + ListenableFuture save(TenantId tenantId, EntityId entityId, AttributeScope attributeScope, AttributeKvEntry attribute); - List> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List keys); + List> removeAll(TenantId tenantId, EntityId entityId, AttributeScope attributeScope, List keys); List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); - List findAllKeysByEntityIds(TenantId tenantId, EntityType entityType, List entityIds); + List findAllKeysByEntityIds(TenantId tenantId, List entityIds); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java index f855c116e2..2defbd9398 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java @@ -22,7 +22,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -57,20 +57,20 @@ public class BaseAttributesService implements AttributesService { public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, String attributeKey) { validate(entityId, scope); Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey); - return Futures.immediateFuture(attributesDao.find(tenantId, entityId, scope, attributeKey)); + return Futures.immediateFuture(attributesDao.find(tenantId, entityId, AttributeScope.valueOf(scope), attributeKey)); } @Override public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, Collection attributeKeys) { validate(entityId, scope); attributeKeys.forEach(attributeKey -> Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey)); - return Futures.immediateFuture(attributesDao.find(tenantId, entityId, scope, attributeKeys)); + return Futures.immediateFuture(attributesDao.find(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys)); } @Override public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String scope) { validate(entityId, scope); - return Futures.immediateFuture(attributesDao.findAll(tenantId, entityId, scope)); + return Futures.immediateFuture(attributesDao.findAll(tenantId, entityId, AttributeScope.valueOf(scope))); } @Override @@ -79,28 +79,28 @@ public class BaseAttributesService implements AttributesService { } @Override - public List findAllKeysByEntityIds(TenantId tenantId, EntityType entityType, List entityIds) { - return attributesDao.findAllKeysByEntityIds(tenantId, entityType, entityIds); + public List findAllKeysByEntityIds(TenantId tenantId, List entityIds) { + return attributesDao.findAllKeysByEntityIds(tenantId, entityIds); } @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) { validate(entityId, scope); AttributeUtils.validate(attribute, valueNoXssValidation); - return attributesDao.save(tenantId, entityId, scope, attribute); + return attributesDao.save(tenantId, entityId, AttributeScope.valueOf(scope), attribute); } @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { validate(entityId, scope); AttributeUtils.validate(attributes, valueNoXssValidation); - List> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, scope, attribute)).collect(Collectors.toList()); + List> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, AttributeScope.valueOf(scope), attribute)).collect(Collectors.toList()); return Futures.allAsList(saveFutures); } @Override public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys) { validate(entityId, scope); - return Futures.allAsList(attributesDao.removeAll(tenantId, entityId, scope, attributeKeys)); + return Futures.allAsList(attributesDao.removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java index 322d725ff3..327f6b5706 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java @@ -26,7 +26,7 @@ import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.TbCacheValueWrapper; import org.thingsboard.server.cache.TbTransactionalCache; -import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; @@ -120,7 +120,7 @@ public class CachedAttributesService implements AttributesService { return cacheExecutor.submit(() -> { var cacheTransaction = cache.newTransactionForKey(attributeCacheKey); try { - Optional result = attributesDao.find(tenantId, entityId, scope, attributeKey); + Optional result = attributesDao.find(tenantId, entityId, AttributeScope.valueOf(scope), attributeKey); cacheTransaction.putIfAbsent(attributeCacheKey, result.orElse(null)); cacheTransaction.commit(); return result; @@ -159,7 +159,7 @@ public class CachedAttributesService implements AttributesService { var cacheTransaction = cache.newTransactionForKeys(notFoundKeys); try { log.trace("[{}][{}] Lookup attributes from db: {}", entityId, scope, notFoundAttributeKeys); - List result = attributesDao.find(tenantId, entityId, scope, notFoundAttributeKeys); + List result = attributesDao.find(tenantId, entityId, AttributeScope.valueOf(scope), notFoundAttributeKeys); for (AttributeKvEntry foundInDbAttribute : result) { AttributeCacheKey attributeCacheKey = new AttributeCacheKey(scope, entityId, foundInDbAttribute.getKey()); cacheTransaction.putIfAbsent(attributeCacheKey, foundInDbAttribute); @@ -198,7 +198,7 @@ public class CachedAttributesService implements AttributesService { @Override public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String scope) { validate(entityId, scope); - return Futures.immediateFuture(attributesDao.findAll(tenantId, entityId, scope)); + return Futures.immediateFuture(attributesDao.findAll(tenantId, entityId, AttributeScope.valueOf(scope))); } @Override @@ -207,15 +207,15 @@ public class CachedAttributesService implements AttributesService { } @Override - public List findAllKeysByEntityIds(TenantId tenantId, EntityType entityType, List entityIds) { - return attributesDao.findAllKeysByEntityIds(tenantId, entityType, entityIds); + public List findAllKeysByEntityIds(TenantId tenantId, List entityIds) { + return attributesDao.findAllKeysByEntityIds(tenantId, entityIds); } @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) { validate(entityId, scope); AttributeUtils.validate(attribute, valueNoXssValidation); - ListenableFuture future = attributesDao.save(tenantId, entityId, scope, attribute); + ListenableFuture future = attributesDao.save(tenantId, entityId, AttributeScope.valueOf(scope), attribute); return Futures.transform(future, key -> evict(entityId, scope, attribute, key), cacheExecutor); } @@ -226,7 +226,7 @@ public class CachedAttributesService implements AttributesService { List> futures = new ArrayList<>(attributes.size()); for (var attribute : attributes) { - ListenableFuture future = attributesDao.save(tenantId, entityId, scope, attribute); + ListenableFuture future = attributesDao.save(tenantId, entityId, AttributeScope.valueOf(scope), attribute); futures.add(Futures.transform(future, key -> evict(entityId, scope, attribute, key), cacheExecutor)); } @@ -243,7 +243,7 @@ public class CachedAttributesService implements AttributesService { @Override public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys) { validate(entityId, scope); - List> futures = attributesDao.removeAll(tenantId, entityId, scope, attributeKeys); + List> futures = attributesDao.removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys); return Futures.allAsList(futures.stream().map(future -> Futures.transform(future, key -> { cache.evict(new AttributeCacheKey(scope, entityId, key)); return key; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvCompositeKey.java index 936a18eeeb..80be913468 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvCompositeKey.java @@ -17,12 +17,9 @@ package org.thingsboard.server.dao.model.sql; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.thingsboard.server.common.data.EntityType; import java.io.Serializable; import java.util.UUID; @@ -30,20 +27,16 @@ import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.ATTRIBUTE_KEY_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.ATTRIBUTE_TYPE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_COLUMN; @Data @AllArgsConstructor @NoArgsConstructor @Embeddable public class AttributeKvCompositeKey implements Serializable { - @Enumerated(EnumType.STRING) - @Column(name = ENTITY_TYPE_COLUMN) - private EntityType entityType; @Column(name = ENTITY_ID_COLUMN, columnDefinition = "uuid") private UUID entityId; @Column(name = ATTRIBUTE_TYPE_COLUMN) - private String attributeType; + private int attributeType; @Column(name = ATTRIBUTE_KEY_COLUMN) - private String attributeKey; + private int attributeKey; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionary.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionary.java new file mode 100644 index 0000000000..1cde08912e --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionary.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; +import org.hibernate.annotations.Generated; + +import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.KEY_ID_COLUMN; + +@Data +@Entity +@Table(name = "attribute_kv_dictionary") +public final class AttributeKvDictionary { + + @Id + @Column(name = KEY_COLUMN) + private String key; + + @Column(name = KEY_ID_COLUMN, unique = true, columnDefinition="int") + @Generated + private int keyId; + +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java index bc97c0394f..1bb23aaa89 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java @@ -30,6 +30,7 @@ import jakarta.persistence.Column; import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; import jakarta.persistence.Table; +import jakarta.persistence.Transient; import java.io.Serializable; import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN; @@ -65,19 +66,22 @@ public class AttributeKvEntity implements ToData, Serializable @Column(name = LAST_UPDATE_TS_COLUMN) private Long lastUpdateTs; + @Transient + protected String strKey; + @Override public AttributeKvEntry toData() { KvEntry kvEntry = null; if (strValue != null) { - kvEntry = new StringDataEntry(id.getAttributeKey(), strValue); + kvEntry = new StringDataEntry(strKey, strValue); } else if (booleanValue != null) { - kvEntry = new BooleanDataEntry(id.getAttributeKey(), booleanValue); + kvEntry = new BooleanDataEntry(strKey, booleanValue); } else if (doubleValue != null) { - kvEntry = new DoubleDataEntry(id.getAttributeKey(), doubleValue); + kvEntry = new DoubleDataEntry(strKey, doubleValue); } else if (longValue != null) { - kvEntry = new LongDataEntry(id.getAttributeKey(), longValue); + kvEntry = new LongDataEntry(strKey, longValue); } else if (jsonValue != null) { - kvEntry = new JsonDataEntry(id.getAttributeKey(), jsonValue); + kvEntry = new JsonDataEntry(strKey, jsonValue); } return new BaseAttributeKvEntry(kvEntry, lastUpdateTs); diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java index d8c4ffddc9..5bf14d06ae 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.service; +import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; import org.thingsboard.common.util.RegexUtils; import org.thingsboard.server.common.data.id.EntityId; @@ -59,6 +60,19 @@ public class Validator { } } + /** + * This method validate String string. If string is null or can not be cast to enum than throw + * IncorrectParameterException exception + * + * @param val the val + * @param errorMessage the error message for exception + */ + public static > void validateEnum(Class enumClass, String val, String errorMessage) { + if (val == null || !EnumUtils.isValidEnum(enumClass, val)) { + throw new IncorrectParameterException(errorMessage); + } + } + /** * This method validate long value. If value isn't possitive than throw diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvDictionaryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvDictionaryRepository.java new file mode 100644 index 0000000000..7af7df70ea --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvDictionaryRepository.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.attributes; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.thingsboard.server.dao.model.sql.AttributeKvDictionary; +import org.thingsboard.server.dao.util.SqlTsOrTsLatestAnyDao; + +import java.util.Optional; + +@SqlTsOrTsLatestAnyDao +public interface AttributeKvDictionaryRepository extends JpaRepository { + + Optional findByKeyId(int keyId); + +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java index 75b7412985..6b33e45546 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java @@ -43,12 +43,12 @@ public abstract class AttributeKvInsertRepository { private static final String EMPTY_STR = ""; private static final String BATCH_UPDATE = "UPDATE attribute_kv SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, json_v = cast(? AS json), last_update_ts = ? " + - "WHERE entity_type = ? and entity_id = ? and attribute_type =? and attribute_key = ?;"; + "WHERE entity_id = ? and attribute_type =? and attribute_key = ?;"; private static final String INSERT_OR_UPDATE = - "INSERT INTO attribute_kv (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, json_v, last_update_ts) " + - "VALUES(?, ?, ?, ?, ?, ?, ?, ?, cast(? AS json), ?) " + - "ON CONFLICT (entity_type, entity_id, attribute_type, attribute_key) " + + "INSERT INTO attribute_kv (entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, json_v, last_update_ts) " + + "VALUES(?, ?, ?, ?, ?, ?, ?, cast(? AS json), ?) " + + "ON CONFLICT (entity_id, attribute_type, attribute_key) " + "DO UPDATE SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, json_v = cast(? AS json), last_update_ts = ?;"; @Autowired @@ -91,10 +91,9 @@ public abstract class AttributeKvInsertRepository { ps.setString(5, replaceNullChars(kvEntity.getJsonValue())); ps.setLong(6, kvEntity.getLastUpdateTs()); - ps.setString(7, kvEntity.getId().getEntityType().name()); - ps.setObject(8, kvEntity.getId().getEntityId()); - ps.setString(9, kvEntity.getId().getAttributeType()); - ps.setString(10, kvEntity.getId().getAttributeKey()); + ps.setObject(7, kvEntity.getId().getEntityId()); + ps.setInt(8, kvEntity.getId().getAttributeType()); + ps.setInt(9, kvEntity.getId().getAttributeKey()); } @Override @@ -121,43 +120,42 @@ public abstract class AttributeKvInsertRepository { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { AttributeKvEntity kvEntity = insertEntities.get(i); - ps.setString(1, kvEntity.getId().getEntityType().name()); - ps.setObject(2, kvEntity.getId().getEntityId()); - ps.setString(3, kvEntity.getId().getAttributeType()); - ps.setString(4, kvEntity.getId().getAttributeKey()); + ps.setObject(1, kvEntity.getId().getEntityId()); + ps.setInt(2, kvEntity.getId().getAttributeType()); + ps.setInt(3, kvEntity.getId().getAttributeKey()); - ps.setString(5, replaceNullChars(kvEntity.getStrValue())); - ps.setString(11, replaceNullChars(kvEntity.getStrValue())); + ps.setString(4, replaceNullChars(kvEntity.getStrValue())); + ps.setString(10, replaceNullChars(kvEntity.getStrValue())); if (kvEntity.getLongValue() != null) { - ps.setLong(6, kvEntity.getLongValue()); - ps.setLong(12, kvEntity.getLongValue()); + ps.setLong(5, kvEntity.getLongValue()); + ps.setLong(11, kvEntity.getLongValue()); } else { - ps.setNull(6, Types.BIGINT); - ps.setNull(12, Types.BIGINT); + ps.setNull(5, Types.BIGINT); + ps.setNull(11, Types.BIGINT); } if (kvEntity.getDoubleValue() != null) { - ps.setDouble(7, kvEntity.getDoubleValue()); - ps.setDouble(13, kvEntity.getDoubleValue()); + ps.setDouble(6, kvEntity.getDoubleValue()); + ps.setDouble(12, kvEntity.getDoubleValue()); } else { - ps.setNull(7, Types.DOUBLE); - ps.setNull(13, Types.DOUBLE); + ps.setNull(6, Types.DOUBLE); + ps.setNull(12, Types.DOUBLE); } if (kvEntity.getBooleanValue() != null) { - ps.setBoolean(8, kvEntity.getBooleanValue()); - ps.setBoolean(14, kvEntity.getBooleanValue()); + ps.setBoolean(7, kvEntity.getBooleanValue()); + ps.setBoolean(13, kvEntity.getBooleanValue()); } else { - ps.setNull(8, Types.BOOLEAN); - ps.setNull(14, Types.BOOLEAN); + ps.setNull(7, Types.BOOLEAN); + ps.setNull(13, Types.BOOLEAN); } - ps.setString(9, replaceNullChars(kvEntity.getJsonValue())); - ps.setString(15, replaceNullChars(kvEntity.getJsonValue())); + ps.setString(8, replaceNullChars(kvEntity.getJsonValue())); + ps.setString(14, replaceNullChars(kvEntity.getJsonValue())); - ps.setLong(10, kvEntity.getLastUpdateTs()); - ps.setLong(16, kvEntity.getLastUpdateTs()); + ps.setLong(9, kvEntity.getLastUpdateTs()); + ps.setLong(15, kvEntity.getLastUpdateTs()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java index 6a0cdfabd0..a43be3e49d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java @@ -20,7 +20,6 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; @@ -29,35 +28,31 @@ import java.util.UUID; public interface AttributeKvRepository extends JpaRepository { - @Query("SELECT a FROM AttributeKvEntity a WHERE a.id.entityType = :entityType " + - "AND a.id.entityId = :entityId " + + @Query("SELECT a FROM AttributeKvEntity a WHERE a.id.entityId = :entityId " + "AND a.id.attributeType = :attributeType") - List findAllByEntityTypeAndEntityIdAndAttributeType(@Param("entityType") EntityType entityType, - @Param("entityId") UUID entityId, - @Param("attributeType") String attributeType); + List findAllEntityIdAndAttributeType(@Param("entityId") UUID entityId, + @Param("attributeType") int attributeType); @Transactional @Modifying - @Query("DELETE FROM AttributeKvEntity a WHERE a.id.entityType = :entityType " + - "AND a.id.entityId = :entityId " + + @Query("DELETE FROM AttributeKvEntity a WHERE a.id.entityId = :entityId " + "AND a.id.attributeType = :attributeType " + "AND a.id.attributeKey = :attributeKey") - void delete(@Param("entityType") EntityType entityType, - @Param("entityId") UUID entityId, - @Param("attributeType") String attributeType, - @Param("attributeKey") String attributeKey); + void delete(@Param("entityId") UUID entityId, + @Param("attributeType") int attributeType, + @Param("attributeKey") int attributeKey); @Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE entity_type = 'DEVICE' " + "AND entity_id in (SELECT id FROM device WHERE tenant_id = :tenantId and device_profile_id = :deviceProfileId limit 100) ORDER BY attribute_key", nativeQuery = true) - List findAllKeysByDeviceProfileId(@Param("tenantId") UUID tenantId, + List findAllKeysByDeviceProfileId(@Param("tenantId") UUID tenantId, @Param("deviceProfileId") UUID deviceProfileId); @Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE entity_type = 'DEVICE' " + "AND entity_id in (SELECT id FROM device WHERE tenant_id = :tenantId limit 100) ORDER BY attribute_key", nativeQuery = true) - List findAllKeysByTenantId(@Param("tenantId") UUID tenantId); + List findAllKeysByTenantId(@Param("tenantId") UUID tenantId); - @Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE entity_type = :entityType " + - "AND entity_id in :entityIds ORDER BY attribute_key", nativeQuery = true) - List findAllKeysByEntityIds(@Param("entityType") String entityType, @Param("entityIds") List entityIds); + @Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE " + + "entity_id in :entityIds ORDER BY attribute_key", nativeQuery = true) + List findAllKeysByEntityIds(@Param("entityIds") List entityIds); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index 6d195d1cc4..c47cd9dbdf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -22,10 +22,12 @@ import com.google.common.util.concurrent.MoreExecutors; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -34,6 +36,7 @@ import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.attributes.AttributesDao; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; +import org.thingsboard.server.dao.model.sql.AttributeKvDictionary; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; @@ -46,6 +49,9 @@ import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import java.util.stream.Collectors; @@ -57,6 +63,9 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @Autowired ScheduledLogExecutorComponent logExecutor; + @Autowired + private AttributeKvDictionaryRepository dictionaryRepository; + @Autowired private AttributeKvRepository attributeKvRepository; @@ -81,6 +90,9 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @Value("${sql.batch_sort:true}") private boolean batchSortEnabled; + private final ConcurrentMap attributeDictionaryMap = new ConcurrentHashMap<>(); + private static final ReentrantLock attributeCreationLock = new ReentrantLock(); + private TbSqlBlockingQueueWrapper queue; @PostConstruct @@ -98,7 +110,6 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl queue = new TbSqlBlockingQueueWrapper<>(params, hashcodeFunction, batchThreads, statsFactory); queue.init(logExecutor, v -> attributeKvInsertRepository.saveOrUpdate(v), Comparator.comparing((AttributeKvEntity attributeKvEntity) -> attributeKvEntity.getId().getEntityId()) - .thenComparing(attributeKvEntity -> attributeKvEntity.getId().getEntityType().name()) .thenComparing(attributeKvEntity -> attributeKvEntity.getId().getAttributeType()) .thenComparing(attributeKvEntity -> attributeKvEntity.getId().getAttributeKey()) ); @@ -112,81 +123,131 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl } @Override - public Optional find(TenantId tenantId, EntityId entityId, String attributeType, String attributeKey) { + public Optional find(TenantId tenantId, EntityId entityId, AttributeScope attributeScope, String attributeKey) { AttributeKvCompositeKey compositeKey = - getAttributeKvCompositeKey(entityId, attributeType, attributeKey); - return Optional.ofNullable(DaoUtil.getData(attributeKvRepository.findById(compositeKey))); + getAttributeKvCompositeKey(entityId, attributeScope.getId(), getOrSaveKeyId(attributeKey)); + Optional attributeKvEntityOptional = attributeKvRepository.findById(compositeKey); + if (attributeKvEntityOptional.isPresent()) { + AttributeKvEntity attributeKvEntity = attributeKvEntityOptional.get(); + attributeKvEntity.setStrKey(attributeKey); + return Optional.ofNullable(DaoUtil.getData(attributeKvEntity)); + } + return Optional.ofNullable(DaoUtil.getData(attributeKvEntityOptional)); } @Override - public List find(TenantId tenantId, EntityId entityId, String attributeType, Collection attributeKeys) { + public List find(TenantId tenantId, EntityId entityId, AttributeScope attributeScope, Collection attributeKeys) { List compositeKeys = attributeKeys .stream() .map(attributeKey -> - getAttributeKvCompositeKey(entityId, attributeType, attributeKey)) + getAttributeKvCompositeKey(entityId, attributeScope.getId(), getOrSaveKeyId(attributeKey))) .collect(Collectors.toList()); - return DaoUtil.convertDataList(Lists.newArrayList(attributeKvRepository.findAllById(compositeKeys))); + List attributes = attributeKvRepository.findAllById(compositeKeys); + attributes.forEach(attributeKvEntity -> attributeKvEntity.setStrKey(getKey(attributeKvEntity.getId().getAttributeKey()))); + return DaoUtil.convertDataList(Lists.newArrayList(attributes)); } @Override - public List findAll(TenantId tenantId, EntityId entityId, String attributeType) { + public List findAll(TenantId tenantId, EntityId entityId, AttributeScope attributeScope) { + List attributes = attributeKvRepository.findAllEntityIdAndAttributeType( + entityId.getId(), + attributeScope.getId()); + attributes.forEach(attributeKvEntity -> attributeKvEntity.setStrKey(getKey(attributeKvEntity.getId().getAttributeKey()))); return DaoUtil.convertDataList(Lists.newArrayList( - attributeKvRepository.findAllByEntityTypeAndEntityIdAndAttributeType( - entityId.getEntityType(), - entityId.getId(), - attributeType))); + attributes)); } @Override public List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) { if (deviceProfileId != null) { - return attributeKvRepository.findAllKeysByDeviceProfileId(tenantId.getId(), deviceProfileId.getId()); + return attributeKvRepository.findAllKeysByDeviceProfileId(tenantId.getId(), deviceProfileId.getId()) + .stream().map(this::getKey).collect(Collectors.toList()); } else { - return attributeKvRepository.findAllKeysByTenantId(tenantId.getId()); + return attributeKvRepository.findAllKeysByTenantId(tenantId.getId()) + .stream().map(this::getKey).collect(Collectors.toList()); } } @Override - public List findAllKeysByEntityIds(TenantId tenantId, EntityType entityType, List entityIds) { + public List findAllKeysByEntityIds(TenantId tenantId, List entityIds) { return attributeKvRepository - .findAllKeysByEntityIds(entityType.name(), entityIds.stream().map(EntityId::getId).collect(Collectors.toList())); + .findAllKeysByEntityIds(entityIds.stream().map(EntityId::getId).collect(Collectors.toList())) + .stream().map(this::getKey).collect(Collectors.toList()); } @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, AttributeScope attributeScope, AttributeKvEntry attribute) { AttributeKvEntity entity = new AttributeKvEntity(); - entity.setId(new AttributeKvCompositeKey(entityId.getEntityType(), entityId.getId(), attributeType, attribute.getKey())); + entity.setId(new AttributeKvCompositeKey(entityId.getId(), attributeScope.getId(), getOrSaveKeyId(attribute.getKey()))); entity.setLastUpdateTs(attribute.getLastUpdateTs()); entity.setStrValue(attribute.getStrValue().orElse(null)); entity.setDoubleValue(attribute.getDoubleValue().orElse(null)); entity.setLongValue(attribute.getLongValue().orElse(null)); entity.setBooleanValue(attribute.getBooleanValue().orElse(null)); entity.setJsonValue(attribute.getJsonValue().orElse(null)); - return addToQueue(entity); + return addToQueue(entity, attribute.getKey()); } - private ListenableFuture addToQueue(AttributeKvEntity entity) { - return Futures.transform(queue.add(entity), v -> entity.getId().getAttributeKey(), MoreExecutors.directExecutor()); + private ListenableFuture addToQueue(AttributeKvEntity entity, String key) { + return Futures.transform(queue.add(entity), v -> key, MoreExecutors.directExecutor()); } @Override - public List> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List keys) { + public List> removeAll(TenantId tenantId, EntityId entityId, AttributeScope attributeScope, List keys) { List> futuresList = new ArrayList<>(keys.size()); for (String key : keys) { futuresList.add(service.submit(() -> { - attributeKvRepository.delete(entityId.getEntityType(), entityId.getId(), attributeType, key); + attributeKvRepository.delete(entityId.getId(), attributeScope.getId(), getOrSaveKeyId(key)); return key; })); } return futuresList; } - private AttributeKvCompositeKey getAttributeKvCompositeKey(EntityId entityId, String attributeType, String attributeKey) { + private AttributeKvCompositeKey getAttributeKvCompositeKey(EntityId entityId, Integer attributeType, Integer attributeKey) { return new AttributeKvCompositeKey( - entityId.getEntityType(), entityId.getId(), attributeType, attributeKey); } + + private Integer getOrSaveKeyId(String attributeKey) { + Integer keyId = attributeDictionaryMap.get(attributeKey); + if (keyId == null) { + Optional byIdOptional = dictionaryRepository.findById(attributeKey); + if (byIdOptional.isEmpty()) { + attributeCreationLock.lock(); + try { + byIdOptional = dictionaryRepository.findById(attributeKey); + if (byIdOptional.isEmpty()) { + AttributeKvDictionary attributeKvDictionary = new AttributeKvDictionary(); + attributeKvDictionary.setKey(attributeKey); + try { + AttributeKvDictionary saved = dictionaryRepository.save(attributeKvDictionary); + attributeDictionaryMap.put(saved.getKey(), saved.getKeyId()); + keyId = saved.getKeyId(); + } catch (DataIntegrityViolationException | ConstraintViolationException e) { + byIdOptional = dictionaryRepository.findById(attributeKey); + AttributeKvDictionary dictionary = byIdOptional.orElseThrow(() -> new RuntimeException("Failed to get AttributeKvDictionary entity from DB!")); + attributeDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); + keyId = dictionary.getKeyId(); + } + } else { + keyId = byIdOptional.get().getKeyId(); + } + } finally { + attributeCreationLock.unlock(); + } + } else { + keyId = byIdOptional.get().getKeyId(); + attributeDictionaryMap.put(attributeKey, keyId); + } + } + return keyId; + } + private String getKey(Integer attributeKey) { + Optional byKeyId = dictionaryRepository.findByKeyId(attributeKey); + return byKeyId.map(AttributeKvDictionary::getKey).orElse(null); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index e9adae29ff..200e93765d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.query; import lombok.Data; -import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; @@ -262,22 +262,22 @@ public class EntityKeyMapping { String query; if (!entityKey.getType().equals(EntityKeyType.ATTRIBUTE)) { String join = (hasFilter() && hasFilterValues(ctx)) ? "inner join" : "left join"; - query = String.format("%s attribute_kv %s ON %s.entity_id=entities.id AND %s.entity_type=%s AND %s.attribute_key=:%s_key_id ", - join, alias, alias, alias, entityTypeStr, alias, alias); - String scope; + query = String.format("%s attribute_kv %s ON %s.entity_id=entities.id AND %s.attribute_key=(select key_id from attribute_kv_dictionary where key = :%s_key_id) ", + join, alias, alias, alias, alias); + int scope; if (entityKey.getType().equals(EntityKeyType.CLIENT_ATTRIBUTE)) { - scope = DataConstants.CLIENT_SCOPE; + scope = AttributeScope.CLIENT_SCOPE.getId(); } else if (entityKey.getType().equals(EntityKeyType.SHARED_ATTRIBUTE)) { - scope = DataConstants.SHARED_SCOPE; + scope = AttributeScope.SHARED_SCOPE.getId();; } else { - scope = DataConstants.SERVER_SCOPE; + scope = AttributeScope.SERVER_SCOPE.getId();; } - query = String.format("%s AND %s.attribute_type='%s' %s", query, alias, scope, filterQuery); + query = String.format("%s AND %s.attribute_type=%s %s", query, alias, scope, filterQuery); } else { String join = (hasFilter() && hasFilterValues(ctx)) ? "join LATERAL" : "left join LATERAL"; - query = String.format("%s (select * from attribute_kv %s WHERE %s.entity_id=entities.id AND %s.entity_type=%s AND %s.attribute_key=:%s_key_id %s " + + query = String.format("%s (select * from attribute_kv %s WHERE %s.entity_id=entities.id AND %s.attribute_key=(select key_id from attribute_kv_dictionary where key = :%s_key_id) %s " + "ORDER BY %s.last_update_ts DESC limit 1) as %s ON true", - join, alias, alias, alias, entityTypeStr, alias, alias, filterQuery, alias, alias); + join, alias, alias, alias, alias, filterQuery, alias, alias); } return query; } diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 85b09e2053..f376a777d3 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -103,17 +103,22 @@ CREATE TABLE IF NOT EXISTS audit_log ( ) PARTITION BY RANGE (created_time); CREATE TABLE IF NOT EXISTS attribute_kv ( - entity_type varchar(255), entity_id uuid, - attribute_type varchar(255), - attribute_key varchar(255), + attribute_type int, + attribute_key int, bool_v boolean, str_v varchar(10000000), long_v bigint, dbl_v double precision, json_v json, last_update_ts bigint, - CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key) + CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_id, attribute_type, attribute_key) +); + +CREATE TABLE IF NOT EXISTS attribute_kv_dictionary ( + key varchar(255) NOT NULL, + key_id serial UNIQUE, + CONSTRAINT attribute_key_id_pkey PRIMARY KEY (key) ); CREATE TABLE IF NOT EXISTS component_descriptor ( diff --git a/dao/src/main/resources/sql/schema-timescale.sql b/dao/src/main/resources/sql/schema-timescale.sql index ed15566268..0a60b64a50 100644 --- a/dao/src/main/resources/sql/schema-timescale.sql +++ b/dao/src/main/resources/sql/schema-timescale.sql @@ -104,7 +104,7 @@ BEGIN WHILE FOUND LOOP EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', tenant_id_record, 'TTL') INTO tenant_ttl; if tenant_ttl IS NULL THEN tenant_ttl := system_ttl; @@ -122,7 +122,7 @@ BEGIN SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record LOOP EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', customer_id_record, 'TTL') INTO customer_ttl; IF customer_ttl IS NULL THEN customer_ttl_ts := tenant_ttl_ts; diff --git a/dao/src/main/resources/sql/schema-ts-psql.sql b/dao/src/main/resources/sql/schema-ts-psql.sql index 8b2b80203e..3f8f380b03 100644 --- a/dao/src/main/resources/sql/schema-ts-psql.sql +++ b/dao/src/main/resources/sql/schema-ts-psql.sql @@ -272,7 +272,7 @@ BEGIN WHILE FOUND LOOP EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', tenant_id_record, 'TTL') INTO tenant_ttl; if tenant_ttl IS NULL THEN tenant_ttl := system_ttl; @@ -290,7 +290,7 @@ BEGIN SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record LOOP EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L', + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', customer_id_record, 'TTL') INTO customer_ttl; IF customer_ttl IS NULL THEN customer_ttl_ts := tenant_ttl_ts; diff --git a/dao/src/main/resources/sql/schema-views-and-functions.sql b/dao/src/main/resources/sql/schema-views-and-functions.sql index 1aa5647bde..4583b71e92 100644 --- a/dao/src/main/resources/sql/schema-views-and-functions.sql +++ b/dao/src/main/resources/sql/schema-views-and-functions.sql @@ -23,7 +23,7 @@ SELECT d.* , COALESCE(da.bool_v, FALSE) as active FROM device d LEFT JOIN customer c ON c.id = d.customer_id - LEFT JOIN attribute_kv da ON da.entity_type = 'DEVICE' AND da.entity_id = d.id AND da.attribute_type = 'SERVER_SCOPE' AND da.attribute_key = 'active'; + LEFT JOIN attribute_kv da ON da.entity_id = d.id AND da.attribute_type = 2 AND da.attribute_key = (select key_id from attribute_kv_dictionary where key = 'active'); DROP VIEW IF EXISTS device_info_active_ts_view CASCADE; CREATE OR REPLACE VIEW device_info_active_ts_view AS diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index ad23b7492b..1c798ea7de 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -321,12 +322,13 @@ public class TbDeviceProfileNodeTest { device.setCustomerId(customerId); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarmEnabled" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); attributeKvEntity.setBooleanValue(Boolean.TRUE); + attributeKvEntity.setStrKey("alarmEnabled"); attributeKvEntity.setLastUpdateTs(System.currentTimeMillis()); AttributeKvEntry entry = attributeKvEntity.toData(); @@ -403,11 +405,12 @@ public class TbDeviceProfileNodeTest { device.setCustomerId(customerId); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, tenantId.getId(), "SERVER_SCOPE", "alarmEnabled" + tenantId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); + attributeKvEntity.setStrKey("alarmEnabled"); attributeKvEntity.setBooleanValue(Boolean.TRUE); attributeKvEntity.setLastUpdateTs(System.currentTimeMillis()); @@ -486,11 +489,12 @@ public class TbDeviceProfileNodeTest { DeviceProfileData deviceProfileData = new DeviceProfileData(); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); + attributeKvEntity.setStrKey("greaterAttribute"); attributeKvEntity.setLongValue(30L); attributeKvEntity.setLastUpdateTs(0L); @@ -555,20 +559,22 @@ public class TbDeviceProfileNodeTest { DeviceProfileData deviceProfileData = new DeviceProfileData(); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); + attributeKvEntity.setStrKey("greaterAttribute"); attributeKvEntity.setLongValue(30L); attributeKvEntity.setLastUpdateTs(0L); AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 11 ); AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity(); alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey); + alarmDelayAttributeKvEntity.setStrKey("alarm_delay"); long alarmDelayInSeconds = 5L; alarmDelayAttributeKvEntity.setLongValue(alarmDelayInSeconds); alarmDelayAttributeKvEntity.setLastUpdateTs(0L); @@ -669,20 +675,22 @@ public class TbDeviceProfileNodeTest { AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); + attributeKvEntity.setStrKey("greaterAttribute"); attributeKvEntity.setLongValue(30L); attributeKvEntity.setLastUpdateTs(0L); AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 11 ); AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity(); alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey); + alarmDelayAttributeKvEntity.setStrKey("alarm_delay"); long alarmDelayInSeconds = 5L; alarmDelayAttributeKvEntity.setLongValue(alarmDelayInSeconds); alarmDelayAttributeKvEntity.setLastUpdateTs(0L); @@ -788,20 +796,22 @@ public class TbDeviceProfileNodeTest { DeviceProfileData deviceProfileData = new DeviceProfileData(); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); + attributeKvEntity.setStrKey("greaterAttribute"); attributeKvEntity.setLongValue(30L); attributeKvEntity.setLastUpdateTs(0L); AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 11 ); AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity(); alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey); + alarmDelayAttributeKvEntity.setStrKey("alarm_delay"); long alarmRepeating = 2; alarmDelayAttributeKvEntity.setLongValue(alarmRepeating); alarmDelayAttributeKvEntity.setLastUpdateTs(0L); @@ -891,7 +901,7 @@ public class TbDeviceProfileNodeTest { DeviceProfileData deviceProfileData = new DeviceProfileData(); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); Device device = new Device(); @@ -900,15 +910,17 @@ public class TbDeviceProfileNodeTest { AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); + attributeKvEntity.setStrKey("greaterAttribute"); attributeKvEntity.setLongValue(30L); attributeKvEntity.setLastUpdateTs(0L); AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 11 ); AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity(); alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey); + alarmDelayAttributeKvEntity.setStrKey("alarm_delay"); long repeatingCondition = 2; alarmDelayAttributeKvEntity.setLongValue(repeatingCondition); alarmDelayAttributeKvEntity.setLastUpdateTs(0L); @@ -1012,11 +1024,12 @@ public class TbDeviceProfileNodeTest { device.setCustomerId(customerId); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); + attributeKvEntity.setStrKey("greaterAttribute"); attributeKvEntity.setLongValue(30L); attributeKvEntity.setLastUpdateTs(0L); @@ -1113,11 +1126,12 @@ public class TbDeviceProfileNodeTest { device.setCustomerId(customerId); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); + attributeKvEntity.setStrKey("greaterAttribute"); attributeKvEntity.setLongValue(30L); attributeKvEntity.setLastUpdateTs(0L); @@ -1196,11 +1210,12 @@ public class TbDeviceProfileNodeTest { device.setCustomerId(customerId); AttributeKvCompositeKey compositeKeyActiveSchedule = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "dynamicValueActiveSchedule" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); AttributeKvEntity attributeKvEntityActiveSchedule = new AttributeKvEntity(); attributeKvEntityActiveSchedule.setId(compositeKeyActiveSchedule); + attributeKvEntityActiveSchedule.setStrKey("dynamicValueActiveSchedule"); attributeKvEntityActiveSchedule.setJsonValue( "{\"timezone\":\"Europe/Kiev\",\"items\":[{\"enabled\":true,\"dayOfWeek\":1,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":2,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":3,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":4,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":5,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":6,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":7,\"startsOn\":0,\"endsOn\":8.64e+7}],\"dynamicValue\":null}" ); @@ -1280,11 +1295,12 @@ public class TbDeviceProfileNodeTest { device.setCustomerId(customerId); AttributeKvCompositeKey compositeKeyInactiveSchedule = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "dynamicValueInactiveSchedule" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); AttributeKvEntity attributeKvEntityInactiveSchedule = new AttributeKvEntity(); attributeKvEntityInactiveSchedule.setId(compositeKeyInactiveSchedule); + attributeKvEntityInactiveSchedule.setStrKey("dynamicValueInactiveSchedule"); attributeKvEntityInactiveSchedule.setJsonValue( "{\"timezone\":\"Europe/Kiev\",\"items\":[{\"enabled\":false,\"dayOfWeek\":1,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":2,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":3,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":4,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":5,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":6,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":7,\"startsOn\":0,\"endsOn\":0}],\"dynamicValue\":null}" ); @@ -1372,11 +1388,12 @@ public class TbDeviceProfileNodeTest { device.setCustomerId(customerId); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.CUSTOMER, deviceId.getId(), "SERVER_SCOPE", "lessAttribute" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); + attributeKvEntity.setStrKey("lessAttribute"); attributeKvEntity.setLongValue(30L); attributeKvEntity.setLastUpdateTs(0L); @@ -1447,11 +1464,12 @@ public class TbDeviceProfileNodeTest { DeviceProfileData deviceProfileData = new DeviceProfileData(); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "lessAttribute" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); + attributeKvEntity.setStrKey("lessAttribute"); attributeKvEntity.setLongValue(50L); attributeKvEntity.setLastUpdateTs(0L); @@ -1520,7 +1538,7 @@ public class TbDeviceProfileNodeTest { DeviceProfileData deviceProfileData = new DeviceProfileData(); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "tenantAttribute" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); Device device = new Device(); @@ -1529,6 +1547,7 @@ public class TbDeviceProfileNodeTest { AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); + attributeKvEntity.setStrKey("tenantAttribute"); attributeKvEntity.setLongValue(100L); attributeKvEntity.setLastUpdateTs(0L); @@ -1605,7 +1624,7 @@ public class TbDeviceProfileNodeTest { DeviceProfileData deviceProfileData = new DeviceProfileData(); AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( - EntityType.DEVICE, deviceId.getId(), EntityKeyType.SERVER_ATTRIBUTE.name(), "tenantAttribute" + deviceId.getId(), AttributeScope.SERVER_SCOPE.getId(), 10 ); Device device = new Device(); @@ -1614,6 +1633,7 @@ public class TbDeviceProfileNodeTest { AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); attributeKvEntity.setId(compositeKey); + attributeKvEntity.setStrKey("tenantAttribute"); attributeKvEntity.setLongValue(100L); attributeKvEntity.setLastUpdateTs(0L); From 5869378c2c64cce434068782068209abd75cef6f Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 14 Dec 2023 20:02:53 +0100 Subject: [PATCH 058/209] Implemented Json serialization, added custom serializer for the Device redis cache --- .../sync/vc/VersionControlTaskRedisCache.java | 4 +- .../AutoCommitSettingsRedisCache.java | 4 +- .../RepositorySettingsRedisCache.java | 4 +- .../transport/DefaultTransportApiService.java | 57 +------- common/cache/pom.xml | 8 ++ .../server/cache/TbFSTRedisSerializer.java | 1 + .../server/cache/TbJsonRedisSerializer.java | 23 ++++ .../cache/TbTypedJsonRedisSerializer.java | 24 ++++ .../server/cache/device/DeviceRedisCache.java | 23 +++- .../resourceInfo/ResourceInfoRedisCache.java | 4 +- .../UsersSessionInvalidationRedisCache.java | 4 +- .../server/common/data/Device.java | 1 + common/proto/pom.xml | 4 + .../server/common/util/ProtoUtils.java | 130 ++++++++++++++++++ common/proto/src/main/proto/queue.proto | 22 +++ .../thingsboard/common/util/JacksonUtil.java | 8 ++ .../dao/alarm/AlarmTypesRedisCache.java | 5 +- .../dao/asset/AssetProfileRedisCache.java | 4 +- .../server/dao/asset/AssetRedisCache.java | 4 +- .../dashboard/DashboardTitlesRedisCache.java | 4 +- .../device/DeviceCredentialsRedisCache.java | 4 +- .../dao/device/DeviceProfileRedisCache.java | 4 +- .../server/dao/edge/EdgeRedisCache.java | 4 +- .../entity/count/EntityCountRedisCache.java | 4 +- .../dao/entityview/EntityViewRedisCache.java | 4 +- .../server/dao/ota/OtaPackageRedisCache.java | 4 +- .../dao/relation/RelationRedisCache.java | 4 +- .../dao/tenant/TenantExistsRedisCache.java | 4 +- .../dao/tenant/TenantProfileRedisCache.java | 4 +- .../server/dao/tenant/TenantRedisCache.java | 4 +- .../dao/user/UserSettingsRedisCache.java | 4 +- 31 files changed, 287 insertions(+), 95 deletions(-) create mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/TbJsonRedisSerializer.java create mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/TbTypedJsonRedisSerializer.java diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java index 5fccbb3cbb..988af2bcff 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import java.util.UUID; @@ -31,6 +31,6 @@ import java.util.UUID; public class VersionControlTaskRedisCache extends RedisTbTransactionalCache { public VersionControlTaskRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.VERSION_CONTROL_TASK_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.VERSION_CONTROL_TASK_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(VersionControlTaskCacheEntry.class)); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java index b3c47cf2a7..46bbcecd87 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; @@ -31,6 +31,6 @@ import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; public class AutoCommitSettingsRedisCache extends RedisTbTransactionalCache { public AutoCommitSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.AUTO_COMMIT_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.AUTO_COMMIT_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(AutoCommitSettings.class)); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java index 27fba3459c..17f7f97437 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.sync.vc.RepositorySettings; @@ -31,6 +31,6 @@ import org.thingsboard.server.common.data.sync.vc.RepositorySettings; public class RepositorySettingsRedisCache extends RedisTbTransactionalCache { public RepositorySettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.REPOSITORY_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.REPOSITORY_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(RepositorySettings.class)); } } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index f622b1fee0..76e8bf3abe 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -47,10 +47,6 @@ import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; -import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.data.PowerMode; -import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; @@ -70,6 +66,7 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; @@ -85,7 +82,6 @@ import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; @@ -108,7 +104,6 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.resource.TbResourceService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -390,7 +385,7 @@ public class DefaultTransportApiService implements TransportApiService { } } GetOrCreateDeviceFromGatewayResponseMsg.Builder builder = GetOrCreateDeviceFromGatewayResponseMsg.newBuilder() - .setDeviceInfo(getDeviceInfoProto(device)); + .setDeviceInfo(ProtoUtils.toDeviceInfoProto(device)); DeviceProfile deviceProfile = deviceProfileCache.get(device.getTenantId(), device.getDeviceProfileId()); if (deviceProfile != null) { builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); @@ -558,7 +553,7 @@ public class DefaultTransportApiService implements TransportApiService { } try { ValidateDeviceCredentialsResponseMsg.Builder builder = ValidateDeviceCredentialsResponseMsg.newBuilder(); - builder.setDeviceInfo(getDeviceInfoProto(device)); + builder.setDeviceInfo(ProtoUtils.toDeviceInfoProto(device)); DeviceProfile deviceProfile = deviceProfileCache.get(device.getTenantId(), device.getDeviceProfileId()); if (deviceProfile != null) { builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); @@ -576,45 +571,6 @@ public class DefaultTransportApiService implements TransportApiService { } } - private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException { - DeviceInfoProto.Builder builder = DeviceInfoProto.newBuilder() - .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) - .setTenantIdLSB(device.getTenantId().getId().getLeastSignificantBits()) - .setCustomerIdMSB(Optional.ofNullable(device.getCustomerId()).map(customerId -> customerId.getId().getMostSignificantBits()).orElse(0L)) - .setCustomerIdLSB(Optional.ofNullable(device.getCustomerId()).map(customerId -> customerId.getId().getLeastSignificantBits()).orElse(0L)) - .setDeviceIdMSB(device.getId().getId().getMostSignificantBits()) - .setDeviceIdLSB(device.getId().getId().getLeastSignificantBits()) - .setDeviceName(device.getName()) - .setDeviceType(device.getType()) - .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) - .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) - .setAdditionalInfo(JacksonUtil.toString(device.getAdditionalInfo())); - - PowerSavingConfiguration psmConfiguration = null; - switch (device.getDeviceData().getTransportConfiguration().getType()) { - case LWM2M: - psmConfiguration = (Lwm2mDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - break; - case COAP: - psmConfiguration = (CoapDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - break; - } - - if (psmConfiguration != null) { - PowerMode powerMode = psmConfiguration.getPowerMode(); - if (powerMode != null) { - builder.setPowerMode(powerMode.name()); - if (powerMode.equals(PowerMode.PSM)) { - builder.setPsmActivityTimer(checkLong(psmConfiguration.getPsmActivityTimer())); - } else if (powerMode.equals(PowerMode.E_DRX)) { - builder.setEdrxCycle(checkLong(psmConfiguration.getEdrxCycle())); - builder.setPagingTransmissionWindow(checkLong(psmConfiguration.getPagingTransmissionWindow())); - } - } - } - return builder.build(); - } - private ListenableFuture getEmptyTransportApiResponseFuture() { return Futures.immediateFuture(getEmptyTransportApiResponse()); } @@ -700,7 +656,7 @@ public class DefaultTransportApiService implements TransportApiService { } TransportProtos.LwM2MRegistrationResponseMsg registrationResponseMsg = TransportProtos.LwM2MRegistrationResponseMsg.newBuilder() - .setDeviceInfo(getDeviceInfoProto(device)).build(); + .setDeviceInfo(ProtoUtils.toDeviceInfoProto(device)).build(); TransportProtos.LwM2MResponseMsg responseMsg = TransportProtos.LwM2MResponseMsg.newBuilder().setRegistrationMsg(registrationResponseMsg).build(); return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setLwM2MResponseMsg(responseMsg).build()); } catch (JsonProcessingException e) { @@ -729,11 +685,6 @@ public class DefaultTransportApiService implements TransportApiService { .build()).collect(Collectors.toList())).build()); } - - private Long checkLong(Long l) { - return l != null ? l : 0; - } - private ProvisionRequest createProvisionRequest(String certificateValue) { return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE, new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue), diff --git a/common/cache/pom.xml b/common/cache/pom.xml index 22987bad50..ce4db035ce 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -40,6 +40,14 @@ org.thingsboard.common data
+ + org.thingsboard.common + util + + + org.thingsboard.common + proto + org.springframework.boot spring-boot-autoconfigure diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbFSTRedisSerializer.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbFSTRedisSerializer.java index 3d8bf66542..c627a82bf0 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TbFSTRedisSerializer.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbFSTRedisSerializer.java @@ -18,6 +18,7 @@ package org.thingsboard.server.cache; import org.springframework.data.redis.serializer.SerializationException; import org.thingsboard.server.common.data.FSTUtils; +@Deprecated(forRemoval = true, since = "3.6.3") public class TbFSTRedisSerializer implements TbRedisSerializer { @Override diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbJsonRedisSerializer.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbJsonRedisSerializer.java new file mode 100644 index 0000000000..5412c6ac1c --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbJsonRedisSerializer.java @@ -0,0 +1,23 @@ +package org.thingsboard.server.cache; + +import org.springframework.data.redis.serializer.SerializationException; +import org.thingsboard.common.util.JacksonUtil; + +public class TbJsonRedisSerializer implements TbRedisSerializer { + + private final Class clazz; + + public TbJsonRedisSerializer(Class clazz) { + this.clazz = clazz; + } + + @Override + public byte[] serialize(V v) throws SerializationException { + return JacksonUtil.writeValueAsBytes(v); + } + + @Override + public V deserialize(K key, byte[] bytes) throws SerializationException { + return JacksonUtil.fromBytes(bytes, clazz); + } +} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbTypedJsonRedisSerializer.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbTypedJsonRedisSerializer.java new file mode 100644 index 0000000000..8f39e3a0d2 --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbTypedJsonRedisSerializer.java @@ -0,0 +1,24 @@ +package org.thingsboard.server.cache; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.springframework.data.redis.serializer.SerializationException; +import org.thingsboard.common.util.JacksonUtil; + +public class TbTypedJsonRedisSerializer implements TbRedisSerializer { + + private final TypeReference valueTypeRef; + + public TbTypedJsonRedisSerializer(TypeReference valueTypeRef) { + this.valueTypeRef = valueTypeRef; + } + + @Override + public byte[] serialize(V v) throws SerializationException { + return JacksonUtil.writeValueAsBytes(v); + } + + @Override + public V deserialize(K key, byte[] bytes) throws SerializationException { + return JacksonUtil.fromBytes(bytes, valueTypeRef); + } +} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java index 591555a3a1..f4ba0a9433 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java @@ -15,21 +15,40 @@ */ package org.thingsboard.server.cache.device; +import com.google.protobuf.InvalidProtocolBufferException; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.SerializationException; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.util.ProtoUtils; +import org.thingsboard.server.gen.transport.TransportProtos; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("DeviceCache") public class DeviceRedisCache extends RedisTbTransactionalCache { public DeviceRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.DEVICE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.DEVICE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>(){ + + @Override + public byte[] serialize(Device device) throws SerializationException { + return ProtoUtils.toDeviceProto(device).toByteArray(); + } + + @Override + public Device deserialize(DeviceCacheKey key, byte[] bytes) throws SerializationException { + try { + return ProtoUtils.fromDeviceProto(TransportProtos.DeviceProto.parseFrom(bytes)); + } catch (InvalidProtocolBufferException e) { + throw new SerializationException(e.getMessage()); + } + } + }); } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/resourceInfo/ResourceInfoRedisCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/resourceInfo/ResourceInfoRedisCache.java index fee14e1ca1..784a49f709 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/resourceInfo/ResourceInfoRedisCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/resourceInfo/ResourceInfoRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.TbResourceInfo; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.TbResourceInfo; public class ResourceInfoRedisCache extends RedisTbTransactionalCache { public ResourceInfoRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.RESOURCE_INFO_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.RESOURCE_INFO_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(TbResourceInfo.class)); } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationRedisCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationRedisCache.java index 83360fb8af..72bf11885f 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationRedisCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationRedisCache.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @@ -31,6 +31,6 @@ public class UsersSessionInvalidationRedisCache extends RedisTbTransactionalCach @Autowired public UsersSessionInvalidationRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.USERS_SESSION_INVALIDATION_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.USERS_SESSION_INVALIDATION_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(Long.class)); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index f16a124ef4..baca0d0209 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -58,6 +58,7 @@ public class Device extends BaseDataWithAdditionalInfo implements HasL private DeviceProfileId deviceProfileId; private transient DeviceData deviceData; @JsonIgnore + @Getter @Setter private byte[] deviceDataBytes; private OtaPackageId firmwareId; diff --git a/common/proto/pom.xml b/common/proto/pom.xml index 4ccf154ead..1ea5ef41ac 100644 --- a/common/proto/pom.xml +++ b/common/proto/pom.xml @@ -44,6 +44,10 @@ org.thingsboard.common message + + org.thingsboard.common + util + com.google.protobuf protobuf-java diff --git a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java index fd9e35b860..86678db00a 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java @@ -15,10 +15,22 @@ */ package org.thingsboard.server.common.util; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.protobuf.ByteString; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.data.PowerMode; +import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -496,4 +508,122 @@ public class ProtoUtils { } return result; } + + public static TransportProtos.DeviceProto toDeviceProto(Device device) { + var builder = TransportProtos.DeviceProto.newBuilder() + .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(device.getTenantId().getId().getLeastSignificantBits()) + .setCustomerIdMSB(getMsb(device.getCustomerId())) + .setCustomerIdLSB(getLsb(device.getCustomerId())) + .setDeviceIdMSB(device.getId().getId().getMostSignificantBits()) + .setDeviceIdLSB(device.getId().getId().getLeastSignificantBits()) + .setDeviceName(device.getName()) + .setDeviceType(device.getType()) + .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) + .setAdditionalInfo(JacksonUtil.toString(device.getAdditionalInfo())) + .setFirmwareIdMSB(getMsb(device.getFirmwareId())) + .setFirmwareIdLSB(getLsb(device.getFirmwareId())) + .setSoftwareIdMSB(getMsb(device.getSoftwareId())) + .setSoftwareIdLSB(getLsb(device.getSoftwareId())) + .setExternalIdMSB(getMsb(device.getExternalId())) + .setExternalIdLSB(getLsb(device.getExternalId())); + + if (device.getLabel() != null) { + builder.setDeviceLabel(device.getLabel()); + } + + if (device.getDeviceDataBytes() != null) { + builder.setDeviceData(ByteString.copyFrom(device.getDeviceDataBytes())); + } + + return builder.build(); + } + + public static Device fromDeviceProto(TransportProtos.DeviceProto deviceProto) { + Device device = new Device(new DeviceId(new UUID(deviceProto.getDeviceIdMSB(), deviceProto.getDeviceIdLSB()))); + device.setTenantId(new TenantId(new UUID(deviceProto.getTenantIdMSB(), deviceProto.getTenantIdLSB()))); + device.setCustomerId(new CustomerId(new UUID(deviceProto.getCustomerIdMSB(), deviceProto.getCustomerIdLSB()))); + device.setName(deviceProto.getDeviceName()); + device.setLabel(deviceProto.getDeviceLabel()); + device.setType(deviceProto.getDeviceType()); + device.setDeviceProfileId(new DeviceProfileId(new UUID(deviceProto.getDeviceProfileIdMSB(), deviceProto.getDeviceProfileIdLSB()))); + device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceProto.getAdditionalInfo())); + device.setFirmwareId(createOtaPackageId(deviceProto.getFirmwareIdMSB(), deviceProto.getFirmwareIdLSB())); + device.setSoftwareId(createOtaPackageId(deviceProto.getSoftwareIdMSB(), deviceProto.getSoftwareIdLSB())); + device.setExternalId(createDeviceId(deviceProto.getExternalIdMSB(), deviceProto.getExternalIdLSB())); + device.setDeviceDataBytes(deviceProto.getDeviceData().toByteArray()); + return device; + } + + private static OtaPackageId createOtaPackageId(long msb, long lsb) { + if (msb != 0 || lsb != 0) { + return new OtaPackageId(new UUID(msb, lsb)); + } + return null; + } + + private static DeviceId createDeviceId(long msb, long lsb) { + if (msb != 0 || lsb != 0) { + return new DeviceId(new UUID(msb, lsb)); + } + return null; + } + + public static TransportProtos.DeviceInfoProto toDeviceInfoProto(Device device) throws JsonProcessingException { + TransportProtos.DeviceInfoProto.Builder builder = TransportProtos.DeviceInfoProto.newBuilder() + .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(device.getTenantId().getId().getLeastSignificantBits()) + .setCustomerIdMSB(getMsb(device.getCustomerId())) + .setCustomerIdLSB(getLsb(device.getCustomerId())) + .setDeviceIdMSB(device.getId().getId().getMostSignificantBits()) + .setDeviceIdLSB(device.getId().getId().getLeastSignificantBits()) + .setDeviceName(device.getName()) + .setDeviceType(device.getType()) + .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) + .setAdditionalInfo(JacksonUtil.toString(device.getAdditionalInfo())); + + PowerSavingConfiguration psmConfiguration = null; + switch (device.getDeviceData().getTransportConfiguration().getType()) { + case LWM2M: + psmConfiguration = (Lwm2mDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); + break; + case COAP: + psmConfiguration = (CoapDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); + break; + } + + if (psmConfiguration != null) { + PowerMode powerMode = psmConfiguration.getPowerMode(); + if (powerMode != null) { + builder.setPowerMode(powerMode.name()); + if (powerMode.equals(PowerMode.PSM)) { + builder.setPsmActivityTimer(checkLong(psmConfiguration.getPsmActivityTimer())); + } else if (powerMode.equals(PowerMode.E_DRX)) { + builder.setEdrxCycle(checkLong(psmConfiguration.getEdrxCycle())); + builder.setPagingTransmissionWindow(checkLong(psmConfiguration.getPagingTransmissionWindow())); + } + } + } + return builder.build(); + } + + public static Long getMsb(EntityId entityId) { + if (entityId != null) { + return entityId.getId().getMostSignificantBits(); + } + return 0L; + } + + public static Long getLsb(EntityId entityId) { + if (entityId != null) { + return entityId.getId().getLeastSignificantBits(); + } + return 0L; + } + + private static Long checkLong(Long l) { + return l != null ? l : 0; + } } diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index 15e08ee905..eaefb08952 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -183,6 +183,28 @@ message DeviceInfoProto { int64 pagingTransmissionWindow = 15; } +message DeviceProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 deviceIdMSB = 3; + int64 deviceIdLSB = 4; + string deviceName = 5; + string deviceLabel = 6; + string deviceType = 7; + string additionalInfo = 8; + int64 deviceProfileIdMSB = 9; + int64 deviceProfileIdLSB = 10; + int64 customerIdMSB = 11; + int64 customerIdLSB = 12; + bytes deviceData = 13; + int64 firmwareIdMSB = 14; + int64 firmwareIdLSB = 15; + int64 softwareIdMSB = 16; + int64 softwareIdLSB = 17; + int64 externalIdMSB = 18; + int64 externalIdLSB = 19; +} + /** * Transport Service Messages; */ diff --git a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java index 1121085ac1..4bde9fdf1c 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java @@ -126,6 +126,14 @@ public class JacksonUtil { } } + public static T fromBytes(byte[] bytes, TypeReference valueTypeRef) { + try { + return bytes != null ? OBJECT_MAPPER.readValue(bytes, valueTypeRef) : null; + } catch (IOException e) { + throw new IllegalArgumentException("The given string value cannot be transformed to Json object: " + Arrays.toString(bytes), e); + } + } + public static JsonNode fromBytes(byte[] bytes) { try { return OBJECT_MAPPER.readTree(bytes); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmTypesRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmTypesRedisCache.java index 18eb81f690..1a7ef3cc73 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmTypesRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmTypesRedisCache.java @@ -15,13 +15,14 @@ */ package org.thingsboard.server.dao.alarm; +import com.fasterxml.jackson.core.type.TypeReference; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbTypedJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.id.TenantId; @@ -32,6 +33,6 @@ import org.thingsboard.server.common.data.page.PageData; public class AlarmTypesRedisCache extends RedisTbTransactionalCache> { public AlarmTypesRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ALARM_TYPES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.ALARM_TYPES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbTypedJsonRedisSerializer<>(new TypeReference<>() {})); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java index 25f919e881..2cd5f835d6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.asset.AssetProfile; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.asset.AssetProfile; public class AssetProfileRedisCache extends RedisTbTransactionalCache { public AssetProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ASSET_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.ASSET_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(AssetProfile.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetRedisCache.java index 25cc9a5f7b..70c0ee941a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.asset.Asset; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.asset.Asset; public class AssetRedisCache extends RedisTbTransactionalCache { public AssetRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ASSET_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.ASSET_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(Asset.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java index 990a175b71..19a60189a7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardTitlesRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.id.DashboardId; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.id.DashboardId; public class DashboardTitlesRedisCache extends RedisTbTransactionalCache { public DashboardTitlesRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.DASHBOARD_TITLES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.DASHBOARD_TITLES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(String.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java index 63fe83a7a4..083de3ac5c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; public class DeviceCredentialsRedisCache extends RedisTbTransactionalCache { public DeviceCredentialsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.DEVICE_CREDENTIALS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.DEVICE_CREDENTIALS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(DeviceCredentials.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java index f90cf67655..a68c169b49 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.DeviceProfile; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.DeviceProfile; public class DeviceProfileRedisCache extends RedisTbTransactionalCache { public DeviceProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.DEVICE_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.DEVICE_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(DeviceProfile.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeRedisCache.java index 1955c6c57e..203519799b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.edge.Edge; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.edge.Edge; public class EdgeRedisCache extends RedisTbTransactionalCache { public EdgeRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.EDGE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.EDGE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(Edge.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/count/EntityCountRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/entity/count/EntityCountRedisCache.java index af431f3085..70177852f8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/count/EntityCountRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/count/EntityCountRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.dao.entity.EntityCountCacheKey; @@ -30,6 +30,6 @@ import org.thingsboard.server.dao.entity.EntityCountCacheKey; public class EntityCountRedisCache extends RedisTbTransactionalCache { public EntityCountRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ENTITY_COUNT_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.ENTITY_COUNT_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(Long.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java index d6d4c7800d..b32e5194f5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @@ -29,6 +29,6 @@ import org.thingsboard.server.common.data.CacheConstants; public class EntityViewRedisCache extends RedisTbTransactionalCache { public EntityViewRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ENTITY_VIEW_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.ENTITY_VIEW_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(EntityViewCacheValue.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageRedisCache.java index 9f77c74967..f4f55121bc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.OtaPackageInfo; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.OtaPackageInfo; public class OtaPackageRedisCache extends RedisTbTransactionalCache { public OtaPackageRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.OTA_PACKAGE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.OTA_PACKAGE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(OtaPackageInfo.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationRedisCache.java index 629d98c42d..83d8ba1fe2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @@ -29,6 +29,6 @@ import org.thingsboard.server.common.data.CacheConstants; public class RelationRedisCache extends RedisTbTransactionalCache { public RelationRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.RELATIONS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.RELATIONS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(RelationCacheValue.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java index 9cd5ba18cb..377bf647c6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.id.TenantId; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.id.TenantId; public class TenantExistsRedisCache extends RedisTbTransactionalCache { public TenantExistsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.TENANTS_EXIST_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.TENANTS_EXIST_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(Boolean.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileRedisCache.java index cb11306cda..3c312a6574 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.TenantProfile; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.TenantProfile; public class TenantProfileRedisCache extends RedisTbTransactionalCache { public TenantProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.TENANT_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.TENANT_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(TenantProfile.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantRedisCache.java index 29ebcd9767..b820e28df6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; @@ -31,6 +31,6 @@ import org.thingsboard.server.common.data.id.TenantId; public class TenantRedisCache extends RedisTbTransactionalCache { public TenantRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.TENANTS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.TENANTS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(Tenant.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java index 70519478e7..57721a1dd2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; @@ -31,6 +31,6 @@ import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey; public class UserSettingsRedisCache extends RedisTbTransactionalCache { public UserSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.USER_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + super(CacheConstants.USER_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(UserSettings.class)); } } From 5b9eae4a6f5015c6591639272c6030fc50e44d4f Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 14 Dec 2023 20:08:52 +0100 Subject: [PATCH 059/209] license format --- .../server/cache/TbJsonRedisSerializer.java | 15 +++++++++++++++ .../server/cache/TbTypedJsonRedisSerializer.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbJsonRedisSerializer.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbJsonRedisSerializer.java index 5412c6ac1c..9fb111c1fb 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TbJsonRedisSerializer.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbJsonRedisSerializer.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.cache; import org.springframework.data.redis.serializer.SerializationException; diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbTypedJsonRedisSerializer.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbTypedJsonRedisSerializer.java index 8f39e3a0d2..820c2df09c 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TbTypedJsonRedisSerializer.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbTypedJsonRedisSerializer.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.cache; import com.fasterxml.jackson.core.type.TypeReference; From 871e4d405db5772d3f0c9a2e8bbfee4f91ec8cf5 Mon Sep 17 00:00:00 2001 From: dudnikmaksim Date: Fri, 15 Dec 2023 17:54:51 +0200 Subject: [PATCH 060/209] 1st batch of minor fixes --- ui-ngx/src/app/core/utils.ts | 1 + ...y-service-rpc-connector-template-dialog.ts | 5 +- ...teway-service-rpc-connector.component.html | 29 +- ...teway-service-rpc-connector.component.scss | 6 +- ...gateway-service-rpc-connector.component.ts | 95 ++- .../gateway/gateway-service-rpc.component.ts | 47 +- .../lib/gateway/gateway-widget.models.ts | 11 + .../assets/locale/locale.constant-en_US.json | 752 +++++++++--------- 8 files changed, 492 insertions(+), 454 deletions(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index b810689b69..012a64ea45 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -26,6 +26,7 @@ import { TranslateService } from '@ngx-translate/core'; import { serverErrorCodesTranslations } from '@shared/models/constants'; const varsRegex = /\${([^}]*)}/g; +export const noLeadTrailSpacesRegex: RegExp = /^(?! )[A-Za-z0-9 ]*(? { const scrollSubject = new Subject(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts index 63f492519c..6ee2a7103f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog.ts @@ -44,7 +44,7 @@ export class GatewayServiceRPCConnectorTemplateDialogComponent extends DialogCom super(store, router, dialogRef); this.config = this.data.config; this.templates = this.data.templates; - this.templateNameCtrl = this.fb.control('', [Validators.required]) + this.templateNameCtrl = this.fb.control('', [Validators.required]); } validateDuplicateName(c: UntypedFormControl) { @@ -57,6 +57,7 @@ export class GatewayServiceRPCConnectorTemplateDialogComponent extends DialogCom } save(): void { - this.dialogRef.close(this.templateNameCtrl.value); + this.templateNameCtrl.setValue(this.templateNameCtrl.value.trim()); + if (this.templateNameCtrl.valid) this.dialogRef.close(this.templateNameCtrl.value); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html index c706244c31..b5dbf3b822 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html @@ -22,7 +22,7 @@ - {{ 'gateway.rpc.method-filter' | translate }} + {{ 'gateway.rpc.methodFilter' | translate }} @@ -65,11 +65,15 @@ {{ 'gateway.rpc.functionCode' | translate }} - {{ code }} + {{ modbusCodesTranslate.get(code) | translate}} + + {{ 'gateway.rpc.value' | translate }} + +
{{ 'gateway.rpc.address' | translate }} @@ -233,11 +237,7 @@ {{ 'gateway.rpc.encoding' | translate }} - - - {{ encoding }} - - + {{ 'gateway.rpc.withResponse' | translate }} @@ -269,12 +269,15 @@ + + {{ 'gateway.rpc.withResponse' | translate }} +
{{ 'gateway.rpc.oids' | translate }}*
- +
- {{ 'gateway.rpc.HTTPMethod' | translate }} - + {{ 'gateway.rpc.httpMethod' | translate }} + {{ method }} @@ -476,7 +479,7 @@ - + {{ 'gateway.rpc.method' | translate }} @@ -485,9 +488,8 @@
- + - - {{'dsadas'}} {{ 'gateway.statistics.command' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss index 65abdb50b3..0cd3c5c737 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss @@ -48,9 +48,9 @@ ::ng-deep .mat-mdc-form-field-subscript-wrapper { display: none; } - ::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__leading, - ::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__notch, - ::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__trailing { + ::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--invalid) .mdc-notched-outline__leading, + ::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--invalid) .mdc-notched-outline__notch, + ::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--invalid) .mdc-notched-outline__trailing { border-color: #e0e0e0; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts index 7a2f91907e..0cfc3d095b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts @@ -34,7 +34,7 @@ import { CANByteOrders, ConnectorType, GatewayConnectorDefaultTypesTranslates, - HTTPMethods, + HTTPMethods, ModbusCodesTranslate, ModbusCommandTypes, RPCCommand, RPCTemplateConfig, @@ -50,7 +50,7 @@ import { JsonObjectEditDialogData } from '@shared/components/dialog/json-object-edit-dialog.component'; import { jsonRequired } from '@shared/components/json-object-edit.component'; -import { deepClone } from '@core/utils'; +import { deepClone, noLeadTrailSpacesRegex } from '@core/utils'; @Component({ selector: 'tb-gateway-service-rpc-connector', @@ -97,6 +97,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue SocketMethodProcessingsTranslates = SocketMethodProcessingsTranslates; SNMPMethodsTranslations = SNMPMethodsTranslations; gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslates; + modbusCodesTranslate = ModbusCodesTranslate; urlPattern = new RegExp( '^(https?:\\/\\/)?' + // protocol @@ -139,7 +140,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue break; } if (this.commandForm.valid) { - this.propagateChange({...this.commandForm.value,...value}); + this.propagateChange({...this.commandForm.value, ...value}); } }) } @@ -150,126 +151,138 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue switch (type) { case ConnectorType.MQTT: formGroup = this.fb.group({ - methodFilter: [null, [Validators.required]], - requestTopicExpression: [null, [Validators.required]], - responseTopicExpression: [null, []], + methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + requestTopicExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + responseTopicExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]], responseTimeout: [null, [Validators.min(10), Validators.pattern("^[0-9]*$")]], - valueExpression: [null, [Validators.required]], + valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], }) break; case ConnectorType.MODBUS: formGroup = this.fb.group({ - tag: [null, [Validators.required]], + tag: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], type: [null, [Validators.required]], functionCode: [null, [Validators.required]], + value: [null, []], address: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]], objectsCount: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]] }) + const valueForm = formGroup.get('value'); + formGroup.get('functionCode').valueChanges.subscribe(value => { + if (value > 4) { + valueForm.addValidators([Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]); + } else { + valueForm.clearValidators(); + valueForm.setValue(null); + } + valueForm.updateValueAndValidity(); + }) break; case ConnectorType.BACNET: formGroup = this.fb.group({ - method: [null, [Validators.required]], - requestType: [null, [Validators.required]], + method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + requestType: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], requestTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], objectType: [null, []], identifier: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]], - propertyId: [null, [Validators.required]] + propertyId: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] }) break; case ConnectorType.BLE: formGroup = this.fb.group({ - methodRPC: [null, [Validators.required]], - characteristicUUID: [null, [Validators.required]], + methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + characteristicUUID: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], methodProcessing: [null, [Validators.required]], withResponse: [false, []] }) break; case ConnectorType.CAN: formGroup = this.fb.group({ - method: [null, [Validators.required]], + method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], nodeID: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]], isExtendedID: [false, []], isFD: [false, []], bitrateSwitch: [false, []], dataLength: [null, [Validators.min(1), Validators.pattern("^[0-9]*$")]], dataByteorder: [null, []], - dataBefore: [null, []], - dataAfter: [null, []], - dataInHEX: [null, []], - dataExpression: [null, []] + dataBefore: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(/^[0-9A-Fa-f]+$/)]], + dataAfter: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(/^[0-9A-Fa-f]+$/)]], + dataInHEX: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(/^[0-9A-Fa-f]+$/)]], + dataExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]] }) break; case ConnectorType.FTP: formGroup = this.fb.group({ - methodFilter: [null, [Validators.required]], - valueExpression: [null, [Validators.required]] + methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] }) break; case ConnectorType.OCPP: formGroup = this.fb.group({ - methodRPC: [null, [Validators.required]], - valueExpression: [null, [Validators.required]], + methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], withResponse: [false, []] }) break; case ConnectorType.SOCKET: formGroup = this.fb.group({ - methodRPC: [null, [Validators.required]], + methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], methodProcessing: [null, [Validators.required]], - encoding: [null, [Validators.required]], + encoding: [SocketEncodings.UTF_8, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], withResponse: [false, []] }) break; case ConnectorType.XMPP: formGroup = this.fb.group({ - methodRPC: [null, [Validators.required]], - valueExpression: [null, [Validators.required]], + methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], withResponse: [false, []] }) break; case ConnectorType.SNMP: formGroup = this.fb.group({ - requestFilter: [null, [Validators.required]], + requestFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], method: [null, [Validators.required]], + withResponse: [false, []], oid: this.fb.array([], [Validators.required]) }) break; case ConnectorType.REST: formGroup = this.fb.group({ - methodFilter: [null, [Validators.required]], - HTTPMethod: [null, [Validators.required]], + methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + httpMethod: [null, [Validators.required]], requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]], responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], timeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], tries: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]], - valueExpression: [null, [Validators.required]], + valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], httpHeaders: this.fb.array([]), security: this.fb.array([]) }) break; case ConnectorType.REQUEST: formGroup = this.fb.group({ - methodFilter: [null, [Validators.required]], + methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], httpMethod: [null, [Validators.required]], requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]], responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], timeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], tries: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]], - requestValueExpression: [null, [Validators.required]], - responseValueExpression: [null, []], + requestValueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + responseValueExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]], httpHeaders: this.fb.array([]), }) break; case ConnectorType.OPCUA: case ConnectorType.OPCUA_ASYNCIO: formGroup = this.fb.group({ - method: [null, [Validators.required]], + method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], arguments: this.fb.array([]), }) break; default: formGroup = this.fb.group({ - command: [null, [Validators.required]], + command: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], params: ['{}', [jsonRequired]], }) @@ -280,7 +293,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue addSNMPoid(value: string = null) { const oidsFA = this.commandForm.get('oid') as FormArray; if (oidsFA) { - oidsFA.push(this.fb.control(value, [Validators.required]), {emitEvent: false}); + oidsFA.push(this.fb.control(value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]), {emitEvent: false}); } } @@ -292,8 +305,8 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue addHTTPHeader(value: { headerName: string, value: string } = {headerName: null, value: null}) { const headerFA = this.commandForm.get('httpHeaders') as FormArray; const formGroup = this.fb.group({ - headerName: [value.headerName, [Validators.required]], - value: [value.value, [Validators.required]] + headerName: [value.headerName, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + value: [value.value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] }) if (headerFA) { headerFA.push(formGroup, {emitEvent: false}); @@ -308,8 +321,8 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue addHTTPSecurity(value: { securityName: string, value: string } = {securityName: null, value: null}) { const securityFA = this.commandForm.get('security') as FormArray; const formGroup = this.fb.group({ - securityName: [value.securityName, [Validators.required]], - value: [value.value, [Validators.required]] + securityName: [value.securityName, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + value: [value.value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] }) if (securityFA) { securityFA.push(formGroup, {emitEvent: false}); @@ -328,7 +341,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue addOCPUAArguments(value: string = null) { const oidsFA = this.commandForm.get('arguments') as FormArray; if (oidsFA) { - oidsFA.push(this.fb.control(value), {emitEvent: false}); + oidsFA.push(this.fb.control(value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]), {emitEvent: false}); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts index 484f8b16b5..50cfff408d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts @@ -170,29 +170,32 @@ export class GatewayServiceRPCComponent implements OnInit { data: {config: this.commandForm.value.params, templates: this.templates} }).afterClosed().subscribe( (res) => { - const templateAttribute: RPCTemplate = { - name: res, - config: this.commandForm.value.params - } - const templatesArray = this.templates; - const existingIndex = templatesArray.findIndex(template=>{ - return template.name == templateAttribute.name; - }) - if (existingIndex > -1 ){ - templatesArray.splice(existingIndex, 1) - } - templatesArray.push(templateAttribute) - const key = `${this.connectorType}_template`; - this.attributeService.saveEntityAttributes( - { - id: this.ctx.defaultSubscription.targetDeviceId, - entityType: EntityType.DEVICE + if (res) { + console.log(res); + const templateAttribute: RPCTemplate = { + name: res, + config: this.commandForm.value.params } - , AttributeScope.SERVER_SCOPE, [{ - key, - value: templatesArray - }]).subscribe(() => { - }) + const templatesArray = this.templates; + const existingIndex = templatesArray.findIndex(template => { + return template.name == templateAttribute.name; + }) + if (existingIndex > -1) { + templatesArray.splice(existingIndex, 1) + } + templatesArray.push(templateAttribute) + const key = `${this.connectorType}_template`; + this.attributeService.saveEntityAttributes( + { + id: this.ctx.defaultSubscription.targetDeviceId, + entityType: EntityType.DEVICE + } + , AttributeScope.SERVER_SCOPE, [{ + key, + value: templatesArray + }]).subscribe(() => { + }) + } } ); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index b8f8c996b3..1d1c280053 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -172,6 +172,17 @@ export enum ModbusCommandTypes { Float64 = '64float' } +export const ModbusCodesTranslate = new Map([ + [1, 'gateway.rpc.read-coils'], + [2, 'gateway.rpc.read-discrete-inputs'], + [3, 'gateway.rpc.read-multiple-holding-registers'], + [4, 'gateway.rpc.read-input-registers'], + [5, 'gateway.rpc.write-single-coil'], + [6, 'gateway.rpc.write-single-holding-register'], + [15, 'gateway.rpc.write-multiple-coils'], + [16, 'gateway.rpc.write-multiple-holding-registers'] +]) + export enum BACnetRequestTypes { WriteProperty = 'writeProperty', ReadProperty = 'readProperty' diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index da5fbf824a..7937b197f3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -203,117 +203,117 @@ "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io", "connection-settings": "Connection settings", "oauth2": { - "access-token-uri": "Access token URI", - "access-token-uri-required": "Access token URI is required.", - "activate-user": "Activate user", - "add-domain": "Add domain", - "delete-domain": "Delete domain", - "add-provider": "Add provider", - "delete-provider": "Delete provider", - "allow-user-creation": "Allow user creation", - "always-fullscreen": "Always fullscreen", - "authorization-uri": "Authorization URI", - "authorization-uri-required": "Authorization URI is required.", - "client-authentication-method": "Client authentication method", - "client-id": "Client ID", - "client-id-required": "Client ID is required.", - "client-id-max-length": "Client ID should be less than 256", - "client-secret": "Client secret", - "client-secret-required": "Client secret is required.", - "client-secret-max-length": "Client secret should be less than 2049", - "custom-setting": "Custom settings", - "customer-name-pattern": "Customer name pattern", - "customer-name-pattern-max-length": "Customer name pattern should be less than 256", - "default-dashboard-name": "Default dashboard name", - "default-dashboard-name-max-length": "Default dashboard name should be less than 256", - "delete-domain-text": "Be careful, after the confirmation a domain and all provider data will be unavailable.", - "delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?", - "delete-registration-text": "Be careful, after the confirmation a provider data will be unavailable.", - "delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?", - "email-attribute-key": "Email attribute key", - "email-attribute-key-required": "Email attribute key is required.", - "email-attribute-key-max-length": "Email attribute key should be less than 32", - "first-name-attribute-key": "First name attribute key", - "first-name-attribute-key-max-length": "First name attribute key should be less than 32", - "general": "General", - "jwk-set-uri": "JSON Web Key URI", - "last-name-attribute-key": "Last name attribute key", - "last-name-attribute-key-max-length": "Last name attribute key should be less than 32", - "login-button-icon": "Login button icon", - "login-button-label": "Provider label", - "login-button-label-placeholder": "Login with $(Provider label)", - "login-button-label-required": "Label is required.", - "login-provider": "Login provider", - "mapper": "Mapper", - "new-domain": "New domain", - "oauth2": "OAuth2", - "password-max-length": "Password should be less than 256", - "redirect-uri-template": "Redirect URI template", - "copy-redirect-uri": "Copy redirect URI", - "registration-id": "Registration ID", - "registration-id-required": "Registration ID is required.", - "registration-id-unique": "Registration ID need to unique for the system.", - "scope": "Scope", - "scope-required": "Scope is required.", - "tenant-name-pattern": "Tenant name pattern", - "tenant-name-pattern-required": "Tenant name pattern is required.", - "tenant-name-pattern-max-length": "Tenant name pattern ishould be less than 256", - "tenant-name-strategy": "Tenant name strategy", - "type": "Mapper type", - "uri-pattern-error": "Invalid URI format.", - "url": "URL", - "url-pattern": "Invalid URL format.", - "url-required": "URL is required.", - "url-max-length": "URL should be less than 256", - "user-info-uri": "User info URI", - "user-info-uri-required": "User info URI is required.", - "username-max-length": "User name should be less than 256", - "user-name-attribute-name": "User name attribute key", - "user-name-attribute-name-required": "User name attribute key is required", - "protocol": "Protocol", - "domain-schema-http": "HTTP", - "domain-schema-https": "HTTPS", - "domain-schema-mixed": "HTTP+HTTPS", - "enable": "Enable OAuth2 settings", - "domains": "Domains", - "mobile-apps": "Mobile applications", - "no-mobile-apps": "No applications configured", - "mobile-package": "Application package", - "mobile-package-placeholder": "Ex.: my.example.app", - "mobile-package-hint": "For Android: your own unique Application ID. For iOS: Product bundle identifier.", - "mobile-package-unique": "Application package must be unique.", - "mobile-app-secret": "Application secret", - "invalid-mobile-app-secret": "Application secret must contain only alphanumeric characters and must be between 16 and 2048 characters long.", - "copy-mobile-app-secret": "Copy application secret", - "add-mobile-app": "Add application", - "delete-mobile-app": "Delete application info", - "providers": "Providers", - "platform-web": "Web", - "platform-android": "Android", - "platform-ios": "iOS", - "all-platforms": "All platforms", - "smtp-provider": "SMTP provider", - "allowed-platforms": "Allowed platforms", - "authentication": "Authentication", - "basic": "Basic", - "provider": "Provider", - "redirect-url": "Redirect URI", - "domain-name": "Domain name", - "redirect-url-template": "Redirect URI template", - "microsoft-tenant-id": "Directory (tenant) Id", - "microsoft-tenant-id-required": "Directory (tenant) Id is required", - "token-uri": "Token URI", - "token-uri-required": "Token URI is required", - "redirect-uri": "Redirect URI", - "google-provider": "Google", - "microsoft-provider": "Office 365", - "sendgrid-provider": "Sendgrid", - "custom-provider": "Custom", - "generate-access-token": "Generate access token", - "update-access-token": "Update access token", - "access-token-status": "Access token status:", - "token-status-generated": "generated", - "token-status-not-generated": "not generated" + "access-token-uri": "Access token URI", + "access-token-uri-required": "Access token URI is required.", + "activate-user": "Activate user", + "add-domain": "Add domain", + "delete-domain": "Delete domain", + "add-provider": "Add provider", + "delete-provider": "Delete provider", + "allow-user-creation": "Allow user creation", + "always-fullscreen": "Always fullscreen", + "authorization-uri": "Authorization URI", + "authorization-uri-required": "Authorization URI is required.", + "client-authentication-method": "Client authentication method", + "client-id": "Client ID", + "client-id-required": "Client ID is required.", + "client-id-max-length": "Client ID should be less than 256", + "client-secret": "Client secret", + "client-secret-required": "Client secret is required.", + "client-secret-max-length": "Client secret should be less than 2049", + "custom-setting": "Custom settings", + "customer-name-pattern": "Customer name pattern", + "customer-name-pattern-max-length": "Customer name pattern should be less than 256", + "default-dashboard-name": "Default dashboard name", + "default-dashboard-name-max-length": "Default dashboard name should be less than 256", + "delete-domain-text": "Be careful, after the confirmation a domain and all provider data will be unavailable.", + "delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?", + "delete-registration-text": "Be careful, after the confirmation a provider data will be unavailable.", + "delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?", + "email-attribute-key": "Email attribute key", + "email-attribute-key-required": "Email attribute key is required.", + "email-attribute-key-max-length": "Email attribute key should be less than 32", + "first-name-attribute-key": "First name attribute key", + "first-name-attribute-key-max-length": "First name attribute key should be less than 32", + "general": "General", + "jwk-set-uri": "JSON Web Key URI", + "last-name-attribute-key": "Last name attribute key", + "last-name-attribute-key-max-length": "Last name attribute key should be less than 32", + "login-button-icon": "Login button icon", + "login-button-label": "Provider label", + "login-button-label-placeholder": "Login with $(Provider label)", + "login-button-label-required": "Label is required.", + "login-provider": "Login provider", + "mapper": "Mapper", + "new-domain": "New domain", + "oauth2": "OAuth2", + "password-max-length": "Password should be less than 256", + "redirect-uri-template": "Redirect URI template", + "copy-redirect-uri": "Copy redirect URI", + "registration-id": "Registration ID", + "registration-id-required": "Registration ID is required.", + "registration-id-unique": "Registration ID need to unique for the system.", + "scope": "Scope", + "scope-required": "Scope is required.", + "tenant-name-pattern": "Tenant name pattern", + "tenant-name-pattern-required": "Tenant name pattern is required.", + "tenant-name-pattern-max-length": "Tenant name pattern ishould be less than 256", + "tenant-name-strategy": "Tenant name strategy", + "type": "Mapper type", + "uri-pattern-error": "Invalid URI format.", + "url": "URL", + "url-pattern": "Invalid URL format.", + "url-required": "URL is required.", + "url-max-length": "URL should be less than 256", + "user-info-uri": "User info URI", + "user-info-uri-required": "User info URI is required.", + "username-max-length": "User name should be less than 256", + "user-name-attribute-name": "User name attribute key", + "user-name-attribute-name-required": "User name attribute key is required", + "protocol": "Protocol", + "domain-schema-http": "HTTP", + "domain-schema-https": "HTTPS", + "domain-schema-mixed": "HTTP+HTTPS", + "enable": "Enable OAuth2 settings", + "domains": "Domains", + "mobile-apps": "Mobile applications", + "no-mobile-apps": "No applications configured", + "mobile-package": "Application package", + "mobile-package-placeholder": "Ex.: my.example.app", + "mobile-package-hint": "For Android: your own unique Application ID. For iOS: Product bundle identifier.", + "mobile-package-unique": "Application package must be unique.", + "mobile-app-secret": "Application secret", + "invalid-mobile-app-secret": "Application secret must contain only alphanumeric characters and must be between 16 and 2048 characters long.", + "copy-mobile-app-secret": "Copy application secret", + "add-mobile-app": "Add application", + "delete-mobile-app": "Delete application info", + "providers": "Providers", + "platform-web": "Web", + "platform-android": "Android", + "platform-ios": "iOS", + "all-platforms": "All platforms", + "smtp-provider": "SMTP provider", + "allowed-platforms": "Allowed platforms", + "authentication": "Authentication", + "basic": "Basic", + "provider": "Provider", + "redirect-url": "Redirect URI", + "domain-name": "Domain name", + "redirect-url-template": "Redirect URI template", + "microsoft-tenant-id": "Directory (tenant) Id", + "microsoft-tenant-id-required": "Directory (tenant) Id is required", + "token-uri": "Token URI", + "token-uri-required": "Token URI is required", + "redirect-uri": "Redirect URI", + "google-provider": "Google", + "microsoft-provider": "Office 365", + "sendgrid-provider": "Sendgrid", + "custom-provider": "Custom", + "generate-access-token": "Generate access token", + "update-access-token": "Update access token", + "access-token-status": "Access token status:", + "token-status-generated": "generated", + "token-status-not-generated": "not generated" }, "smpp-provider": { "smpp-version": "SMPP version", @@ -410,7 +410,7 @@ "no-auto-commit-entities-prompt": "No entities configured for auto-commit", "delete-auto-commit-settings-title": "Are you sure you want to delete auto-commit settings?", "delete-auto-commit-settings-text": "Be careful, after the confirmation the auto-commit settings will be removed and auto-commit will be disabled for all entities.", - "2fa": { + "2fa": { "2fa": "Two-factor authentication", "available-providers": "Available providers", "issuer-name": "Issuer name", @@ -471,7 +471,7 @@ "slack-api-token": "Slack API token", "slack": "Slack", "slack-settings": "Slack settings" - }, + }, "alarm": { "alarm": "Alarm", "alarms": "Alarms", @@ -582,7 +582,7 @@ "oldest-first": "Oldest first", "newest-first": "Newest first", "activity": "Activity", - "export": "Export to CSV", + "export": "Export to CSV", "author": "Author", "created-date": "Created date", "edited-date": "Edited date", @@ -683,7 +683,7 @@ "assign-assets": "Assign assets", "assign-assets-text": "Assign { count, plural, =1 {1 asset} other {# assets} } to customer", "assign-asset-to-edge-title": "Assign Asset(s) To Edge", - "assign-asset-to-edge-text":"Please select the assets to assign to the edge", + "assign-asset-to-edge-text": "Please select the assets to assign to the edge", "delete-assets": "Delete assets", "unassign-assets": "Unassign assets", "unassign-assets-action-title": "Unassign { count, plural, =1 {1 asset} other {# assets} } from customer", @@ -1304,7 +1304,7 @@ "assign-device-to-customer": "Assign Device(s) To Customer", "assign-device-to-customer-text": "Please select the devices to assign to the customer", "assign-device-to-edge-title": "Assign Device(s) To Edge", - "assign-device-to-edge-text":"Please select the devices to assign to the edge", + "assign-device-to-edge-text": "Please select the devices to assign to the edge", "make-public": "Make device public", "make-private": "Make device private", "no-devices-text": "No devices found", @@ -1357,34 +1357,34 @@ "generate-password": "Generate Password", "generate-access-token": "Generate Access Token", "lwm2m-security-config": { - "identity": "Client Identity", - "identity-required": "Client Identity is required.", - "identity-tooltip": "The PSK identifier is an arbitrary PSK identifier up to 128 bytes, as described in the standard [RFC7925].\nThe PSK identifier MUST first be converted to a character string and then encoded into octets using UTF-8.", - "client-key": "Client Key", - "client-key-required": "Client Key is required.", - "client-key-tooltip-prk": "RPK public key or id must be in the standard [RFC7250] and encoded to Base64 format!", - "client-key-tooltip-psk": "PSK key must be in the standard [RFC4279] and HexDec format: 32, 64, 128 characters!", - "endpoint": "Endpoint Client Name", - "endpoint-required": "Endpoint Client Name is required.", - "client-public-key": "Client public key", - "client-public-key-hint": "If client public key is empty, the trusted certificate will be used", - "client-public-key-tooltip": "X509 public key must be in DER-encoded X509v3 format and support exclusively EC algorithm and then encoded to Base64 format!", - "mode": "Security config mode", - "client-tab": "Client Security Config", - "client-certificate": "Client certificate", - "bootstrap-tab": "Bootstrap Client", - "bootstrap-server": "Bootstrap Server", - "lwm2m-server": "LwM2M Server", - "client-publicKey-or-id": "Client Public Key or Id", - "client-publicKey-or-id-required": "Client Public Key or Id is required.", - "client-publicKey-or-id-tooltip-psk": "The PSK identifier is an arbitrary PSK identifier up to 128 bytes, as described in the standard [RFC7925].\nThe PSK identifier MUST first be converted to a character string and then encoded into octets using UTF-8.", - "client-publicKey-or-id-tooltip-rpk": "RPK public key or id must be in the standard [RFC7250] and encoded to Base64 format!", - "client-publicKey-or-id-tooltip-x509": "X509 public key must be in DER-encoded X509v3 format and support exclusively EC algorithm and then encoded to Base64 format", - "client-secret-key": "Client Secret Key", - "client-secret-key-required": "Client Secret Key is required.", - "client-secret-key-tooltip-psk": "PSK key must be in the standard [RFC4279] and HexDec format: 32, 64, 128 characters!", - "client-secret-key-tooltip-prk": "RPK secret key must be in PKCS_8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!", - "client-secret-key-tooltip-x509": "X509 secret key must be in PKCS_8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!" + "identity": "Client Identity", + "identity-required": "Client Identity is required.", + "identity-tooltip": "The PSK identifier is an arbitrary PSK identifier up to 128 bytes, as described in the standard [RFC7925].\nThe PSK identifier MUST first be converted to a character string and then encoded into octets using UTF-8.", + "client-key": "Client Key", + "client-key-required": "Client Key is required.", + "client-key-tooltip-prk": "RPK public key or id must be in the standard [RFC7250] and encoded to Base64 format!", + "client-key-tooltip-psk": "PSK key must be in the standard [RFC4279] and HexDec format: 32, 64, 128 characters!", + "endpoint": "Endpoint Client Name", + "endpoint-required": "Endpoint Client Name is required.", + "client-public-key": "Client public key", + "client-public-key-hint": "If client public key is empty, the trusted certificate will be used", + "client-public-key-tooltip": "X509 public key must be in DER-encoded X509v3 format and support exclusively EC algorithm and then encoded to Base64 format!", + "mode": "Security config mode", + "client-tab": "Client Security Config", + "client-certificate": "Client certificate", + "bootstrap-tab": "Bootstrap Client", + "bootstrap-server": "Bootstrap Server", + "lwm2m-server": "LwM2M Server", + "client-publicKey-or-id": "Client Public Key or Id", + "client-publicKey-or-id-required": "Client Public Key or Id is required.", + "client-publicKey-or-id-tooltip-psk": "The PSK identifier is an arbitrary PSK identifier up to 128 bytes, as described in the standard [RFC7925].\nThe PSK identifier MUST first be converted to a character string and then encoded into octets using UTF-8.", + "client-publicKey-or-id-tooltip-rpk": "RPK public key or id must be in the standard [RFC7250] and encoded to Base64 format!", + "client-publicKey-or-id-tooltip-x509": "X509 public key must be in DER-encoded X509v3 format and support exclusively EC algorithm and then encoded to Base64 format", + "client-secret-key": "Client Secret Key", + "client-secret-key-required": "Client Secret Key is required.", + "client-secret-key-tooltip-psk": "PSK key must be in the standard [RFC4279] and HexDec format: 32, 64, 128 characters!", + "client-secret-key-tooltip-prk": "RPK secret key must be in PKCS_8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!", + "client-secret-key-tooltip-x509": "X509 secret key must be in PKCS_8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!" }, "client-id": "Client ID", "client-id-pattern": "Contains invalid character.", @@ -1704,13 +1704,13 @@ "schedule-specific-time": "Active at a specific time", "schedule-custom": "Custom", "schedule-day": { - "monday": "Monday", - "tuesday": "Tuesday", - "wednesday": "Wednesday", - "thursday": "Thursday", - "friday": "Friday", - "saturday": "Saturday", - "sunday": "Sunday" + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday" }, "schedule-days": "Days", "schedule-time": "Time", @@ -2336,7 +2336,7 @@ "make-private-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' private?", "make-private-entity-view-text": "After the confirmation the entity view and all its data will be made private and won't be accessible by others.", "assign-entity-view-to-edge": "Assign Entity View(s) To Edge", - "assign-entity-view-to-edge-text":"Please select the entity views to assign to the edge", + "assign-entity-view-to-edge-text": "Please select the entity views to assign to the edge", "unassign-entity-view-from-edge-title": "Are you sure you want to unassign the entity view '{{entityViewName}}'?", "unassign-entity-view-from-edge-text": "After the confirmation the entity view will be unassigned and won't be accessible by the edge.", "unassign-entity-views-from-edge-action-title": "Unassign { count, plural, =1 {1 entity view} other {# entity views} } from edge", @@ -2550,105 +2550,105 @@ "invalid-file-error": "Invalid extension file" }, "feature": { - "advanced-features": "Advanced features" + "advanced-features": "Advanced features" }, "filter": { - "add": "Add filter", - "edit": "Edit filter", - "name": "Filter name", - "name-required": "Filter name is required.", - "duplicate-filter": "Filter with same name is already exists.", - "filters": "Filters", - "unable-delete-filter-title": "Unable to delete filter", - "unable-delete-filter-text": "Filter '{{filter}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", - "duplicate-filter-error": "Duplicate filter found '{{filter}}'.
Filters must be unique within the dashboard.", - "missing-key-filters-error": "Key filters is missing for filter '{{filter}}'.", - "filter": "Filter", - "editable": "Editable", - "no-filters-found": "No filters found.", - "no-filter-text": "No filter specified", - "add-filter-prompt": "Please add filter", - "no-filter-matching": "'{{filter}}' not found.", - "create-new-filter": "Create a new one!", - "create-new": "Create new", - "filter-required": "Filter is required.", - "operation": { - "operation": "Operation", - "equal": "equal", - "not-equal": "not equal", - "starts-with": "starts with", - "ends-with": "ends with", - "contains": "contains", - "not-contains": "not contains", - "greater": "greater than", - "less": "less than", - "greater-or-equal": "greater or equal", - "less-or-equal": "less or equal", - "and": "and", - "or": "or", - "in": "in", - "not-in": "not in" - }, - "ignore-case": "ignore case", - "value": "Value", - "remove-filter": "Remove filter", - "duplicate-filter-action": "Duplicate filter", - "preview": "Filter preview", - "no-filters": "No filters configured", - "add-filter": "Add filter", - "add-complex-filter": "Add complex filter", - "add-complex": "Add complex", - "complex-filter": "Complex filter", - "edit-complex-filter": "Edit complex filter", - "edit-filter-user-params": "Edit filter predicate user parameters", - "filter-user-params": "Filter predicate user parameters", - "user-parameters": "User parameters", - "display-label": "Label to display", - "autogenerated-label": "Auto generate label", - "order-priority": "Field order priority", - "key-filter": "Key filter", - "key-filters": "Key filters", - "key-name": "Key name", - "key-name-required": "Key name is required.", - "key-type": { - "key-type": "Key type", - "attribute": "Attribute", - "timeseries": "Timeseries", - "entity-field": "Entity field", - "constant": "Constant", - "client-attribute": "Client attribute", - "server-attribute": "Server attribute", - "shared-attribute": "Shared attribute" - }, - "value-type": { - "value-type": "Value type", - "string": "String", - "numeric": "Numeric", - "boolean": "Boolean", - "date-time": "Datetime" - }, - "value-type-required": "Key value type is required.", - "key-value-type-change-title": "Are you sure you want to change key value type?", - "key-value-type-change-message": "If you confirm new value type all entered key filters will be removed.", - "no-key-filters": "No key filters configured", - "add-key-filter": "Add key filter", - "remove-key-filter": "Remove key filter", - "edit-key-filter": "Edit key filter", - "date": "Date", - "time": "Time", - "current-tenant": "Current tenant", - "current-customer": "Current customer", - "current-user": "Current user", - "current-device": "Current device", - "default-value": "Default value", - "dynamic-source-type": "Dynamic source type", - "dynamic-value": "Dynamic value", - "no-dynamic-value": "No dynamic value", - "source-attribute": "Source attribute", - "switch-to-dynamic-value": "Switch to dynamic value", - "switch-to-default-value": "Switch to default value", - "inherit-owner": "Inherit from owner", - "source-attribute-not-set": "If source attribute isn't set" + "add": "Add filter", + "edit": "Edit filter", + "name": "Filter name", + "name-required": "Filter name is required.", + "duplicate-filter": "Filter with same name is already exists.", + "filters": "Filters", + "unable-delete-filter-title": "Unable to delete filter", + "unable-delete-filter-text": "Filter '{{filter}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", + "duplicate-filter-error": "Duplicate filter found '{{filter}}'.
Filters must be unique within the dashboard.", + "missing-key-filters-error": "Key filters is missing for filter '{{filter}}'.", + "filter": "Filter", + "editable": "Editable", + "no-filters-found": "No filters found.", + "no-filter-text": "No filter specified", + "add-filter-prompt": "Please add filter", + "no-filter-matching": "'{{filter}}' not found.", + "create-new-filter": "Create a new one!", + "create-new": "Create new", + "filter-required": "Filter is required.", + "operation": { + "operation": "Operation", + "equal": "equal", + "not-equal": "not equal", + "starts-with": "starts with", + "ends-with": "ends with", + "contains": "contains", + "not-contains": "not contains", + "greater": "greater than", + "less": "less than", + "greater-or-equal": "greater or equal", + "less-or-equal": "less or equal", + "and": "and", + "or": "or", + "in": "in", + "not-in": "not in" + }, + "ignore-case": "ignore case", + "value": "Value", + "remove-filter": "Remove filter", + "duplicate-filter-action": "Duplicate filter", + "preview": "Filter preview", + "no-filters": "No filters configured", + "add-filter": "Add filter", + "add-complex-filter": "Add complex filter", + "add-complex": "Add complex", + "complex-filter": "Complex filter", + "edit-complex-filter": "Edit complex filter", + "edit-filter-user-params": "Edit filter predicate user parameters", + "filter-user-params": "Filter predicate user parameters", + "user-parameters": "User parameters", + "display-label": "Label to display", + "autogenerated-label": "Auto generate label", + "order-priority": "Field order priority", + "key-filter": "Key filter", + "key-filters": "Key filters", + "key-name": "Key name", + "key-name-required": "Key name is required.", + "key-type": { + "key-type": "Key type", + "attribute": "Attribute", + "timeseries": "Timeseries", + "entity-field": "Entity field", + "constant": "Constant", + "client-attribute": "Client attribute", + "server-attribute": "Server attribute", + "shared-attribute": "Shared attribute" + }, + "value-type": { + "value-type": "Value type", + "string": "String", + "numeric": "Numeric", + "boolean": "Boolean", + "date-time": "Datetime" + }, + "value-type-required": "Key value type is required.", + "key-value-type-change-title": "Are you sure you want to change key value type?", + "key-value-type-change-message": "If you confirm new value type all entered key filters will be removed.", + "no-key-filters": "No key filters configured", + "add-key-filter": "Add key filter", + "remove-key-filter": "Remove key filter", + "edit-key-filter": "Edit key filter", + "date": "Date", + "time": "Time", + "current-tenant": "Current tenant", + "current-customer": "Current customer", + "current-user": "Current user", + "current-device": "Current device", + "default-value": "Default value", + "dynamic-source-type": "Dynamic source type", + "dynamic-value": "Dynamic value", + "no-dynamic-value": "No dynamic value", + "source-attribute": "Source attribute", + "switch-to-dynamic-value": "Switch to dynamic value", + "switch-to-default-value": "Switch to default value", + "inherit-owner": "Inherit from owner", + "source-attribute-not-set": "If source attribute isn't set" }, "fullscreen": { "expand": "Expand to fullscreen", @@ -2814,7 +2814,7 @@ "remove": "Remove", "requestFilter": "Request Filter", "requestUrlExpression": "Request URL Expression", - "HTTPMethod": "HTTP Method", + "httpMethod": "HTTP Method", "timeout": "Timeout", "tries": "Tries", "httpHeaders": "HTTP Headers", @@ -2847,9 +2847,17 @@ "save-template": "Save template", "template-name": "Template name", "template-name-required": "Template name is required.", - "template-name-duplicate": "The name is already used." - - + "template-name-duplicate": "Template with such name already exists, it will be updated.", + "command": "Command", + "params": "Params", + "read-coils": "01: Read Coils", + "read-discrete-inputs": "02: Read Discrete Inputs", + "read-multiple-holding-registers": "03: Read Multiple Holding Registers", + "read-input-registers": "04: Read Input Registers", + "write-single-coil": "05: Write Single Coil", + "write-single-holding-register": "06: Write Single Holding Register", + "write-multiple-coils": "15: Write Multiple Coils", + "write-multiple-holding-registers": "16: Write Multiple Holding Registers" }, "other": "Other", "save-tip": "Save configuration file", @@ -3057,22 +3065,22 @@ "access-token": "Access token", "x509": "X.509", "mqtt": { - "client-id": "MQTT client ID", - "user-name": "MQTT user name", - "password": "MQTT password" + "client-id": "MQTT client ID", + "user-name": "MQTT user name", + "password": "MQTT password" }, "lwm2m": { - "client-endpoint": "LwM2M endpoint client name", - "security-config-mode": "LwM2M security config mode", - "client-identity": "LwM2M client identity", - "client-key": "LwM2M client key", - "client-cert": "LwM2M client public key", - "bootstrap-server-security-mode": "LwM2M bootstrap server security mode", - "bootstrap-server-secret-key": "LwM2M bootstrap server secret key", - "bootstrap-server-public-key-id": "LwM2M bootstrap server public key or id", - "lwm2m-server-security-mode": "LwM2M server security mode", - "lwm2m-server-secret-key": "LwM2M server secret key", - "lwm2m-server-public-key-id": "LwM2M server public key or id" + "client-endpoint": "LwM2M endpoint client name", + "security-config-mode": "LwM2M security config mode", + "client-identity": "LwM2M client identity", + "client-key": "LwM2M client key", + "client-cert": "LwM2M client public key", + "bootstrap-server-security-mode": "LwM2M bootstrap server security mode", + "bootstrap-server-secret-key": "LwM2M bootstrap server secret key", + "bootstrap-server-public-key-id": "LwM2M bootstrap server public key or id", + "lwm2m-server-security-mode": "LwM2M server security mode", + "lwm2m-server-secret-key": "LwM2M server secret key", + "lwm2m-server-public-key-id": "LwM2M server public key or id" }, "snmp": { "host": "SNMP host", @@ -3086,7 +3094,7 @@ "routing-key": "Edge key", "secret": "Edge secret" }, - "stepper-text":{ + "stepper-text": { "select-file": "Select a file", "configuration": "Import configuration", "column-type": "Select columns type", @@ -3477,69 +3485,69 @@ } }, "ota-update": { - "add": "Add package", - "assign-firmware": "Assigned firmware", - "assign-firmware-required": "Assigned firmware is required", - "assign-software": "Assigned software", - "assign-software-required": "Assigned software is required", - "auto-generate-checksum": "Auto-generate checksum", - "checksum": "Checksum", - "checksum-hint": "If checksum is empty, it will be generated automatically", - "checksum-algorithm": "Checksum algorithm", - "checksum-copied-message": "Package checksum has been copied to clipboard", - "change-firmware": "Change of the firmware may cause update of { count, plural, =1 {1 device} other {# devices} }.", - "change-software": "Change of the software may cause update of { count, plural, =1 {1 device} other {# devices} }.", - "chose-compatible-device-profile": "The uploaded package will be available only for devices with the chosen profile.", - "chose-firmware-distributed-device": "Choose firmware that will be distributed to the devices", - "chose-software-distributed-device": "Choose software that will be distributed to the devices", - "content-type": "Content type", - "copy-checksum": "Copy checksum", - "copy-direct-url": "Copy direct URL", - "copyId": "Copy package Id", - "copied": "Copied!", - "delete": "Delete package", - "delete-ota-update-text": "Be careful, after the confirmation the OTA update will become unrecoverable.", - "delete-ota-update-title": "Are you sure you want to delete the OTA update '{{title}}'?", - "delete-ota-updates-text": "Be careful, after the confirmation all selected OTA updates will be removed.", - "delete-ota-updates-title": "Are you sure you want to delete { count, plural, =1 {1 OTA update} other {# OTA updates} }?", - "description": "Description", - "direct-url": "Direct URL", - "direct-url-copied-message": "Package direct URL has been copied to clipboard", - "direct-url-required": "Direct URL is required", - "download": "Download package", - "drop-file": "Drop a package file or click to select a file to upload.", - "drop-package-file-or": "Drag and drop a package file or", - "file-name": "File name", - "file-size": "File size", - "file-size-bytes": "File size in bytes", - "idCopiedMessage": "Package Id has been copied to clipboard", - "no-firmware-matching": "No compatible Firmware OTA Update packages matching '{{entity}}' were found.", - "no-firmware-text": "No compatible Firmware OTA Update packages provisioned.", - "no-packages-text": "No packages found", - "no-software-matching": "No compatible Software OTA Update packages matching '{{entity}}' were found.", - "no-software-text": "No compatible Software OTA Update packages provisioned.", - "ota-update": "OTA update", - "ota-update-details": "OTA update details", - "ota-updates": "OTA updates", - "package-type": "Package type", - "packages-repository": "Packages repository", - "search": "Search packages", - "selected-package": "{ count, plural, =1 {1 package} other {# packages} } selected", - "title": "Title", - "title-required": "Title is required.", - "title-max-length": "Title should be less than 256", - "types": { - "firmware": "Firmware", - "software": "Software" - }, - "upload-binary-file": "Upload binary file", - "use-external-url": "Use external URL", - "version": "Version", - "version-required": "Version is required.", - "version-tag": "Version tag", - "version-tag-hint": "Custom tag should match the package version reported by your device.", - "version-max-length": "Version should be less than 256", - "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type." + "add": "Add package", + "assign-firmware": "Assigned firmware", + "assign-firmware-required": "Assigned firmware is required", + "assign-software": "Assigned software", + "assign-software-required": "Assigned software is required", + "auto-generate-checksum": "Auto-generate checksum", + "checksum": "Checksum", + "checksum-hint": "If checksum is empty, it will be generated automatically", + "checksum-algorithm": "Checksum algorithm", + "checksum-copied-message": "Package checksum has been copied to clipboard", + "change-firmware": "Change of the firmware may cause update of { count, plural, =1 {1 device} other {# devices} }.", + "change-software": "Change of the software may cause update of { count, plural, =1 {1 device} other {# devices} }.", + "chose-compatible-device-profile": "The uploaded package will be available only for devices with the chosen profile.", + "chose-firmware-distributed-device": "Choose firmware that will be distributed to the devices", + "chose-software-distributed-device": "Choose software that will be distributed to the devices", + "content-type": "Content type", + "copy-checksum": "Copy checksum", + "copy-direct-url": "Copy direct URL", + "copyId": "Copy package Id", + "copied": "Copied!", + "delete": "Delete package", + "delete-ota-update-text": "Be careful, after the confirmation the OTA update will become unrecoverable.", + "delete-ota-update-title": "Are you sure you want to delete the OTA update '{{title}}'?", + "delete-ota-updates-text": "Be careful, after the confirmation all selected OTA updates will be removed.", + "delete-ota-updates-title": "Are you sure you want to delete { count, plural, =1 {1 OTA update} other {# OTA updates} }?", + "description": "Description", + "direct-url": "Direct URL", + "direct-url-copied-message": "Package direct URL has been copied to clipboard", + "direct-url-required": "Direct URL is required", + "download": "Download package", + "drop-file": "Drop a package file or click to select a file to upload.", + "drop-package-file-or": "Drag and drop a package file or", + "file-name": "File name", + "file-size": "File size", + "file-size-bytes": "File size in bytes", + "idCopiedMessage": "Package Id has been copied to clipboard", + "no-firmware-matching": "No compatible Firmware OTA Update packages matching '{{entity}}' were found.", + "no-firmware-text": "No compatible Firmware OTA Update packages provisioned.", + "no-packages-text": "No packages found", + "no-software-matching": "No compatible Software OTA Update packages matching '{{entity}}' were found.", + "no-software-text": "No compatible Software OTA Update packages provisioned.", + "ota-update": "OTA update", + "ota-update-details": "OTA update details", + "ota-updates": "OTA updates", + "package-type": "Package type", + "packages-repository": "Packages repository", + "search": "Search packages", + "selected-package": "{ count, plural, =1 {1 package} other {# packages} } selected", + "title": "Title", + "title-required": "Title is required.", + "title-max-length": "Title should be less than 256", + "types": { + "firmware": "Firmware", + "software": "Software" + }, + "upload-binary-file": "Upload binary file", + "use-external-url": "Use external URL", + "version": "Version", + "version-required": "Version is required.", + "version-tag": "Version tag", + "version-tag-hint": "Custom tag should match the package version reported by your device.", + "version-max-length": "Version should be less than 256", + "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type." }, "position": { "top": "Top", @@ -3860,11 +3868,11 @@ "test-with-this-message": "{{test}} with this message" }, "timezone": { - "timezone": "Timezone", - "select-timezone": "Select timezone", - "no-timezones-matching": "No timezones matching '{{timezone}}' were found.", - "timezone-required": "Timezone is required.", - "browser-time": "Browser Time" + "timezone": "Timezone", + "select-timezone": "Select timezone", + "no-timezones-matching": "No timezones matching '{{timezone}}' were found.", + "timezone-required": "Timezone is required.", + "browser-time": "Browser Time" }, "queue": { "queue-name": "Queue", @@ -3903,7 +3911,7 @@ "delete-queue-text": "Be careful, after the confirmation the queue and all related data will become unrecoverable.", "delete-queues-text": "After the confirmation all selected queues will be deleted and won't be accessible.", "search": "Search queue", - "add" : "Add queue", + "add": "Add queue", "details": "Queue details", "topic": "Topic", "submit-settings": "Submit settings", @@ -4198,30 +4206,30 @@ "seconds": "Seconds", "advanced": "Advanced", "predefined": { - "yesterday": "Yesterday", - "day-before-yesterday": "Day before yesterday", - "this-day-last-week": "This day last week", - "previous-week": "Previous week (Sun - Sat)", - "previous-week-iso": "Previous week (Mon - Sun)", - "previous-month": "Previous month", - "previous-quarter": "Previous quarter", - "previous-half-year": "Previous half year", - "previous-year": "Previous year", - "current-hour": "Current hour", - "current-day": "Current day", - "current-day-so-far": "Current day so far", - "current-week": "Current week (Sun - Sat)", - "current-week-iso": "Current week (Mon - Sun)", - "current-week-so-far": "Current week so far (Sun - Sat)", - "current-week-iso-so-far": "Current week so far (Mon - Sun)", - "current-month": "Current month", - "current-month-so-far": "Current month so far", - "current-quarter": "Current quarter", - "current-quarter-so-far": "Current quarter so far", - "current-half-year": "Current half year", - "current-half-year-so-far": "Current half year so far", - "current-year": "Current year", - "current-year-so-far": "Current year so far" + "yesterday": "Yesterday", + "day-before-yesterday": "Day before yesterday", + "this-day-last-week": "This day last week", + "previous-week": "Previous week (Sun - Sat)", + "previous-week-iso": "Previous week (Mon - Sun)", + "previous-month": "Previous month", + "previous-quarter": "Previous quarter", + "previous-half-year": "Previous half year", + "previous-year": "Previous year", + "current-hour": "Current hour", + "current-day": "Current day", + "current-day-so-far": "Current day so far", + "current-week": "Current week (Sun - Sat)", + "current-week-iso": "Current week (Mon - Sun)", + "current-week-so-far": "Current week so far (Sun - Sat)", + "current-week-iso-so-far": "Current week so far (Mon - Sun)", + "current-month": "Current month", + "current-month-so-far": "Current month so far", + "current-quarter": "Current quarter", + "current-quarter-so-far": "Current quarter so far", + "current-half-year": "Current half year", + "current-half-year-so-far": "Current half year so far", + "current-year": "Current year", + "current-year-so-far": "Current year so far" } }, "timeunit": { @@ -4990,16 +4998,16 @@ "popover-style": "Popover style", "open-new-browser-tab": "Open in a new browser tab", "mobile": { - "action-type": "Mobile action type", - "action-type-required": "Mobile action type is required", - "take-picture-from-gallery": "Take picture from gallery", - "take-photo": "Take photo", - "map-direction": "Open map directions", - "map-location": "Open map location", - "scan-qr-code": "Scan QR Code", - "make-phone-call": "Make phone call", - "get-location": "Get phone location", - "take-screenshot": "Take screenshot" + "action-type": "Mobile action type", + "action-type-required": "Mobile action type is required", + "take-picture-from-gallery": "Take picture from gallery", + "take-photo": "Take photo", + "map-direction": "Open map directions", + "map-location": "Open map location", + "scan-qr-code": "Scan QR Code", + "make-phone-call": "Make phone call", + "get-location": "Get phone location", + "take-screenshot": "Take screenshot" } }, "widgets-bundle": { @@ -6677,7 +6685,7 @@ "card-click": "On card click" } }, - "paginator" : { + "paginator": { "items-per-page": "Items per page:", "first-page-label": "First page", "last-page-label": "Last page", From 5eadc90d262bbe3f8c8696aef17f86e372e42ccd Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 18 Dec 2023 10:00:47 +0100 Subject: [PATCH 061/209] added custome serializer for device profile --- .../server/common/data/DeviceProfile.java | 4 + .../server/common/util/ProtoUtils.java | 125 +++++++++++++----- common/proto/src/main/proto/queue.proto | 61 ++++++--- .../dao/device/DeviceProfileRedisCache.java | 22 ++- 4 files changed, 164 insertions(+), 48 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index be4426a1d4..020836d31a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -21,6 +21,8 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; @@ -78,6 +80,8 @@ public class DeviceProfile extends BaseData implements HasName, @Valid private transient DeviceProfileData profileData; @JsonIgnore + @Getter + @Setter private byte[] profileDataBytes; @NoXss @ApiModelProperty(position = 13, value = "Unique provisioning key used by 'Device Provisioning' feature.") diff --git a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java index 86678db00a..95dac5d4ee 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java @@ -19,18 +19,25 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.google.protobuf.ByteString; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.OtaPackageId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -68,6 +75,7 @@ import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.UUID; +import java.util.function.Function; import java.util.stream.Collectors; public class ProtoUtils { @@ -517,7 +525,9 @@ public class ProtoUtils { .setCustomerIdLSB(getLsb(device.getCustomerId())) .setDeviceIdMSB(device.getId().getId().getMostSignificantBits()) .setDeviceIdLSB(device.getId().getId().getLeastSignificantBits()) + .setCreatedTime(device.getCreatedTime()) .setDeviceName(device.getName()) + .setDeviceLabel(toProtoString(device.getLabel())) .setDeviceType(device.getType()) .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) @@ -529,10 +539,6 @@ public class ProtoUtils { .setExternalIdMSB(getMsb(device.getExternalId())) .setExternalIdLSB(getLsb(device.getExternalId())); - if (device.getLabel() != null) { - builder.setDeviceLabel(device.getLabel()); - } - if (device.getDeviceDataBytes() != null) { builder.setDeviceData(ByteString.copyFrom(device.getDeviceDataBytes())); } @@ -540,34 +546,76 @@ public class ProtoUtils { return builder.build(); } - public static Device fromDeviceProto(TransportProtos.DeviceProto deviceProto) { - Device device = new Device(new DeviceId(new UUID(deviceProto.getDeviceIdMSB(), deviceProto.getDeviceIdLSB()))); - device.setTenantId(new TenantId(new UUID(deviceProto.getTenantIdMSB(), deviceProto.getTenantIdLSB()))); - device.setCustomerId(new CustomerId(new UUID(deviceProto.getCustomerIdMSB(), deviceProto.getCustomerIdLSB()))); - device.setName(deviceProto.getDeviceName()); - device.setLabel(deviceProto.getDeviceLabel()); - device.setType(deviceProto.getDeviceType()); - device.setDeviceProfileId(new DeviceProfileId(new UUID(deviceProto.getDeviceProfileIdMSB(), deviceProto.getDeviceProfileIdLSB()))); - device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceProto.getAdditionalInfo())); - device.setFirmwareId(createOtaPackageId(deviceProto.getFirmwareIdMSB(), deviceProto.getFirmwareIdLSB())); - device.setSoftwareId(createOtaPackageId(deviceProto.getSoftwareIdMSB(), deviceProto.getSoftwareIdLSB())); - device.setExternalId(createDeviceId(deviceProto.getExternalIdMSB(), deviceProto.getExternalIdLSB())); - device.setDeviceDataBytes(deviceProto.getDeviceData().toByteArray()); + public static Device fromDeviceProto(TransportProtos.DeviceProto proto) { + Device device = new Device(getEntityId(proto.getDeviceIdMSB(), proto.getDeviceIdLSB(), DeviceId::new)); + device.setCreatedTime(proto.getCreatedTime()); + device.setTenantId(getEntityId(proto.getTenantIdMSB(), proto.getTenantIdLSB(), TenantId::new)); + device.setCustomerId(getEntityId(proto.getCustomerIdMSB(), proto.getCustomerIdLSB(), CustomerId::new)); + device.setName(proto.getDeviceName()); + device.setLabel(fromProtoString(proto.getDeviceLabel())); + device.setType(proto.getDeviceType()); + device.setDeviceProfileId(getEntityId(proto.getDeviceProfileIdMSB(), proto.getDeviceProfileIdLSB(), DeviceProfileId::new)); + device.setAdditionalInfo(JacksonUtil.toJsonNode(proto.getAdditionalInfo())); + device.setFirmwareId(getEntityId(proto.getFirmwareIdMSB(), proto.getFirmwareIdLSB(), OtaPackageId::new)); + device.setSoftwareId(getEntityId(proto.getSoftwareIdMSB(), proto.getSoftwareIdLSB(), OtaPackageId::new)); + device.setExternalId(getEntityId(proto.getExternalIdMSB(), proto.getExternalIdLSB(), DeviceId::new)); + device.setDeviceDataBytes(proto.getDeviceData().toByteArray()); return device; } - private static OtaPackageId createOtaPackageId(long msb, long lsb) { - if (msb != 0 || lsb != 0) { - return new OtaPackageId(new UUID(msb, lsb)); - } - return null; - } - - private static DeviceId createDeviceId(long msb, long lsb) { - if (msb != 0 || lsb != 0) { - return new DeviceId(new UUID(msb, lsb)); - } - return null; + public static TransportProtos.DeviceProfileProto toDeviceProfileProto(DeviceProfile deviceProfile) { + return TransportProtos.DeviceProfileProto.newBuilder() + .setTenantIdMSB(getMsb(deviceProfile.getTenantId())) + .setTenantIdLSB(getLsb(deviceProfile.getTenantId())) + .setDeviceProfileIdMSB(getMsb(deviceProfile.getId())) + .setDeviceProfileIdLSB(getLsb(deviceProfile.getId())) + .setCreatedTime(deviceProfile.getCreatedTime()) + .setName(deviceProfile.getName()) + .setIsDefault(deviceProfile.isDefault()) + .setType(deviceProfile.getType().name()) + .setTransportType(deviceProfile.getTransportType().name()) + .setProvisionType(deviceProfile.getProvisionType().name()) + .setDeviceProfileData(ByteString.copyFrom(deviceProfile.getProfileDataBytes())) + .setDescription(toProtoString(deviceProfile.getDescription())) + .setImage(toProtoString(deviceProfile.getImage())) + .setDefaultRuleChainIdMSB(getMsb(deviceProfile.getDefaultRuleChainId())) + .setDefaultRuleChainIdLSB(getMsb(deviceProfile.getDefaultRuleChainId())) + .setDefaultDashboardIdMSB(getMsb(deviceProfile.getDefaultDashboardId())) + .setDefaultDashboardIdLSB(getLsb(deviceProfile.getDefaultDashboardId())) + .setDefaultQueueName(toProtoString(deviceProfile.getDefaultQueueName())) + .setProvisionDeviceKey(toProtoString(deviceProfile.getProvisionDeviceKey())) + .setFirmwareIdMSB(getMsb(deviceProfile.getFirmwareId())) + .setFirmwareIdLSB(getLsb(deviceProfile.getFirmwareId())) + .setSoftwareIdMSB(getMsb(deviceProfile.getSoftwareId())) + .setSoftwareIdLSB(getLsb(deviceProfile.getSoftwareId())) + .setDefaultEdgeRuleChainIdMSB(getMsb(deviceProfile.getDefaultEdgeRuleChainId())) + .setDefaultEdgeRuleChainIdLSB(getLsb(deviceProfile.getDefaultEdgeRuleChainId())) + .setExternalIdMSB(getMsb(deviceProfile.getExternalId())) + .setExternalIdLSB(getLsb(deviceProfile.getExternalId())).build(); + } + + public static DeviceProfile fromDeviceProfileProto(TransportProtos.DeviceProfileProto proto) { + DeviceProfile deviceProfile = new DeviceProfile(getEntityId(proto.getDeviceProfileIdMSB(), proto.getDeviceProfileIdLSB(), DeviceProfileId::new)); + deviceProfile.setCreatedTime(proto.getCreatedTime()); + deviceProfile.setTenantId(getEntityId(proto.getTenantIdMSB(), proto.getTenantIdLSB(), TenantId::new)); + deviceProfile.setName(proto.getName()); + deviceProfile.setDefault(proto.getIsDefault()); + deviceProfile.setType(DeviceProfileType.valueOf(proto.getType())); + deviceProfile.setTransportType(DeviceTransportType.valueOf(proto.getTransportType())); + deviceProfile.setProvisionType(DeviceProfileProvisionType.valueOf(proto.getProvisionType())); + deviceProfile.setProfileDataBytes(proto.getDeviceProfileData().toByteArray()); + deviceProfile.setDescription(fromProtoString(proto.getDescription())); + deviceProfile.setImage(fromProtoString(proto.getImage())); + deviceProfile.setDefaultRuleChainId(getEntityId(proto.getDefaultRuleChainIdMSB(), proto.getDefaultRuleChainIdLSB(), RuleChainId::new)); + deviceProfile.setDefaultDashboardId(getEntityId(proto.getDefaultDashboardIdMSB(), proto.getDefaultDashboardIdLSB(), DashboardId::new)); + deviceProfile.setDefaultQueueName(fromProtoString(proto.getDefaultQueueName())); + deviceProfile.setProvisionDeviceKey(fromProtoString(proto.getProvisionDeviceKey())); + deviceProfile.setFirmwareId(getEntityId(proto.getFirmwareIdMSB(), proto.getFirmwareIdLSB(), OtaPackageId::new)); + deviceProfile.setSoftwareId(getEntityId(proto.getSoftwareIdMSB(), proto.getSoftwareIdLSB(), OtaPackageId::new)); + deviceProfile.setDefaultEdgeRuleChainId(getEntityId(proto.getDefaultEdgeRuleChainIdMSB(), proto.getDefaultEdgeRuleChainIdLSB(), RuleChainId::new)); + deviceProfile.setExternalId(getEntityId(proto.getExternalIdMSB(), proto.getExternalIdLSB(), DeviceProfileId::new)); + + return deviceProfile; } public static TransportProtos.DeviceInfoProto toDeviceInfoProto(Device device) throws JsonProcessingException { @@ -609,14 +657,29 @@ public class ProtoUtils { return builder.build(); } - public static Long getMsb(EntityId entityId) { + private static T getEntityId(long msb, long lsb, Function entityId) { + if (msb != 0 || lsb != 0) { + return entityId.apply(new UUID(msb, lsb)); + } + return null; + } + + private static String toProtoString(String str) { + return str != null ? str : ""; + } + + private static String fromProtoString(String str) { + return StringUtils.isNotEmpty(str) ? str : null; + } + + private static Long getMsb(EntityId entityId) { if (entityId != null) { return entityId.getId().getMostSignificantBits(); } return 0L; } - public static Long getLsb(EntityId entityId) { + private static Long getLsb(EntityId entityId) { if (entityId != null) { return entityId.getId().getLeastSignificantBits(); } diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index eaefb08952..d8f5517e38 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -188,21 +188,52 @@ message DeviceProto { int64 tenantIdLSB = 2; int64 deviceIdMSB = 3; int64 deviceIdLSB = 4; - string deviceName = 5; - string deviceLabel = 6; - string deviceType = 7; - string additionalInfo = 8; - int64 deviceProfileIdMSB = 9; - int64 deviceProfileIdLSB = 10; - int64 customerIdMSB = 11; - int64 customerIdLSB = 12; - bytes deviceData = 13; - int64 firmwareIdMSB = 14; - int64 firmwareIdLSB = 15; - int64 softwareIdMSB = 16; - int64 softwareIdLSB = 17; - int64 externalIdMSB = 18; - int64 externalIdLSB = 19; + int64 createdTime = 5; + string deviceName = 6; + string deviceLabel = 7; + string deviceType = 8; + string additionalInfo = 9; + int64 deviceProfileIdMSB = 10; + int64 deviceProfileIdLSB = 11; + int64 customerIdMSB = 12; + int64 customerIdLSB = 13; + bytes deviceData = 14; + int64 firmwareIdMSB = 15; + int64 firmwareIdLSB = 16; + int64 softwareIdMSB = 17; + int64 softwareIdLSB = 18; + int64 externalIdMSB = 19; + int64 externalIdLSB = 20; +} + +message DeviceProfileProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 deviceProfileIdMSB = 3; + int64 deviceProfileIdLSB = 4; + int64 createdTime = 5; + string name = 6; + string description = 7; + string image = 8; + bool isDefault = 9; + string type = 10; + string transportType = 11; + string provisionType = 12; + int64 defaultRuleChainIdMSB = 13; + int64 defaultRuleChainIdLSB = 14; + int64 defaultDashboardIdMSB = 15; + int64 defaultDashboardIdLSB = 16; + string defaultQueueName = 17; + bytes deviceProfileData = 18; + string provisionDeviceKey = 19; + int64 firmwareIdMSB = 20; + int64 firmwareIdLSB = 21; + int64 softwareIdMSB = 22; + int64 softwareIdLSB = 23; + int64 defaultEdgeRuleChainIdMSB = 24; + int64 defaultEdgeRuleChainIdLSB = 25; + int64 externalIdMSB = 26; + int64 externalIdLSB = 27; } /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java index a68c169b49..a61762c9aa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java @@ -15,21 +15,39 @@ */ package org.thingsboard.server.dao.device; +import com.google.protobuf.InvalidProtocolBufferException; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.SerializationException; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbJsonRedisSerializer; +import org.thingsboard.server.cache.TbRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.util.ProtoUtils; +import org.thingsboard.server.gen.transport.TransportProtos; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("DeviceProfileCache") public class DeviceProfileRedisCache extends RedisTbTransactionalCache { public DeviceProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.DEVICE_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(DeviceProfile.class)); + super(CacheConstants.DEVICE_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer() { + @Override + public byte[] serialize(DeviceProfile deviceProfile) throws SerializationException { + return ProtoUtils.toDeviceProfileProto(deviceProfile).toByteArray(); + } + + @Override + public DeviceProfile deserialize(DeviceProfileCacheKey key, byte[] bytes) throws SerializationException { + try { + return ProtoUtils.fromDeviceProfileProto(TransportProtos.DeviceProfileProto.parseFrom(bytes)); + } catch (InvalidProtocolBufferException e) { + throw new SerializationException(e.getMessage()); + } + } + }); } } From d1765bfe7a5045923ccedf3d32166da3483f6351 Mon Sep 17 00:00:00 2001 From: Maksym Dudnik Date: Mon, 18 Dec 2023 13:07:04 +0200 Subject: [PATCH 062/209] trailing regex fix --- ui-ngx/src/app/core/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 012a64ea45..2590c68f3e 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -26,7 +26,7 @@ import { TranslateService } from '@ngx-translate/core'; import { serverErrorCodesTranslations } from '@shared/models/constants'; const varsRegex = /\${([^}]*)}/g; -export const noLeadTrailSpacesRegex: RegExp = /^(?! )[A-Za-z0-9 ]*(? { const scrollSubject = new Subject(); From e779c06ec1d39b27d6d3502aed468f5cf576aa9e Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 18 Dec 2023 14:20:29 +0200 Subject: [PATCH 063/209] changed AttributeService interface to accept AttributeScope instead of String --- .../3.6.2/schema_update_attribute_kv.sql | 26 ------ .../data/upgrade/3.6.2/schema_update_ttl.sql | 87 ------------------- .../device/DeviceActorMessageProcessor.java | 19 ++-- .../controller/TelemetryController.java | 86 +++++++++--------- .../install/ThingsboardInstallService.java | 3 - .../device/ClaimDevicesServiceImpl.java | 5 +- .../device/DeviceProvisionServiceImpl.java | 5 +- .../service/edge/rpc/EdgeGrpcSession.java | 5 +- .../telemetry/BaseTelemetryProcessor.java | 3 +- .../rpc/sync/DefaultEdgeRequestsService.java | 3 +- .../DefaultTbEntityViewService.java | 25 +++--- .../CassandraTsDatabaseUpgradeService.java | 1 - .../DefaultSystemDataLoaderService.java | 7 +- .../install/SqlDatabaseUpgradeService.java | 11 +-- .../query/DefaultEntityQueryService.java | 14 +-- .../state/DefaultDeviceStateService.java | 5 +- .../DefaultSubscriptionManagerService.java | 3 +- .../subscription/TbAbstractSubCtx.java | 3 +- .../impl/DefaultEntityExportService.java | 10 +-- .../DefaultTelemetrySubscriptionService.java | 5 +- .../service/ws/DefaultWebSocketService.java | 10 +-- ...AbstractRuleEngineFlowIntegrationTest.java | 9 +- ...actRuleEngineLifecycleIntegrationTest.java | 3 +- .../dao/attributes/AttributesService.java | 13 +-- .../importing/csv/BulkImportColumnType.java | 6 +- .../dao/attributes/AttributeCacheKey.java | 3 +- .../server/dao/attributes/AttributeUtils.java | 4 +- .../dao/attributes/BaseAttributesService.java | 24 ++--- .../attributes/CachedAttributesService.java | 28 +++--- ...y.java => AttributeKvDictionaryEntry.java} | 2 +- .../server/dao/service/Validator.java | 14 --- .../AttributeKvDictionaryRepository.java | 8 +- .../dao/sql/attributes/JpaAttributeDao.java | 16 ++-- .../sql/schema-views-and-functions.sql | 72 +++++++++++++++ .../server/dao/service/EntityServiceTest.java | 17 ++-- .../attributes/BaseAttributesServiceTest.java | 40 ++++----- .../edge/BaseTbMsgPushNodeConfiguration.java | 4 +- .../engine/geo/TbGpsGeofencingActionNode.java | 5 +- .../rule/engine/math/TbMathNode.java | 13 +-- .../metadata/TbAbstractGetAttributesNode.java | 13 +-- .../metadata/TbAbstractGetMappedDataNode.java | 4 +- .../rule/engine/profile/DeviceState.java | 7 +- .../profile/DynamicPredicateValueCtxImpl.java | 3 +- .../engine/telemetry/TbMsgAttributesNode.java | 3 +- .../rule/engine/math/TbMathNodeTest.java | 3 +- .../metadata/TbGetAttributesNodeTest.java | 7 +- .../TbGetCustomerAttributeNodeTest.java | 5 +- .../TbGetRelatedAttributeNodeTest.java | 5 +- .../TbGetTenantAttributeNodeTest.java | 5 +- .../profile/TbDeviceProfileNodeTest.java | 54 ++++++------ 50 files changed, 343 insertions(+), 383 deletions(-) delete mode 100644 application/src/main/data/upgrade/3.6.2/schema_update_ttl.sql rename dao/src/main/java/org/thingsboard/server/dao/model/sql/{AttributeKvDictionary.java => AttributeKvDictionaryEntry.java} (96%) diff --git a/application/src/main/data/upgrade/3.6.2/schema_update_attribute_kv.sql b/application/src/main/data/upgrade/3.6.2/schema_update_attribute_kv.sql index 142b3c1844..8a13029310 100644 --- a/application/src/main/data/upgrade/3.6.2/schema_update_attribute_kv.sql +++ b/application/src/main/data/upgrade/3.6.2/schema_update_attribute_kv.sql @@ -136,32 +136,6 @@ EXCEPTION END $$; -CREATE OR REPLACE PROCEDURE recreate_device_info_active_attribute_view() - LANGUAGE plpgsql AS -$$ -BEGIN - DROP VIEW IF EXISTS device_info_active_attribute_view CASCADE; - CREATE OR REPLACE VIEW device_info_active_attribute_view AS - SELECT d.* - , c.title as customer_title - , COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public - , d.type as device_profile_name - , COALESCE(da.bool_v, FALSE) as active - FROM device d - LEFT JOIN customer c ON c.id = d.customer_id - LEFT JOIN attribute_kv da ON da.entity_id = d.id AND da.attribute_type = 2 AND da.attribute_key = (select key_id from attribute_kv_dictionary where key = 'active'); -END; -$$; - -CREATE OR REPLACE PROCEDURE recreate_device_info_view() - LANGUAGE plpgsql AS -$$ -BEGIN - DROP VIEW IF EXISTS device_info_view CASCADE; - CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_attribute_view; -END; -$$; - CREATE OR REPLACE PROCEDURE drop_attribute_kv_old_table() LANGUAGE plpgsql AS $$ diff --git a/application/src/main/data/upgrade/3.6.2/schema_update_ttl.sql b/application/src/main/data/upgrade/3.6.2/schema_update_ttl.sql deleted file mode 100644 index 67ec25656e..0000000000 --- a/application/src/main/data/upgrade/3.6.2/schema_update_ttl.sql +++ /dev/null @@ -1,87 +0,0 @@ --- --- Copyright © 2016-2023 The Thingsboard Authors --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid, - IN system_ttl bigint, INOUT deleted bigint) - LANGUAGE plpgsql AS -$$ -DECLARE -tenant_cursor CURSOR FOR select tenant.id as tenant_id - from tenant; -tenant_id_record uuid; - customer_id_record uuid; - tenant_ttl bigint; - customer_ttl bigint; - deleted_for_entities bigint; - tenant_ttl_ts bigint; - customer_ttl_ts bigint; -BEGIN -OPEN tenant_cursor; -FETCH tenant_cursor INTO tenant_id_record; -WHILE FOUND - LOOP - EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', - tenant_id_record, 'TTL') INTO tenant_ttl; - if tenant_ttl IS NULL THEN - tenant_ttl := system_ttl; -END IF; - IF tenant_ttl > 0 THEN - tenant_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - tenant_ttl::bigint * 1000)::bigint; - deleted_for_entities := delete_device_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for devices where tenant_id = %', deleted_for_entities, tenant_id_record; - deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for assets where tenant_id = %', deleted_for_entities, tenant_id_record; -END IF; -FOR customer_id_record IN -SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record - LOOP - EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', - customer_id_record, 'TTL') INTO customer_ttl; -IF customer_ttl IS NULL THEN - customer_ttl_ts := tenant_ttl_ts; -ELSE - IF customer_ttl > 0 THEN - customer_ttl_ts := - (EXTRACT(EPOCH FROM current_timestamp) * 1000 - - customer_ttl::bigint * 1000)::bigint; -END IF; -END IF; - IF customer_ttl_ts IS NOT NULL AND customer_ttl_ts > 0 THEN - deleted_for_entities := - delete_customer_records_from_ts_kv(tenant_id_record, customer_id_record, - customer_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for customer with id = % where tenant_id = %', deleted_for_entities, customer_id_record, tenant_id_record; - deleted_for_entities := - delete_device_records_from_ts_kv(tenant_id_record, customer_id_record, - customer_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for devices where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; - deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, - customer_id_record, - customer_ttl_ts); - deleted := deleted + deleted_for_entities; - RAISE NOTICE '% telemetry removed for assets where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; -END IF; -END LOOP; -FETCH tenant_cursor INTO tenant_id_record; -END LOOP; -END -$$; \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index 99e61ee8dc..be7dad7abc 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -32,6 +32,7 @@ import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EdgeUtils; @@ -501,7 +502,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso private void handleGetAttributesRequest(SessionInfoProto sessionInfo, GetAttributeRequestMsg request) { int requestId = request.getRequestId(); if (request.getOnlyShared()) { - Futures.addCallback(findAllAttributesByScope(DataConstants.SHARED_SCOPE), new FutureCallback<>() { + Futures.addCallback(findAllAttributesByScope(AttributeScope.SHARED_SCOPE), new FutureCallback<>() { @Override public void onSuccess(@Nullable List result) { GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder() @@ -551,26 +552,26 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso ListenableFuture> clientAttributesFuture; ListenableFuture> sharedAttributesFuture; if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { - clientAttributesFuture = findAllAttributesByScope(DataConstants.CLIENT_SCOPE); - sharedAttributesFuture = findAllAttributesByScope(DataConstants.SHARED_SCOPE); + clientAttributesFuture = findAllAttributesByScope(AttributeScope.CLIENT_SCOPE); + sharedAttributesFuture = findAllAttributesByScope(AttributeScope.SHARED_SCOPE); } else if (!CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { - clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE); - sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE); + clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), AttributeScope.CLIENT_SCOPE); + sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), AttributeScope.SHARED_SCOPE); } else if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) { clientAttributesFuture = Futures.immediateFuture(Collections.emptyList()); - sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE); + sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), AttributeScope.SHARED_SCOPE); } else { sharedAttributesFuture = Futures.immediateFuture(Collections.emptyList()); - clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE); + clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), AttributeScope.CLIENT_SCOPE); } return Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)); } - private ListenableFuture> findAllAttributesByScope(String scope) { + private ListenableFuture> findAllAttributesByScope(AttributeScope scope) { return systemContext.getAttributesService().findAll(tenantId, deviceId, scope); } - private ListenableFuture> findAttributesByScope(Set attributesSet, String scope) { + private ListenableFuture> findAttributesByScope(Set attributesSet, AttributeScope scope) { return systemContext.getAttributesService().find(tenantId, deviceId, scope, attributesSet); } diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 7056cab3f1..7033a9babc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -51,7 +51,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.common.adaptor.JsonConverter; -import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TenantProfile; @@ -198,7 +198,7 @@ public class TelemetryController extends BaseController { public DeferredResult getAttributeKeysByScope( @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope") String scope) throws ThingsboardException { + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope") AttributeScope scope) throws ThingsboardException { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope)); } @@ -240,7 +240,7 @@ public class TelemetryController extends BaseController { public DeferredResult getAttributesByScope( @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, required = true)) @PathVariable("scope") String scope, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, required = true)) @PathVariable("scope") AttributeScope scope, @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, @@ -352,7 +352,7 @@ public class TelemetryController extends BaseController { @ResponseBody public DeferredResult saveDeviceAttributes( @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr, - @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, required = true)) @PathVariable("scope") String scope, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, required = true)) @PathVariable("scope") AttributeScope scope, @Parameter(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); return saveAttributes(getTenantId(), entityId, scope, request); @@ -376,7 +376,7 @@ public class TelemetryController extends BaseController { public DeferredResult saveEntityAttributesV1( @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"})) @PathVariable("scope")String scope, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"})) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody JsonNode request) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return saveAttributes(getTenantId(), entityId, scope, request); @@ -400,7 +400,7 @@ public class TelemetryController extends BaseController { public DeferredResult saveEntityAttributesV2( @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, required = true)) @PathVariable("scope")String scope, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, required = true)) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody JsonNode request) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return saveAttributes(getTenantId(), entityId, scope, request); @@ -425,7 +425,7 @@ public class TelemetryController extends BaseController { public DeferredResult saveEntityTelemetry( @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope, + @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")AttributeScope scope, @Parameter(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return saveTelemetry(getTenantId(), entityId, requestBody, 0L); @@ -450,7 +450,7 @@ public class TelemetryController extends BaseController { public DeferredResult saveEntityTelemetryWithTTL( @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope, + @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")AttributeScope scope, @Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true)@PathVariable("ttl")Long ttl, @Parameter(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); @@ -555,7 +555,7 @@ public class TelemetryController extends BaseController { @ResponseBody public DeferredResult deleteDeviceAttributes( @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr, - @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, required = true)) @PathVariable("scope")String scope, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, required = true)) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); return deleteAttributes(entityId, scope, keysStr); @@ -579,49 +579,43 @@ public class TelemetryController extends BaseController { public DeferredResult deleteEntityAttributes( @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope")String scope, + @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return deleteAttributes(entityId, scope, keysStr); } - private DeferredResult deleteAttributes(EntityId entityIdSrc, String scope, String keysStr) throws ThingsboardException { + private DeferredResult deleteAttributes(EntityId entityIdSrc, AttributeScope scope, String keysStr) throws ThingsboardException { List keys = toKeysList(keysStr); if (keys.isEmpty()) { return getImmediateDeferredResult("Empty keys: " + keysStr, HttpStatus.BAD_REQUEST); } SecurityUser user = getCurrentUser(); - if (DataConstants.SERVER_SCOPE.equals(scope) || - DataConstants.SHARED_SCOPE.equals(scope) || - DataConstants.CLIENT_SCOPE.equals(scope)) { - return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { - tsSubService.deleteAndNotify(tenantId, entityId, scope, keys, new FutureCallback() { - @Override - public void onSuccess(@Nullable Void tmp) { - logAttributesDeleted(user, entityId, scope, keys, null); - if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) { - DeviceId deviceId = new DeviceId(entityId.getId()); - tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete( - user.getTenantId(), deviceId, scope, keys), null); - } - result.setResult(new ResponseEntity<>(HttpStatus.OK)); + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { + tsSubService.deleteAndNotify(tenantId, entityId, scope.name(), keys, new FutureCallback() { + @Override + public void onSuccess(@Nullable Void tmp) { + logAttributesDeleted(user, entityId, scope, keys, null); + if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) { + DeviceId deviceId = new DeviceId(entityId.getId()); + tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete( + user.getTenantId(), deviceId, scope.name(), keys), null); } + result.setResult(new ResponseEntity<>(HttpStatus.OK)); + } - @Override - public void onFailure(Throwable t) { - logAttributesDeleted(user, entityId, scope, keys, t); - result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); - } - }); + @Override + public void onFailure(Throwable t) { + logAttributesDeleted(user, entityId, scope, keys, t); + result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); + } }); - } else { - return getImmediateDeferredResult("Invalid attribute scope: " + scope, HttpStatus.BAD_REQUEST); - } + }); } - private DeferredResult saveAttributes(TenantId srcTenantId, EntityId entityIdSrc, String scope, JsonNode json) throws ThingsboardException { - if (!DataConstants.SERVER_SCOPE.equals(scope) && !DataConstants.SHARED_SCOPE.equals(scope)) { + private DeferredResult saveAttributes(TenantId srcTenantId, EntityId entityIdSrc, AttributeScope scope, JsonNode json) throws ThingsboardException { + if (AttributeScope.SERVER_SCOPE != scope && AttributeScope.SHARED_SCOPE != scope) { return getImmediateDeferredResult("Invalid scope: " + scope, HttpStatus.BAD_REQUEST); } if (json.isObject()) { @@ -636,7 +630,7 @@ public class TelemetryController extends BaseController { } SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { - tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback() { + tsSubService.saveAndNotify(tenantId, entityId, scope.name(), attributes, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { logAttributesUpdated(user, entityId, scope, attributes, null); @@ -710,10 +704,10 @@ public class TelemetryController extends BaseController { Futures.addCallback(future, getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor()); } - private void getAttributeValuesCallback(@Nullable DeferredResult result, SecurityUser user, EntityId entityId, String scope, String keys) { + private void getAttributeValuesCallback(@Nullable DeferredResult result, SecurityUser user, EntityId entityId, AttributeScope scope, String keys) { List keyList = toKeysList(keys); FutureCallback> callback = getAttributeValuesToResponseCallback(result, user, scope, entityId, keyList); - if (!StringUtils.isEmpty(scope)) { + if (scope != null) { if (keyList != null && !keyList.isEmpty()) { Futures.addCallback(attributesService.find(user.getTenantId(), entityId, scope, keyList), callback, MoreExecutors.directExecutor()); } else { @@ -721,7 +715,7 @@ public class TelemetryController extends BaseController { } } else { List>> futures = new ArrayList<>(); - for (String tmpScope : DataConstants.allScopes()) { + for (AttributeScope tmpScope : AttributeScope.values()) { if (keyList != null && !keyList.isEmpty()) { futures.add(attributesService.find(user.getTenantId(), entityId, tmpScope, keyList)); } else { @@ -735,13 +729,13 @@ public class TelemetryController extends BaseController { } } - private void getAttributeKeysCallback(@Nullable DeferredResult result, TenantId tenantId, EntityId entityId, String scope) { + private void getAttributeKeysCallback(@Nullable DeferredResult result, TenantId tenantId, EntityId entityId, AttributeScope scope) { Futures.addCallback(attributesService.findAll(tenantId, entityId, scope), getAttributeKeysToResponseCallback(result), MoreExecutors.directExecutor()); } private void getAttributeKeysCallback(@Nullable DeferredResult result, TenantId tenantId, EntityId entityId) { List>> futures = new ArrayList<>(); - for (String scope : DataConstants.allScopes()) { + for (AttributeScope scope : AttributeScope.values()) { futures.add(attributesService.findAll(tenantId, entityId, scope)); } @@ -784,7 +778,7 @@ public class TelemetryController extends BaseController { } private FutureCallback> getAttributeValuesToResponseCallback(final DeferredResult response, - final SecurityUser user, final String scope, + final SecurityUser user, final AttributeScope scope, final EntityId entityId, final List keyList) { return new FutureCallback<>() { @Override @@ -835,18 +829,18 @@ public class TelemetryController extends BaseController { toException(e), telemetry); } - private void logAttributesDeleted(SecurityUser user, EntityId entityId, String scope, List keys, Throwable e) { + private void logAttributesDeleted(SecurityUser user, EntityId entityId, AttributeScope scope, List keys, Throwable e) { notificationEntityService.logEntityAction(user.getTenantId(), (UUIDBased & EntityId) entityId, ActionType.ATTRIBUTES_DELETED, user, toException(e), scope, keys); } - private void logAttributesUpdated(SecurityUser user, EntityId entityId, String scope, List attributes, Throwable e) { + private void logAttributesUpdated(SecurityUser user, EntityId entityId, AttributeScope scope, List attributes, Throwable e) { notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.ATTRIBUTES_UPDATED, user, toException(e), scope, attributes); } - private void logAttributesRead(SecurityUser user, EntityId entityId, String scope, List keys, Throwable e) { + private void logAttributesRead(SecurityUser user, EntityId entityId, AttributeScope scope, List keys, Throwable e) { notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.ATTRIBUTES_READ, user, toException(e), scope, keys); } diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index ac348c1249..f9fcf77001 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -267,9 +267,6 @@ public class ThingsboardInstallService { databaseEntitiesUpgradeService.upgradeDatabase("3.6.0"); case "3.6.2": log.info("Upgrading ThingsBoard from version 3.6.2 to 3.7.0 ..."); - if (databaseTsUpgradeService != null) { - databaseTsUpgradeService.upgradeDatabase("3.6.2"); - } databaseEntitiesUpgradeService.upgradeDatabase("3.6.2"); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache break; diff --git a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java index a4ba2c8311..08852be7fa 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java @@ -29,6 +29,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -100,7 +101,7 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { return Futures.immediateFailedFuture(new IllegalArgumentException()); } else { ListenableFuture> claimingAllowedFuture = attributesService.find(tenantId, device.getId(), - DataConstants.SERVER_SCOPE, Collections.singletonList(CLAIM_ATTRIBUTE_NAME)); + AttributeScope.SERVER_SCOPE, Collections.singletonList(CLAIM_ATTRIBUTE_NAME)); return Futures.transform(claimingAllowedFuture, list -> { if (list != null && !list.isEmpty()) { Optional claimingAllowedOptional = list.get(0).getBooleanValue(); @@ -123,7 +124,7 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { return Futures.immediateFuture(new ClaimDataInfo(true, key, claimDataFromCache)); } else { ListenableFuture> claimDataAttrFuture = attributesService.find(device.getTenantId(), device.getId(), - DataConstants.SERVER_SCOPE, CLAIM_DATA_ATTRIBUTE_NAME); + AttributeScope.SERVER_SCOPE, CLAIM_DATA_ATTRIBUTE_NAME); return Futures.transform(claimDataAttrFuture, claimDataAttr -> { if (claimDataAttr.isPresent()) { diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 763ce01116..c01c2330d2 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -189,7 +190,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private ProvisionResponse processProvision(Device device, ProvisionRequest provisionRequest) { try { Optional provisionState = attributesService.find(device.getTenantId(), device.getId(), - DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); + AttributeScope.SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); if (provisionState != null && provisionState.isPresent() && !provisionState.get().getValueAsString().equals(PROVISIONED_STATE)) { notify(device, provisionRequest, TbMsgType.PROVISION_FAILURE, false); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); @@ -245,7 +246,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } private ListenableFuture> saveProvisionStateAttribute(Device device) { - return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, + return attributesService.save(device.getTenantId(), device.getId(), AttributeScope.SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE), System.currentTimeMillis()))); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 59d744c3f1..003162816b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -25,6 +25,7 @@ import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.data.util.Pair; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.Edge; @@ -543,7 +544,7 @@ public final class EdgeGrpcSession implements Closeable { private ListenableFuture> getQueueStartTsAndSeqId() { ListenableFuture> future = - ctx.getAttributesService().find(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, Arrays.asList(QUEUE_START_TS_ATTR_KEY, QUEUE_START_SEQ_ID_ATTR_KEY)); + ctx.getAttributesService().find(edge.getTenantId(), edge.getId(), AttributeScope.SERVER_SCOPE, Arrays.asList(QUEUE_START_TS_ATTR_KEY, QUEUE_START_SEQ_ID_ATTR_KEY)); return Futures.transform(future, attributeKvEntries -> { long startTs = 0L; long startSeqId = 0L; @@ -605,7 +606,7 @@ public final class EdgeGrpcSession implements Closeable { List attributes = Arrays.asList( new BaseAttributeKvEntry(new LongDataEntry(QUEUE_START_TS_ATTR_KEY, this.newStartTs), System.currentTimeMillis()), new BaseAttributeKvEntry(new LongDataEntry(QUEUE_START_SEQ_ID_ATTR_KEY, this.newStartSeqId), System.currentTimeMillis())); - return ctx.getAttributesService().save(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, attributes); + return ctx.getAttributesService().save(edge.getTenantId(), edge.getId(), AttributeScope.SERVER_SCOPE, attributes); } private DownlinkMsg convertEntityEventToDownlink(EdgeEvent edgeEvent) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index 2f4c3ff2d0..6641753ae5 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -30,6 +30,7 @@ import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -287,7 +288,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { String scope = attributeDeleteMsg.getScope(); List attributeKeys = attributeDeleteMsg.getAttributeNamesList(); - ListenableFuture> removeAllFuture = attributesService.removeAll(tenantId, entityId, scope, attributeKeys); + ListenableFuture> removeAllFuture = attributesService.removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys); return Futures.transformAsync(removeAllFuture, removeAttributes -> { if (EntityType.DEVICE.name().equals(entityType)) { SettableFuture futureToSet = SettableFuture.create(); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java index 46bb940da9..71856ed61f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java @@ -28,6 +28,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; @@ -136,7 +137,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { return Futures.immediateFuture(null); } String scope = attributesRequestMsg.getScope(); - ListenableFuture> findAttrFuture = attributesService.findAll(tenantId, entityId, scope); + ListenableFuture> findAttrFuture = attributesService.findAll(tenantId, entityId, AttributeScope.valueOf(scope)); return Futures.transformAsync(findAttrFuture, ssAttributes -> processEntityAttributesAndAddToEdgeQueue(tenantId, entityId, edge, entityType, scope, ssAttributes, attributesRequestMsg), dbCallbackExecutorService); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java index d511d6d4e4..665735cc22 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java @@ -24,6 +24,7 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.ConcurrentReferenceHashMap; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; @@ -99,9 +100,9 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen if (oldEntityView != null) { if (oldEntityView.getKeys() != null && oldEntityView.getKeys().getAttributes() != null) { - futures.add(deleteAttributesFromEntityView(oldEntityView, DataConstants.CLIENT_SCOPE, oldEntityView.getKeys().getAttributes().getCs(), user)); - futures.add(deleteAttributesFromEntityView(oldEntityView, DataConstants.SERVER_SCOPE, oldEntityView.getKeys().getAttributes().getSs(), user)); - futures.add(deleteAttributesFromEntityView(oldEntityView, DataConstants.SHARED_SCOPE, oldEntityView.getKeys().getAttributes().getSh(), user)); + futures.add(deleteAttributesFromEntityView(oldEntityView, AttributeScope.CLIENT_SCOPE, oldEntityView.getKeys().getAttributes().getCs(), user)); + futures.add(deleteAttributesFromEntityView(oldEntityView, AttributeScope.SERVER_SCOPE, oldEntityView.getKeys().getAttributes().getSs(), user)); + futures.add(deleteAttributesFromEntityView(oldEntityView, AttributeScope.SHARED_SCOPE, oldEntityView.getKeys().getAttributes().getSh(), user)); } List tsKeys = oldEntityView.getKeys() != null && oldEntityView.getKeys().getTimeseries() != null ? oldEntityView.getKeys().getTimeseries() : Collections.emptyList(); @@ -109,9 +110,9 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen } if (savedEntityView.getKeys() != null) { if (savedEntityView.getKeys().getAttributes() != null) { - futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs(), user)); - futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs(), user)); - futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh(), user)); + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, AttributeScope.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs(), user)); + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, AttributeScope.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs(), user)); + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, AttributeScope.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh(), user)); } futures.add(copyLatestFromEntityToEntityView(tenantId, savedEntityView)); } @@ -269,7 +270,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen } } - private ListenableFuture> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection keys, User user) throws ThingsboardException { + private ListenableFuture> copyAttributesFromEntityToEntityView(EntityView entityView, AttributeScope scope, Collection keys, User user) throws ThingsboardException { EntityViewId entityId = entityView.getId(); if (keys != null && !keys.isEmpty()) { ListenableFuture> getAttrFuture = attributesService.find(entityView.getTenantId(), entityView.getEntityId(), scope, keys); @@ -287,7 +288,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen (startTime == 0 && endTime > lastUpdateTs) || (startTime < lastUpdateTs && endTime > lastUpdateTs); }).collect(Collectors.toList()); - tsSubService.saveAndNotify(entityView.getTenantId(), entityId, scope, attributes, new FutureCallback() { + tsSubService.saveAndNotify(entityView.getTenantId(), entityId, scope.name(), attributes, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { try { @@ -351,11 +352,11 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen }, MoreExecutors.directExecutor()); } - private ListenableFuture deleteAttributesFromEntityView(EntityView entityView, String scope, List keys, User user) { + private ListenableFuture deleteAttributesFromEntityView(EntityView entityView, AttributeScope scope, List keys, User user) { EntityViewId entityId = entityView.getId(); SettableFuture resultFuture = SettableFuture.create(); if (keys != null && !keys.isEmpty()) { - tsSubService.deleteAndNotify(entityView.getTenantId(), entityId, scope, keys, new FutureCallback() { + tsSubService.deleteAndNotify(entityView.getTenantId(), entityId, scope.name(), keys, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { try { @@ -433,11 +434,11 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen return resultFuture; } - private void logAttributesUpdated(TenantId tenantId, User user, EntityId entityId, String scope, List attributes, Throwable e) throws ThingsboardException { + private void logAttributesUpdated(TenantId tenantId, User user, EntityId entityId, AttributeScope scope, List attributes, Throwable e) throws ThingsboardException { notificationEntityService.logEntityAction(tenantId, entityId, ActionType.ATTRIBUTES_UPDATED, user, toException(e), scope, attributes); } - private void logAttributesDeleted(TenantId tenantId, User user, EntityId entityId, String scope, List keys, Throwable e) throws ThingsboardException { + private void logAttributesDeleted(TenantId tenantId, User user, EntityId entityId, AttributeScope scope, List keys, Throwable e) throws ThingsboardException { notificationEntityService.logEntityAction(tenantId, entityId, ActionType.ATTRIBUTES_DELETED, user, toException(e), scope, keys); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java index 3e2f29440c..5c7c51eb2c 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java @@ -52,7 +52,6 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase case "3.1.1": case "3.2.1": case "3.2.2": - case "3.6.2": break; default: throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 54c6dab5e5..402236c8c6 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -32,6 +32,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -469,7 +470,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { DeviceId t1Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); DeviceId t2Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); - attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE, + attributesService.save(demoTenant.getId(), t1Id, AttributeScope.SERVER_SCOPE, Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)), new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -122.1503)), new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("temperatureAlarmFlag", true)), @@ -477,7 +478,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperatureAlarmThreshold", (long) 20)), new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("humidityAlarmThreshold", (long) 50)))); - attributesService.save(demoTenant.getId(), t2Id, DataConstants.SERVER_SCOPE, + attributesService.save(demoTenant.getId(), t2Id, AttributeScope.SERVER_SCOPE, Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.493801)), new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -121.948769)), new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("temperatureAlarmFlag", true)), @@ -586,7 +587,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))), 0L); addTsCallback(saveFuture, new TelemetrySaveCallback<>(deviceId, key, value)); } else { - ListenableFuture> saveFuture = attributesService.save(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, + ListenableFuture> saveFuture = attributesService.save(TenantId.SYS_TENANT_ID, deviceId, AttributeScope.SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value) , System.currentTimeMillis()))); addTsCallback(saveFuture, new TelemetrySaveCallback<>(deviceId, key, value)); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 2983dea649..1a22b6e0e2 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -826,11 +826,9 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService executeQuery(conn, "CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribute_kv(entity_id, attribute_key, last_update_ts desc);"); // remove temp files - if (pathToTempAttributeKvFile.toFile().exists()) { - boolean deleteTsKvFile = pathToTempAttributeKvFile.toFile().delete(); - if (deleteTsKvFile) { - log.info("Successfully deleted the temp file for attribute_kv table upgrade!"); - } + boolean deleteTsKvFile = Files.deleteIfExists(pathToTempAttributeKvFile); + if (deleteTsKvFile) { + log.info("Successfully deleted the temp file for attribute_kv table upgrade!"); } executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 3007000;"); @@ -900,9 +898,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService Statement statement = conn.createStatement(); statement.execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script printWarnings(statement); - Thread.sleep(2000); log.info("Successfully executed query: {}", query); - } catch (InterruptedException | SQLException e) { + } catch (SQLException e) { log.error("Failed to execute query: {} due to: {}", query, e.getMessage()); throw new RuntimeException("Failed to execute query:" + query + " due to: ", e); } diff --git a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java index 296c992ccf..22912ebe9e 100644 --- a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java +++ b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java @@ -31,6 +31,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.KvUtil; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -154,7 +155,7 @@ public class DefaultEntityQueryService implements EntityQueryService { try { Optional valueOpt = attributesService.find(user.getTenantId(), entityId, - TbAttributeSubscriptionScope.SERVER_SCOPE.name(), dynamicValue.getSourceAttribute()).get(); + AttributeScope.SERVER_SCOPE, dynamicValue.getSourceAttribute()).get(); if (valueOpt.isPresent()) { AttributeKvEntry entry = valueOpt.get(); @@ -251,13 +252,14 @@ public class DefaultEntityQueryService implements EntityQueryService { } if (isAttributes) { - ListenableFuture> future; - future = dbCallbackExecutor.submit(() -> attributesService.findAllKeysByEntityIds(tenantId, ids)); - attributesKeysFuture = Futures.transform(future, list -> { - if (CollectionUtils.isEmpty(list)) { + Map> typesMap = ids.stream().collect(Collectors.groupingBy(EntityId::getEntityType)); + List>> futures = new ArrayList<>(typesMap.size()); + typesMap.forEach((type, entityIds) -> futures.add(dbCallbackExecutor.submit(() -> attributesService.findAllKeysByEntityIds(tenantId, entityIds)))); + attributesKeysFuture = Futures.transform(Futures.allAsList(futures), lists -> { + if (CollectionUtils.isEmpty(lists)) { return Collections.emptyList(); } - return list.stream().distinct().sorted().collect(Collectors.toList()); + return lists.stream().flatMap(List::stream).distinct().sorted().collect(Collectors.toList()); }, dbCallbackExecutor); } else { attributesKeysFuture = null; diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 64df0b0a43..0affb51fc4 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -36,6 +36,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.EntityType; @@ -585,7 +586,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService> tsData = tsService.findLatest(TenantId.SYS_TENANT_ID, device.getId(), PERSISTENT_ATTRIBUTES); future = Futures.transform(tsData, extractDeviceStateData(device), deviceStateExecutor); } else { - ListenableFuture> attrData = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), SERVER_SCOPE, PERSISTENT_ATTRIBUTES); + ListenableFuture> attrData = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), AttributeScope.SERVER_SCOPE, PERSISTENT_ATTRIBUTES); future = Futures.transform(attrData, extractDeviceStateData(device), deviceStateExecutor); } return transformInactivityTimeout(future); @@ -596,7 +597,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService { attributes.flatMap(KvEntry::getLongValue).ifPresent((inactivityTimeout) -> { if (inactivityTimeout > 0) { diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java index c3c050f73b..ed02fa4d06 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java @@ -23,6 +23,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -496,7 +497,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene serviceId, subscription.getSessionId(), subscription.getSubscriptionId(), subscription.getEntityId()); final Map keyStates = subscription.getKeyStates(); - DonAsynchron.withCallback(attrService.find(subscription.getTenantId(), subscription.getEntityId(), DataConstants.CLIENT_SCOPE, keyStates.keySet()), values -> { + DonAsynchron.withCallback(attrService.find(subscription.getTenantId(), subscription.getEntityId(), AttributeScope.CLIENT_SCOPE, keyStates.keySet()), values -> { List missedUpdates = new ArrayList<>(); values.forEach(latestEntry -> { if (latestEntry.getLastUpdateTs() > keyStates.get(latestEntry.getKey())) { diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java index 7eba28bccd..b6046aef1d 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java @@ -22,6 +22,7 @@ import lombok.Data; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -215,7 +216,7 @@ public abstract class TbAbstractSubCtx { private ListenableFuture resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) { ListenableFuture> entry = attributesService.find(tenantId, entityId, - TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute()); + AttributeScope.SERVER_SCOPE, key.getSourceAttribute()); return Futures.transform(entry, attributeOpt -> { DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId); if (attributeOpt.isPresent()) { diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java index a9e0cf1166..1e079e0c0f 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java @@ -19,7 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -108,16 +108,16 @@ public class DefaultEntityExportService> exportAttributes(EntitiesExportCtx ctx, E entity) throws ThingsboardException { - List scopes; + List scopes; if (entity.getId().getEntityType() == EntityType.DEVICE) { - scopes = List.of(DataConstants.SERVER_SCOPE, DataConstants.SHARED_SCOPE); + scopes = List.of(AttributeScope.SERVER_SCOPE, AttributeScope.SHARED_SCOPE); } else { - scopes = Collections.singletonList(DataConstants.SERVER_SCOPE); + scopes = Collections.singletonList(AttributeScope.SERVER_SCOPE); } Map> attributes = new LinkedHashMap<>(); scopes.forEach(scope -> { try { - attributes.put(scope, attributesService.findAll(ctx.getTenantId(), entity.getId(), scope).get().stream() + attributes.put(scope.name(), attributesService.findAll(ctx.getTenantId(), entity.getId(), scope).get().stream() .map(attribute -> { AttributeExportData attributeExportData = new AttributeExportData(); attributeExportData.setKey(attribute.getKey()); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index d65896a9d8..5139a6ee65 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -26,6 +26,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.id.CustomerId; @@ -248,7 +249,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback) { - ListenableFuture> saveFuture = attrService.save(tenantId, entityId, scope, attributes); + ListenableFuture> saveFuture = attrService.save(tenantId, entityId, AttributeScope.valueOf(scope), attributes); addVoidCallback(saveFuture, callback); addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice)); } @@ -280,7 +281,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, FutureCallback callback) { - ListenableFuture> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys); + ListenableFuture> deleteFuture = attrService.removeAll(tenantId, entityId, AttributeScope.valueOf(scope), keys); addVoidCallback(deleteFuture, callback); addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice)); } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java index 711a218f0e..6a8d41208d 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java @@ -29,7 +29,7 @@ import org.springframework.web.socket.CloseStatus; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.CustomerId; @@ -949,7 +949,7 @@ public class DefaultWebSocketService implements WebSocketService { @Override public void onSuccess(@Nullable ValidationResult result) { List>> futures = new ArrayList<>(); - for (String scope : DataConstants.allScopes()) { + for (AttributeScope scope : AttributeScope.values()) { futures.add(attributesService.find(tenantId, entityId, scope, keys)); } @@ -968,7 +968,7 @@ public class DefaultWebSocketService implements WebSocketService { return new FutureCallback() { @Override public void onSuccess(@Nullable ValidationResult result) { - Futures.addCallback(attributesService.find(tenantId, entityId, scope, keys), callback, MoreExecutors.directExecutor()); + Futures.addCallback(attributesService.find(tenantId, entityId, AttributeScope.valueOf(scope), keys), callback, MoreExecutors.directExecutor()); } @Override @@ -983,7 +983,7 @@ public class DefaultWebSocketService implements WebSocketService { @Override public void onSuccess(@Nullable ValidationResult result) { List>> futures = new ArrayList<>(); - for (String scope : DataConstants.allScopes()) { + for (AttributeScope scope : AttributeScope.values()) { futures.add(attributesService.findAll(tenantId, entityId, scope)); } @@ -1002,7 +1002,7 @@ public class DefaultWebSocketService implements WebSocketService { return new FutureCallback() { @Override public void onSuccess(@Nullable ValidationResult result) { - Futures.addCallback(attributesService.findAll(tenantId, entityId, scope), callback, MoreExecutors.directExecutor()); + Futures.addCallback(attributesService.findAll(tenantId, entityId, AttributeScope.valueOf(scope)), callback, MoreExecutors.directExecutor()); } @Override diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java index b86287c5d7..4cb36bb0a8 100644 --- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java @@ -31,6 +31,7 @@ import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.rule.engine.metadata.TbGetAttributesNode; import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EventInfo; @@ -174,9 +175,9 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule device.setType("default"); device = doPost("/api/device", device, Device.class); - attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, + attributesService.save(device.getTenantId(), device.getId(), AttributeScope.SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis()))).get(); - attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, + attributesService.save(device.getTenantId(), device.getId(), AttributeScope.SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis()))).get(); TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); @@ -299,9 +300,9 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule device.setType("default"); device = doPost("/api/device", device, Device.class); - attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, + attributesService.save(device.getTenantId(), device.getId(), AttributeScope.SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis()))).get(); - attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, + attributesService.save(device.getTenantId(), device.getId(), AttributeScope.SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis()))).get(); TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java index 9b05be4a05..fa909b20e4 100644 --- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java @@ -28,6 +28,7 @@ import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.rule.engine.metadata.TbGetAttributesNode; import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EventInfo; @@ -134,7 +135,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac device = doPost("/api/device", device, Device.class); log.warn("before update attr"); - attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, + attributesService.save(device.getTenantId(), device.getId(), AttributeScope.SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey", "serverAttributeValue"), System.currentTimeMillis()))) .get(TIMEOUT, TimeUnit.SECONDS); log.warn("attr updated"); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java index de1386d0c6..ccbc90825a 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.attributes; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -30,17 +31,17 @@ import java.util.Optional; */ public interface AttributesService { - ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, String attributeKey); + ListenableFuture> find(TenantId tenantId, EntityId entityId, AttributeScope scope, String attributeKey); - ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, Collection attributeKeys); + ListenableFuture> find(TenantId tenantId, EntityId entityId, AttributeScope scope, Collection attributeKeys); - ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String scope); + ListenableFuture> findAll(TenantId tenantId, EntityId entityId, AttributeScope scope); - ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes); + ListenableFuture> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes); - ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute); + ListenableFuture save(TenantId tenantId, EntityId entityId, AttributeScope scope, AttributeKvEntry attribute); - ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys); + ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributeKeys); List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java index 87582b4d6d..4890d902b6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.data.sync.ie.importing.csv; import lombok.Getter; -import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; @Getter @@ -24,8 +24,8 @@ public enum BulkImportColumnType { NAME, TYPE, LABEL, - SHARED_ATTRIBUTE(DataConstants.SHARED_SCOPE, true), - SERVER_ATTRIBUTE(DataConstants.SERVER_SCOPE, true), + SHARED_ATTRIBUTE(AttributeScope.SHARED_SCOPE.name(), true), + SERVER_ATTRIBUTE(AttributeScope.SERVER_SCOPE.name(), true), TIMESERIES(true), ACCESS_TOKEN, X509, diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeCacheKey.java index f3fd154b0e..6fa1a7840f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeCacheKey.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.attributes; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.EntityId; import java.io.Serializable; @@ -28,7 +29,7 @@ import java.io.Serializable; public class AttributeCacheKey implements Serializable { private static final long serialVersionUID = 2013369077925351881L; - private final String scope; + private final AttributeScope scope; private final EntityId entityId; private final String key; diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java index 29c44edd68..983661ff6e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java @@ -26,9 +26,9 @@ import java.util.List; public class AttributeUtils { - public static void validate(EntityId id, String scope) { + public static void validate(EntityId id, AttributeScope scope) { Validator.validateId(id.getId(), "Incorrect id " + id); - Validator.validateEnum(AttributeScope.class, scope, "Incorrect scope " + scope); + Validator.checkNotNull(scope, "Incorrect scope " + scope); } public static void validate(List kvEntries, boolean valueNoXssValidation) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java index 2defbd9398..3f8c88d609 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java @@ -54,23 +54,23 @@ public class BaseAttributesService implements AttributesService { } @Override - public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, String attributeKey) { + public ListenableFuture> find(TenantId tenantId, EntityId entityId, AttributeScope scope, String attributeKey) { validate(entityId, scope); Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey); - return Futures.immediateFuture(attributesDao.find(tenantId, entityId, AttributeScope.valueOf(scope), attributeKey)); + return Futures.immediateFuture(attributesDao.find(tenantId, entityId, scope, attributeKey)); } @Override - public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, Collection attributeKeys) { + public ListenableFuture> find(TenantId tenantId, EntityId entityId, AttributeScope scope, Collection attributeKeys) { validate(entityId, scope); attributeKeys.forEach(attributeKey -> Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey)); - return Futures.immediateFuture(attributesDao.find(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys)); + return Futures.immediateFuture(attributesDao.find(tenantId, entityId, scope, attributeKeys)); } @Override - public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String scope) { + public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, AttributeScope scope) { validate(entityId, scope); - return Futures.immediateFuture(attributesDao.findAll(tenantId, entityId, AttributeScope.valueOf(scope))); + return Futures.immediateFuture(attributesDao.findAll(tenantId, entityId, scope)); } @Override @@ -84,23 +84,23 @@ public class BaseAttributesService implements AttributesService { } @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, AttributeScope scope, AttributeKvEntry attribute) { validate(entityId, scope); AttributeUtils.validate(attribute, valueNoXssValidation); - return attributesDao.save(tenantId, entityId, AttributeScope.valueOf(scope), attribute); + return attributesDao.save(tenantId, entityId, scope, attribute); } @Override - public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { + public ListenableFuture> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes) { validate(entityId, scope); AttributeUtils.validate(attributes, valueNoXssValidation); - List> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, AttributeScope.valueOf(scope), attribute)).collect(Collectors.toList()); + List> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, scope, attribute)).collect(Collectors.toList()); return Futures.allAsList(saveFutures); } @Override - public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys) { + public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributeKeys) { validate(entityId, scope); - return Futures.allAsList(attributesDao.removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys)); + return Futures.allAsList(attributesDao.removeAll(tenantId, entityId, scope, attributeKeys)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java index 327f6b5706..cd633462a3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java @@ -105,7 +105,7 @@ public class CachedAttributesService implements AttributesService { @Override - public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, String attributeKey) { + public ListenableFuture> find(TenantId tenantId, EntityId entityId, AttributeScope scope, String attributeKey) { validate(entityId, scope); Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey); @@ -120,7 +120,7 @@ public class CachedAttributesService implements AttributesService { return cacheExecutor.submit(() -> { var cacheTransaction = cache.newTransactionForKey(attributeCacheKey); try { - Optional result = attributesDao.find(tenantId, entityId, AttributeScope.valueOf(scope), attributeKey); + Optional result = attributesDao.find(tenantId, entityId, scope, attributeKey); cacheTransaction.putIfAbsent(attributeCacheKey, result.orElse(null)); cacheTransaction.commit(); return result; @@ -134,7 +134,7 @@ public class CachedAttributesService implements AttributesService { } @Override - public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, Collection attributeKeys) { + public ListenableFuture> find(TenantId tenantId, EntityId entityId, AttributeScope scope, Collection attributeKeys) { validate(entityId, scope); attributeKeys = new LinkedHashSet<>(attributeKeys); // deduplicate the attributes attributeKeys.forEach(attributeKey -> Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey)); @@ -159,7 +159,7 @@ public class CachedAttributesService implements AttributesService { var cacheTransaction = cache.newTransactionForKeys(notFoundKeys); try { log.trace("[{}][{}] Lookup attributes from db: {}", entityId, scope, notFoundAttributeKeys); - List result = attributesDao.find(tenantId, entityId, AttributeScope.valueOf(scope), notFoundAttributeKeys); + List result = attributesDao.find(tenantId, entityId, scope, notFoundAttributeKeys); for (AttributeKvEntry foundInDbAttribute : result) { AttributeCacheKey attributeCacheKey = new AttributeCacheKey(scope, entityId, foundInDbAttribute.getKey()); cacheTransaction.putIfAbsent(attributeCacheKey, foundInDbAttribute); @@ -181,7 +181,7 @@ public class CachedAttributesService implements AttributesService { }); } - private Map> findCachedAttributes(EntityId entityId, String scope, Collection attributeKeys) { + private Map> findCachedAttributes(EntityId entityId, AttributeScope scope, Collection attributeKeys) { Map> cachedAttributes = new HashMap<>(); for (String attributeKey : attributeKeys) { var cachedAttributeValue = cache.get(new AttributeCacheKey(scope, entityId, attributeKey)); @@ -196,9 +196,9 @@ public class CachedAttributesService implements AttributesService { } @Override - public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String scope) { + public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, AttributeScope scope) { validate(entityId, scope); - return Futures.immediateFuture(attributesDao.findAll(tenantId, entityId, AttributeScope.valueOf(scope))); + return Futures.immediateFuture(attributesDao.findAll(tenantId, entityId, scope)); } @Override @@ -212,28 +212,28 @@ public class CachedAttributesService implements AttributesService { } @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, AttributeScope scope, AttributeKvEntry attribute) { validate(entityId, scope); AttributeUtils.validate(attribute, valueNoXssValidation); - ListenableFuture future = attributesDao.save(tenantId, entityId, AttributeScope.valueOf(scope), attribute); + ListenableFuture future = attributesDao.save(tenantId, entityId, scope, attribute); return Futures.transform(future, key -> evict(entityId, scope, attribute, key), cacheExecutor); } @Override - public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { + public ListenableFuture> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes) { validate(entityId, scope); AttributeUtils.validate(attributes, valueNoXssValidation); List> futures = new ArrayList<>(attributes.size()); for (var attribute : attributes) { - ListenableFuture future = attributesDao.save(tenantId, entityId, AttributeScope.valueOf(scope), attribute); + ListenableFuture future = attributesDao.save(tenantId, entityId, scope, attribute); futures.add(Futures.transform(future, key -> evict(entityId, scope, attribute, key), cacheExecutor)); } return Futures.allAsList(futures); } - private String evict(EntityId entityId, String scope, AttributeKvEntry attribute, String key) { + private String evict(EntityId entityId, AttributeScope scope, AttributeKvEntry attribute, String key) { log.trace("[{}][{}][{}] Before cache evict: {}", entityId, scope, key, attribute); cache.evictOrPut(new AttributeCacheKey(scope, entityId, key), attribute); log.trace("[{}][{}][{}] after cache evict.", entityId, scope, key); @@ -241,9 +241,9 @@ public class CachedAttributesService implements AttributesService { } @Override - public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys) { + public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributeKeys) { validate(entityId, scope); - List> futures = attributesDao.removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys); + List> futures = attributesDao.removeAll(tenantId, entityId, scope, attributeKeys); return Futures.allAsList(futures.stream().map(future -> Futures.transform(future, key -> { cache.evict(new AttributeCacheKey(scope, entityId, key)); return key; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionary.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionaryEntry.java similarity index 96% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionary.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionaryEntry.java index 1cde08912e..98e3a0f5ab 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionary.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionaryEntry.java @@ -28,7 +28,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.KEY_ID_COLUMN; @Data @Entity @Table(name = "attribute_kv_dictionary") -public final class AttributeKvDictionary { +public final class AttributeKvDictionaryEntry { @Id @Column(name = KEY_COLUMN) diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java index 5bf14d06ae..d8c4ffddc9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.service; -import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; import org.thingsboard.common.util.RegexUtils; import org.thingsboard.server.common.data.id.EntityId; @@ -60,19 +59,6 @@ public class Validator { } } - /** - * This method validate String string. If string is null or can not be cast to enum than throw - * IncorrectParameterException exception - * - * @param val the val - * @param errorMessage the error message for exception - */ - public static > void validateEnum(Class enumClass, String val, String errorMessage) { - if (val == null || !EnumUtils.isValidEnum(enumClass, val)) { - throw new IncorrectParameterException(errorMessage); - } - } - /** * This method validate long value. If value isn't possitive than throw diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvDictionaryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvDictionaryRepository.java index 7af7df70ea..aa0ca5b8a5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvDictionaryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvDictionaryRepository.java @@ -16,14 +16,12 @@ package org.thingsboard.server.dao.sql.attributes; import org.springframework.data.jpa.repository.JpaRepository; -import org.thingsboard.server.dao.model.sql.AttributeKvDictionary; -import org.thingsboard.server.dao.util.SqlTsOrTsLatestAnyDao; +import org.thingsboard.server.dao.model.sql.AttributeKvDictionaryEntry; import java.util.Optional; -@SqlTsOrTsLatestAnyDao -public interface AttributeKvDictionaryRepository extends JpaRepository { +public interface AttributeKvDictionaryRepository extends JpaRepository { - Optional findByKeyId(int keyId); + Optional findByKeyId(int keyId); } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index c47cd9dbdf..50ee8c462c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -36,7 +36,7 @@ import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.attributes.AttributesDao; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; -import org.thingsboard.server.dao.model.sql.AttributeKvDictionary; +import org.thingsboard.server.dao.model.sql.AttributeKvDictionaryEntry; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; @@ -215,21 +215,21 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl private Integer getOrSaveKeyId(String attributeKey) { Integer keyId = attributeDictionaryMap.get(attributeKey); if (keyId == null) { - Optional byIdOptional = dictionaryRepository.findById(attributeKey); + Optional byIdOptional = dictionaryRepository.findById(attributeKey); if (byIdOptional.isEmpty()) { attributeCreationLock.lock(); try { byIdOptional = dictionaryRepository.findById(attributeKey); if (byIdOptional.isEmpty()) { - AttributeKvDictionary attributeKvDictionary = new AttributeKvDictionary(); - attributeKvDictionary.setKey(attributeKey); + AttributeKvDictionaryEntry attributeKvDictionaryEntry = new AttributeKvDictionaryEntry(); + attributeKvDictionaryEntry.setKey(attributeKey); try { - AttributeKvDictionary saved = dictionaryRepository.save(attributeKvDictionary); + AttributeKvDictionaryEntry saved = dictionaryRepository.save(attributeKvDictionaryEntry); attributeDictionaryMap.put(saved.getKey(), saved.getKeyId()); keyId = saved.getKeyId(); } catch (DataIntegrityViolationException | ConstraintViolationException e) { byIdOptional = dictionaryRepository.findById(attributeKey); - AttributeKvDictionary dictionary = byIdOptional.orElseThrow(() -> new RuntimeException("Failed to get AttributeKvDictionary entity from DB!")); + AttributeKvDictionaryEntry dictionary = byIdOptional.orElseThrow(() -> new RuntimeException("Failed to get AttributeKvDictionary entity from DB!")); attributeDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); keyId = dictionary.getKeyId(); } @@ -247,7 +247,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl return keyId; } private String getKey(Integer attributeKey) { - Optional byKeyId = dictionaryRepository.findByKeyId(attributeKey); - return byKeyId.map(AttributeKvDictionary::getKey).orElse(null); + Optional byKeyId = dictionaryRepository.findByKeyId(attributeKey); + return byKeyId.map(AttributeKvDictionaryEntry::getKey).orElse(null); } } diff --git a/dao/src/main/resources/sql/schema-views-and-functions.sql b/dao/src/main/resources/sql/schema-views-and-functions.sql index 4583b71e92..4d684c6406 100644 --- a/dao/src/main/resources/sql/schema-views-and-functions.sql +++ b/dao/src/main/resources/sql/schema-views-and-functions.sql @@ -291,3 +291,75 @@ CREATE OR REPLACE VIEW widget_type_info_view AS SELECT t.* , COALESCE((t.descriptor::json->>'type')::text, '') as widget_type FROM widget_type t; + +CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid, + IN system_ttl bigint, INOUT deleted bigint) + LANGUAGE plpgsql AS +$$ +DECLARE + tenant_cursor CURSOR FOR select tenant.id as tenant_id + from tenant; + tenant_id_record uuid; + customer_id_record uuid; + tenant_ttl bigint; + customer_ttl bigint; + deleted_for_entities bigint; + tenant_ttl_ts bigint; + customer_ttl_ts bigint; +BEGIN + OPEN tenant_cursor; + FETCH tenant_cursor INTO tenant_id_record; + WHILE FOUND + LOOP + EXECUTE format( + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', + tenant_id_record, 'TTL') INTO tenant_ttl; + if tenant_ttl IS NULL THEN + tenant_ttl := system_ttl; + END IF; + IF tenant_ttl > 0 THEN + tenant_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - tenant_ttl::bigint * 1000)::bigint; + deleted_for_entities := delete_device_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for devices where tenant_id = %', deleted_for_entities, tenant_id_record; + deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for assets where tenant_id = %', deleted_for_entities, tenant_id_record; + END IF; + FOR customer_id_record IN + SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record + LOOP + EXECUTE format( + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', + customer_id_record, 'TTL') INTO customer_ttl; + IF customer_ttl IS NULL THEN + customer_ttl_ts := tenant_ttl_ts; + ELSE + IF customer_ttl > 0 THEN + customer_ttl_ts := + (EXTRACT(EPOCH FROM current_timestamp) * 1000 - + customer_ttl::bigint * 1000)::bigint; + END IF; + END IF; + IF customer_ttl_ts IS NOT NULL AND customer_ttl_ts > 0 THEN + deleted_for_entities := + delete_customer_records_from_ts_kv(tenant_id_record, customer_id_record, + customer_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for customer with id = % where tenant_id = %', deleted_for_entities, customer_id_record, tenant_id_record; + deleted_for_entities := + delete_device_records_from_ts_kv(tenant_id_record, customer_id_record, + customer_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for devices where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; + deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, + customer_id_record, + customer_ttl_ts); + deleted := deleted + deleted_for_entities; + RAISE NOTICE '% telemetry removed for assets where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record; + END IF; + END LOOP; + FETCH tenant_cursor INTO tenant_id_record; + END LOOP; +END +$$; \ No newline at end of file diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java index f41e92a305..b3b56ffcba 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java @@ -25,6 +25,7 @@ import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.ResultSetExtractor; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; @@ -373,7 +374,7 @@ public class EntityServiceTest extends AbstractServiceTest { List>> attributeFutures = new ArrayList<>(); for (int i = 0; i < devices.size(); i++) { Device device = devices.get(i); - attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), DataConstants.CLIENT_SCOPE)); + attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), AttributeScope.CLIENT_SCOPE)); } Futures.allAsList(attributeFutures).get(); @@ -552,7 +553,7 @@ public class EntityServiceTest extends AbstractServiceTest { List>> attributeFutures = new ArrayList<>(); for (int i = 0; i < devices.size(); i++) { Device device = devices.get(i); - attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), DataConstants.CLIENT_SCOPE)); + attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), AttributeScope.CLIENT_SCOPE)); } Futures.allAsList(attributeFutures).get(); @@ -627,7 +628,7 @@ public class EntityServiceTest extends AbstractServiceTest { List>> attributeFutures = new ArrayList<>(); for (int i = 0; i < assets.size(); i++) { Asset asset = assets.get(i); - attributeFutures.add(saveLongAttribute(asset.getId(), "consumption", consumptions.get(i), DataConstants.SERVER_SCOPE)); + attributeFutures.add(saveLongAttribute(asset.getId(), "consumption", consumptions.get(i), AttributeScope.SERVER_SCOPE)); } Futures.allAsList(attributeFutures).get(); @@ -1436,7 +1437,7 @@ public class EntityServiceTest extends AbstractServiceTest { List>> attributeFutures = new ArrayList<>(); for (int i = 0; i < devices.size(); i++) { Device device = devices.get(i); - for (String currentScope : DataConstants.allScopes()) { + for (AttributeScope currentScope : AttributeScope.values()) { attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), currentScope)); } } @@ -1538,7 +1539,7 @@ public class EntityServiceTest extends AbstractServiceTest { List>> attributeFutures = new ArrayList<>(); for (int i = 0; i < devices.size(); i++) { Device device = devices.get(i); - attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), DataConstants.CLIENT_SCOPE)); + attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), AttributeScope.CLIENT_SCOPE)); } Futures.allAsList(attributeFutures).get(); @@ -1816,7 +1817,7 @@ public class EntityServiceTest extends AbstractServiceTest { List>> attributeFutures = new ArrayList<>(); for (int i = 0; i < devices.size(); i++) { Device device = devices.get(i); - attributeFutures.add(saveStringAttribute(device.getId(), "attributeString", attributeStrings.get(i), DataConstants.CLIENT_SCOPE)); + attributeFutures.add(saveStringAttribute(device.getId(), "attributeString", attributeStrings.get(i), AttributeScope.CLIENT_SCOPE)); } Futures.allAsList(attributeFutures).get(); @@ -2149,13 +2150,13 @@ public class EntityServiceTest extends AbstractServiceTest { return filter; } - private ListenableFuture> saveLongAttribute(EntityId entityId, String key, long value, String scope) { + private ListenableFuture> saveLongAttribute(EntityId entityId, String key, long value, AttributeScope scope) { KvEntry attrValue = new LongDataEntry(key, value); AttributeKvEntry attr = new BaseAttributeKvEntry(attrValue, 42L); return attributesService.save(SYSTEM_TENANT_ID, entityId, scope, Collections.singletonList(attr)); } - private ListenableFuture> saveStringAttribute(EntityId entityId, String key, String value, String scope) { + private ListenableFuture> saveStringAttribute(EntityId entityId, String key, String value, AttributeScope scope) { KvEntry attrValue = new StringDataEntry(key, value); AttributeKvEntry attr = new BaseAttributeKvEntry(attrValue, 42L); return attributesService.save(SYSTEM_TENANT_ID, entityId, scope, Collections.singletonList(attr)); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/attributes/BaseAttributesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/attributes/BaseAttributesServiceTest.java index a863310b06..5bf8d24117 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/attributes/BaseAttributesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/attributes/BaseAttributesServiceTest.java @@ -26,7 +26,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.cache.TbTransactionalCache; -import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -71,8 +71,8 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest { DeviceId deviceId = new DeviceId(Uuids.timeBased()); KvEntry attrValue = new StringDataEntry("attribute1", "value1"); AttributeKvEntry attr = new BaseAttributeKvEntry(attrValue, 42L); - attributesService.save(SYSTEM_TENANT_ID, deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attr)).get(); - Optional saved = attributesService.find(SYSTEM_TENANT_ID, deviceId, DataConstants.CLIENT_SCOPE, attr.getKey()).get(); + attributesService.save(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, Collections.singletonList(attr)).get(); + Optional saved = attributesService.find(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, attr.getKey()).get(); Assert.assertTrue(saved.isPresent()); Assert.assertEquals(attr, saved.get()); } @@ -83,17 +83,17 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest { KvEntry attrOldValue = new StringDataEntry("attribute1", "value1"); AttributeKvEntry attrOld = new BaseAttributeKvEntry(attrOldValue, 42L); - attributesService.save(SYSTEM_TENANT_ID, deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrOld)).get(); - Optional saved = attributesService.find(SYSTEM_TENANT_ID, deviceId, DataConstants.CLIENT_SCOPE, attrOld.getKey()).get(); + attributesService.save(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, Collections.singletonList(attrOld)).get(); + Optional saved = attributesService.find(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, attrOld.getKey()).get(); Assert.assertTrue(saved.isPresent()); Assert.assertEquals(attrOld, saved.get()); KvEntry attrNewValue = new StringDataEntry("attribute1", "value2"); AttributeKvEntry attrNew = new BaseAttributeKvEntry(attrNewValue, 73L); - attributesService.save(SYSTEM_TENANT_ID, deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrNew)).get(); + attributesService.save(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, Collections.singletonList(attrNew)).get(); - saved = attributesService.find(SYSTEM_TENANT_ID, deviceId, DataConstants.CLIENT_SCOPE, attrOld.getKey()).get(); + saved = attributesService.find(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, attrOld.getKey()).get(); Assert.assertEquals(attrNew, saved.get()); } @@ -108,11 +108,11 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest { KvEntry attrBNewValue = new StringDataEntry("B", "value3"); AttributeKvEntry attrBNew = new BaseAttributeKvEntry(attrBNewValue, 73L); - attributesService.save(SYSTEM_TENANT_ID, deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrAOld)).get(); - attributesService.save(SYSTEM_TENANT_ID, deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrANew)).get(); - attributesService.save(SYSTEM_TENANT_ID, deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrBNew)).get(); + attributesService.save(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, Collections.singletonList(attrAOld)).get(); + attributesService.save(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, Collections.singletonList(attrANew)).get(); + attributesService.save(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, Collections.singletonList(attrBNew)).get(); - List saved = attributesService.findAll(SYSTEM_TENANT_ID, deviceId, DataConstants.CLIENT_SCOPE).get(); + List saved = attributesService.findAll(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE).get(); Assert.assertNotNull(saved); Assert.assertEquals(2, saved.size()); @@ -123,7 +123,7 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest { @Test public void testDummyRequestWithEmptyResult() throws Exception { - var future = attributesService.find(new TenantId(UUID.randomUUID()), new DeviceId(UUID.randomUUID()), DataConstants.SERVER_SCOPE, "TEST"); + var future = attributesService.find(new TenantId(UUID.randomUUID()), new DeviceId(UUID.randomUUID()), AttributeScope.SERVER_SCOPE, "TEST"); Assert.assertNotNull(future); var result = future.get(10, TimeUnit.SECONDS); Assert.assertTrue(result.isEmpty()); @@ -133,7 +133,7 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest { public void testConcurrentTransaction() throws Exception { var tenantId = new TenantId(UUID.randomUUID()); var deviceId = new DeviceId(UUID.randomUUID()); - var scope = DataConstants.SERVER_SCOPE; + var scope = AttributeScope.SERVER_SCOPE; var key = "TEST"; var attrKey = new AttributeCacheKey(scope, deviceId, "TEST"); @@ -179,7 +179,7 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest { public void testFetchAndUpdateEmpty() throws Exception { var tenantId = new TenantId(UUID.randomUUID()); var deviceId = new DeviceId(UUID.randomUUID()); - var scope = DataConstants.SERVER_SCOPE; + var scope = AttributeScope.SERVER_SCOPE; var key = "TEST"; Optional emptyValue = attributesService.find(tenantId, deviceId, scope, key).get(10, TimeUnit.SECONDS); @@ -193,7 +193,7 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest { public void testFetchAndUpdateMulti() throws Exception { var tenantId = new TenantId(UUID.randomUUID()); var deviceId = new DeviceId(UUID.randomUUID()); - var scope = DataConstants.SERVER_SCOPE; + var scope = AttributeScope.SERVER_SCOPE; var key1 = "TEST1"; var key2 = "TEST2"; @@ -222,7 +222,7 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest { } private void testConcurrentFetchAndUpdate(TenantId tenantId, DeviceId deviceId, ListeningExecutorService pool) throws Exception { - var scope = DataConstants.SERVER_SCOPE; + var scope = AttributeScope.SERVER_SCOPE; var key = "TEST"; saveAttribute(tenantId, deviceId, scope, key, OLD_VALUE); List> futures = new ArrayList<>(); @@ -236,7 +236,7 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest { } private void testConcurrentFetchAndUpdateMulti(TenantId tenantId, DeviceId deviceId, ListeningExecutorService pool) throws Exception { - var scope = DataConstants.SERVER_SCOPE; + var scope = AttributeScope.SERVER_SCOPE; var key1 = "TEST1"; var key2 = "TEST2"; saveAttribute(tenantId, deviceId, scope, key1, OLD_VALUE); @@ -258,7 +258,7 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest { Assert.assertEquals(NEW_VALUE, newResult.get(1)); } - private String getAttributeValue(TenantId tenantId, DeviceId deviceId, String scope, String key) { + private String getAttributeValue(TenantId tenantId, DeviceId deviceId, AttributeScope scope, String key) { try { Optional entry = attributesService.find(tenantId, deviceId, scope, key).get(10, TimeUnit.SECONDS); return entry.orElseThrow(RuntimeException::new).getStrValue().orElse("Unknown"); @@ -268,7 +268,7 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest { } } - private List getAttributeValues(TenantId tenantId, DeviceId deviceId, String scope, List keys) { + private List getAttributeValues(TenantId tenantId, DeviceId deviceId, AttributeScope scope, List keys) { try { List entry = attributesService.find(tenantId, deviceId, scope, keys).get(10, TimeUnit.SECONDS); return entry.stream().map(e -> e.getStrValue().orElse(null)).collect(Collectors.toList()); @@ -278,7 +278,7 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest { } } - private void saveAttribute(TenantId tenantId, DeviceId deviceId, String scope, String key, String s) { + private void saveAttribute(TenantId tenantId, DeviceId deviceId, AttributeScope scope, String key, String s) { try { AttributeKvEntry newEntry = new BaseAttributeKvEntry(System.currentTimeMillis(), new StringDataEntry(key, s)); attributesService.save(tenantId, deviceId, scope, Collections.singletonList(newEntry)).get(10, TimeUnit.SECONDS); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/BaseTbMsgPushNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/BaseTbMsgPushNodeConfiguration.java index 64f80a7b9a..ffa1d0ff06 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/BaseTbMsgPushNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/BaseTbMsgPushNodeConfiguration.java @@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.edge; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; -import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.AttributeScope; @Data public class BaseTbMsgPushNodeConfiguration implements NodeConfiguration { @@ -27,7 +27,7 @@ public class BaseTbMsgPushNodeConfiguration implements NodeConfiguration { try { Optional entry = ctx.getAttributesService() - .find(ctx.getTenantId(), msg.getOriginator(), DataConstants.SERVER_SCOPE, ctx.getServiceId()) + .find(ctx.getTenantId(), msg.getOriginator(), AttributeScope.SERVER_SCOPE, ctx.getServiceId()) .get(1, TimeUnit.MINUTES); if (entry.isPresent()) { JsonObject element = parser.parse(entry.get().getValueAsString()).getAsJsonObject(); @@ -120,7 +121,7 @@ public class TbGpsGeofencingActionNode extends AbstractGeofencingNode attributeKvEntryList = Collections.singletonList(entry); - ctx.getAttributesService().save(ctx.getTenantId(), entityId, DataConstants.SERVER_SCOPE, attributeKvEntryList); + ctx.getAttributesService().save(ctx.getTenantId(), entityId, AttributeScope.SERVER_SCOPE, attributeKvEntryList); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java index 095612b4e1..455e5c8da7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java @@ -33,6 +33,7 @@ import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; @@ -208,15 +209,15 @@ public class TbMathNode implements TbNode { } private ListenableFuture saveAttribute(TbContext ctx, TbMsg msg, double result, TbMathResult mathResultDef) { - String attributeScope = getAttributeScope(mathResultDef.getAttributeScope()); + AttributeScope attributeScope = getAttributeScope(mathResultDef.getAttributeScope()); if (isIntegerResult(mathResultDef, config.getOperation())) { var value = toIntValue(result); return ctx.getTelemetryService().saveAttrAndNotify( - ctx.getTenantId(), msg.getOriginator(), attributeScope, mathResultDef.getKey(), value); + ctx.getTenantId(), msg.getOriginator(), attributeScope.name(), mathResultDef.getKey(), value); } else { var value = toDoubleValue(mathResultDef, result); return ctx.getTelemetryService().saveAttrAndNotify( - ctx.getTenantId(), msg.getOriginator(), attributeScope, mathResultDef.getKey(), value); + ctx.getTenantId(), msg.getOriginator(), attributeScope.name(), mathResultDef.getKey(), value); } } @@ -384,7 +385,7 @@ public class TbMathNode implements TbNode { case MESSAGE_METADATA: return Futures.immediateFuture(TbMathArgumentValue.fromMessageMetadata(arg, argKey, msg.getMetaData())); case ATTRIBUTE: - String scope = getAttributeScope(arg.getAttributeScope()); + AttributeScope scope = getAttributeScope(arg.getAttributeScope()); return Futures.transform(ctx.getAttributesService().find(ctx.getTenantId(), msg.getOriginator(), scope, argKey), opt -> getTbMathArgumentValue(arg, opt, "Attribute: " + argKey + " with scope: " + scope + " not found for entity: " + msg.getOriginator()) , MoreExecutors.directExecutor()); @@ -402,8 +403,8 @@ public class TbMathNode implements TbNode { return CONSTANT.equals(type) ? keyPattern : TbNodeUtils.processPattern(keyPattern, msg); } - private String getAttributeScope(String attrScope) { - return StringUtils.isEmpty(attrScope) ? DataConstants.SERVER_SCOPE : attrScope; + private AttributeScope getAttributeScope(String attrScope) { + return StringUtils.isEmpty(attrScope) ? AttributeScope.SERVER_SCOPE : AttributeScope.valueOf(attrScope); } private TbMathArgumentValue getTbMathArgumentValue(TbMathArgument arg, Optional kvOpt, String error) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java index 50d71f88a0..bb17f61f75 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.util.TbMsgSource; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; @@ -100,9 +101,9 @@ public abstract class TbAbstractGetAttributesNode>> failuresPairSet = ConcurrentHashMap.newKeySet(); var getKvEntryPairFutures = Futures.allAsList( getLatestTelemetry(ctx, entityId, TbNodeUtils.processPatterns(config.getLatestTsKeyNames(), msg), failuresPairSet), - getAttrAsync(ctx, entityId, CLIENT_SCOPE, TbNodeUtils.processPatterns(config.getClientAttributeNames(), msg), failuresPairSet), - getAttrAsync(ctx, entityId, SHARED_SCOPE, TbNodeUtils.processPatterns(config.getSharedAttributeNames(), msg), failuresPairSet), - getAttrAsync(ctx, entityId, SERVER_SCOPE, TbNodeUtils.processPatterns(config.getServerAttributeNames(), msg), failuresPairSet) + getAttrAsync(ctx, entityId, AttributeScope.CLIENT_SCOPE, TbNodeUtils.processPatterns(config.getClientAttributeNames(), msg), failuresPairSet), + getAttrAsync(ctx, entityId, AttributeScope.SHARED_SCOPE, TbNodeUtils.processPatterns(config.getSharedAttributeNames(), msg), failuresPairSet), + getAttrAsync(ctx, entityId, AttributeScope.SERVER_SCOPE, TbNodeUtils.processPatterns(config.getServerAttributeNames(), msg), failuresPairSet) ); withCallback(getKvEntryPairFutures, futuresList -> { var msgMetaData = msg.getMetaData().copy(); @@ -127,7 +128,7 @@ public abstract class TbAbstractGetAttributesNode>> getAttrAsync( TbContext ctx, EntityId entityId, - String scope, + AttributeScope scope, List keys, Set>> failuresPairSet ) { @@ -138,9 +139,9 @@ public abstract class TbAbstractGetAttributesNode { if (isTellFailureIfAbsent && attributeKvEntryList.size() != keys.size()) { List nonExistentKeys = getNonExistentKeys(attributeKvEntryList, keys); - failuresPairSet.add(new TbPair<>(scope, nonExistentKeys)); + failuresPairSet.add(new TbPair<>(scope.name(), nonExistentKeys)); } - return new TbPair<>(scope, attributeKvEntryList); + return new TbPair<>(scope.name(), attributeKvEntryList); }, ctx.getDbCallbackExecutor()); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetMappedDataNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetMappedDataNode.java index 8362918e6d..1fd8832525 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetMappedDataNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetMappedDataNode.java @@ -25,6 +25,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.util.EntitiesFieldsAsyncLoader; import org.thingsboard.rule.engine.util.TbMsgSource; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.msg.TbMsg; @@ -36,7 +37,6 @@ import java.util.Map; import java.util.stream.Collectors; import static org.thingsboard.common.util.DonAsynchron.withCallback; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; @Slf4j public abstract class TbAbstractGetMappedDataNode extends TbAbstractNodeWithFetchTo { @@ -134,7 +134,7 @@ public abstract class TbAbstractGetMappedDataNode> getAttributesAsync(TbContext ctx, EntityId entityId, List attrKeys) { - var latest = ctx.getAttributesService().find(ctx.getTenantId(), entityId, SERVER_SCOPE, attrKeys); + var latest = ctx.getAttributesService().find(ctx.getTenantId(), entityId, AttributeScope.SERVER_SCOPE, attrKeys); return Futures.transform(latest, l -> l.stream() .map(i -> (KvEntry) i) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index b38fd29af7..73a4cc1fcd 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -21,6 +21,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.profile.state.PersistedAlarmState; import org.thingsboard.rule.engine.profile.state.PersistedDeviceState; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -382,9 +383,9 @@ class DeviceState { } } if (!attributeKeys.isEmpty()) { - addToSnapshot(result, ctx.getAttributesService().find(ctx.getTenantId(), originator, DataConstants.CLIENT_SCOPE, attributeKeys).get()); - addToSnapshot(result, ctx.getAttributesService().find(ctx.getTenantId(), originator, DataConstants.SHARED_SCOPE, attributeKeys).get()); - addToSnapshot(result, ctx.getAttributesService().find(ctx.getTenantId(), originator, DataConstants.SERVER_SCOPE, attributeKeys).get()); + addToSnapshot(result, ctx.getAttributesService().find(ctx.getTenantId(), originator, AttributeScope.CLIENT_SCOPE, attributeKeys).get()); + addToSnapshot(result, ctx.getAttributesService().find(ctx.getTenantId(), originator, AttributeScope.SHARED_SCOPE, attributeKeys).get()); + addToSnapshot(result, ctx.getAttributesService().find(ctx.getTenantId(), originator, AttributeScope.SERVER_SCOPE, attributeKeys).get()); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtxImpl.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtxImpl.java index 93ee38992f..111861ad34 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtxImpl.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtxImpl.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.profile; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.CustomerId; @@ -62,7 +63,7 @@ public class DynamicPredicateValueCtxImpl implements DynamicPredicateValueCtx { private EntityKeyValue getValue(EntityId entityId, String key) { try { - Optional entry = ctx.getAttributesService().find(tenantId, entityId, DataConstants.SERVER_SCOPE, key).get(); + Optional entry = ctx.getAttributesService().find(tenantId, entityId, AttributeScope.SERVER_SCOPE, key).get(); if (entry.isPresent()) { return DeviceState.toEntityValue(entry.get()); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 06498241fb..10c3a0c5db 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -28,6 +28,7 @@ import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.KvEntry; @@ -96,7 +97,7 @@ public class TbMsgAttributesNode implements TbNode { } List keys = newAttributes.stream().map(KvEntry::getKey).collect(Collectors.toList()); - ListenableFuture> findFuture = ctx.getAttributesService().find(ctx.getTenantId(), msg.getOriginator(), scope, keys); + ListenableFuture> findFuture = ctx.getAttributesService().find(ctx.getTenantId(), msg.getOriginator(), AttributeScope.valueOf(scope), keys); DonAsynchron.withCallback(findFuture, currentAttributes -> { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java index 9eec38cfae..8ee70f23d7 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java @@ -39,6 +39,7 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; @@ -365,7 +366,7 @@ public class TbMathNodeTest { TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().toString()); - when(attributesService.find(tenantId, originator, DataConstants.SERVER_SCOPE, "a")) + when(attributesService.find(tenantId, originator, AttributeScope.SERVER_SCOPE, "a")) .thenReturn(Futures.immediateFuture(Optional.of(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("a", 2.0))))); when(tsService.findLatest(tenantId, originator, "b")) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java index 1b8c95ab22..ebbaf90bf6 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java @@ -32,6 +32,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.util.TbMsgSource; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; @@ -106,13 +107,13 @@ public class TbGetAttributesNodeTest { tsKeys = List.of("temperature", "humidity", "unknown"); ts = System.currentTimeMillis(); - when(attributesServiceMock.find(TENANT_ID, ORIGINATOR, DataConstants.CLIENT_SCOPE, clientAttributes)) + when(attributesServiceMock.find(TENANT_ID, ORIGINATOR, AttributeScope.CLIENT_SCOPE, clientAttributes)) .thenReturn(Futures.immediateFuture(getListAttributeKvEntry(clientAttributes, ts))); - when(attributesServiceMock.find(TENANT_ID, ORIGINATOR, DataConstants.SERVER_SCOPE, serverAttributes)) + when(attributesServiceMock.find(TENANT_ID, ORIGINATOR, AttributeScope.SERVER_SCOPE, serverAttributes)) .thenReturn(Futures.immediateFuture(getListAttributeKvEntry(serverAttributes, ts))); - when(attributesServiceMock.find(TENANT_ID, ORIGINATOR, DataConstants.SHARED_SCOPE, sharedAttributes)) + when(attributesServiceMock.find(TENANT_ID, ORIGINATOR, AttributeScope.SHARED_SCOPE, sharedAttributes)) .thenReturn(Futures.immediateFuture(getListAttributeKvEntry(sharedAttributes, ts))); when(timeseriesServiceMock.findLatest(TENANT_ID, ORIGINATOR, tsKeys)) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 291614464a..247d1d76bd 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -33,6 +33,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.util.TbMsgSource; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -277,7 +278,7 @@ public class TbGetCustomerAttributeNodeTest { doReturn(device).when(deviceServiceMock).findDeviceById(eq(TENANT_ID), eq(device.getId())); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(AttributeScope.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributesList)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -324,7 +325,7 @@ public class TbGetCustomerAttributeNodeTest { doReturn(Futures.immediateFuture(user)).when(userServiceMock).findUserByIdAsync(eq(TENANT_ID), eq(user.getId())); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(AttributeScope.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributesList)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index dcf5c22ea5..216c5cf1cb 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -34,6 +34,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.data.RelationsQuery; import org.thingsboard.rule.engine.util.TbMsgSource; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DataConstants; @@ -291,7 +292,7 @@ public class TbGetRelatedAttributeNodeTest { doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(user.getId()), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(user.getId()), eq(AttributeScope.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributes)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -342,7 +343,7 @@ public class TbGetRelatedAttributeNodeTest { doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(secondCustomer.getId()), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(secondCustomer.getId()), eq(AttributeScope.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributes)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index 87b34cffe0..8fb36f810b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -32,6 +32,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.util.TbMsgSource; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; @@ -216,7 +217,7 @@ public class TbGetTenantAttributeNodeTest { when(ctxMock.getTenantId()).thenReturn(TENANT_ID); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(AttributeScope.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributesList)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -257,7 +258,7 @@ public class TbGetTenantAttributeNodeTest { when(ctxMock.getTenantId()).thenReturn(TENANT_ID); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(AttributeScope.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributesList)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index 1c798ea7de..0b94f07255 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -374,7 +374,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(attrListListenableFuture); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -458,11 +458,11 @@ public class TbDeviceProfileNodeTest { .thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(Futures.immediateFuture(Collections.emptyList())); - Mockito.when(attributesService.find(eq(tenantId), eq(customerId), Mockito.anyString(), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), Mockito.any(AttributeScope.class), Mockito.anyString())) .thenReturn(Futures.immediateFuture(Optional.empty())); - Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.any(AttributeScope.class), Mockito.anyString())) .thenReturn(attrListListenableFuture); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -532,7 +532,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listListenableFutureWithLess); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -628,7 +628,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listListenableFuture); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -748,13 +748,13 @@ public class TbDeviceProfileNodeTest { .thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.any(AttributeScope.class), Mockito.anyString())) .thenReturn(optionalDurationAttribute); Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) .thenReturn(device); - Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(AttributeScope.SERVER_SCOPE), Mockito.anyString())) .thenReturn(emptyOptional); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listNoDurationAttribute); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -865,7 +865,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listListenableFuture); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -977,13 +977,13 @@ public class TbDeviceProfileNodeTest { .thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.any(AttributeScope.class), Mockito.anyString())) .thenReturn(optionalDurationAttribute); Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) .thenReturn(device); - Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(AttributeScope.SERVER_SCOPE), Mockito.anyString())) .thenReturn(emptyOptional); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listNoDurationAttribute); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -1080,7 +1080,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listListenableFuture); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -1179,7 +1179,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listListenableFuture); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -1262,7 +1262,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listListenableFutureActiveSchedule); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -1359,7 +1359,7 @@ public class TbDeviceProfileNodeTest { Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) .thenReturn(null); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listListenableFutureInactiveSchedule); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -1434,11 +1434,11 @@ public class TbDeviceProfileNodeTest { .thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listListenableFutureWithLess); Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) .thenReturn(device); - Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(AttributeScope.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -1510,9 +1510,9 @@ public class TbDeviceProfileNodeTest { .thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listListenableFutureWithLess); - Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(AttributeScope.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -1592,11 +1592,11 @@ public class TbDeviceProfileNodeTest { Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) .thenReturn(device); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listListenableFutureWithLess); - Mockito.when(attributesService.find(eq(tenantId), eq(customerId), Mockito.anyString(), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), Mockito.any(AttributeScope.class), Mockito.anyString())) .thenReturn(emptyOptionalFuture); - Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(AttributeScope.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); @@ -1678,11 +1678,11 @@ public class TbDeviceProfileNodeTest { Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) .thenReturn(device); - Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.any(AttributeScope.class), Mockito.anySet())) .thenReturn(listListenableFutureWithLess); - Mockito.when(attributesService.find(eq(tenantId), eq(customerId), Mockito.anyString(), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), Mockito.any(AttributeScope.class), Mockito.anyString())) .thenReturn(emptyOptionalFuture); - Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(AttributeScope.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); From f16b0376cba5ff2f1cd0eaec27f9c5c047191ae0 Mon Sep 17 00:00:00 2001 From: Maksym Dudnik Date: Mon, 18 Dec 2023 15:49:58 +0200 Subject: [PATCH 064/209] hex regex single to multiple fix --- .../lib/gateway/gateway-service-rpc-connector.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts index 0cfc3d095b..4e95ba1aa8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts @@ -205,9 +205,9 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue bitrateSwitch: [false, []], dataLength: [null, [Validators.min(1), Validators.pattern("^[0-9]*$")]], dataByteorder: [null, []], - dataBefore: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(/^[0-9A-Fa-f]+$/)]], - dataAfter: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(/^[0-9A-Fa-f]+$/)]], - dataInHEX: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(/^[0-9A-Fa-f]+$/)]], + dataBefore: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(/^[0-9A-Fa-f ]+$/)]], + dataAfter: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(/^[0-9A-Fa-f ]+$/)]], + dataInHEX: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(/^[0-9A-Fa-f ]+$/)]], dataExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]] }) break; From 5cf224481aac65353610c35e1b5f035b3dddf798 Mon Sep 17 00:00:00 2001 From: Maksym Dudnik Date: Mon, 18 Dec 2023 19:03:35 +0200 Subject: [PATCH 065/209] final fixes --- ...rvice-rpc-connector-templates.component.ts | 2 - ...gateway-service-rpc-connector.component.ts | 58 ++++++++----------- .../gateway/gateway-service-rpc.component.ts | 1 - 3 files changed, 24 insertions(+), 37 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts index 357dd0504b..c1416b4dcf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts @@ -19,12 +19,10 @@ import { ConnectorType, RPCTemplate } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { KeyValue } from '@angular/common'; import { EntityType } from '@shared/models/entity-type.models'; import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; import { AttributeService } from '@core/http/attribute.service'; import { WidgetContext } from '@home/models/widget-component.models'; -import { MatDialog } from '@angular/material/dialog'; @Component({ selector: 'tb-gateway-service-rpc-connector-templates', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts index 4e95ba1aa8..a2dd9ec9fe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts @@ -34,7 +34,8 @@ import { CANByteOrders, ConnectorType, GatewayConnectorDefaultTypesTranslates, - HTTPMethods, ModbusCodesTranslate, + HTTPMethods, + ModbusCodesTranslate, ModbusCommandTypes, RPCCommand, RPCTemplateConfig, @@ -76,11 +77,8 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue saveTemplate: EventEmitter = new EventEmitter(); commandForm: FormGroup; - codesArray: Array = [1, 2, 3, 4, 5, 6, 15, 16]; - - readonly ConnectorType = ConnectorType; - + ConnectorType = ConnectorType; modbusCommandTypes = Object.values(ModbusCommandTypes) as ModbusCommandTypes[]; bACnetRequestTypes = Object.values(BACnetRequestTypes) as BACnetRequestTypes[]; bACnetObjectTypes = Object.values(BACnetObjectTypes) as BACnetObjectTypes[]; @@ -99,15 +97,10 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslates; modbusCodesTranslate = ModbusCodesTranslate; - urlPattern = new RegExp( - '^(https?:\\/\\/)?' + // protocol - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name - '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR IP (v4) address - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path - '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string - '(\\#[-a-z\\d_]*)?$', // fragment locator - 'i' - ); + urlPattern = /^[-a-zA-Zd_$:{}?~+=\/.0-9-]*$/; + numbersOnlyPattern = /^[0-9]*$/; + hexOnlyPattern = /^[0-9A-Fa-f ]+$/; + private propagateChange = (v: any) => { } @@ -115,7 +108,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue private dialog: MatDialog,) { } - ngOnInit() { this.commandForm = this.connectorParamsFormGroupByType(this.connectorType); this.commandForm.valueChanges.subscribe(value => { @@ -154,7 +146,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], requestTopicExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], responseTopicExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]], - responseTimeout: [null, [Validators.min(10), Validators.pattern("^[0-9]*$")]], + responseTimeout: [null, [Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], }) break; @@ -164,8 +156,8 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue type: [null, [Validators.required]], functionCode: [null, [Validators.required]], value: [null, []], - address: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]], - objectsCount: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]] + address: [null, [Validators.required, Validators.min(0), Validators.pattern(this.numbersOnlyPattern)]], + objectsCount: [null, [Validators.required, Validators.min(0), Validators.pattern(this.numbersOnlyPattern)]] }) const valueForm = formGroup.get('value'); formGroup.get('functionCode').valueChanges.subscribe(value => { @@ -182,16 +174,16 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue formGroup = this.fb.group({ method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], requestType: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - requestTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], + requestTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], objectType: [null, []], - identifier: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]], + identifier: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]], propertyId: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]] }) break; case ConnectorType.BLE: formGroup = this.fb.group({ methodRPC: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - characteristicUUID: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + characteristicUUID: ["00002A00-0000-1000-8000-00805F9B34FB", [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], methodProcessing: [null, [Validators.required]], withResponse: [false, []] }) @@ -199,15 +191,15 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue case ConnectorType.CAN: formGroup = this.fb.group({ method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - nodeID: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]], + nodeID: [null, [Validators.required, Validators.min(0), Validators.pattern(this.numbersOnlyPattern)]], isExtendedID: [false, []], isFD: [false, []], bitrateSwitch: [false, []], - dataLength: [null, [Validators.min(1), Validators.pattern("^[0-9]*$")]], + dataLength: [null, [Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]], dataByteorder: [null, []], - dataBefore: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(/^[0-9A-Fa-f ]+$/)]], - dataAfter: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(/^[0-9A-Fa-f ]+$/)]], - dataInHEX: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(/^[0-9A-Fa-f ]+$/)]], + dataBefore: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(this.hexOnlyPattern)]], + dataAfter: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(this.hexOnlyPattern)]], + dataInHEX: [null, [Validators.pattern(noLeadTrailSpacesRegex), Validators.pattern(this.hexOnlyPattern)]], dataExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]] }) break; @@ -252,9 +244,9 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], httpMethod: [null, [Validators.required]], requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]], - responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], - timeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], - tries: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]], + responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], + timeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], + tries: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]], valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], httpHeaders: this.fb.array([]), security: this.fb.array([]) @@ -265,9 +257,9 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], httpMethod: [null, [Validators.required]], requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]], - responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], - timeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]], - tries: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]], + responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], + timeout: [null, [Validators.required, Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]], + tries: [null, [Validators.required, Validators.min(1), Validators.pattern(this.numbersOnlyPattern)]], requestValueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], responseValueExpression: [null, [Validators.pattern(noLeadTrailSpacesRegex)]], httpHeaders: this.fb.array([]), @@ -285,7 +277,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue command: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], params: ['{}', [jsonRequired]], }) - } return formGroup; } @@ -430,5 +421,4 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue this.commandForm.patchValue(value, {onlySelf: false}); } } - } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts index 50cfff408d..051f602ccb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts @@ -171,7 +171,6 @@ export class GatewayServiceRPCComponent implements OnInit { }).afterClosed().subscribe( (res) => { if (res) { - console.log(res); const templateAttribute: RPCTemplate = { name: res, config: this.commandForm.value.params From c7e06ec97e15b78d72a04b2a9d080b69c81afd79 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 20 Dec 2023 15:45:27 +0200 Subject: [PATCH 066/209] fixed TelemetryConrollerTest --- .../org/thingsboard/server/controller/TelemetryController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 7033a9babc..91e5010888 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -425,7 +425,7 @@ public class TelemetryController extends BaseController { public DeferredResult saveEntityTelemetry( @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, - @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")AttributeScope scope, + @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope, @Parameter(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return saveTelemetry(getTenantId(), entityId, requestBody, 0L); From 8ccd70fc6571b9a413f0c0f941d025b1c82d4432 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 20 Dec 2023 17:33:04 +0100 Subject: [PATCH 067/209] Used ProtoUtils instead of ProtoWIthFSTService for serialization --- .../server/actors/ActorSystemContext.java | 7 +- .../device/DeviceMsgConstructorV1.java | 9 +- .../tenant/TenantMsgConstructorV1.java | 7 +- .../device/DeviceEdgeProcessorV1.java | 10 +- .../profile/DeviceProfileEdgeProcessorV1.java | 11 +- .../queue/DefaultTbClusterService.java | 11 +- .../queue/DefaultTbCoreConsumerService.java | 42 +- .../DefaultTbRuleEngineConsumerService.java | 8 +- .../service/queue/TbCoreConsumerStats.java | 11 +- .../processing/AbstractConsumerService.java | 29 +- .../DefaultGitVersionControlQueueService.java | 8 +- .../transport/DefaultTransportApiService.java | 19 +- .../server/edge/AbstractEdgeTest.java | 19 +- .../rpc/processor/BaseEdgeProcessorTest.java | 4 - .../device/AbstractDeviceProcessorTest.java | 2 - .../queue/DefaultTbClusterServiceTest.java | 5 +- .../DefaultTransportApiServiceTest.java | 3 - .../server/cache/device/DeviceRedisCache.java | 4 +- .../server/common/util/ProtoUtils.java | 522 +++++++++++++++--- common/proto/src/main/proto/queue.proto | 192 +++++-- .../RemoteNotificationRuleProcessor.java | 7 +- .../util/DataDecodingEncodingService.java | 27 - .../queue/util/ProtoWithFSTService.java | 63 --- .../service/ProtoTransportEntityService.java | 16 +- .../TransportDeviceProfileCache.java | 6 +- .../TransportTenantProfileCache.java | 4 +- .../DefaultTransportDeviceProfileCache.java | 40 +- .../DefaultTransportResourceCache.java | 16 +- .../service/DefaultTransportService.java | 91 ++- .../DefaultTransportTenantProfileCache.java | 51 +- .../DefaultClusterVersionControlService.java | 18 +- .../dao/device/DeviceProfileRedisCache.java | 4 +- 32 files changed, 727 insertions(+), 539 deletions(-) delete mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/util/DataDecodingEncodingService.java delete mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 09dcf3ca1e..edbc2c34ea 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -29,7 +29,6 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.service.executors.PubSubRuleNodeExecutorProvider; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.SmsService; @@ -93,7 +92,6 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.queue.discovery.DiscoveryService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService; @@ -101,6 +99,7 @@ import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.executors.NotificationExecutorService; +import org.thingsboard.server.service.executors.PubSubRuleNodeExecutorProvider; import org.thingsboard.server.service.executors.SharedEventLoopGroupService; import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.profile.TbAssetProfileCache; @@ -183,10 +182,6 @@ public class ActorSystemContext { @Getter private DiscoveryService discoveryService; - @Autowired - @Getter - private DataDecodingEncodingService encodingService; - @Autowired @Getter private DeviceService deviceService; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java index 4442387b21..3211a08d58 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java @@ -16,7 +16,6 @@ package org.thingsboard.server.service.edge.rpc.constructor.device; import com.google.protobuf.ByteString; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; @@ -26,7 +25,6 @@ import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import java.nio.charset.StandardCharsets; @@ -35,9 +33,6 @@ import java.nio.charset.StandardCharsets; @TbCoreComponent public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor { - @Autowired - private DataDecodingEncodingService dataDecodingEncodingService; - @Override public DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device) { DeviceUpdateMsg.Builder builder = DeviceUpdateMsg.newBuilder() @@ -69,7 +64,7 @@ public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor { .setSoftwareIdLSB(device.getSoftwareId().getId().getLeastSignificantBits()); } if (device.getDeviceData() != null) { - builder.setDeviceDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(device.getDeviceData()))); + builder.setDeviceDataBytes(ByteString.copyFrom(device.getDeviceDataBytes())); } return builder.build(); } @@ -98,7 +93,7 @@ public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor { .setName(deviceProfile.getName()) .setDefault(deviceProfile.isDefault()) .setType(deviceProfile.getType().name()) - .setProfileDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile.getProfileData()))); + .setProfileDataBytes(ByteString.copyFrom(deviceProfile.getProfileDataBytes())); if (deviceProfile.getDefaultQueueName() != null) { builder.setDefaultQueueName(deviceProfile.getDefaultQueueName()); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java index a3f815cc18..ca2cc5fbb4 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java @@ -16,7 +16,6 @@ package org.thingsboard.server.service.edge.rpc.constructor.tenant; import com.google.protobuf.ByteString; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Tenant; @@ -25,7 +24,6 @@ import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils; @@ -33,9 +31,6 @@ import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils; @TbCoreComponent public class TenantMsgConstructorV1 implements TenantMsgConstructor { - @Autowired - private DataDecodingEncodingService dataDecodingEncodingService; - @Override public TenantUpdateMsg constructTenantUpdateMsg(UpdateMsgType msgType, Tenant tenant) { TenantUpdateMsg.Builder builder = TenantUpdateMsg.newBuilder() @@ -79,7 +74,7 @@ public class TenantMsgConstructorV1 implements TenantMsgConstructor { @Override public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) { ByteString profileData = EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_1) ? - ByteString.empty() : ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile.getProfileData())); + ByteString.empty() : ByteString.copyFrom(tenantProfile.getProfileDataBytes()); TenantProfileUpdateMsg.Builder builder = TenantProfileUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(tenantProfile.getId().getId().getMostSignificantBits()) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorV1.java index 98405ca44b..9d970b2bc6 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorV1.java @@ -16,11 +16,9 @@ package org.thingsboard.server.service.edge.rpc.processor.device; import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -30,19 +28,14 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; -import java.util.Optional; import java.util.UUID; @Component @TbCoreComponent public class DeviceEdgeProcessorV1 extends DeviceEdgeProcessor { - @Autowired - private DataDecodingEncodingService dataDecodingEncodingService; - @Override protected Device constructDeviceFromUpdateMsg(TenantId tenantId, DeviceId deviceId, DeviceUpdateMsg deviceUpdateMsg) { Device device = new Device(); @@ -57,8 +50,7 @@ public class DeviceEdgeProcessorV1 extends DeviceEdgeProcessor { UUID deviceProfileUUID = safeGetUUID(deviceUpdateMsg.getDeviceProfileIdMSB(), deviceUpdateMsg.getDeviceProfileIdLSB()); device.setDeviceProfileId(deviceProfileUUID != null ? new DeviceProfileId(deviceProfileUUID) : null); - Optional deviceDataOpt = dataDecodingEncodingService.decode(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); - device.setDeviceData(deviceDataOpt.orElse(null)); + device.setDeviceDataBytes(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); UUID firmwareUUID = safeGetUUID(deviceUpdateMsg.getFirmwareIdMSB(), deviceUpdateMsg.getFirmwareIdLSB()); device.setFirmwareId(firmwareUUID != null ? new OtaPackageId(firmwareUUID) : null); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorV1.java index 03461356df..d0f7c2004f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorV1.java @@ -16,34 +16,27 @@ package org.thingsboard.server.service.edge.rpc.processor.device.profile; import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import java.nio.charset.StandardCharsets; -import java.util.Optional; import java.util.UUID; @Component @TbCoreComponent public class DeviceProfileEdgeProcessorV1 extends DeviceProfileEdgeProcessor { - @Autowired - private DataDecodingEncodingService dataDecodingEncodingService; - @Override protected DeviceProfile constructDeviceProfileFromUpdateMsg(TenantId tenantId, DeviceProfileId deviceProfileId, DeviceProfileUpdateMsg deviceProfileUpdateMsg) { DeviceProfile deviceProfile = new DeviceProfile(); @@ -63,9 +56,7 @@ public class DeviceProfileEdgeProcessorV1 extends DeviceProfileEdgeProcessor { ? deviceProfileUpdateMsg.getProvisionDeviceKey() : null); deviceProfile.setDefaultQueueName(deviceProfileUpdateMsg.getDefaultQueueName()); - Optional profileDataOpt = - dataDecodingEncodingService.decode(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray()); - deviceProfile.setProfileData(profileDataOpt.orElse(null)); + deviceProfile.setProfileDataBytes(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray()); String defaultQueueName = StringUtils.isNotBlank(deviceProfileUpdateMsg.getDefaultQueueName()) ? deviceProfileUpdateMsg.getDefaultQueueName() : null; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 14930912d1..ccdf6ea595 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.queue; -import com.google.protobuf.ByteString; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -57,6 +56,7 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rule.engine.DeviceEdgeUpdateMsg; import org.thingsboard.server.common.msg.rule.engine.DeviceNameOrTypeUpdateMsg; +import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; @@ -68,10 +68,9 @@ import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.MultipleTbQueueCallbackWrapper; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; @@ -112,7 +111,6 @@ public class DefaultTbClusterService implements TbClusterService { private OtaPackageStateService otaPackageStateService; private final TopicService topicService; - private final DataDecodingEncodingService encodingService; private final TbDeviceProfileCache deviceProfileCache; private final TbAssetProfileCache assetProfileCache; private final GatewayNotificationsService gatewayNotificationsService; @@ -351,10 +349,7 @@ public class DefaultTbClusterService implements TbClusterService { public void broadcastEntityChangeToTransport(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName(); log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName); - TransportProtos.EntityUpdateMsg entityUpdateMsg = TransportProtos.EntityUpdateMsg.newBuilder() - .setEntityType(entityid.getEntityType().name()) - .setData(ByteString.copyFrom(encodingService.encode(entity))).build(); - ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityUpdateMsg(entityUpdateMsg).build(); + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityUpdateMsg(ProtoUtils.toEntityUpdateProto(entity)).build(); broadcast(transportMsg, callback); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 5283713126..54242f8c71 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -44,8 +44,10 @@ import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.util.ProtoUtils; +import org.thingsboard.server.dao.resource.ImageCacheKey; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; @@ -59,6 +61,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto; +import org.thingsboard.server.gen.transport.TransportProtos.TbEntitySubEventProto; import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesDeleteProto; import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; @@ -66,14 +69,12 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; -import org.thingsboard.server.gen.transport.TransportProtos.TbEntitySubEventProto; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.AfterStartUp; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.edge.EdgeNotificationService; @@ -83,10 +84,8 @@ import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.processing.IdMsgPair; -import org.thingsboard.server.dao.resource.ImageCacheKey; import org.thingsboard.server.service.resource.TbImageService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; -import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; @@ -154,7 +153,6 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); - if (actorMsg.isPresent()) { - TbActorMsg tbActorMsg = actorMsg.get(); - if (tbActorMsg.getMsgType().equals(MsgType.DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG)) { - tbCoreDeviceRpcService.forwardRpcRequestToDeviceActor((ToDeviceRpcRequestActorMsg) tbActorMsg); - } else { - log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); - actorContext.tell(actorMsg.get()); - } - } - callback.onSuccess(); } else if (toCoreMsg.hasNotificationSchedulerServiceMsg()) { TransportProtos.NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = toCoreMsg.getNotificationSchedulerServiceMsg(); log.trace("[{}] Forwarding message to notification scheduler service {}", id, toCoreMsg.getNotificationSchedulerServiceMsg()); @@ -373,28 +358,15 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService notificationRuleTrigger = encodingService.decode(toCoreNotification - .getNotificationRuleProcessorMsg().getTrigger().toByteArray()); - notificationRuleTrigger.ifPresent(notificationRuleProcessor::process); + NotificationRuleTrigger notificationRuleTrigger = + JacksonUtil.fromBytes(toCoreNotification.getNotificationRuleProcessorMsg().getTrigger().toByteArray(), NotificationRuleTrigger.class); + notificationRuleProcessor.process(notificationRuleTrigger); callback.onSuccess(); } else if (toCoreNotification.hasResourceCacheInvalidateMsg()) { forwardToResourceService(toCoreNotification.getResourceCacheInvalidateMsg(), callback); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java index 273d7afc97..d67f0f8354 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java @@ -39,7 +39,6 @@ import org.thingsboard.server.queue.discovery.QueueKey; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory; import org.thingsboard.server.queue.util.AfterStartUp; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbRuleEngineComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; @@ -71,7 +70,6 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< public DefaultTbRuleEngineConsumerService(TbRuleEngineConsumerContext ctx, TbRuleEngineQueueFactory tbRuleEngineQueueFactory, ActorSystemContext actorContext, - DataDecodingEncodingService encodingService, TbRuleEngineDeviceRpcService tbDeviceRpcService, QueueService queueService, TbDeviceProfileCache deviceProfileCache, @@ -79,7 +77,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< TbTenantProfileCache tenantProfileCache, TbApiUsageStateService apiUsageStateService, PartitionService partitionService, ApplicationEventPublisher eventPublisher) { - super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, + super(actorContext, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty()); this.ctx = ctx; this.tbDeviceRpcService = tbDeviceRpcService; @@ -152,10 +150,6 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< if (nfMsg.hasComponentLifecycle()) { handleComponentLifecycleMsg(id, ProtoUtils.fromProto(nfMsg.getComponentLifecycle())); callback.onSuccess(); - } else if (!nfMsg.getComponentLifecycleMsg().isEmpty()) { - //will be removed in 3.6.1 in favour of hasComponentLifecycle() - handleComponentLifecycleMsg(id, nfMsg.getComponentLifecycleMsg()); - callback.onSuccess(); } else if (nfMsg.hasFromDeviceRpcResponse()) { TransportProtos.FromDeviceRPCResponseProto proto = nfMsg.getFromDeviceRpcResponse(); RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java b/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java index 5ff72e97e3..5e45d8c9df 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java @@ -109,10 +109,9 @@ public class TbCoreConsumerStats { this.toCoreNfSubscriptionServiceCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_SUBSCRIPTION_SERVICE)); this.toCoreNfSubscriptionManagerCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_SUBSCRIPTION_MANAGER)); this.toCoreNfVersionControlResponseCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_VC_RESPONSE)); - } - private StatsCounter register(StatsCounter counter){ + private StatsCounter register(StatsCounter counter) { counters.add(counter); return counter; } @@ -170,20 +169,12 @@ public class TbCoreConsumerStats { toCoreNfDeviceRpcResponseCounter.increment(); } else if (msg.hasComponentLifecycle()) { toCoreNfComponentLifecycleCounter.increment(); - } else if (!msg.getComponentLifecycleMsg().isEmpty()) { - toCoreNfComponentLifecycleCounter.increment(); } else if (msg.hasEdgeEventUpdate()) { toCoreNfEdgeEventUpdateCounter.increment(); - } else if (!msg.getEdgeEventUpdateMsg().isEmpty()) { - toCoreNfEdgeEventUpdateCounter.increment(); } else if (msg.hasToEdgeSyncRequest()) { toCoreNfEdgeSyncRequestCounter.increment(); - } else if (!msg.getToEdgeSyncRequestMsg().isEmpty()) { - toCoreNfEdgeSyncRequestCounter.increment(); } else if (msg.hasFromEdgeSyncResponse()) { toCoreNfEdgeSyncResponseCounter.increment(); - } else if (!msg.getFromEdgeSyncResponseMsg().isEmpty()) { - toCoreNfEdgeSyncResponseCounter.increment(); } else if (msg.hasQueueUpdateMsg()) { toCoreNfQueueUpdateCounter.increment(); } else if (msg.hasQueueDeleteMsg()) { diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 091a5a4988..2e2910beb0 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.service.queue.processing; -import com.google.protobuf.ByteString; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationEventPublisher; @@ -41,7 +41,6 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.util.AfterStartUp; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; @@ -62,13 +61,13 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j +@RequiredArgsConstructor public abstract class AbstractConsumerService extends TbApplicationEventListener { protected volatile ExecutorService notificationsConsumerExecutor; protected volatile boolean stopped = false; protected volatile boolean isReady = false; protected final ActorSystemContext actorContext; - protected final DataDecodingEncodingService encodingService; protected final TbTenantProfileCache tenantProfileCache; protected final TbDeviceProfileCache deviceProfileCache; protected final TbAssetProfileCache assetProfileCache; @@ -79,24 +78,6 @@ public abstract class AbstractConsumerService> nfConsumer; protected final Optional jwtSettingsService; - - public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, - TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache, - TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService, - PartitionService partitionService, ApplicationEventPublisher eventPublisher, - TbQueueConsumer> nfConsumer, Optional jwtSettingsService) { - this.actorContext = actorContext; - this.encodingService = encodingService; - this.tenantProfileCache = tenantProfileCache; - this.deviceProfileCache = deviceProfileCache; - this.assetProfileCache = assetProfileCache; - this.apiUsageStateService = apiUsageStateService; - this.partitionService = partitionService; - this.eventPublisher = eventPublisher; - this.nfConsumer = nfConsumer; - this.jwtSettingsService = jwtSettingsService; - } - public void init(String nfConsumerThreadName) { this.notificationsConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName(nfConsumerThreadName)); } @@ -166,12 +147,6 @@ public abstract class AbstractConsumerService actorMsgOpt = encodingService.decode(nfMsg.toByteArray()); - actorMsgOpt.ifPresent(tbActorMsg -> handleComponentLifecycleMsg(id, tbActorMsg)); - } - protected void handleComponentLifecycleMsg(UUID id, TbActorMsg actorMsg) { if (actorMsg instanceof ComponentLifecycleMsg) { ComponentLifecycleMsg componentLifecycleMsg = (ComponentLifecycleMsg) actorMsg; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java index 0cc73cd945..c426d39ab5 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java @@ -20,7 +20,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; -import com.google.protobuf.ByteString; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -46,6 +45,7 @@ import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest; import org.thingsboard.server.common.data.util.CollectionsUtil; +import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.CommitRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.EntitiesContentRequestMsg; @@ -60,7 +60,6 @@ import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.scheduler.SchedulerComponent; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.sync.vc.data.ClearRepositoryGitRequest; import org.thingsboard.server.service.sync.vc.data.CommitGitRequest; @@ -96,7 +95,6 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu private final TbServiceInfoProvider serviceInfoProvider; private final TbClusterService clusterService; - private final DataDecodingEncodingService encodingService; private final DefaultEntitiesVersionControlService entitiesVersionControlService; private final SchedulerComponent scheduler; @@ -109,12 +107,10 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu private int msgChunkSize; public DefaultGitVersionControlQueueService(TbServiceInfoProvider serviceInfoProvider, TbClusterService clusterService, - DataDecodingEncodingService encodingService, @Lazy DefaultEntitiesVersionControlService entitiesVersionControlService, SchedulerComponent scheduler) { this.serviceInfoProvider = serviceInfoProvider; this.clusterService = clusterService; - this.encodingService = encodingService; this.entitiesVersionControlService = entitiesVersionControlService; this.scheduler = scheduler; } @@ -588,7 +584,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu vcSettings = entitiesVersionControlService.getVersionControlSettings(tenantId); } if (vcSettings != null) { - builder.setVcSettings(ByteString.copyFrom(encodingService.encode(vcSettings))); + builder.setVcSettings(ProtoUtils.toProto(vcSettings)); } else if (request.requiresSettings()) { throw new RuntimeException("No entity version control settings provisioned!"); } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 76e8bf3abe..edaeb67c7e 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -99,7 +99,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MC import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; @@ -139,7 +138,6 @@ public class DefaultTransportApiService implements TransportApiService { private final DeviceCredentialsService deviceCredentialsService; private final DbCallbackExecutorService dbCallbackExecutorService; private final TbClusterService tbClusterService; - private final DataDecodingEncodingService dataDecodingEncodingService; private final DeviceProvisionService deviceProvisionService; private final ResourceService resourceService; private final OtaPackageService otaPackageService; @@ -388,7 +386,7 @@ public class DefaultTransportApiService implements TransportApiService { .setDeviceInfo(ProtoUtils.toDeviceInfoProto(device)); DeviceProfile deviceProfile = deviceProfileCache.get(device.getTenantId(), device.getDeviceProfileId()); if (deviceProfile != null) { - builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); + builder.setDeviceProfile(ProtoUtils.toProto(deviceProfile)); } else { log.warn("[{}] Failed to find device profile [{}] for device. ", device.getId(), device.getDeviceProfileId()); } @@ -465,13 +463,13 @@ public class DefaultTransportApiService implements TransportApiService { if (entityType.equals(EntityType.DEVICE_PROFILE)) { DeviceProfileId deviceProfileId = new DeviceProfileId(entityUuid); DeviceProfile deviceProfile = deviceProfileCache.find(deviceProfileId); - builder.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); + builder.setDeviceProfile(ProtoUtils.toProto(deviceProfile)); } else if (entityType.equals(EntityType.TENANT)) { TenantId tenantId = TenantId.fromUUID(entityUuid); TenantProfile tenantProfile = tenantProfileCache.get(tenantId); ApiUsageState state = apiUsageStateService.getApiUsageState(tenantId); - builder.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile))); - builder.setApiState(ByteString.copyFrom(dataDecodingEncodingService.encode(state))); + builder.setTenantProfile(ProtoUtils.toProto(tenantProfile)); + builder.setApiState(ProtoUtils.toProto(state)); } else { throw new RuntimeException("Invalid entity profile request: " + entityType); } @@ -490,7 +488,7 @@ public class DefaultTransportApiService implements TransportApiService { .setDeviceProfileIdMSB(deviceProfileId.getMostSignificantBits()) .setDeviceProfileIdLSB(deviceProfileId.getLeastSignificantBits()) .setDeviceTransportConfiguration(ByteString.copyFrom( - dataDecodingEncodingService.encode(device.getDeviceData().getTransportConfiguration()) + JacksonUtil.writeValueAsBytes(device.getDeviceData().getTransportConfiguration()) ))) .build(); } else { @@ -506,11 +504,10 @@ public class DefaultTransportApiService implements TransportApiService { return Futures.immediateFuture(TransportApiResponseMsg.newBuilder() .setDeviceCredentialsResponseMsg(TransportProtos.GetDeviceCredentialsResponseMsg.newBuilder() - .setDeviceCredentialsData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceCredentials)))) + .setDeviceCredentialsData(ProtoUtils.toProto(deviceCredentials))) .build()); } - private ListenableFuture handle(GetResourceRequestMsg requestMsg) { TenantId tenantId = TenantId.fromUUID(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); ResourceType resourceType = ResourceType.valueOf(requestMsg.getResourceType()); @@ -523,7 +520,7 @@ public class DefaultTransportApiService implements TransportApiService { } if (resource != null) { - builder.setResource(ByteString.copyFrom(dataDecodingEncodingService.encode(resource))); + builder.setResource(ProtoUtils.toProto(resource)); } return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setResourceResponseMsg(builder).build()); @@ -556,7 +553,7 @@ public class DefaultTransportApiService implements TransportApiService { builder.setDeviceInfo(ProtoUtils.toDeviceInfoProto(device)); DeviceProfile deviceProfile = deviceProfileCache.get(device.getTenantId(), device.getDeviceProfileId()); if (deviceProfile != null) { - builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); + builder.setDeviceProfile(ProtoUtils.toProto(deviceProfile)); } else { log.warn("[{}] Failed to find device profile [{}] for device. ", device.getId(), device.getDeviceProfileId()); } diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java index 7dd2547597..8e1ec146c9 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -95,7 +95,6 @@ import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UplinkMsg; import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import java.util.ArrayList; import java.util.List; @@ -124,9 +123,6 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { @Autowired protected EdgeEventService edgeEventService; - @Autowired - protected DataDecodingEncodingService dataDecodingEncodingService; - @Autowired protected TbClusterService clusterService; @@ -165,7 +161,8 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { private RuleChainId getEdgeRootRuleChainId() throws Exception { List edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/ruleChains?", - new TypeReference>() {}, new PageLink(100)).getData(); + new TypeReference>() { + }, new PageLink(100)).getData(); for (RuleChain edgeRuleChain : edgeRuleChains) { if (edgeRuleChain.isRoot()) { return edgeRuleChain.getId(); @@ -182,7 +179,8 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { doDelete("/api/edge/" + edge.getId().toString()) .andExpect(status().isOk()); edgeImitator.disconnect(); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } } private void installation() throws Exception { @@ -379,7 +377,8 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { Device device = doGet("/api/device/" + deviceUUID, Device.class); Assert.assertNotNull(device); List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", - new TypeReference>() {}, new PageLink(100)).getData(); + new TypeReference>() { + }, new PageLink(100)).getData(); Assert.assertTrue(edgeDevices.stream().map(DeviceInfo::getId).anyMatch(id -> id.equals(device.getId()))); testAutoGeneratedCodeByProtobuf(deviceUpdateMsg); @@ -398,7 +397,8 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { Asset asset = doGet("/api/asset/" + assetUUID, Asset.class); Assert.assertNotNull(asset); List edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/assets?", - new TypeReference>() {}, new PageLink(100)).getData(); + new TypeReference>() { + }, new PageLink(100)).getData(); Assert.assertTrue(edgeAssets.contains(asset)); testAutoGeneratedCodeByProtobuf(assetUpdateMsg); @@ -418,7 +418,8 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { RuleChain ruleChain = doGet("/api/ruleChain/" + ruleChainUUID, RuleChain.class); Assert.assertNotNull(ruleChain); List edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/ruleChains?", - new TypeReference>() {}, new PageLink(100)).getData(); + new TypeReference>() { + }, new PageLink(100)).getData(); Assert.assertTrue(edgeRuleChains.contains(ruleChain)); testAutoGeneratedCodeByProtobuf(ruleChainUpdateMsg); } diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java index ba0c770632..d44ac535f9 100644 --- a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java @@ -59,7 +59,6 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.edge.rpc.constructor.alarm.AlarmMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.constructor.alarm.AlarmMsgConstructorV1; import org.thingsboard.server.service.edge.rpc.constructor.alarm.AlarmMsgConstructorV2; @@ -477,9 +476,6 @@ public abstract class BaseEdgeProcessorTest { @MockBean protected DbCallbackExecutorService dbCallbackExecutorService; - - @MockBean - protected DataDecodingEncodingService dataDecodingEncodingService; protected EdgeId edgeId; protected TenantId tenantId; diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/AbstractDeviceProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/AbstractDeviceProcessorTest.java index 6f2249b173..491f10c145 100644 --- a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/AbstractDeviceProcessorTest.java +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/AbstractDeviceProcessorTest.java @@ -74,8 +74,6 @@ public abstract class AbstractDeviceProcessorTest extends BaseEdgeProcessorTest willReturn(device).given(deviceService).findDeviceById(tenantId, deviceId); willReturn(deviceProfile).given(deviceProfileService).findDeviceProfileById(tenantId, deviceProfileId); - willReturn(new byte[]{0x00}).given(dataDecodingEncodingService).encode(deviceProfileData); - } protected void updateDeviceProfileDefaultFields(long expectedDashboardIdMSB, long expectedDashboardIdLSB, diff --git a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java index 233839bb5f..203aa8306a 100644 --- a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java @@ -32,10 +32,9 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; @@ -64,8 +63,6 @@ public class DefaultTbClusterServiceTest { public static final String TRANSPORT = "transport"; - @MockBean - protected DataDecodingEncodingService encodingService; @MockBean protected TbDeviceProfileCache deviceProfileCache; @MockBean diff --git a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java index 43d8960f3b..5de5bac8e3 100644 --- a/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java @@ -47,7 +47,6 @@ import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; @@ -92,8 +91,6 @@ public class DefaultTransportApiServiceTest { @MockBean protected TbClusterService tbClusterService; @MockBean - protected DataDecodingEncodingService dataDecodingEncodingService; - @MockBean protected DeviceProvisionService deviceProvisionService; @MockBean protected ResourceService resourceService; diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java index f4ba0a9433..3ebb53afee 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java @@ -38,13 +38,13 @@ public class DeviceRedisCache extends RedisTbTransactionalCache TransportProtos.EntityUpdateMsg toEntityUpdateProto(T entity) { + var builder = TransportProtos.EntityUpdateMsg.newBuilder(); + if (entity instanceof Device) { + builder.setDevice(toProto((Device) entity)); + } else if (entity instanceof DeviceProfile) { + builder.setDeviceProfile(toProto((DeviceProfile) entity)); + } else if (entity instanceof Tenant) { + builder.setTenant(toProto((Tenant) entity)); + } else if (entity instanceof TenantProfile) { + builder.setTenantProfile(toProto((TenantProfile) entity)); + } else if (entity instanceof ApiUsageState) { + builder.setApiUsageState(toProto((ApiUsageState) entity)); + } else { + log.warn("[{}] entity does not support toProto serialization .", entity.getClass().getSimpleName()); + } + return builder.build(); + } + public static TransportProtos.DeviceInfoProto toDeviceInfoProto(Device device) throws JsonProcessingException { TransportProtos.DeviceInfoProto.Builder builder = TransportProtos.DeviceInfoProto.newBuilder() .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) @@ -657,36 +1040,29 @@ public class ProtoUtils { return builder.build(); } - private static T getEntityId(long msb, long lsb, Function entityId) { - if (msb != 0 || lsb != 0) { - return entityId.apply(new UUID(msb, lsb)); - } - return null; - } - - private static String toProtoString(String str) { - return str != null ? str : ""; + private static boolean isNotNull(Object obj) { + return obj != null; } - private static String fromProtoString(String str) { - return StringUtils.isNotEmpty(str) ? str : null; + private static T getEntityId(long msb, long lsb, Function entityId) { + return entityId.apply(new UUID(msb, lsb)); } private static Long getMsb(EntityId entityId) { - if (entityId != null) { + if (isNotNull(entityId)) { return entityId.getId().getMostSignificantBits(); } return 0L; } private static Long getLsb(EntityId entityId) { - if (entityId != null) { + if (isNotNull(entityId)) { return entityId.getId().getLeastSignificantBits(); } return 0L; } private static Long checkLong(Long l) { - return l != null ? l : 0; + return isNotNull(l) ? l : 0; } } diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index d8f5517e38..1d4c7c7f47 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -190,20 +190,20 @@ message DeviceProto { int64 deviceIdLSB = 4; int64 createdTime = 5; string deviceName = 6; - string deviceLabel = 7; + optional string deviceLabel = 7; string deviceType = 8; - string additionalInfo = 9; + optional string additionalInfo = 9; int64 deviceProfileIdMSB = 10; int64 deviceProfileIdLSB = 11; - int64 customerIdMSB = 12; - int64 customerIdLSB = 13; - bytes deviceData = 14; - int64 firmwareIdMSB = 15; - int64 firmwareIdLSB = 16; - int64 softwareIdMSB = 17; - int64 softwareIdLSB = 18; - int64 externalIdMSB = 19; - int64 externalIdLSB = 20; + optional int64 customerIdMSB = 12; + optional int64 customerIdLSB = 13; + optional bytes deviceData = 14; + optional int64 firmwareIdMSB = 15; + optional int64 firmwareIdLSB = 16; + optional int64 softwareIdMSB = 17; + optional int64 softwareIdLSB = 18; + optional int64 externalIdMSB = 19; + optional int64 externalIdLSB = 20; } message DeviceProfileProto { @@ -213,27 +213,108 @@ message DeviceProfileProto { int64 deviceProfileIdLSB = 4; int64 createdTime = 5; string name = 6; - string description = 7; - string image = 8; + optional string description = 7; + optional string image = 8; bool isDefault = 9; string type = 10; string transportType = 11; string provisionType = 12; - int64 defaultRuleChainIdMSB = 13; - int64 defaultRuleChainIdLSB = 14; - int64 defaultDashboardIdMSB = 15; - int64 defaultDashboardIdLSB = 16; - string defaultQueueName = 17; - bytes deviceProfileData = 18; - string provisionDeviceKey = 19; - int64 firmwareIdMSB = 20; - int64 firmwareIdLSB = 21; - int64 softwareIdMSB = 22; - int64 softwareIdLSB = 23; - int64 defaultEdgeRuleChainIdMSB = 24; - int64 defaultEdgeRuleChainIdLSB = 25; - int64 externalIdMSB = 26; - int64 externalIdLSB = 27; + optional int64 defaultRuleChainIdMSB = 13; + optional int64 defaultRuleChainIdLSB = 14; + optional int64 defaultDashboardIdMSB = 15; + optional int64 defaultDashboardIdLSB = 16; + optional string defaultQueueName = 17; + optional bytes deviceProfileData = 18; + optional string provisionDeviceKey = 19; + optional int64 firmwareIdMSB = 20; + optional int64 firmwareIdLSB = 21; + optional int64 softwareIdMSB = 22; + optional int64 softwareIdLSB = 23; + optional int64 defaultEdgeRuleChainIdMSB = 24; + optional int64 defaultEdgeRuleChainIdLSB = 25; + optional int64 externalIdMSB = 26; + optional int64 externalIdLSB = 27; +} + +message TenantProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 createdTime = 3; + string title = 4; + optional string region = 5; + int64 tenantProfileIdMSB = 6; + int64 tenantProfileIdLSB = 7; + optional string country = 8; + optional string state = 9; + optional string city = 10; + optional string address = 11; + optional string address2 = 12; + optional string zip = 13; + optional string phone = 14; + optional string email = 15; + optional string additionalInfo = 16; +} + +message TenantProfileProto { + int64 tenantProfileIdMSB = 1; + int64 tenantProfileIdLSB = 2; + int64 createdTime = 3; + string name = 4; + optional string description = 5; + bool isDefault = 6; + bool isolatedTbRuleEngine = 7; + optional bytes profileData = 8; +} + +message TbResourceProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 resourceIdMSB = 3; + int64 resourceIdLSB = 4; + int64 createdTime = 5; + string title = 6; + string resourceType = 7; + string resourceKey = 8; + string searchText = 9; + optional string etag = 10; + string fileName = 11; + optional string resourceDescriptor = 12; + optional int64 externalIdMSB = 13; + optional int64 externalIdLSB = 14; + optional bytes data = 15; + optional bytes preview = 16; +} + +message ApiUsageStateProto { + int64 tenantProfileIdMSB = 1; + int64 tenantProfileIdLSB = 2; + int64 apiUsageStateIdMSB = 3; + int64 apiUsageStateIdLSB = 4; + int64 createdTime = 5; + EntityTypeProto entityType = 6; + int64 entityIdMSB = 7; + int64 entityIdLSB = 8; + string transportState = 9; + string dbStorageState = 10; + string reExecState = 11; + string jsExecState = 12; + string tbelExecState = 13; + string emailExecState = 14; + string smsExecState = 15; + string alarmExecState = 16; +} + +message RepositorySettingsProto { + string repositoryUri = 1; + string authMethod = 2; + optional string username = 3; + optional string password = 4; + optional string privateKeyFileName = 5; + optional string privateKey = 6; + optional string privateKeyPassword = 7; + optional string defaultBranch = 8; + bool readOnly = 9; + bool showMergeCommits = 10; } /** @@ -295,7 +376,7 @@ message ValidateBasicMqttCredRequestMsg { message ValidateDeviceCredentialsResponseMsg { DeviceInfoProto deviceInfo = 1; string credentialsBody = 2; - bytes profileBody = 3; + DeviceProfileProto deviceProfile = 3; } message GetOrCreateDeviceFromGatewayRequestMsg { @@ -307,7 +388,7 @@ message GetOrCreateDeviceFromGatewayRequestMsg { message GetOrCreateDeviceFromGatewayResponseMsg { DeviceInfoProto deviceInfo = 1; - bytes profileBody = 2; + DeviceProfileProto deviceProfile = 2; TransportApiRequestErrorCode error = 3; } @@ -390,7 +471,7 @@ message GetResourceRequestMsg { } message GetResourceResponseMsg { - bytes resource = 1; + TbResourceProto resource = 1; } message ValidateDeviceLwM2MCredentialsRequestMsg { @@ -418,8 +499,11 @@ message GetDeviceProfileRequestMsg { message GetEntityProfileResponseMsg { string entityType = 1; - bytes data = 2; - bytes apiState = 3; + oneof data { + TenantProfileProto tenantProfile = 2; + DeviceProfileProto deviceProfile = 3; + } + ApiUsageStateProto apiState = 4; } message GetDeviceRequestMsg { @@ -430,6 +514,7 @@ message GetDeviceRequestMsg { message GetDeviceResponseMsg { int64 deviceProfileIdMSB = 1; int64 deviceProfileIdLSB = 2; + //Json bytes deviceTransportConfiguration = 3; } @@ -439,7 +524,7 @@ message GetDeviceCredentialsRequestMsg { } message GetDeviceCredentialsResponseMsg { - bytes deviceCredentialsData = 1; + DeviceCredentialsProto deviceCredentialsData = 1; } message GetSnmpDevicesRequestMsg { @@ -453,8 +538,13 @@ message GetSnmpDevicesResponseMsg { } message EntityUpdateMsg { - string entityType = 1; - bytes data = 2; + oneof entityUpdate { + TenantProto tenant = 1; + TenantProfileProto tenantProfile = 2; + DeviceProto device = 3; + DeviceProfileProto deviceProfile = 4; + ApiUsageStateProto apiUsageState = 5; + } } message EntityDeleteMsg { @@ -1242,7 +1332,7 @@ message ToVersionControlServiceMsg { int64 tenantIdLSB = 3; int64 requestIdMSB = 4; int64 requestIdLSB = 5; - bytes vcSettings = 6; + RepositorySettingsProto vcSettings = 6; GenericRepositoryRequestMsg initRepositoryRequest = 7; GenericRepositoryRequestMsg testRepositoryRequest = 8; GenericRepositoryRequestMsg clearRepositoryRequest = 9; @@ -1326,21 +1416,17 @@ message ToCoreMsg { message ToCoreNotificationMsg { LocalSubscriptionServiceMsgProto toLocalSubscriptionServiceMsg = 1; FromDeviceRPCResponseProto fromDeviceRpcResponse = 2; - bytes componentLifecycleMsg = 3 [deprecated = true]; - bytes edgeEventUpdateMsg = 4 [deprecated = true]; - QueueUpdateMsg queueUpdateMsg = 5; - QueueDeleteMsg queueDeleteMsg = 6; - VersionControlResponseMsg vcResponseMsg = 7; - bytes toEdgeSyncRequestMsg = 8 [deprecated = true]; - bytes fromEdgeSyncResponseMsg = 9 [deprecated = true]; - SubscriptionMgrMsgProto toSubscriptionMgrMsg = 10; - NotificationRuleProcessorMsg notificationRuleProcessorMsg = 11; - ComponentLifecycleMsgProto componentLifecycle = 12; - CoreStartupMsg coreStartupMsg = 13; - EdgeEventUpdateMsgProto edgeEventUpdate = 14; - ToEdgeSyncRequestMsgProto toEdgeSyncRequest = 15; - FromEdgeSyncResponseMsgProto fromEdgeSyncResponse = 16; - ResourceCacheInvalidateMsg resourceCacheInvalidateMsg = 17; + QueueUpdateMsg queueUpdateMsg = 3; + QueueDeleteMsg queueDeleteMsg = 4; + VersionControlResponseMsg vcResponseMsg = 5; + SubscriptionMgrMsgProto toSubscriptionMgrMsg = 6; + NotificationRuleProcessorMsg notificationRuleProcessorMsg = 7; + ComponentLifecycleMsgProto componentLifecycle = 8; + CoreStartupMsg coreStartupMsg = 9; + EdgeEventUpdateMsgProto edgeEventUpdate = 10; + ToEdgeSyncRequestMsgProto toEdgeSyncRequest = 11; + FromEdgeSyncResponseMsgProto fromEdgeSyncResponse = 12; + ResourceCacheInvalidateMsg resourceCacheInvalidateMsg = 13; } /* Messages that are handled by ThingsBoard RuleEngine Service */ @@ -1353,7 +1439,6 @@ message ToRuleEngineMsg { } message ToRuleEngineNotificationMsg { - bytes componentLifecycleMsg = 1 [deprecated = true]; FromDeviceRPCResponseProto fromDeviceRpcResponse = 2; QueueUpdateMsg queueUpdateMsg = 3; QueueDeleteMsg queueDeleteMsg = 4; @@ -1416,6 +1501,7 @@ message NotificationSchedulerServiceMsg { } message NotificationRuleProcessorMsg { + //Json bytes trigger = 1; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java b/common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java index 8ad5816b77..3208a8cf0d 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java @@ -20,16 +20,16 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTrigger; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import java.util.UUID; @@ -43,7 +43,6 @@ public class RemoteNotificationRuleProcessor implements NotificationRuleProcesso private final TbQueueProducerProvider producerProvider; private final TopicService topicService; private final PartitionService partitionService; - private final DataDecodingEncodingService encodingService; @Override public void process(NotificationRuleTrigger trigger) { @@ -54,7 +53,7 @@ public class RemoteNotificationRuleProcessor implements NotificationRuleProcesso log.debug("Submitting notification rule trigger: {}", trigger); TransportProtos.NotificationRuleProcessorMsg.Builder msg = TransportProtos.NotificationRuleProcessorMsg.newBuilder() - .setTrigger(ByteString.copyFrom(encodingService.encode(trigger))); + .setTrigger(ByteString.copyFrom(JacksonUtil.writeValueAsBytes(trigger))); partitionService.getAllServiceIds(ServiceType.TB_CORE).stream().findAny().ifPresent(serviceId -> { TopicPartitionInfo tpi = topicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/DataDecodingEncodingService.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/DataDecodingEncodingService.java deleted file mode 100644 index 2ac96142d9..0000000000 --- a/common/queue/src/main/java/org/thingsboard/server/queue/util/DataDecodingEncodingService.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.queue.util; - -import java.util.Optional; - -public interface DataDecodingEncodingService { - - Optional decode(byte[] byteArray); - - byte[] encode(T msq); - -} - diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java deleted file mode 100644 index 204502068c..0000000000 --- a/common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.queue.util; - -import lombok.extern.slf4j.Slf4j; -import org.nustaq.serialization.FSTConfiguration; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.FSTUtils; -import org.thingsboard.server.common.data.FstStatsService; - -import java.util.Optional; - -@Slf4j -@Service -public class ProtoWithFSTService implements DataDecodingEncodingService { - - @Autowired - private FstStatsService fstStatsService; - - public static final FSTConfiguration CONFIG = FSTConfiguration.createDefaultConfiguration(); - - @Override - public Optional decode(byte[] byteArray) { - try { - long startTime = System.nanoTime(); - Optional optional = Optional.ofNullable(FSTUtils.decode(byteArray)); - optional.ifPresent(obj -> { - fstStatsService.recordDecodeTime(obj.getClass(), startTime); - fstStatsService.incrementDecode(obj.getClass()); - }); - return optional; - } catch (IllegalArgumentException e) { - log.error("Error during deserialization message, [{}]", e.getMessage()); - return Optional.empty(); - } - } - - - @Override - public byte[] encode(T msq) { - long startTime = System.nanoTime(); - var bytes = FSTUtils.encode(msq); - fstStatsService.recordEncodeTime(msq.getClass(), startTime); - fstStatsService.incrementEncode(msq.getClass()); - return bytes; - } - - -} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java index 22cfaab8c7..0fdd480152 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.snmp.service; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration; @@ -24,8 +25,8 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import java.util.UUID; @@ -35,7 +36,6 @@ import java.util.UUID; @RequiredArgsConstructor public class ProtoTransportEntityService { private final TransportService transportService; - private final DataDecodingEncodingService dataDecodingEncodingService; public Device getDeviceById(DeviceId id) { TransportProtos.GetDeviceResponseMsg deviceProto = transportService.getDevice(TransportProtos.GetDeviceRequestMsg.newBuilder() @@ -55,9 +55,8 @@ public class ProtoTransportEntityService { device.setId(id); device.setDeviceProfileId(deviceProfileId); - DeviceTransportConfiguration deviceTransportConfiguration = (DeviceTransportConfiguration) dataDecodingEncodingService.decode( - deviceProto.getDeviceTransportConfiguration().toByteArray() - ).orElseThrow(() -> new IllegalStateException("Can't find device transport configuration")); + DeviceTransportConfiguration deviceTransportConfiguration = JacksonUtil.fromBytes( + deviceProto.getDeviceTransportConfiguration().toByteArray(), DeviceTransportConfiguration.class); DeviceData deviceData = new DeviceData(); deviceData.setTransportConfiguration(deviceTransportConfiguration); @@ -74,8 +73,11 @@ public class ProtoTransportEntityService { .build() ); - return (DeviceCredentials) dataDecodingEncodingService.decode(deviceCredentialsResponse.getDeviceCredentialsData().toByteArray()) - .orElseThrow(() -> new IllegalArgumentException("Device credentials not found")); + if (deviceCredentialsResponse.hasDeviceCredentialsData()) { + return ProtoUtils.fromProto(deviceCredentialsResponse.getDeviceCredentialsData()); + } else { + throw new IllegalArgumentException("Device credentials not found"); + } } public TransportProtos.GetSnmpDevicesResponseMsg getSnmpDevicesIds(int page, int pageSize) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportDeviceProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportDeviceProfileCache.java index dcef95c778..0d518f6d01 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportDeviceProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportDeviceProfileCache.java @@ -15,19 +15,19 @@ */ package org.thingsboard.server.common.transport; -import com.google.protobuf.ByteString; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.gen.transport.TransportProtos; public interface TransportDeviceProfileCache { - DeviceProfile getOrCreate(DeviceProfileId id, ByteString profileBody); + DeviceProfile getOrCreate(DeviceProfileId id, TransportProtos.DeviceProfileProto proto); DeviceProfile get(DeviceProfileId id); void put(DeviceProfile profile); - DeviceProfile put(ByteString profileBody); + DeviceProfile put(TransportProtos.DeviceProfileProto proto); void evict(DeviceProfileId id); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java index 97fcd925ce..052e020108 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java @@ -15,11 +15,11 @@ */ package org.thingsboard.server.common.transport; -import com.google.protobuf.ByteString; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; +import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Set; @@ -27,7 +27,7 @@ public interface TransportTenantProfileCache { TenantProfile get(TenantId tenantId); - TenantProfileUpdateResult put(ByteString profileBody); + TenantProfileUpdateResult put(TransportProtos.TenantProfileProto proto); boolean put(TenantId tenantId, TenantProfileId profileId); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java index 15ac6ba469..2f53c59bd0 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.common.transport.service; -import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; @@ -25,11 +24,10 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbTransportComponent; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; @@ -42,7 +40,6 @@ public class DefaultTransportDeviceProfileCache implements TransportDeviceProfil private final Lock deviceProfileFetchLock = new ReentrantLock(); private final ConcurrentMap deviceProfiles = new ConcurrentHashMap<>(); - private final DataDecodingEncodingService dataDecodingEncodingService; private TransportService transportService; @@ -52,19 +49,12 @@ public class DefaultTransportDeviceProfileCache implements TransportDeviceProfil this.transportService = transportService; } - public DefaultTransportDeviceProfileCache(DataDecodingEncodingService dataDecodingEncodingService) { - this.dataDecodingEncodingService = dataDecodingEncodingService; - } - @Override - public DeviceProfile getOrCreate(DeviceProfileId id, ByteString profileBody) { + public DeviceProfile getOrCreate(DeviceProfileId id, TransportProtos.DeviceProfileProto proto) { DeviceProfile profile = deviceProfiles.get(id); if (profile == null) { - Optional deviceProfile = dataDecodingEncodingService.decode(profileBody.toByteArray()); - if (deviceProfile.isPresent()) { - profile = deviceProfile.get(); - deviceProfiles.put(id, profile); - } + profile = ProtoUtils.fromProto(proto); + deviceProfiles.put(id, profile); } return profile; } @@ -80,14 +70,10 @@ public class DefaultTransportDeviceProfileCache implements TransportDeviceProfil } @Override - public DeviceProfile put(ByteString profileBody) { - Optional deviceProfile = dataDecodingEncodingService.decode(profileBody.toByteArray()); - if (deviceProfile.isPresent()) { - put(deviceProfile.get()); - return deviceProfile.get(); - } else { - return null; - } + public DeviceProfile put(TransportProtos.DeviceProfileProto proto) { + DeviceProfile deviceProfile = ProtoUtils.fromProto(proto); + put(deviceProfile); + return deviceProfile; } @Override @@ -107,14 +93,8 @@ public class DefaultTransportDeviceProfileCache implements TransportDeviceProfil .setEntityIdLSB(id.getId().getLeastSignificantBits()) .build(); TransportProtos.GetEntityProfileResponseMsg entityProfileMsg = transportService.getEntityProfile(msg); - Optional profileOpt = dataDecodingEncodingService.decode(entityProfileMsg.getData().toByteArray()); - if (profileOpt.isPresent()) { - profile = profileOpt.get(); - this.put(profile); - } else { - log.warn("[{}] Can't find device profile: {}", id, entityProfileMsg.getData()); - throw new RuntimeException("Can't find device profile!"); - } + profile = ProtoUtils.fromProto(entityProfileMsg.getDeviceProfile()); + this.put(profile); } finally { deviceProfileFetchLock.unlock(); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportResourceCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportResourceCache.java index 2db0cdb9b8..c0e189c755 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportResourceCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportResourceCache.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.transport.service; import lombok.Data; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -24,8 +25,8 @@ import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.transport.TransportResourceCache; import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.Optional; @@ -39,19 +40,15 @@ import java.util.concurrent.locks.ReentrantLock; @Slf4j @Component @TbTransportComponent +@RequiredArgsConstructor public class DefaultTransportResourceCache implements TransportResourceCache { private final Lock resourceFetchLock = new ReentrantLock(); private final ConcurrentMap resources = new ConcurrentHashMap<>(); private final Set keys = ConcurrentHashMap.newKeySet(); - private final DataDecodingEncodingService dataDecodingEncodingService; + @Lazy private final TransportService transportService; - public DefaultTransportResourceCache(DataDecodingEncodingService dataDecodingEncodingService, @Lazy TransportService transportService) { - this.dataDecodingEncodingService = dataDecodingEncodingService; - this.transportService = transportService; - } - @Override public Optional get(TenantId tenantId, ResourceType resourceType, String resourceKey) { ResourceCompositeKey compositeKey = new ResourceCompositeKey(tenantId, resourceType, resourceKey); @@ -92,9 +89,8 @@ public class DefaultTransportResourceCache implements TransportResourceCache { .setResourceKey(compositeKey.resourceKey); TransportProtos.GetResourceResponseMsg responseMsg = transportService.getResource(builder.build()); - Optional optionalResource = dataDecodingEncodingService.decode(responseMsg.getResource().toByteArray()); - if (optionalResource.isPresent()) { - TbResource resource = optionalResource.get(); + if (responseMsg.hasResource()) { + TbResource resource = ProtoUtils.fromProto(responseMsg.getResource()); resources.put(new ResourceCompositeKey(resource.getTenantId(), resource.getResourceType(), resource.getResourceKey()), resource); return resource; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index c4840b2402..383c909d13 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -20,7 +20,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -50,9 +49,9 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.limit.LimitedApi; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; -import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -80,6 +79,7 @@ import org.thingsboard.server.common.transport.limits.EntityLimitKey; import org.thingsboard.server.common.transport.limits.EntityLimitsCache; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.common.transport.util.JsonUtils; +import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; @@ -96,14 +96,13 @@ import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.common.AsyncCallbackTemplate; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.AfterStartUp; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; @@ -163,7 +162,6 @@ public class DefaultTransportService implements TransportService { @Value("${transport.stats.enabled:false}") private boolean statsEnabled; - @Autowired @Lazy private TbApiUsageReportClient apiUsageClient; @@ -181,7 +179,6 @@ public class DefaultTransportService implements TransportService { private final TransportTenantProfileCache tenantProfileCache; private final TransportRateLimitService rateLimitService; - private final DataDecodingEncodingService dataDecodingEncodingService; private final SchedulerComponent scheduler; private final ApplicationEventPublisher eventPublisher; private final TransportResourceCache transportResourceCache; @@ -216,7 +213,7 @@ public class DefaultTransportService implements TransportService { TransportDeviceProfileCache deviceProfileCache, TransportTenantProfileCache tenantProfileCache, TransportRateLimitService rateLimitService, - DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler, TransportResourceCache transportResourceCache, + SchedulerComponent scheduler, TransportResourceCache transportResourceCache, ApplicationEventPublisher eventPublisher, NotificationRuleProcessor notificationRuleProcessor, EntityLimitsCache entityLimitsCache) { this.partitionService = partitionService; @@ -228,7 +225,6 @@ public class DefaultTransportService implements TransportService { this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; this.rateLimitService = rateLimitService; - this.dataDecodingEncodingService = dataDecodingEncodingService; this.scheduler = scheduler; this.transportResourceCache = transportResourceCache; this.eventPublisher = eventPublisher; @@ -430,9 +426,8 @@ public class DefaultTransportService implements TransportService { result.credentials(msg.getCredentialsBody()); TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo()); result.deviceInfo(tdi); - ByteString profileBody = msg.getProfileBody(); - if (!profileBody.isEmpty()) { - DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody); + if (msg.hasDeviceProfile()) { + DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), msg.getDeviceProfile()); result.deviceProfile(profile); } } @@ -464,9 +459,8 @@ public class DefaultTransportService implements TransportService { result.credentials(msg.getCredentialsBody()); TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo()); result.deviceInfo(tdi); - ByteString profileBody = msg.getProfileBody(); - if (!profileBody.isEmpty()) { - DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody); + if (msg.hasDeviceProfile()) { + DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), msg.getDeviceProfile()); if (transportType != DeviceTransportType.DEFAULT && profile != null && profile.getTransportType() != DeviceTransportType.DEFAULT && profile.getTransportType() != transportType) { log.debug("[{}] Device profile [{}] has different transport type: {}, expected: {}", tdi.getDeviceId(), tdi.getDeviceProfileId(), profile.getTransportType(), transportType); @@ -480,7 +474,6 @@ public class DefaultTransportService implements TransportService { AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); } - @Override public void process(TenantId tenantId, TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback callback) { TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(requestMsg).build()); @@ -495,9 +488,8 @@ public class DefaultTransportService implements TransportService { if (msg.hasDeviceInfo()) { TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo()); result.deviceInfo(tdi); - ByteString profileBody = msg.getProfileBody(); - if (!profileBody.isEmpty()) { - result.deviceProfile(deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody)); + if (msg.hasDeviceProfile()) { + result.deviceProfile(deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), msg.getDeviceProfile())); } } else if (TransportProtos.TransportApiRequestErrorCode.ENTITY_LIMIT.equals(msg.getError())) { entityLimitsCache.put(key, true); @@ -987,37 +979,7 @@ public class DefaultTransportService implements TransportService { } else { log.trace("Processing broadcast notification: {}", toSessionMsg); if (toSessionMsg.hasEntityUpdateMsg()) { - TransportProtos.EntityUpdateMsg msg = toSessionMsg.getEntityUpdateMsg(); - EntityType entityType = EntityType.valueOf(msg.getEntityType()); - if (EntityType.DEVICE_PROFILE.equals(entityType)) { - DeviceProfile deviceProfile = deviceProfileCache.put(msg.getData()); - if (deviceProfile != null) { - log.debug("On device profile update: {}", deviceProfile); - onProfileUpdate(deviceProfile); - } - } else if (EntityType.TENANT_PROFILE.equals(entityType)) { - rateLimitService.update(tenantProfileCache.put(msg.getData())); - } else if (EntityType.TENANT.equals(entityType)) { - Optional profileOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); - if (profileOpt.isPresent()) { - Tenant tenant = profileOpt.get(); - partitionService.removeTenant(tenant.getId()); - boolean updated = tenantProfileCache.put(tenant.getId(), tenant.getTenantProfileId()); - if (updated) { - rateLimitService.update(tenant.getId()); - } - } - } else if (EntityType.API_USAGE_STATE.equals(entityType)) { - Optional stateOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); - if (stateOpt.isPresent()) { - ApiUsageState apiUsageState = stateOpt.get(); - rateLimitService.update(apiUsageState.getTenantId(), apiUsageState.isTransportEnabled()); - //TODO: if transport is disabled, we should close all sessions and not to check credentials. - } - } else if (EntityType.DEVICE.equals(entityType)) { - Optional deviceOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); - deviceOpt.ifPresent(this::onDeviceUpdate); - } + onEntityUpdate(toSessionMsg.getEntityUpdateMsg()); } else if (toSessionMsg.hasEntityDeleteMsg()) { TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); EntityType entityType = EntityType.valueOf(msg.getEntityType()); @@ -1086,6 +1048,37 @@ public class DefaultTransportService implements TransportService { eventPublisher.publishEvent(new DeviceProfileUpdatedEvent(deviceProfile)); } + private void onEntityUpdate(TransportProtos.EntityUpdateMsg msg) { + switch (msg.getEntityUpdateCase()) { + case DEVICEPROFILE: + DeviceProfile deviceProfile = deviceProfileCache.put(msg.getDeviceProfile()); + log.debug("On device profile update: {}", deviceProfile); + onProfileUpdate(deviceProfile); + break; + case TENANTPROFILE: + rateLimitService.update(tenantProfileCache.put(msg.getTenantProfile())); + break; + case TENANT: + Tenant tenant = ProtoUtils.fromProto(msg.getTenant()); + partitionService.removeTenant(tenant.getId()); + boolean updated = tenantProfileCache.put(tenant.getId(), tenant.getTenantProfileId()); + if (updated) { + rateLimitService.update(tenant.getId()); + } + break; + case APIUSAGESTATE: + ApiUsageState apiUsageState = ProtoUtils.fromProto(msg.getApiUsageState()); + rateLimitService.update(apiUsageState.getTenantId(), apiUsageState.isTransportEnabled()); + //TODO: if transport is disabled, we should close all sessions and not to check credentials. + break; + case DEVICE: + onDeviceUpdate(ProtoUtils.fromProto(msg.getDevice())); + break; + default: + log.warn("UNKNOWN entity update type: [{}]", msg.getEntityUpdateCase()); + } + } + private void onDeviceUpdate(Device device) { long deviceIdMSB = device.getId().getId().getMostSignificantBits(); long deviceIdLSB = device.getId().getId().getLeastSignificantBits(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java index 12f89f29e6..1a8165c860 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.common.transport.service; -import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; @@ -29,12 +28,11 @@ import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; +import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.Collections; -import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -50,7 +48,6 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil private final ConcurrentMap profiles = new ConcurrentHashMap<>(); private final ConcurrentMap tenantIds = new ConcurrentHashMap<>(); private final ConcurrentMap> tenantProfileIds = new ConcurrentHashMap<>(); - private final DataDecodingEncodingService dataDecodingEncodingService; private TransportRateLimitService rateLimitService; private TransportService transportService; @@ -67,28 +64,18 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil this.transportService = transportService; } - public DefaultTransportTenantProfileCache(DataDecodingEncodingService dataDecodingEncodingService) { - this.dataDecodingEncodingService = dataDecodingEncodingService; - } - @Override public TenantProfile get(TenantId tenantId) { return getTenantProfile(tenantId); } @Override - public TenantProfileUpdateResult put(ByteString profileBody) { - Optional profileOpt = dataDecodingEncodingService.decode(profileBody.toByteArray()); - if (profileOpt.isPresent()) { - TenantProfile newProfile = profileOpt.get(); - log.trace("[{}] put: {}", newProfile.getId(), newProfile); - profiles.put(newProfile.getId(), newProfile); - Set affectedTenants = tenantProfileIds.get(newProfile.getId()); - return new TenantProfileUpdateResult(newProfile, affectedTenants != null ? affectedTenants : Collections.emptySet()); - } else { - log.warn("Failed to decode profile: {}", profileBody.toString()); - return new TenantProfileUpdateResult(null, Collections.emptySet()); - } + public TenantProfileUpdateResult put(TransportProtos.TenantProfileProto proto) { + TenantProfile profile = ProtoUtils.fromProto(proto); + log.trace("[{}] put: {}", profile.getId(), profile); + profiles.put(profile.getId(), profile); + Set affectedTenants = tenantProfileIds.get(profile.getId()); + return new TenantProfileUpdateResult(profile, affectedTenants != null ? affectedTenants : Collections.emptySet()); } @Override @@ -135,23 +122,17 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil .setEntityIdLSB(tenantId.getId().getLeastSignificantBits()) .build(); TransportProtos.GetEntityProfileResponseMsg entityProfileMsg = transportService.getEntityProfile(msg); - Optional profileOpt = dataDecodingEncodingService.decode(entityProfileMsg.getData().toByteArray()); - if (profileOpt.isPresent()) { - profile = profileOpt.get(); - TenantProfile existingProfile = profiles.get(profile.getId()); - if (existingProfile != null) { - profile = existingProfile; - } else { - profiles.put(profile.getId(), profile); - } - tenantProfileIds.computeIfAbsent(profile.getId(), id -> ConcurrentHashMap.newKeySet()).add(tenantId); - tenantIds.put(tenantId, profile.getId()); + profile = ProtoUtils.fromProto(entityProfileMsg.getTenantProfile()); + TenantProfile existingProfile = profiles.get(profile.getId()); + if (existingProfile != null) { + profile = existingProfile; } else { - log.warn("[{}] Can't decode tenant profile: {}", tenantId, entityProfileMsg.getData()); - throw new RuntimeException("Can't decode tenant profile!"); + profiles.put(profile.getId(), profile); } - Optional apiStateOpt = dataDecodingEncodingService.decode(entityProfileMsg.getApiState().toByteArray()); - apiStateOpt.ifPresent(apiUsageState -> rateLimitService.update(tenantId, apiUsageState.isTransportEnabled())); + tenantProfileIds.computeIfAbsent(profile.getId(), id -> ConcurrentHashMap.newKeySet()).add(tenantId); + tenantIds.put(tenantId, profile.getId()); + ApiUsageState apiUsageState = ProtoUtils.fromProto(entityProfileMsg.getApiState()); + rateLimitService.update(tenantId, apiUsageState.isTransportEnabled()); } } finally { tenantProfileFetchLock.unlock(); diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java index e98d49b1dc..1a1654af66 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java @@ -36,12 +36,12 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; -import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; import org.thingsboard.server.common.data.util.CollectionsUtil; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AddMsg; import org.thingsboard.server.gen.transport.TransportProtos.BranchInfoProto; @@ -67,13 +67,12 @@ import org.thingsboard.server.gen.transport.TransportProtos.VersionedEntityInfoP import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; +import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbVersionControlQueueFactory; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbVersionControlComponent; import javax.annotation.PostConstruct; @@ -108,7 +107,6 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe private final PartitionService partitionService; private final TbQueueProducerProvider producerProvider; private final TbVersionControlQueueFactory queueFactory; - private final DataDecodingEncodingService encodingService; private final GitRepositoryService vcService; private final TopicService topicService; @@ -196,7 +194,7 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe } for (TbProtoQueueMsg msgWrapper : msgs) { ToVersionControlServiceMsg msg = msgWrapper.getValue(); - var ctx = new VersionControlRequestCtx(msg, msg.hasClearRepositoryRequest() ? null : getEntitiesVersionControlSettings(msg)); + var ctx = new VersionControlRequestCtx(msg, msg.hasClearRepositoryRequest() ? null : ProtoUtils.fromProto(msg.getVcSettings())); long startTs = System.currentTimeMillis(); log.trace("[{}][{}] RECEIVED task: {}", ctx.getTenantId(), ctx.getRequestId(), msg); int threadIdx = Math.abs(ctx.getTenantId().hashCode() % ioPoolSize); @@ -542,16 +540,6 @@ public class DefaultClusterVersionControlService extends TbApplicationEventListe producer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null); } - private RepositorySettings getEntitiesVersionControlSettings(ToVersionControlServiceMsg msg) { - Optional settingsOpt = encodingService.decode(msg.getVcSettings().toByteArray()); - if (settingsOpt.isPresent()) { - return settingsOpt.get(); - } else { - log.warn("Failed to parse VC settings: {}", msg.getVcSettings()); - throw new RuntimeException("Failed to parse vc settings!"); - } - } - private String getRelativePath(EntityType entityType, String entityId) { String path = entityType.name().toLowerCase(); if (entityId != null) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java index a61762c9aa..0a8aa9a7fe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java @@ -37,13 +37,13 @@ public class DeviceProfileRedisCache extends RedisTbTransactionalCache() { @Override public byte[] serialize(DeviceProfile deviceProfile) throws SerializationException { - return ProtoUtils.toDeviceProfileProto(deviceProfile).toByteArray(); + return ProtoUtils.toProto(deviceProfile).toByteArray(); } @Override public DeviceProfile deserialize(DeviceProfileCacheKey key, byte[] bytes) throws SerializationException { try { - return ProtoUtils.fromDeviceProfileProto(TransportProtos.DeviceProfileProto.parseFrom(bytes)); + return ProtoUtils.fromProto(TransportProtos.DeviceProfileProto.parseFrom(bytes)); } catch (InvalidProtocolBufferException e) { throw new SerializationException(e.getMessage()); } From ec19c2a718bbfaee70eea66a719b57aedd5658b8 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 21 Dec 2023 10:56:29 +0100 Subject: [PATCH 068/209] minor proto fix --- .../java/org/thingsboard/server/common/util/ProtoUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java index a39052de08..3ee6751370 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java @@ -877,7 +877,7 @@ public class ProtoUtils { .setApiUsageStateIdMSB(getMsb(apiUsageState.getId())) .setApiUsageStateIdLSB(getLsb(apiUsageState.getId())) .setCreatedTime(apiUsageState.getCreatedTime()) - .setEntityType(TransportProtos.EntityTypeProto.forNumber(apiUsageState.getEntityId().getEntityType().ordinal())) + .setEntityType(toProto(apiUsageState.getEntityId().getEntityType())) .setEntityIdMSB(getMsb(apiUsageState.getEntityId())) .setEntityIdLSB(getLsb(apiUsageState.getEntityId())) .setTransportState(apiUsageState.getTransportState().name()) From 74daec474f96e6dd0b804d9e0ef1e1d1fb14a9df Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 25 Dec 2023 22:09:39 +0200 Subject: [PATCH 069/209] lwm2ml: ver 3.9.1 - start --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 51 +-- .../DefaultLwM2mAttributeParserTest.java | 147 +++++++ .../DefaultLwM2mLinkParserTest.java | 76 ++++ .../lwm2m/attributes/LwM2mAttributesTest.java | 87 ++++ .../transport/lwm2m/client/FwLwM2MDevice.java | 20 +- .../lwm2m/client/LwM2MTestClient.java | 243 +++++++---- .../client/LwM2mBinaryAppDataContainer.java | 5 +- .../transport/lwm2m/client/LwM2mLocation.java | 3 +- .../lwm2m/client/LwM2mTemperatureSensor.java | 8 +- .../transport/lwm2m/client/Lwm2mServer.java | 11 +- .../lwm2m/client/SimpleLwM2MDevice.java | 17 +- .../transport/lwm2m/client/SwLwM2MDevice.java | 19 +- .../ota/sql/OtaLwM2MIntegrationTest.java | 12 +- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 7 +- .../sql/RpcLwm2mIntegrationCreateTest.java | 4 +- .../sql/RpcLwm2mIntegrationDiscoverTest.java | 17 +- .../sql/RpcLwm2mIntegrationObserveTest.java | 4 +- .../rpc/sql/RpcLwm2mIntegrationReadTest.java | 36 +- ...pcLwm2mIntegrationWriteAttributesTest.java | 8 +- .../rpc/sql/RpcLwm2mIntegrationWriteTest.java | 67 +-- .../AbstractSecurityLwM2MIntegrationTest.java | 35 +- .../sql/NoSecLwM2MIntegrationTest.java | 18 +- .../security/sql/PskLwm2mIntegrationTest.java | 9 +- .../security/sql/RpkLwM2MIntegrationTest.java | 10 +- .../sql/X509_NoTrustLwM2MIntegrationTest.java | 6 +- .../sql/X509_TrustLwM2MIntegrationTest.java | 6 +- .../bootstrap/LwM2MServerSecurityConfig.java | 2 +- .../common/data/util/TbDDFFileParser.java | 6 +- .../data/util/TbDefaultDDFFileValidator.java | 129 ++++++ .../LwM2MTransportBootstrapService.java | 149 +++++-- .../LwM2mDefaultBootstrapSessionManager.java | 35 +- ...LwM2MBootstrapConfigStoreTaskProvider.java | 13 +- .../store/LwM2MBootstrapSecurityStore.java | 8 +- .../store/LwM2MConfigurationChecker.java | 22 +- .../LwM2MInMemoryBootstrapConfigStore.java | 6 +- ...LwM2mCredentialsSecurityInfoValidator.java | 4 +- .../lwm2m/secure/TbLwM2MAuthorizer.java | 38 +- .../AbstractLwM2mTransportResource.java | 3 +- .../server/DefaultLwM2mTransportService.java | 141 +++++-- .../lwm2m/server/LwM2MNetworkConfig.java | 6 +- .../lwm2m/server/LwM2mServerListener.java | 12 +- .../lwm2m/server/LwM2mTransportContext.java | 2 +- .../server/LwM2mVersionedModelProvider.java | 10 +- .../lwm2m/server/client/LwM2mClient.java | 21 +- .../DefaultLwM2mDownlinkMsgHandler.java | 88 ++-- .../downlink/LwM2mDownlinkMsgHandler.java | 8 +- .../rpc/DefaultLwM2MRpcRequestHandler.java | 115 +++--- .../lwm2m/server/rpc/RpcDiscoverCallback.java | 2 +- .../server/store/TbInMemorySecurityStore.java | 16 +- .../store/TbLwM2mRedisRegistrationStore.java | 99 +++-- .../store/TbLwM2mRedisSecurityStore.java | 22 +- .../server/store/TbLwM2mSecurityStore.java | 8 +- .../server/store/TbLwM2mStoreFactory.java | 6 +- .../server/store/util/LwM2MClientSerDes.java | 383 +++++++++--------- .../uplink/DefaultLwM2mUplinkMsgHandler.java | 4 +- .../lwm2m/utils/LwM2MTransportUtil.java | 133 +++--- .../validator/DeviceProfileDataValidator.java | 9 +- pom.xml | 10 +- .../lwm2m/lwm2m-profile-config.models.ts | 2 +- 59 files changed, 1629 insertions(+), 809 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mAttributeParserTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mLinkParserTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/util/TbDefaultDDFFileValidator.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 7d2bc51ce6..a160ba81f8 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.eclipse.californium.elements.config.Configuration; -import org.eclipse.leshan.client.californium.LeshanClient; +import org.eclipse.leshan.client.LeshanClient; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.ResponseCode; import org.junit.After; @@ -88,7 +88,6 @@ import static org.awaitility.Awaitility.await; import static org.eclipse.californium.core.config.CoapConfig.COAP_PORT; import static org.eclipse.californium.core.config.CoapConfig.COAP_SECURE_PORT; import static org.eclipse.leshan.client.object.Security.noSec; -import static org.eclipse.leshan.client.object.Security.noSecBootstap; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED; @@ -121,8 +120,11 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public static final String host = "localhost"; public static final String hostBs = "localhost"; - public static final int shortServerId = 123; - public static final int shortServerIdBs = 111; + public static final Integer shortServerId = 123; + public static final Integer shortServerIdBs0 = 0; + public static final int serverId = 1; + public static final int serverIdBs = 0; + public static final String COAP = "coap://"; public static final String COAPS = "coaps://"; public static final String URI = COAP + host + ":" + port; @@ -132,7 +134,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public static final Configuration COAP_CONFIG = new Configuration().set(COAP_PORT, port).set(COAP_SECURE_PORT, securityPort); public static Configuration COAP_CONFIG_BS = new Configuration().set(COAP_PORT, portBs).set(COAP_SECURE_PORT, securityPortBs); public static final Security SECURITY_NO_SEC = noSec(URI, shortServerId); - public static final Security SECURITY_NO_SEC_BS = noSecBootstap(URI_BS); protected final String OBSERVE_ATTRIBUTES_WITHOUT_PARAMS = " {\n" + @@ -235,8 +236,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte getWsClient().waitForReply(); getWsClient().registerWaitForUpdate(); - createNewClient(security, coapConfig, false, endpoint, false, null); - awaitObserveReadAll(0, false, device.getId().getId().toString()); + createNewClient(security, null, coapConfig, false, endpoint); + awaitObserveReadAll(0, device.getId().getId().toString()); String msg = getWsClient().waitForUpdate(); EntityDataUpdate update = JacksonUtil.fromString(msg, EntityDataUpdate.class); @@ -252,6 +253,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte int expectedMin = 5; Assert.assertTrue(expectedMax >= Long.parseLong(tsValue.getValue())); Assert.assertTrue(expectedMin <= Long.parseLong(tsValue.getValue())); + + } protected void createDeviceProfile(Lwm2mDeviceProfileTransportConfiguration transportConfiguration) throws Exception { @@ -300,14 +303,14 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte this.resources = resources; } - public void createNewClient(Security security, Configuration coapConfig, boolean isRpc, String endpoint, boolean isBootstrap, Security securityBs) throws Exception { + public void createNewClient(Security security, Security securityBs, Configuration coapConfig, boolean isRpc, String endpoint) throws Exception { this.clientDestroy(); lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint); try (ServerSocket socket = new ServerSocket(0)) { int clientPort = socket.getLocalPort(); - lwM2MTestClient.init(security, coapConfig, clientPort, isRpc, isBootstrap, this.shortServerId, this.shortServerIdBs, - securityBs, this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest); + lwM2MTestClient.init(security, securityBs, coapConfig, clientPort, isRpc, + this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest); } } @@ -354,7 +357,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte private AbstractLwM2MBootstrapServerCredential getBootstrapServerCredentialNoSec(boolean isBootstrap) { AbstractLwM2MBootstrapServerCredential bootstrapServerCredential = new NoSecLwM2MBootstrapServerCredential(); bootstrapServerCredential.setServerPublicKey(""); - bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs : shortServerId); + bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs0 : shortServerId); bootstrapServerCredential.setBootstrapServerIs(isBootstrap); bootstrapServerCredential.setHost(isBootstrap ? hostBs : host); bootstrapServerCredential.setPort(isBootstrap ? portBs : port); @@ -397,20 +400,18 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte .until(() -> leshanClient.getRegisteredServers().size() == 0); } - protected void awaitObserveReadAll(int cntObserve, boolean isBootstrap, String deviceIdStr) throws Exception { - if (!isBootstrap) { - await("ObserveReadAll after start client: countObserve " + cntObserve) - .atMost(40, TimeUnit.SECONDS) - .until(() -> { - String actualResultReadAll = sendObserve("ObserveReadAll", null, deviceIdStr); - ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - Assert.assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValuesReadAll = rpcActualResultReadAll.get("value").asText(); - log.warn("ObserveReadAll: [{}]", actualValuesReadAll); - int actualCntObserve = "[]".equals(actualValuesReadAll) ? 0 : actualValuesReadAll.split(",").length; - return cntObserve == actualCntObserve; - }); - } + protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception { + await("ObserveReadAll after start client: countObserve " + cntObserve) + .atMost(40, TimeUnit.SECONDS) + .until(() -> { + String actualResultReadAll = sendObserve("ObserveReadAll", null, deviceIdStr); + ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); + Assert.assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); + String actualValuesReadAll = rpcActualResultReadAll.get("value").asText(); + log.warn("ObserveReadAll: [{}]", actualValuesReadAll); + int actualCntObserve = "[]".equals(actualValuesReadAll) ? 0 : actualValuesReadAll.split(",").length; + return cntObserve == actualCntObserve; + }); } protected String sendObserve(String method, String params, String deviceIdStr) throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mAttributeParserTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mAttributeParserTest.java new file mode 100644 index 0000000000..0172710d0d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mAttributeParserTest.java @@ -0,0 +1,147 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.attributes; + +import org.eclipse.leshan.core.link.LinkParseException; +import org.eclipse.leshan.core.link.attributes.InvalidAttributeException; +import org.eclipse.leshan.core.link.lwm2m.attributes.DefaultLwM2mAttributeParser; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeSet; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class DefaultLwM2mAttributeParserTest { + + private final DefaultLwM2mAttributeParser parser = new DefaultLwM2mAttributeParser(); + + private static Stream validAttributes() throws InvalidAttributeException { + return Stream.of(// + Arguments.of("pmin", LwM2mAttributes.create(LwM2mAttributes.MINIMUM_PERIOD)), // + Arguments.of("pmin=30", LwM2mAttributes.create(LwM2mAttributes.MINIMUM_PERIOD, 30l)), // + Arguments.of("pmax", LwM2mAttributes.create(LwM2mAttributes.MAXIMUM_PERIOD)), // + Arguments.of("pmax=60", LwM2mAttributes.create(LwM2mAttributes.MAXIMUM_PERIOD, 60l)), // + Arguments.of("dim=2", LwM2mAttributes.create(LwM2mAttributes.DIMENSION, 2l)), // + Arguments.of("epmin", LwM2mAttributes.create(LwM2mAttributes.EVALUATE_MINIMUM_PERIOD)), // + Arguments.of("epmin=30", LwM2mAttributes.create(LwM2mAttributes.EVALUATE_MINIMUM_PERIOD, 30l)), // + Arguments.of("epmax", LwM2mAttributes.create(LwM2mAttributes.EVALUATE_MAXIMUM_PERIOD)), // + Arguments.of("epmax=60", LwM2mAttributes.create(LwM2mAttributes.EVALUATE_MAXIMUM_PERIOD, 60l)), // + Arguments.of("lt", LwM2mAttributes.create(LwM2mAttributes.LESSER_THAN)), // + Arguments.of("lt=30", LwM2mAttributes.create(LwM2mAttributes.LESSER_THAN, 30d)), // + Arguments.of("lt=-30", LwM2mAttributes.create(LwM2mAttributes.LESSER_THAN, -30d)), // + Arguments.of("lt=30.55", LwM2mAttributes.create(LwM2mAttributes.LESSER_THAN, 30.55)), // + Arguments.of("lt=-30.55", LwM2mAttributes.create(LwM2mAttributes.LESSER_THAN, -30.55)), // + Arguments.of("gt", LwM2mAttributes.create(LwM2mAttributes.GREATER_THAN)), // + Arguments.of("gt=60", LwM2mAttributes.create(LwM2mAttributes.GREATER_THAN, 60d)), // + Arguments.of("gt=-60", LwM2mAttributes.create(LwM2mAttributes.GREATER_THAN, -60d)), // + Arguments.of("gt=60.55", LwM2mAttributes.create(LwM2mAttributes.GREATER_THAN, 60.55)), // + Arguments.of("gt=-60.55", LwM2mAttributes.create(LwM2mAttributes.GREATER_THAN, -60.55)), // + Arguments.of("st", LwM2mAttributes.create(LwM2mAttributes.STEP)), // + Arguments.of("st=60", LwM2mAttributes.create(LwM2mAttributes.STEP, 60d)), // + Arguments.of("st=60.55", LwM2mAttributes.create(LwM2mAttributes.STEP, 60.55)) // + ); + } + + @ParameterizedTest(name = "Tests {index} : {0}") + @MethodSource("validAttributes") + public void check_one_valid_attribute(String attributeAsString, LwM2mAttribute expectedValue) + throws LinkParseException, InvalidAttributeException { + LwM2mAttributeSet parsed = new LwM2mAttributeSet(parser.parseUriQuery(attributeAsString)); + LwM2mAttributeSet attResult = new LwM2mAttributeSet(expectedValue); + assertEquals(parsed, attResult); + } + + private static String[] invalidAttributes() { + return new String[] { // + // MINIMUM_PERIOD + "pmin=", // + "pmin=1.9", // + "pmin=-1.8", // + "pmin=a", // + // MAXIMUM_PERIOD + "pmax=", // + "pmax=-2", // + "pmax=2.7", // + "pmax=-2.6", // + "pmax=bc", // + // DIMENSION + "dim", // + "dim=", // + "dim=-3", // + "dim=3.5", // + "dim=-3.4", // + "dim=def", // + // EVALUATE_MINIMUM_PERIOD + "epmin=", // + "epmin=-4", // + "epmin=4.3", // + "epmin=-4.2", // + "epmin=ghij", // + // EVALUATE_MAXIMUM_PERIOD + "epmax=", // + "epmax=-5", // + "epmax=5.1", // + "epmax=-5.9", // + "epmax=klmno", // + // LESSER_THAN + "lt=", // + "lt=pqrts", // + "lt=0abc", // + // GREATER_THAN + "gt=", // + "gt=uvwxyz", // + "gt=0.xyz", // + // STEP + "st=", // + "st=-6", // + "st=-6.8", // + // Multi-attributes + "&pmin&pmax=60>=2&", // + "&pmin&pmax=60>=2", // + "pmin&pmax=60>=2&", // + "pmin&pmax=60&>=2", // + "pmin&pmax=60&>=2&&&", // + "&&&pmin&pmax=60&>=2", // + }; + } + + @ParameterizedTest(name = "Test {index} : {0}") + @MethodSource("invalidAttributes") + public void check_invalid_attributes(String invalidAttributesAsString) { + assertThrows(InvalidAttributeException.class, + () -> new LwM2mAttributeSet(parser.parseUriQuery(invalidAttributesAsString))); + + } + + @Test + public void check_multiple_valid_attributes() throws LinkParseException, InvalidAttributeException { + + LwM2mAttributeSet parsed = new LwM2mAttributeSet(parser.parseUriQuery("pmin&pmax=60>=2")); + LwM2mAttributeSet attResult = new LwM2mAttributeSet( // + LwM2mAttributes.create(LwM2mAttributes.MINIMUM_PERIOD), // + LwM2mAttributes.create(LwM2mAttributes.MAXIMUM_PERIOD, 60l), // + LwM2mAttributes.create(LwM2mAttributes.GREATER_THAN, 2d)); + + assertEquals(attResult, parsed); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mLinkParserTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mLinkParserTest.java new file mode 100644 index 0000000000..b5d8fecf10 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mLinkParserTest.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.attributes; + +import org.eclipse.leshan.core.link.LinkParseException; +import org.eclipse.leshan.core.link.attributes.AttributeSet; +import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; +import org.eclipse.leshan.core.link.lwm2m.LwM2mLink; +import org.eclipse.leshan.core.link.lwm2m.LwM2mLinkParser; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeSet; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; + +public class DefaultLwM2mLinkParserTest { + + private final LwM2mLinkParser parser = new DefaultLwM2mLinkParser(); + + @Test + public void check_invalid_values() throws LinkParseException { + // first check it's OK with valid value + LwM2mLink[] parsed = parser.parseLwM2mLinkFromCoreLinkFormat(";dim=255".getBytes(), null); + assertEquals(new LwM2mPath(3, 0, 11), parsed[0].getPath()); + AttributeSet attResult = new LwM2mAttributeSet(LwM2mAttributes.create(LwM2mAttributes.DIMENSION, 255l)); + assertEquals(attResult, parsed[0].getAttributes()); + + // then check an invalid one + assertThrowsExactly(LinkParseException.class, () -> { + // dim should be between 0-255 + parser.parseLwM2mLinkFromCoreLinkFormat(";dim=256".getBytes(), null); + }); + + // first check it's OK with valid value + parsed = parser.parseLwM2mLinkFromCoreLinkFormat(";ssid=1".getBytes(), null); + assertEquals(new LwM2mPath(0, 1), parsed[0].getPath()); + attResult = new LwM2mAttributeSet(LwM2mAttributes.create(LwM2mAttributes.SHORT_SERVER_ID, 1l)); + assertEquals(attResult, parsed[0].getAttributes()); + + // then check an invalid one + assertThrowsExactly(LinkParseException.class, () -> { + // ssid should be between 1-65534 + parser.parseLwM2mLinkFromCoreLinkFormat(";ssid=0".getBytes(), null); + }); + } + + @Test + public void check_attribute_with_no_value_failed() throws LinkParseException { + // first check it's OK with value + LwM2mLink[] parsed = parser.parseLwM2mLinkFromCoreLinkFormat(";pmin=200".getBytes(), null); + assertEquals(new LwM2mPath(3, 0, 11), parsed[0].getPath()); + AttributeSet attResult = new LwM2mAttributeSet(LwM2mAttributes.create(LwM2mAttributes.MINIMUM_PERIOD, 200l)); + assertEquals(attResult, parsed[0].getAttributes()); + + // then check an invalid one + assertThrowsExactly(LinkParseException.class, () -> { + // dim should be between 0-255 + parser.parseLwM2mLinkFromCoreLinkFormat(";pmin".getBytes(), null); + }); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java new file mode 100644 index 0000000000..d77a3252c0 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java @@ -0,0 +1,87 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.attributes; + +import org.eclipse.leshan.core.link.attributes.InvalidAttributeException; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeModel; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class LwM2mAttributesTest { + + @ParameterizedTest(name = "Tests {index} : {0}") + @MethodSource("supportNullAttributes") + public void check_attribute_can_be_created_with_null_value(LwM2mAttributeModel model) + throws InvalidAttributeException { + LwM2mAttribute attribute = LwM2mAttributes.create(model); + assertNotNull(attribute); + assertFalse(attribute.hasValue()); + assertNull(attribute.getValue()); + attribute = LwM2mAttributes.create(model, null); + assertNotNull(attribute); + assertFalse(attribute.hasValue()); + assertNull(attribute.getValue()); + } + + @ParameterizedTest(name = "Tests {index} : {0}") + @MethodSource("doesntSupportAttributesWithoutValue") + public void check_attribute_can_not_be_created_without_value(LwM2mAttributeModel model) { + assertThrows(UnsupportedOperationException.class, () -> LwM2mAttributes.create(model)); + } + + @ParameterizedTest(name = "Tests {index} : {0}") + @MethodSource("doesntSupportAttributesWithValueNull") + public void check_attribute_can_not_be_created_with_null(LwM2mAttributeModel model) { + assertThrows(NullPointerException.class, () -> LwM2mAttributes.create(model, null)); + } + + private static Stream supportNullAttributes() throws InvalidAttributeException { + return Stream.of(// + Arguments.of(LwM2mAttributes.MINIMUM_PERIOD), // + Arguments.of(LwM2mAttributes.MAXIMUM_PERIOD), // + Arguments.of(LwM2mAttributes.EVALUATE_MINIMUM_PERIOD), // + Arguments.of(LwM2mAttributes.EVALUATE_MAXIMUM_PERIOD), // + Arguments.of(LwM2mAttributes.LESSER_THAN), // + Arguments.of(LwM2mAttributes.GREATER_THAN), // + Arguments.of(LwM2mAttributes.STEP) // + ); + } + + private static Stream doesntSupportAttributesWithoutValue() throws InvalidAttributeException { + return Stream.of(// + Arguments.of(LwM2mAttributes.ENABLER_VERSION), // + Arguments.of(LwM2mAttributes.OBJECT_VERSION)// + ); + } + + private static Stream doesntSupportAttributesWithValueNull() throws InvalidAttributeException { + return Stream.of(// + Arguments.of(LwM2mAttributes.DIMENSION), // + Arguments.of(LwM2mAttributes.SHORT_SERVER_ID) // + ); + } +} + diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java index 5fb21b344a..86124ccb5f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java @@ -17,9 +17,9 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; @@ -45,7 +45,7 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { private final AtomicInteger updateResult = new AtomicInteger(0); @Override - public ReadResponse read(ServerIdentity identity, int resourceId) { + public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { if (!identity.isSystem()) log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { @@ -65,24 +65,24 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { } @Override - public ExecuteResponse execute(ServerIdentity identity, int resourceId, String params) { - String withParams = null; - if (params != null && params.length() != 0) { - withParams = " with params " + params; - } - log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withParams != null ? withParams : ""); + public ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId, Arguments arguments) { + String withArguments = ""; + if (!arguments.isEmpty()) + withArguments = " with arguments " + arguments; + log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withArguments); + switch (resourceId) { case 2: startUpdating(); return ExecuteResponse.success(); default: - return super.execute(identity, resourceId, params); + return super.execute(identity, resourceId, arguments); } } @Override - public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { + public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 449ac4c2ae..6e6fa12cf7 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -18,17 +18,28 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.californium.scandium.config.DtlsConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; -import org.eclipse.leshan.client.californium.LeshanClient; -import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.LeshanClient; +import org.eclipse.leshan.client.LeshanClientBuilder; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointFactory; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointsProvider; +import org.eclipse.leshan.client.californium.endpoint.ClientProtocolProvider; +import org.eclipse.leshan.client.californium.endpoint.coap.CoapOscoreProtocolProvider; +import org.eclipse.leshan.client.californium.endpoint.coaps.CoapsClientEndpointFactory; +import org.eclipse.leshan.client.californium.endpoint.coaps.CoapsClientProtocolProvider; +import org.eclipse.leshan.client.endpoint.LwM2mClientEndpointsProvider; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.observer.LwM2mClientObserver; import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; +import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.ObjectsInitializer; -import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter; +import org.eclipse.leshan.client.send.ManualDataSender; +import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.model.InvalidDDFFileException; import org.eclipse.leshan.core.model.LwM2mModel; @@ -49,13 +60,13 @@ import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandle import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.io.IOException; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; -import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.eclipse.leshan.core.LwM2mId.DEVICE; import static org.eclipse.leshan.core.LwM2mId.FIRMWARE; @@ -63,6 +74,10 @@ import static org.eclipse.leshan.core.LwM2mId.LOCATION; import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; +import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.serverId; +import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.serverIdBs; +import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.shortServerId; +import static org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest.shortServerIdBs0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE; @@ -97,11 +112,6 @@ public class LwM2MTestClient { private final ScheduledExecutorService executor; private final String endpoint; private LeshanClient leshanClient; - - private Security lwm2mSecurity; - private Security lwm2mSecurityBs; - private Lwm2mServer lwm2mServer; - private Lwm2mServer lwm2mServerBs; private SimpleLwM2MDevice lwM2MDevice; private FwLwM2MDevice fwLwM2MDevice; private SwLwM2MDevice swLwM2MDevice; @@ -112,8 +122,7 @@ public class LwM2MTestClient { private LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; private LwM2mClientContext clientContext; - public void init(Security security, Configuration coapConfig, int port, boolean isRpc, boolean isBootstrap, - int shortServerId, int shortServerIdBs, Security securityBs, + public void init(Security security, Security securityBs,Configuration coapConfig, int port, boolean isRpc, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, LwM2mClientContext clientContext) throws InvalidDDFFileException, IOException { Assert.assertNull("client already initialized", leshanClient); @@ -125,30 +134,36 @@ public class LwM2MTestClient { } LwM2mModel model = new StaticModel(models); ObjectsInitializer initializer = new ObjectsInitializer(model); - if (securityBs == null) { - initializer.setInstancesForObject(SECURITY, this.lwm2mSecurity = security); - } else { - securityBs.setId(0); - security.setId(1); - LwM2mInstanceEnabler[] instances = new LwM2mInstanceEnabler[]{this.lwm2mSecurityBs = securityBs, this.lwm2mSecurity = security}; + + if (securityBs != null && security != null) { + // SECURITY + security.setId(serverId); + securityBs.setId(serverIdBs); + LwM2mInstanceEnabler[] instances = new LwM2mInstanceEnabler[]{securityBs, security}; initializer.setClassForObject(SECURITY, Security.class); initializer.setInstancesForObject(SECURITY, instances); - } - if (isBootstrap) { - initializer.setInstancesForObject(SERVER, lwm2mServerBs = new Lwm2mServer(shortServerIdBs, 300)); + // SERVER + Server lwm2mServer = new Server(shortServerId, 300); + lwm2mServer.setId(serverId); + Server serverBs = new Server(shortServerIdBs0, 300); + serverBs.setId(serverIdBs); + instances = new LwM2mInstanceEnabler[]{serverBs, lwm2mServer}; + initializer.setClassForObject(SERVER, Server.class); + initializer.setInstancesForObject(SERVER, instances); + } else if (securityBs != null) { + // SECURITY + initializer.setInstancesForObject(SECURITY, securityBs); + // SERVER + initializer.setClassForObject(SERVER, Server.class); } else { - if (securityBs == null) { - initializer.setInstancesForObject(SERVER, lwm2mServer = new Lwm2mServer(shortServerId, 300)); - } else { - lwm2mServerBs = new Lwm2mServer(shortServerIdBs, 300); - lwm2mServerBs.setId(0); - lwm2mServer = new Lwm2mServer(shortServerId, 300); - lwm2mServer.setId(1); - LwM2mInstanceEnabler[] instances = new LwM2mInstanceEnabler[]{lwm2mServerBs, lwm2mServer}; - initializer.setClassForObject(SERVER, Server.class); - initializer.setInstancesForObject(SERVER, instances); - } + // SECURITY + initializer.setInstancesForObject(SECURITY, security); + // SERVER + Server lwm2mServer = new Server(shortServerId, 300); + lwm2mServer.setId(serverId); + initializer.setInstancesForObject(SERVER, lwm2mServer ); } + initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice(executor)); initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice()); initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, swLwM2MDevice = new SwLwM2MDevice()); @@ -160,106 +175,193 @@ public class LwM2MTestClient { initializer.setInstancesForObject(LOCATION, new LwM2mLocation(locationParams.getLatitude(), locationParams.getLongitude(), locationParams.getScaleFactor(), executor, OBJECT_INSTANCE_ID_0)); initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2MTemperatureSensor = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_0), new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12)); + List enablers = initializer.createAll(); + + // Create Californium Endpoints Provider: + // -------------------------------------- + // Define Custom CoAPS protocol provider + CoapsClientProtocolProvider customCoapsProtocolProvider = new CoapsClientProtocolProvider() { + @Override + public CaliforniumClientEndpointFactory createDefaultEndpointFactory() { + return new CoapsClientEndpointFactory() { + + @Override + protected DtlsConnectorConfig.Builder createRootDtlsConnectorConfigBuilder( + Configuration configuration) { + DtlsConnectorConfig.Builder builder = super.createRootDtlsConnectorConfigBuilder(configuration); + return builder; + }; + }; + } + }; + + // Create client endpoints Provider + List protocolProvider = new ArrayList<>(); + + /** + * "Use java-coap for CoAP protocol instead of Californium." + */ + boolean useJavaCoap = false; + if (!useJavaCoap) protocolProvider.add(new CoapOscoreProtocolProvider()); + protocolProvider.add(customCoapsProtocolProvider); + CaliforniumClientEndpointsProvider.Builder endpointsBuilder = new CaliforniumClientEndpointsProvider.Builder( + protocolProvider.toArray(new ClientProtocolProvider[protocolProvider.size()])); + + + // Create Californium Configuration + Configuration clientCoapConfig = endpointsBuilder.createDefaultConfiguration(); DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(coapConfig); - dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, true); + // Set some DTLS stuff + // These configuration values are always overwritten by CLI therefore set them to transient. + clientCoapConfig.setTransient(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); + clientCoapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); + boolean supportDeprecatedCiphers = false; + clientCoapConfig.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !supportDeprecatedCiphers); + /** + * "Control usage of DTLS connection ID.", // + * "- 'on' to activate Connection ID support (same as -cid 0)", // + * "- 'off' to deactivate it", // + * "- Positive value define the size in byte of CID generated.", // + * "- 0 value means we accept to use CID but will not generated one for foreign peer.", // + * "Default: off" + */ + Integer cid = null; + clientCoapConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, cid); + + // Set Californium Configuration + endpointsBuilder.setConfiguration(clientCoapConfig); + endpointsBuilder.setClientAddress(new InetSocketAddress(port).getAddress()); + + + // creates EndpointsProvider + List endpointsProvider = new ArrayList<>(); + endpointsProvider.add(endpointsBuilder.build()); + /** + * dependency -> org.eclipse.leshan.transport.javacoap.client.endpoint; + */ +// if (useJavaCoap) {endpointsProvider.add(new JavaCoapClientEndpointsProvider()); + + // Configure Registration Engine DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory(); - engineFactory.setReconnectOnUpdate(false); + // old + /** + * Force reconnection/rehandshake on registration update. + */ + int comPeriodInSec = 5; + if (comPeriodInSec > 0) engineFactory.setCommunicationPeriod(comPeriodInSec * 1000); +// engineFactory.setCommunicationPeriod(5000); // old + /** + * By default client will try to resume DTLS session by using abbreviated Handshake. This option force to always do a full handshake." + */ + boolean reconnectOnUpdate = false; + engineFactory.setReconnectOnUpdate(reconnectOnUpdate); +// engineFactory.setReconnectOnUpdate(false); // old engineFactory.setResumeOnConnect(true); - engineFactory.setCommunicationPeriod(5000); - + // new + /** + * Client use queue mode (not fully implemented). + */ + boolean queueMode = false; + engineFactory.setQueueMode(queueMode); + + // Create client LeshanClientBuilder builder = new LeshanClientBuilder(endpoint); - builder.setLocalAddress("0.0.0.0", port); - builder.setObjects(initializer.createAll()); - builder.setCoapConfig(coapConfig); - builder.setDtlsConfig(dtlsConfig); + builder.setObjects(enablers); + builder.setEndpointsProviders(endpointsProvider.toArray(new LwM2mClientEndpointsProvider[endpointsProvider.size()])); + builder.setDataSenders(new ManualDataSender()); + builder.setRegistrationEngineFactory(engineFactory); + boolean supportOldFormat = true; + if (supportOldFormat) { + builder.setDecoder(new DefaultLwM2mDecoder(supportOldFormat)); + builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), supportOldFormat)); + } + builder.setRegistrationEngineFactory(engineFactory); builder.setSharedExecutor(executor); - builder.setDecoder(new DefaultLwM2mDecoder(false)); - builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), false)); clientStates = new HashSet<>(); clientStates.add(ON_INIT); leshanClient = builder.build(); LwM2mClientObserver observer = new LwM2mClientObserver() { @Override - public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) { + public void onBootstrapStarted(LwM2mServer bsserver, BootstrapRequest request) { clientStates.add(ON_BOOTSTRAP_STARTED); } @Override - public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) { + public void onBootstrapSuccess(LwM2mServer bsserver, BootstrapRequest request) { clientStates.add(ON_BOOTSTRAP_SUCCESS); } @Override - public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + public void onBootstrapFailure(LwM2mServer bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { clientStates.add(ON_BOOTSTRAP_FAILURE); } @Override - public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) { + public void onBootstrapTimeout(LwM2mServer bsserver, BootstrapRequest request) { clientStates.add(ON_BOOTSTRAP_TIMEOUT); } @Override - public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) { + public void onRegistrationStarted(LwM2mServer server, RegisterRequest request) { clientStates.add(ON_REGISTRATION_STARTED); } @Override - public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) { + public void onRegistrationSuccess(LwM2mServer server, RegisterRequest request, String registrationID) { clientStates.add(ON_REGISTRATION_SUCCESS); } @Override - public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + public void onRegistrationFailure(LwM2mServer server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { clientStates.add(ON_REGISTRATION_FAILURE); } @Override - public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) { + public void onRegistrationTimeout(LwM2mServer server, RegisterRequest request) { clientStates.add(ON_REGISTRATION_TIMEOUT); } @Override - public void onUpdateStarted(ServerIdentity server, UpdateRequest request) { + public void onUpdateStarted(LwM2mServer server, UpdateRequest request) { clientStates.add(ON_UPDATE_STARTED); } @Override - public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) { + public void onUpdateSuccess(LwM2mServer server, UpdateRequest request) { clientStates.add(ON_UPDATE_SUCCESS); } @Override - public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + public void onUpdateFailure(LwM2mServer server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { clientStates.add(ON_UPDATE_FAILURE); } @Override - public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) { + public void onUpdateTimeout(LwM2mServer server, UpdateRequest request) { clientStates.add(ON_UPDATE_TIMEOUT); } @Override - public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) { + public void onDeregistrationStarted(LwM2mServer server, DeregisterRequest request) { clientStates.add(ON_DEREGISTRATION_STARTED); } @Override - public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) { + public void onDeregistrationSuccess(LwM2mServer server, DeregisterRequest request) { clientStates.add(ON_DEREGISTRATION_SUCCESS); } @Override - public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + public void onDeregistrationFailure(LwM2mServer server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { clientStates.add(ON_DEREGISTRATION_FAILURE); } @Override - public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) { + public void onDeregistrationTimeout(LwM2mServer server, DeregisterRequest request) { clientStates.add(ON_DEREGISTRATION_TIMEOUT); } @@ -270,6 +372,21 @@ public class LwM2MTestClient { }; this.leshanClient.addObserver(observer); + // Add some log about object tree life cycle. + this.leshanClient.getObjectTree().addListener(new ObjectsListenerAdapter() { + + @Override + public void objectRemoved(LwM2mObjectEnabler object) { + log.info("Object {} v{} disabled.", object.getId(), object.getObjectModel().version); + } + + @Override + public void objectAdded(LwM2mObjectEnabler object) { + log.info("Object {} v{} enabled.", object.getId(), object.getObjectModel().version); + } + }); + + if (!isRpc) { this.start(true); } @@ -279,18 +396,6 @@ public class LwM2MTestClient { if (leshanClient != null) { leshanClient.destroy(true); } - if (lwm2mSecurityBs != null) { - lwm2mSecurityBs = null; - } - if (lwm2mSecurity != null) { - lwm2mSecurity = null; - } - if (lwm2mServerBs != null) { - lwm2mServerBs = null; - } - if (lwm2mServer != null) { - lwm2mServer = null; - } if (lwM2MDevice != null) { lwM2MDevice.destroy(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java index 12a29ab216..35866dea0f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mMultipleResource; @@ -93,7 +92,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements } @Override - public ReadResponse read(ServerIdentity identity, int resourceId) { + public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { try { switch (resourceId) { case 0: @@ -118,7 +117,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements } @Override - public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { + public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { log.info("Write on Device resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); switch (resourceId) { case 0: diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java index 48cba35e36..4599f7c0ee 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.response.ReadResponse; @@ -85,7 +84,7 @@ public class LwM2mLocation extends BaseInstanceEnabler implements Destroyable { } @Override - public ReadResponse read(ServerIdentity identity, int resourceId) { + public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { log.info("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); switch (resourceId) { case 0: diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java index 7836bbf881..ae0297e02b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java @@ -17,8 +17,8 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; @@ -56,7 +56,7 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr } @Override - public synchronized ReadResponse read(ServerIdentity identity, int resourceId) { + public synchronized ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); switch (resourceId) { case 5601: @@ -73,14 +73,14 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr } @Override - public synchronized ExecuteResponse execute(ServerIdentity identity, int resourceId, String params) { + public synchronized ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId, Arguments arguments) { log.info("Execute on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); switch (resourceId) { case 5605: resetMinMaxMeasuredValues(); return ExecuteResponse.success(); default: - return super.execute(identity, resourceId, params); + return super.execute(identity, resourceId, arguments); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java index 86f6f63a62..1cddbb17f8 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java @@ -18,11 +18,11 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.request.BindingMode; +import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; @@ -70,7 +70,7 @@ public class Lwm2mServer extends BaseInstanceEnabler { } @Override - public ReadResponse read(ServerIdentity identity, int resourceid) { + public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceid) { if (!identity.isSystem()) LOG.debug("Read on Server resource /{}/{}/{}", getModel().id, getId(), resourceid); @@ -108,7 +108,7 @@ public class Lwm2mServer extends BaseInstanceEnabler { } @Override - public WriteResponse write(ServerIdentity identity, boolean replace, int resourceid, LwM2mResource value) { + public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceid, LwM2mResource value) { if (!identity.isSystem()) log.debug("Write on Server resource /{}/{}/{}", getModel().id, getId(), resourceid); @@ -195,8 +195,7 @@ public class Lwm2mServer extends BaseInstanceEnabler { } } - @Override - public ExecuteResponse execute(ServerIdentity identity, int resourceid, String params) { + public ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceid, Arguments arguments) { log.info("Execute on Server resource /{}/{}/{}", getModel().id, getId(), resourceid); if (resourceid == 8) { getLwM2mClient().triggerRegistrationUpdate(identity); @@ -210,7 +209,7 @@ public class Lwm2mServer extends BaseInstanceEnabler { return ExecuteResponse.badRequest("probably no bootstrap server configured"); } } else { - return super.execute(identity, resourceid, params); + return super.execute(identity, resourceid, arguments); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java index dbb75c01cd..8a32092572 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -17,11 +17,11 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; @@ -66,7 +66,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl @Override - public ReadResponse read(ServerIdentity identity, int resourceId) { + public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { if (!identity.isSystem()) log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { @@ -108,17 +108,16 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl } @Override - public ExecuteResponse execute(ServerIdentity identity, int resourceId, String params) { - String withParams = null; - if (params != null && params.length() != 0) { - withParams = " with params " + params; - } - log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withParams != null ? withParams : ""); + public ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId, Arguments arguments) { + String withArguments = ""; + if (!arguments.isEmpty()) + withArguments = " with arguments " + arguments; + log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withArguments); return ExecuteResponse.success(); } @Override - public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { + public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java index 2be8c2d2ae..340f9ffcb2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java @@ -17,9 +17,9 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; -import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; @@ -45,7 +45,7 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { private final AtomicInteger updateResult = new AtomicInteger(0); @Override - public ReadResponse read(ServerIdentity identity, int resourceId) { + public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { if (!identity.isSystem()) log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { @@ -63,12 +63,11 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { } @Override - public ExecuteResponse execute(ServerIdentity identity, int resourceId, String params) { - String withParams = null; - if (params != null && params.length() != 0) { - withParams = " with params " + params; - } - log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withParams != null ? withParams : ""); + public ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId, Arguments arguments) { + String withArguments = ""; + if (!arguments.isEmpty()) + withArguments = " with arguments " + arguments; + log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withArguments); switch (resourceId) { case 4: @@ -77,12 +76,12 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { case 6: return ExecuteResponse.success(); default: - return super.execute(identity, resourceId, params); + return super.execute(identity, resourceId, arguments); } } @Override - public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { + public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java index 5c580adc85..6e6b0f2c2a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java @@ -99,8 +99,8 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); - createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, false, null); - awaitObserveReadAll(0, false, device.getId().getId().toString()); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); + awaitObserveReadAll(0, device.getId().getId().toString()); device.setFirmwareId(createFirmware().getId()); final Device savedDevice = doPost("/api/device", device, Device.class); @@ -124,8 +124,8 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5); - createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5, false, null); - awaitObserveReadAll(9, false, device.getId().getId().toString()); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5); + awaitObserveReadAll(9, device.getId().getId().toString()); device.setFirmwareId(createFirmware().getId()); final Device savedDevice = doPost("/api/device", device, Device.class); @@ -154,8 +154,8 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9); - createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9, false, null); - awaitObserveReadAll(9, false, device.getId().getId().toString()); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9); + awaitObserveReadAll(9, device.getId().getId().toString()); device.setSoftwareId(createSoftware().getId()); final Device savedDevice = doPost("/api/device", device, Device.class); //sync call diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index 2e51c300ee..ddd1329254 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.rpc; +import org.eclipse.leshan.core.link.LinkParser; +import org.eclipse.leshan.core.link.lwm2m.DefaultLwM2mLinkParser; import org.junit.Before; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; @@ -50,6 +52,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; @DaoSqlTest public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + protected final LinkParser linkParser = new DefaultLwM2mLinkParser(); protected String OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC; protected String deviceId; public Set expectedObjects; @@ -84,7 +87,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg private void initRpc () throws Exception { String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet(); - createNewClient(SECURITY_NO_SEC, COAP_CONFIG, true, endpoint, false, null); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, true, endpoint); expectedObjects = ConcurrentHashMap.newKeySet(); expectedObjectIdVers = ConcurrentHashMap.newKeySet(); expectedInstances = ConcurrentHashMap.newKeySet(); @@ -147,7 +150,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg deviceId = device.getId().getId().toString(); lwM2MTestClient.start(true); - awaitObserveReadAll(2, false, device.getId().getId().toString()); +// awaitObserveReadAll(2, true, device.getId().getId().toString()); } protected String pathIdVerToObjectId(String pathIdVer) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java index 6e005fcaf8..3eb0d25e85 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationCreateTest.java @@ -99,7 +99,7 @@ public class RpcLwm2mIntegrationCreateTest extends AbstractRpcLwM2MIntegrationTe String actualResult = sendRPCreateById(expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expectedObjectId = pathIdVerToObjectId((String) expectedPath); + String expectedObjectId = pathIdVerToObjectId(expectedPath); LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!"; String actual = rpcActualResult.get("error").asText(); @@ -118,7 +118,7 @@ public class RpcLwm2mIntegrationCreateTest extends AbstractRpcLwM2MIntegrationTe String actualResult = sendRPCreateById(expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expectedObjectId = pathIdVerToObjectId((String) expectedPath); + String expectedObjectId = pathIdVerToObjectId(expectedPath); LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!"; String actual = rpcActualResult.get("error").asText(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java index 7d3cf6acd1..ea0a1ad4ca 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java @@ -18,6 +18,8 @@ package org.thingsboard.server.transport.lwm2m.rpc.sql; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.LinkParseException; import org.eclipse.leshan.core.node.LwM2mPath; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; @@ -52,12 +54,17 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration Set actualObjects = ConcurrentHashMap.newKeySet(); Set actualInstances = ConcurrentHashMap.newKeySet(); rpcActualValue.forEach(node -> { - if (!node.get("uriReference").asText().equals("/")) { - LwM2mPath path = new LwM2mPath(node.get("uriReference").asText()); - actualObjects.add("/" + path.getObjectId()); - if (path.isObjectInstance()) { - actualInstances.add("/" + path.getObjectId() + "/" + path.getObjectInstanceId()); + try { + Link[] parsedLink = linkParser.parseCoreLinkFormat(node.asText().getBytes()); + if (!parsedLink[0].getUriReference().equals("/")) { + LwM2mPath path = new LwM2mPath(parsedLink[0].getUriReference()); + actualObjects.add("/" + path.getObjectId()); + if (path.isObjectInstance()) { + actualInstances.add("/" + path.getObjectId() + "/" + path.getObjectInstanceId()); + } } + } catch (LinkParseException e) { + throw new RuntimeException(e); } }); assertEquals(expectedInstances, actualInstances); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java index a54fdce6b5..9a17978e83 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java @@ -168,12 +168,14 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT */ @Test public void testObserveReadAll_Result_CONTENT_Value_Contains_Paths_Count_ObserveReadAll() throws Exception { + String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; + sendRpcObserve("Observe", expectedId); String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); String actualValuesReadAll = rpcActualResultReadAll.get("value").asText(); log.warn("ObserveReadAll: [{}]", actualValuesReadAll); - assertEquals(2, actualValuesReadAll.split(",").length); + assertEquals(3, actualValuesReadAll.split(",").length); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java index cb9702aa70..cb92aada30 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java @@ -50,22 +50,28 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest */ @Test public void testReadAllObjectsInClientById_Result_CONTENT_Value_IsLwM2mObject_IsInstances() throws Exception { - expectedObjectIdVers.forEach(expected -> { - try { - String actualResult = sendRPCById((String) expected); - String expectedObjectId = pathIdVerToObjectId((String) expected); - LwM2mPath expectedPath = new LwM2mPath(expectedObjectId); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String expectedObjectInstances = "LwM2mObject [id=" + expectedPath.getObjectId() + ", instances={0=LwM2mObjectInstance [id=0, resources="; - if (expectedPath.getObjectId() == 2) { - expectedObjectInstances = "LwM2mObject [id=2, instances={}]"; + try { + expectedObjectIdVers.forEach(expected -> { + try { + String actualResult = sendRPCById((String) expected); + String expectedObjectId = pathIdVerToObjectId((String) expected); + LwM2mPath expectedPath = new LwM2mPath(expectedObjectId); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expectedObjectInstances = "LwM2mObject [id=" + expectedPath.getObjectId() + ", instances={0=LwM2mObjectInstance [id=0, resources="; + if (expectedPath.getObjectId() == 1) { + expectedObjectInstances = "LwM2mObject [id=1, instances={1="; + } else if (expectedPath.getObjectId() == 2) { + expectedObjectInstances = "LwM2mObject [id=2, instances={}]"; + } + assertTrue(rpcActualResult.get("value").asText().contains(expectedObjectInstances)); + } catch (Exception e) { + e.printStackTrace(); } - assertTrue(rpcActualResult.get("value").asText().contains(expectedObjectInstances)); - } catch (Exception e) { - e.printStackTrace(); - } - }); + }); + } catch (Exception e2){ + e2.printStackTrace(); + } } /** diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java index f9e0725084..6275a657b5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.rpc.sql; import com.fasterxml.jackson.databind.node.ObjectNode; import org.eclipse.leshan.core.ResponseCode; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; @@ -29,10 +30,11 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MIntegrationTest { + @Ignore /** * WriteAttributes {"id":"/3/0/14","attributes":{"pmax":100, "pmin":10}} * if not implemented: - * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} + * {"result":"BAD_REQUEST","error":"Content Format is mandatory"} * if implemented: * {"result":"CHANGED"} */ @@ -42,8 +44,8 @@ public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MInte String expectedValue = "{\"pmax\":100, \"pmin\":10}"; String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); - String expected = "not implemented"; + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Content Format is mandatory"; String actual = rpcActualResult.get("error").asText(); assertTrue(actual.equals(expected)); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java index 3aff2e4725..e1f1cf2f15 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java @@ -161,8 +161,8 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes int resourceInstanceId25 = 25; String expectedValue0 = "00ad45675600"; String expectedValue25 = "25ad45675600cdef"; - String expectedValue = "{\"" + RESOURCE_ID_0 + "\":{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId25 + "\":\"" + expectedValue25 + "\"}}"; - String actualResult = sendRPCWriteObjectById("WriteUpdate", expectedPath, expectedValue); + String expectedValueResourcesObject19 = "{\"" + RESOURCE_ID_0 + "\":{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId25 + "\":\"" + expectedValue25 + "\"}}"; + String actualResult = sendRPCWriteObjectById("WriteUpdate", expectedPath, expectedValueResourcesObject19); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); String expectedPath0 = expectedPath + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0; @@ -181,29 +181,37 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes /** * ResourceInstance + KeySingleResource + IdSingleResource - * WriteComposite {"nodes":{"/19/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}} + * WriteComposite {"/19_1.1/0":{"0":{"0":"00ad45675600", "25":"25ad45675600cdef"}}, "UtfOffset":"+04", "/3_1.0/0/15":"Kiyv/Europe"} * {"result":"CHANGED"} */ @Test - public void testWriteCompositeValueSingleResourceResourceInstanceByIdKey_Result_CHANGED() throws Exception { - int resourceInstanceId2 = 2; - String expectedPath19_1_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId2; - String expectedValue19_1_0_2 = "00001234"; + public void testWriteCompositeValueSingleResourceWithMultiResourceInstanceByIdKey_Result_CHANGED() throws Exception { + String expectedPath_19_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0; + int resourceInstanceId0 = 0; + int resourceInstanceId25 = 25; + String expectedValue0 = "00ad45675600"; + String expectedValue25 = "25ad45675600cdef"; + String expectedValue_19_Resources = "{\"" + RESOURCE_ID_0 + "\":{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId25 + "\":\"" + expectedValue25 + "\"}}"; + String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; String expectedValue3_0_14 = "+04"; String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_15; String expectedValue3_0_15 = "Kiyv/Europe"; - String nodes = "{\"" + expectedPath19_1_0_2 + "\":\"" + expectedValue19_1_0_2 + "\", \"" + expectedKey3_0_14 + + String nodes = "{\"" + expectedPath_19_0 + "\":" + expectedValue_19_Resources + ", \"" + expectedKey3_0_14 + "\":\"" + expectedValue3_0_14 + "\", \"" + expectedPath3_0_15 + "\":\"" + expectedValue3_0_15 + "\"}"; String actualResult = sendCompositeRPC(nodes); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); - actualResult = sendRPCReadById(expectedPath19_1_0_2); + actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + resourceInstanceId2 + ", value=" + expectedValue19_1_0_2.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); - actualResult = sendRPCReadByKey(expectedKey3_0_14); + actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadByKey(expectedKey3_0_14); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); actualValues = rpcActualResult.get("value").asText(); expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=" + expectedValue3_0_14 + ", type=STRING]"; @@ -217,29 +225,36 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes /** * multipleResource == error - * bad - cannot be used for value Json, only primitive: SingleResource, ResourceInstance (for Json: WriteUpdate, WriteReplace) - * WriteComposite {"nodes":{"/19/0/0":{"0":"abcd5678", "10":"abcd5678"}}} + * WriteComposite {"/19_1.1/0/0":{"0":"00ad45675600", "25":"25ad45675600cdef"}} + * {"result":"CHANGED"} */ @Test - public void testWriteCompositeValueSingleMultipleResourceByIdKey_Result_BAD_REQUEST_WriteComposite_operation_for_SingleResources_or_and_ResourceInstance() throws Exception { - String nodes = "{\"/19/0/0\":{\"0\":\"abcd5678\", \"10\":\"abcd5678\"}}"; + public void testWriteCompositeValueSingleMultipleResourceOpaqueValueInputHexStringByIdKey_Result_CHANGED() throws Exception { + String expectedPath_19_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0; + int resourceInstanceId0 = 0; + int resourceInstanceId25 = 25; + String expectedValue0 = "00ad45675600"; + String expectedValue25 = "25ad45675600cdef"; + String nodes = "{\"" + expectedPath_19_0 + "/" + RESOURCE_ID_0 + "\":{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId25 + "\":\"" + expectedValue25 + "\"}}"; + String actualResult = sendCompositeRPC(nodes); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String actualValues = rpcActualResult.get("error").asText(); - String expectedNodes = nodes.replaceAll("\"", "").replaceAll(":", "="); - String expected = String.format("nodes: %s is not validate value. " + - "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", expectedNodes); - assertEquals(expected, actualValues); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + String actualValues = rpcActualResult.get("value").asText(); + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); } /** * update_resourceInstances&update_singleResource - * new ResourceInstance if Resource is Multiple & Resource Single - * - WriteReplace {"id":"/19_1.2/1/0","value":{"2":ddff12"}} - * - WriteReplace {"key":"UtfOffset","value":"+04"} - * - WriteReplace {"id":"/3/0/15","value":"Kiyv/Europe"} * WriteComposite {"nodes":{"/19_1.1/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}}} * {"result":"CHANGED"} */ @@ -331,4 +346,4 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes String setRpcRequest = "{\"method\": \"WriteComposite\", \"params\": {\"nodes\":" + nodes + "}}"; return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); } -} \ No newline at end of file +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java index 69b257d910..b97adf035c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -63,7 +63,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; -import static org.eclipse.leshan.client.object.Security.noSecBootstap; +import static org.eclipse.leshan.client.object.Security.noSecBootstrap; import static org.junit.Assert.assertEquals; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED; @@ -89,6 +89,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M protected static final String SERVER_STORE_PWD = "server_ks_password"; protected static final String SERVER_CERT_ALIAS = "server"; protected static final String SERVER_CERT_ALIAS_BS = "bootstrap"; + protected static final Security SECURITY_NO_SEC_BS = noSecBootstrap(URI_BS);; protected final X509Certificate serverX509Cert; // server certificate signed by rootCA protected final X509Certificate serverX509CertBs; // serverBs certificate signed by rootCA protected final PublicKey serverPublicKeyFromCert; // server public key used for RPK @@ -171,7 +172,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M LwM2MClientState finishState) throws Exception { Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsNoSec(type)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); - this.basicTestConnection(noSecBootstap(URI_BS), + this.basicTestConnection(null , SECURITY_NO_SEC_BS, deviceCredentials, COAP_CONFIG_BS, clientEndpoint, @@ -183,22 +184,24 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M false); } - protected void basicTestConnection(Security security, + protected void basicTestConnection(Security security, Security securityBs, LwM2MDeviceCredentials deviceCredentials, Configuration coapConfig, String endpoint, Lwm2mDeviceProfileTransportConfiguration transportConfiguration, String awaitAlias, Set expectedStatuses, - boolean isBootstrap, + boolean isAwaitObserveReadAll, LwM2MClientState finishState, boolean isStartLw) throws Exception { createDeviceProfile(transportConfiguration); final Device device = createDevice(deviceCredentials, endpoint); device.getId().getId().toString(); - createNewClient(security, coapConfig, true, endpoint, isBootstrap, null); + createNewClient(security, securityBs, coapConfig, true, endpoint); lwM2MTestClient.start(isStartLw); - awaitObserveReadAll(0, isBootstrap, device.getId().getId().toString()); + if (isAwaitObserveReadAll) { + awaitObserveReadAll(0, device.getId().getId().toString()); + } await(awaitAlias) .atMost(40, TimeUnit.SECONDS) .until(() -> { @@ -220,34 +223,31 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); this.basicTestConnectionBootstrapRequestTrigger( SECURITY_NO_SEC, + SECURITY_NO_SEC_BS, deviceCredentials, COAP_CONFIG, clientEndpoint, transportConfiguration, awaitAlias, expectedStatusesRegistrationLwm2mSuccess, - expectedStatusesRegistrationBsSuccess, - false, - SECURITY_NO_SEC_BS); + expectedStatusesRegistrationBsSuccess); } - private void basicTestConnectionBootstrapRequestTrigger(Security security, + private void basicTestConnectionBootstrapRequestTrigger(Security security, Security securityBs, LwM2MDeviceCredentials deviceCredentials, Configuration coapConfig, String endpoint, Lwm2mDeviceProfileTransportConfiguration transportConfiguration, String awaitAlias, Set expectedStatusesLwm2m, - Set expectedStatusesBs, - boolean isBootstrap, - Security securityBs) throws Exception { + Set expectedStatusesBs) throws Exception { createDeviceProfile(transportConfiguration); final Device device = createDevice(deviceCredentials, endpoint); String deviceIdStr = device.getId().getId().toString(); - createNewClient(security, coapConfig, true, endpoint, isBootstrap, securityBs); + createNewClient(security, securityBs, coapConfig, true, endpoint); lwM2MTestClient.start(true); - awaitObserveReadAll(0, isBootstrap, deviceIdStr); + awaitObserveReadAll(0, deviceIdStr); await(awaitAlias) .atMost(40, TimeUnit.SECONDS) .until(() -> { @@ -263,7 +263,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatusesLwm2m)); String executedPath = "/" + OBJECT_ID_1 + "_" + lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_1).version - + "/0/" + RESOURCE_ID_9; + + "/" +serverId + "/" + RESOURCE_ID_9; lwM2MTestClient.setClientStates(new HashSet<>()); String actualResult = sendRPCSecurityExecuteById(executedPath, deviceIdStr, endpoint); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); @@ -337,7 +337,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M default: throw new IllegalStateException("Unexpected value: " + mode); } - bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs : shortServerId); + bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs0 : shortServerId); bootstrapServerCredential.setBootstrapServerIs(isBootstrap); bootstrapServerCredential.setHost(isBootstrap ? hostBs : host); bootstrapServerCredential.setPort(isBootstrap ? securityPortBs : securityPort); @@ -443,3 +443,4 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); } } + diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java index 49c28cc13c..33e2965d62 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java @@ -19,7 +19,6 @@ import org.junit.Test; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOOTSTRAP_ONLY; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH; @@ -39,7 +38,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationT // Bootstrap + Lwm2m @Test public void testWithNoSecConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS; + String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + BOTH.name(); String awaitAlias = "await on client state (NoSecBS two section)"; basicTestConnectionBefore(clientEndpoint, awaitAlias, BOTH, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS); } @@ -51,20 +50,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationT basicTestConnectionBefore(clientEndpoint, awaitAlias, LWM2M_ONLY, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS); } - @Test - public void testWithNoSecConnectBsSuccess_UpdateBootstrapSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC + BOOTSTRAP_ONLY.name(); - String awaitAlias = "await on client state (NoSecBS Bootstrap section)"; - basicTestConnectionBefore(clientEndpoint, awaitAlias, BOOTSTRAP_ONLY, expectedStatusesBsSuccess, ON_BOOTSTRAP_SUCCESS); - } - - @Test - public void testWithNoSecConnectBsSuccess_UpdateNoneSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_NO_SEC + NONE.name(); - String awaitAlias = "await on client state (NoSecBS None section)"; - basicTestConnectionBefore(clientEndpoint, awaitAlias, NONE, expectedStatusesBsSuccess, ON_BOOTSTRAP_SUCCESS); - } - + // Bs trigger @Test public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateTwoSectionAndLm2m_ConnectLwm2mSuccess() throws Exception { String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOTH.name(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java index f041f0be55..a6211b2594 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java @@ -55,13 +55,14 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); this.basicTestConnection(security, + null, deviceCredentials, COAP_CONFIG, clientEndpoint, transportConfiguration, "await on client state (Psk_Lwm2m)", expectedStatusesRegistrationLwm2mSuccess, - false, + true, ON_REGISTRATION_SUCCESS, true); } @@ -100,15 +101,15 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes Hex.decodeHex(keyPsk.toCharArray())); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, BOTH)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); - this.basicTestConnection(securityBs, + this.basicTestConnection(null, securityBs, deviceCredentials, COAP_CONFIG_BS, clientEndpoint, transportConfiguration, "await on client state (PskBS two section)", expectedStatusesRegistrationBsSuccess, - true, + false, ON_REGISTRATION_SUCCESS, - true); + false); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java index c75ecbd820..73176b1646 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java @@ -49,21 +49,21 @@ public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTes RPKClientCredential clientCredentials = new RPKClientCredential(); clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setKey(Base64.encodeBase64String(certificate.getPublicKey().getEncoded())); - Security securityBs = rpk(SECURE_URI, + Security security = rpk(SECURE_URI, shortServerId, certificate.getPublicKey().getEncoded(), privateKey.getEncoded(), serverX509Cert.getPublicKey().getEncoded()); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, false); - this.basicTestConnection(securityBs, + this.basicTestConnection(security, null, deviceCredentials, COAP_CONFIG, clientEndpoint, transportConfiguration, "await on client state (Rpk_Lwm2m)", expectedStatusesRegistrationLwm2mSuccess, - false, + true, ON_REGISTRATION_SUCCESS, true); } @@ -117,14 +117,14 @@ public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTes serverX509CertBs.getPublicKey().getEncoded()); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, BOTH)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, clientPrivateKeyFromCertTrust, certificate, RPK, false); - this.basicTestConnection(securityBs, + this.basicTestConnection(null, securityBs, deviceCredentials, COAP_CONFIG_BS, clientEndpoint, transportConfiguration, "await on client state (RpkBS two section)", expectedStatusesRegistrationBsSuccess, - true, + false, ON_REGISTRATION_SUCCESS, true); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java index 1a908bffb8..5970f16958 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java @@ -57,13 +57,14 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); this.basicTestConnection(security, + null, deviceCredentials, COAP_CONFIG, clientEndpoint, transportConfiguration, "await on client state (X509_Trust_Lwm2m)", expectedStatusesRegistrationLwm2mSuccess, - false, + true, ON_REGISTRATION_SUCCESS, true); } @@ -118,13 +119,14 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, BOTH)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); this.basicTestConnection(security, + null, deviceCredentials, COAP_CONFIG_BS, clientEndpoint, transportConfiguration, "await on client state (X509NoTrust two section)", expectedStatusesRegistrationBsSuccess, - true, + false, ON_REGISTRATION_SUCCESS, true); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java index 98a5d018bc..d07d0c1df6 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java @@ -51,13 +51,14 @@ public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegra Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); this.basicTestConnection(security, + null, deviceCredentials, COAP_CONFIG, clientEndpoint, transportConfiguration, "await on client state (X509_Trust_Lwm2m)", expectedStatusesRegistrationLwm2mSuccess, - false, + true, ON_REGISTRATION_SUCCESS, true); } @@ -78,13 +79,14 @@ public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegra Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, BOTH)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); this.basicTestConnection(security, + null, deviceCredentials, COAP_CONFIG_BS, clientEndpoint, transportConfiguration, "await on client state (X509Trust two section)", expectedStatusesRegistrationBsSuccess, - true, + false, ON_REGISTRATION_SUCCESS, true); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java index b59c5fadea..8644ff1b79 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java @@ -26,7 +26,7 @@ public class LwM2MServerSecurityConfig implements Serializable { @Schema(description = "Server short Id. Used as link to associate server Object Instance. This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. " + "This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'. " + - "The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.", example = "123", accessMode = Schema.AccessMode.READ_ONLY) + "The values ID:1 and ID:65534 values MUST NOT be used for identifying the LwM2M Server.", example = "123", accessMode = Schema.AccessMode.READ_ONLY) protected Integer shortServerId = 123; /** Security -> ObjectId = 0 'LWM2M Security' */ @Schema(description = "Is Bootstrap Server or Lwm2m Server. " + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java index 7c1b9307c5..1a84f45b7d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java @@ -17,8 +17,6 @@ package org.thingsboard.server.common.data.util; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.LwM2m; -import org.eclipse.leshan.core.model.DDFFileValidator; -import org.eclipse.leshan.core.model.DefaultDDFFileValidator; import org.eclipse.leshan.core.model.InvalidDDFFileException; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; @@ -41,7 +39,7 @@ import java.util.Map; @Slf4j public class TbDDFFileParser { - private static final DDFFileValidator ddfFileValidator = new DefaultDDFFileValidator(); + private static final TbDefaultDDFFileValidator ddfFileValidator = new TbDefaultDDFFileValidator(); public List parse(InputStream inputStream, String streamName) throws InvalidDDFFileException, IOException { @@ -269,4 +267,4 @@ public class TbDDFFileParser { } return new ResourceModel(id, name, operations, multiple, mandatory, type, rangeEnumeration, units, description); } -} \ No newline at end of file +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDefaultDDFFileValidator.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDefaultDDFFileValidator.java new file mode 100644 index 0000000000..36ed0f668d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/TbDefaultDDFFileValidator.java @@ -0,0 +1,129 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.util; + + +import org.eclipse.leshan.core.LwM2m.LwM2mVersion; +import org.eclipse.leshan.core.model.DDFFileValidator; +import org.eclipse.leshan.core.model.InvalidDDFFileException; +import org.eclipse.leshan.core.util.Validate; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; + +import javax.xml.XMLConstants; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import java.io.IOException; +import java.io.InputStream; + +/** + * A DDF File Validator. + *

+ * Validate a DDF File against the embedded LWM2M schema. + *

+ * Support LWM2M version 1.0 and 1.1. + */ + +public class TbDefaultDDFFileValidator implements DDFFileValidator { + private static String LWM2M_V1_0_SCHEMA_PATH = "/schemas/LWM2M.xsd"; + private static String LWM2M_V1_1_SCHEMA_PATH = "/schemas/LWM2M-v1_1.xsd"; + + private final String schema; + + /** + * Create a {@link DDFFileValidator} using the LWM2M v1.1 schema. + */ + public TbDefaultDDFFileValidator() { + this(LwM2mVersion.V1_1); + } + + /** + * Create a {@link DDFFileValidator} using schema corresponding to LWM2M {@link LwM2mVersion}. + */ + public TbDefaultDDFFileValidator(LwM2mVersion version) { + Validate.notNull(version, "version must not be null"); + if (LwM2mVersion.V1_0.equals(version)) { + schema = LWM2M_V1_0_SCHEMA_PATH; + } else if (LwM2mVersion.V1_1.equals(version)) { + schema = LWM2M_V1_1_SCHEMA_PATH; + } else { + throw new IllegalStateException(String.format("Unsupported version %s", version)); + } + } + + @Override + public void validate(Node xmlToValidate) throws InvalidDDFFileException { + try { + validate(new DOMSource(xmlToValidate)); + } catch (SAXException | IOException e) { + throw new InvalidDDFFileException(e); + } + } + + /** + * Validate a XML {@link Source} against the embedded LWM2M Schema. + * + * @param xmlToValidate an XML source to validate + * @throws SAXException see {@link Validator#validate(Source)} + * @throws IOException see {@link Validator#validate(Source)} + */ + public void validate(Source xmlToValidate) throws SAXException, IOException { + Validator validator = getEmbeddedLwM2mSchema().newValidator(); + validator.validate(xmlToValidate); + } + + /** + * Get the Embedded the LWM2M.xsd Schema. + * + * @throws SAXException see {@link SchemaFactory#newSchema(Source)} + */ + protected Schema getEmbeddedLwM2mSchema() throws SAXException { + InputStream inputStream = DDFFileValidator.class.getResourceAsStream(schema); + Source source = new StreamSource(inputStream); + SchemaFactory schemaFactory = createSchemaFactory(); + return schemaFactory.newSchema(source); + } + + protected SchemaFactory createSchemaFactory() { + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); +// SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); + try { + // Create Safe SchemaFactory (not vulnerable to XXE Attacks) + // -------------------------------------------------------- + // There is several recommendation from different source we try to apply all, even if some are maybe + // redundant. + + // from : + // https://semgrep.dev/docs/cheat-sheets/java-xxe/ + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + + // from : + // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#schemafactory +// factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); +// factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + + } catch (SAXNotRecognizedException | SAXNotSupportedException e) { + throw new IllegalStateException("Unable to create SchemaFactory", e); + } + return factory; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 75a2f936ca..96a02bdf97 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -15,13 +15,20 @@ */ package org.thingsboard.server.transport.lwm2m.bootstrap; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.elements.config.Configuration; import org.eclipse.californium.scandium.config.DtlsConfig; -import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.bootstrap.LeshanBootstrapServerBuilder; +import org.eclipse.leshan.server.californium.bootstrap.LwM2mBootstrapPskStore; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.CaliforniumBootstrapServerEndpointsProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coap.CoapBootstrapServerProtocolProvider; +import org.eclipse.leshan.server.californium.bootstrap.endpoint.coaps.CoapsBootstrapServerProtocolProvider; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.config.ssl.SslCredentials; @@ -33,16 +40,12 @@ import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MInMemoryBoots import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; +import java.net.InetSocketAddress; import java.security.cert.X509Certificate; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT; -import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_ROLE; -import static org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole.SERVER_ONLY; import static org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService.PSK_CIPHER_SUITES; import static org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService.RPK_OR_X509_CIPHER_SUITES; import static org.thingsboard.server.transport.lwm2m.server.LwM2MNetworkConfig.getCoapConfig; @@ -82,24 +85,121 @@ public class LwM2MTransportBootstrapService { public LeshanBootstrapServer getLhBootstrapServer() { LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder(); - builder.setLocalAddress(bootstrapConfig.getHost(), bootstrapConfig.getPort()); - builder.setLocalSecureAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort()); - /* Create CoAP Config */ - builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); + // Create Californium Endpoints Provider: + // ------------------ + // Create Server Endpoints Provider + CaliforniumBootstrapServerEndpointsProvider.Builder endpointsBuilder = new CaliforniumBootstrapServerEndpointsProvider.Builder( + // Add coap Protocol support + new CoapBootstrapServerProtocolProvider(), + + // Add coaps/dtls protocol support + new CoapsBootstrapServerProtocolProvider(c -> { + if (this.bootstrapConfig.getSslCredentials() != null) { + c.setAdvancedCertificateVerifier(certificateVerifier); + c.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, RPK_OR_X509_CIPHER_SUITES); + } else { + log.info("Unable to load X509 files for LWM2MServer"); + LwM2mBootstrapPskStore lwM2mBsPskStore = new LwM2mBootstrapPskStore(lwM2MBootstrapSecurityStore); + c.setAdvancedPskStore(lwM2mBsPskStore); + c.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, PSK_CIPHER_SUITES); + } + })); + + + // Create Californium Configuration + Configuration serverCoapConfig = endpointsBuilder.createDefaultConfiguration(); + getCoapConfig(serverCoapConfig, bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(),serverConfig); + serverCoapConfig.setTransient(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); + serverCoapConfig.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, serverConfig.isRecommendedCiphers()); + serverCoapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); + int cid = 6; + if (cid > 0) { + serverCoapConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, cid); + } + + serverCoapConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups()); + + serverCoapConfig.setTransient(DTLS_RETRANSMISSION_TIMEOUT); + serverCoapConfig.set(DTLS_RETRANSMISSION_TIMEOUT, serverConfig.getDtlsRetransmissionTimeout(), MILLISECONDS); + + + + /* Create DTLS Config */ + this.setServerWithCredentials(builder); +// DtlsConnectorConfig dtlsConfig; +// try { +// dtlsConfig = dtlsConfigBuilder.build(); +// } catch (IllegalStateException e) { +// log.warn("Unable to create DTLS config for endpont {}.", endpointUri.toString(), e); +// return null; +// } +// +// Connector dTLSConnector = new DTLSConnector(dtlsConfig); - /* Create and Set DTLS Config */ - DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); +// endpointsBuilder.setConnector(dTLSConnector); +// endpointsBuilder.setConfiguration(serverCoapConfig); - dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups()); - dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, serverConfig.isRecommendedCiphers()); - dtlsConfig.set(DTLS_RETRANSMISSION_TIMEOUT, serverConfig.getDtlsRetransmissionTimeout(), MILLISECONDS); - dtlsConfig.set(DTLS_ROLE, SERVER_ONLY); - setServerWithCredentials(builder, dtlsConfig); +// endpointsBuilder.setLoggingTag(String.format("[%s]", "/" + "options.getUriPathString()")); +// endpointsBuilder.setEndpointContextMatcher(new Lwm2mEndpointContextMatcher()); - /* Set DTLS Config */ - builder.setDtlsConfig(dtlsConfig); + /* Create credentials */ + + + + + // Set Californium Configuration + endpointsBuilder.setConfiguration(serverCoapConfig); + + + // Create CoAP endpoint + InetSocketAddress coapAddr = new InetSocketAddress(bootstrapConfig.getHost(), bootstrapConfig.getPort()); + endpointsBuilder.addEndpoint(coapAddr, Protocol.COAP); + + // Create CoAP over DTLS endpoint + InetSocketAddress coapsAddr = new InetSocketAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort()); + endpointsBuilder.addEndpoint(coapsAddr, Protocol.COAPS); + + +// builder.setLocalAddress(config.getHost(), config.getPort()); +// builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort()); +// builder.setDecoder(new DefaultLwM2mDecoder()); + /* Use a magic converter to support bad type send by the UI. */ +// builder.setEncoder(new DefaultLwM2mEncoder(LwM2mValueConverterImpl.getInstance())); + + /* Create CoAP Config */ +// builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); + +// +// /* Set securityStore with new registrationStore */ +// builder.setSecurityStore(securityStore); +// builder.setRegistrationStore(registrationStore); +// +// +// // Create LWM2M server +// builder.setEndpointsProviders(endpointsBuilder.build()); +// +// +// +// builder.setLocalAddress(bootstrapConfig.getHost(), bootstrapConfig.getPort()); +// builder.setLocalSecureAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort()); +// +// /* Create CoAP Config */ +// builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); +// +// +// /* Create and Set DTLS Config */ +// DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); +// +// dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups()); +// dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, serverConfig.isRecommendedCiphers()); +// dtlsConfig.set(DTLS_RETRANSMISSION_TIMEOUT, serverConfig.getDtlsRetransmissionTimeout(), MILLISECONDS); +// dtlsConfig.set(DTLS_ROLE, SERVER_ONLY); +// setServerWithCredentials(builder, dtlsConfig); +// +// /* Set DTLS Config */ +// builder.setDtlsConfig(dtlsConfig); /* Set securityStore with new ConfigStore */ builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); @@ -112,22 +212,19 @@ public class LwM2MTransportBootstrapService { builder.setSessionManager(sessionManager); /* Create BootstrapServer */ + builder.setEndpointsProviders(endpointsBuilder.build()); return builder.build(); } - private void setServerWithCredentials(LeshanBootstrapServerBuilder builder, DtlsConnectorConfig.Builder dtlsConfig) { + private void setServerWithCredentials(LeshanBootstrapServerBuilder builder) { if (this.bootstrapConfig.getSslCredentials() != null) { SslCredentials sslCredentials = this.bootstrapConfig.getSslCredentials(); builder.setPublicKey(sslCredentials.getPublicKey()); builder.setPrivateKey(sslCredentials.getPrivateKey()); builder.setCertificateChain(sslCredentials.getCertificateChain()); - dtlsConfig.setAdvancedCertificateVerifier(certificateVerifier); - dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, RPK_OR_X509_CIPHER_SUITES); } else { /* by default trust all */ builder.setTrustedCertificates(new X509Certificate[0]); - log.info("Unable to load X509 files for BootStrap Server"); - dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, PSK_CIPHER_SUITES); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java index ea3b8e60c9..03b99f12c9 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java @@ -16,10 +16,13 @@ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.peer.IpPeer; +import org.eclipse.leshan.core.peer.LwM2mPeer; +import org.eclipse.leshan.core.peer.PskIdentity; +import org.eclipse.leshan.core.peer.X509Identity; import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; import org.eclipse.leshan.core.request.BootstrapFinishRequest; import org.eclipse.leshan.core.request.BootstrapRequest; -import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.BootstrapFailureCause; @@ -39,6 +42,7 @@ import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecu import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapTaskProvider; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -63,7 +67,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession * @param bsSecurityStore the {@link BootstrapSecurityStore} used by default {@link SecurityChecker}. */ public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, BootstrapConfigStore configStore, TransportService transportService) { - this(bsSecurityStore, new SecurityChecker(), new LwM2MBootstrapConfigStoreTaskProvider(configStore), + this(bsSecurityStore, configStore, new SecurityChecker(), new LwM2MBootstrapConfigStoreTaskProvider(configStore), new StandardBootstrapModelProvider()); this.transportService = transportService; } @@ -74,9 +78,9 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession * @param bsSecurityStore the {@link BootstrapSecurityStore} used by {@link SecurityChecker}. * @param securityChecker used to accept or refuse new {@link BootstrapSession}. */ - public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker, + public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, BootstrapConfigStore configStore, SecurityChecker securityChecker, LwM2MBootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) { - super(bsSecurityStore, securityChecker, tasksProvider, modelProvider); + super(bsSecurityStore, configStore); this.bsSecurityStore = bsSecurityStore; this.securityChecker = securityChecker; this.tasksProvider = tasksProvider; @@ -84,23 +88,24 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession } @Override - public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) { - boolean authorized = true; + public BootstrapSession begin(BootstrapRequest request, LwM2mPeer sender, URI endpointUsed) { + boolean authorized = sender.getIdentity().isSecure(); Iterator securityInfos = null; - try { + try { if (bsSecurityStore != null && securityChecker != null) { - if (clientIdentity.isPSK()) { - SecurityInfo securityInfo = bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity()); + if (((IpPeer) sender).isPSK()) { + SecurityInfo securityInfo = bsSecurityStore.getByIdentity(((PskIdentity) sender.getIdentity()).getPskIdentity()); securityInfos = Collections.singletonList(securityInfo).iterator(); - } else if (!clientIdentity.isX509()) { + } + else if (!((IpPeer) sender).isX509()) { securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName()); } - authorized = this.checkSecurityInfo(request.getEndpointName(), clientIdentity, securityInfos); + authorized = this.checkSecurityInfo(request.getEndpointName(), sender, securityInfos); } } catch (LwM2MAuthException e) { authorized = false; } - DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized); + DefaultBootstrapSession session = new DefaultBootstrapSession(request, sender, authorized, null, endpointUsed); if (authorized) { try { this.tasksProvider.put(session.getEndpoint()); @@ -239,9 +244,9 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession transportService.log(((LwM2MBootstrapSecurityStore) bsSecurityStore).getSessionByEndpoint(endpointName), logMsg); } - private boolean checkSecurityInfo(String endpoint, Identity clientIdentity, Iterator securityInfos) { - if (clientIdentity.isX509()) { - return clientIdentity.getX509CommonName().equals(endpoint) + private boolean checkSecurityInfo(String endpoint, LwM2mPeer clientIdentity, Iterator securityInfos) { + if (((IpPeer) clientIdentity).isX509()) { + return ((X509Identity)clientIdentity.getIdentity()).getX509CommonName().equals(endpoint) & ((LwM2MBootstrapSecurityStore) bsSecurityStore).getBootstrapConfigByEndpoint(endpoint) != null; } else { return securityChecker.checkSecurityInfos(endpoint, clientIdentity, securityInfos); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java index 53d72521b0..018648100d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java @@ -76,7 +76,8 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask @Override public Tasks getTasks(BootstrapSession session, List previousResponse) { - BootstrapConfig config = store.get(session.getEndpoint(), session.getIdentity(), session); +// BootstrapConfig config = store.get(session.getEndpoint(), session.getClientTransportData().getIdentity(), session); + BootstrapConfig config = store.get(session); if (config == null) { return null; } @@ -150,7 +151,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask * The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server. * "Short Server ID": * - Link Instance (lwm2m Server) hase linkParams with key = "ssid" value = "shortId" (ver lvm2m = 1.1). - * - Link Instance (bootstrap Server) hase not linkParams with key = "ssid" (ver lvm2m = 1.1). + * - Link Instance (bootstrap Server) hase not linkParams with key = "ssid" (ver lvm2m = 1.0). */ protected void findSecurityInstanceId(Link[] objectLinks, String endpoint) { log.info("Object after discover: [{}]", objectLinks); @@ -159,14 +160,14 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask try { LwM2mPath path = new LwM2mPath(link.getUriReference()); if (path.isObjectInstance()) { - if (link.getLinkParams().containsKey("ssid")) { - int serverId = Integer.parseInt(link.getLinkParams().get("ssid").getUnquoted()); + if (link.getAttributes().get("ssid") != null) { + int serverId = Integer.parseInt(link.getAttributes().get("ssid").getCoreLinkValue()); if (!lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(serverId)) { lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(serverId, path.getObjectInstanceId()); } else { log.error("Invalid lwm2mSecurityInstance by [{}]", path.getObjectInstanceId()); } - lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(Integer.valueOf(link.getLinkParams().get("ssid").getUnquoted()), path.getObjectInstanceId()); + lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(serverId, path.getObjectInstanceId()); } else { if (!this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(0)) { this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(BOOTSTRAP_DEFAULT_SHORT_ID, path.getObjectInstanceId()); @@ -221,7 +222,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTask LwM2mPath path = new LwM2mPath(link.getUriReference()); if (!path.isRoot() && path.getObjectId() < 3) { if (path.isObject()) { - String ver = link.getLinkParams().get("ver") != null ? link.getLinkParams().get("ver").getUnquoted() : "1.0"; + String ver = link.getAttributes().get("ver") != null ? link.getAttributes().get("ver").getCoreLinkValue() : "1.0"; this.supportedObjects.put(path.getObjectId(), ver); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java index 5dd4e41c3e..be4d0b15ad 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.bootstrap.store; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.peer.OscoreIdentity; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.bootstrap.EditableBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; @@ -98,6 +99,11 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } } + @Override + public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { + return null; + } + public TbLwM2MSecurityInfo getX509ByEndpoint(String endPoint) { TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, BOOTSTRAP); this.addValueToStore(store, store.getEndpoint()); @@ -210,4 +216,4 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } return securityInfo; } -} \ No newline at end of file +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java index 3d3a96ac9e..1466012e2b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java @@ -58,17 +58,25 @@ public class LwM2MConfigurationChecker extends ConfigurationChecker { for (Map.Entry e : config.servers.entrySet()) { BootstrapConfig.ServerConfig srvCfg = e.getValue(); - // shortId checks - if (srvCfg.shortId == 0) { - throw new InvalidConfigurationException("short ID must not be 0"); - } - // look for security entry BootstrapConfig.ServerSecurity security = getSecurityEntry(config, srvCfg.shortId); - if (security == null) { throw new InvalidConfigurationException("no security entry for server instance: " + e.getKey()); } + // BS Server + if (security.bootstrapServer && srvCfg.shortId != 0) { + throw new InvalidConfigurationException("short ID must be 0"); + } + + // LwM2M Server + /** + * This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. + * This Resource MUST be set when the Bootstrap-Server Resource has false value. + * Specific ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server (Section 6.3 of the LwM2M version 1.0 specification). + */ + if (!security.bootstrapServer && (srvCfg.shortId < 1 && srvCfg.shortId > 65534 )) { + throw new InvalidConfigurationException("Specific ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server"); + } } } @@ -81,4 +89,4 @@ public class LwM2MConfigurationChecker extends ConfigurationChecker { return null; } -} \ No newline at end of file +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java index bf2cc003a0..62339211bd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java @@ -16,9 +16,7 @@ package org.thingsboard.server.transport.lwm2m.bootstrap.store; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; -import org.eclipse.leshan.server.bootstrap.BootstrapSession; import org.eclipse.leshan.server.bootstrap.ConfigurationChecker; import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; @@ -39,8 +37,8 @@ public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigSt private final Lock writeLock = readWriteLock.writeLock(); protected final ConfigurationChecker configChecker = new LwM2MConfigurationChecker(); - @Override - public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) { + + public BootstrapConfig get(String endpoint) { return bootstrapByEndpoint.get(endpoint); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index 63b2a60e8e..4a3eccd664 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -67,14 +67,14 @@ public class LwM2mCredentialsSecurityInfoValidator { new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { - log.trace("Validated credentials: [{}] [{}]", credentialsId, msg); + log.info("Validated credentials: [{}] [{}]", credentialsId, msg); resultSecurityStore[0] = createSecurityInfo(credentialsId, msg, keyValue); latch.countDown(); } @Override public void onError(Throwable e) { - log.trace("[{}] [{}] Failed to process credentials ", credentialsId, e); + log.info("[{}] [{}] Failed to process credentials ", credentialsId, e); TbLwM2MSecurityInfo result = new TbLwM2MSecurityInfo(); result.setEndpoint(credentialsId); resultSecurityStore[0] = result; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java index 7cdbe609e1..573e2fa1e7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java @@ -18,9 +18,11 @@ package org.thingsboard.server.transport.lwm2m.secure; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; -import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.peer.LwM2mPeer; +import org.eclipse.leshan.core.peer.X509Identity; import org.eclipse.leshan.core.request.UplinkRequest; import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.security.Authorization; import org.eclipse.leshan.server.security.Authorizer; import org.eclipse.leshan.server.security.SecurityChecker; import org.eclipse.leshan.server.security.SecurityInfo; @@ -45,38 +47,30 @@ public class TbLwM2MAuthorizer implements Authorizer { private final LwM2mClientContext clientContext; @Override - public Registration isAuthorized(UplinkRequest request, Registration registration, Identity senderIdentity) { - if (senderIdentity.isX509()) { - TbX509DtlsSessionInfo sessionInfo = sessionStorage.get(registration.getEndpoint()); - if (sessionInfo != null) { - if (sessionInfo.getX509CommonName().endsWith(senderIdentity.getX509CommonName())) { - clientContext.registerClient(registration, sessionInfo.getCredentials()); + public Authorization isAuthorized(UplinkRequest request, Registration registration, LwM2mPeer sender) { + SecurityInfo expectedSecurityInfo = null; + if (securityStore != null) expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint()); + if (securityChecker.checkSecurityInfo(registration.getEndpoint(), sender, expectedSecurityInfo)) { + if (sender.getIdentity() instanceof X509Identity) { + TbX509DtlsSessionInfo sessionInfo = sessionStorage.get(registration.getEndpoint()); + if (sessionInfo != null) { // X509 certificate is valid and matches endpoint. - return registration; - } else { - // X509 certificate is not valid. - return null; + clientContext.registerClient(registration, sessionInfo.getCredentials()); } } - // If session info is not found, this may be the trusted certificate, so we still need to check all other options below. - } - SecurityInfo expectedSecurityInfo; try { - expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint()); if (expectedSecurityInfo != null && expectedSecurityInfo.usePSK() && expectedSecurityInfo.getEndpoint().equals(SecurityMode.NO_SEC.toString()) - && expectedSecurityInfo.getIdentity().equals(SecurityMode.NO_SEC.toString()) + && expectedSecurityInfo.getPskIdentity().equals(SecurityMode.NO_SEC.toString()) && Arrays.equals(SecurityMode.NO_SEC.toString().getBytes(), expectedSecurityInfo.getPreSharedKey())) { - expectedSecurityInfo = null; + return Authorization.declined(); } } catch (LwM2MAuthException e) { log.info("Registration failed: FORBIDDEN, endpointId: [{}]", registration.getEndpoint()); - return null; + return Authorization.declined(); } - if (securityChecker.checkSecurityInfo(registration.getEndpoint(), senderIdentity, expectedSecurityInfo)) { - return registration; + return Authorization.approved(); } else { - securityStore.remove(registration.getEndpoint(), registration.getId()); - return null; + return Authorization.declined(); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java index d757c91648..dd387f5264 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java @@ -18,12 +18,13 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.leshan.core.californium.LwM2mCoapResource; +import org.eclipse.leshan.core.californium.identity.IdentityHandlerProvider; @Slf4j public abstract class AbstractLwM2mTransportResource extends LwM2mCoapResource { public AbstractLwM2mTransportResource(String name) { - super(name); + super(name, new IdentityHandlerProvider()); } @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 5fb36f6f55..5c2045f61a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -15,16 +15,22 @@ */ package org.thingsboard.server.transport.lwm2m.server; +import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.elements.config.Configuration; import org.eclipse.californium.scandium.config.DtlsConfig; -import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; +import org.eclipse.leshan.core.endpoint.Protocol; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; -import org.eclipse.leshan.server.californium.LeshanServer; -import org.eclipse.leshan.server.californium.LeshanServerBuilder; -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; +import org.eclipse.leshan.server.LeshanServer; +import org.eclipse.leshan.server.LeshanServerBuilder; +import org.eclipse.leshan.server.californium.LwM2mPskStore; +import org.eclipse.leshan.server.californium.endpoint.CaliforniumServerEndpointsProvider; +import org.eclipse.leshan.server.californium.endpoint.coap.CoapServerProtocolProvider; +import org.eclipse.leshan.server.californium.endpoint.coaps.CoapsServerProtocolProvider; +import org.eclipse.leshan.server.registration.RegistrationStore; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; import org.thingsboard.server.cache.ota.OtaPackageDataCache; @@ -37,13 +43,11 @@ import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier; import org.thingsboard.server.transport.lwm2m.server.store.TbSecurityStore; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; -import jakarta.annotation.PreDestroy; +import java.net.InetSocketAddress; import java.security.cert.X509Certificate; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_ROLE; @@ -64,12 +68,13 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { public static final CipherSuite[] RPK_OR_X509_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}; public static final CipherSuite[] PSK_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256}; +// public static final CipherSuite[] PSK_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8}; private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; private final OtaPackageDataCache otaPackageDataCache; private final LwM2mUplinkMsgHandler handler; - private final CaliforniumRegistrationStore registrationStore; + private final RegistrationStore registrationStore; private final TbSecurityStore securityStore; private final TbLwM2MDtlsCertificateVerifier certificateVerifier; private final TbLwM2MAuthorizer authorizer; @@ -88,7 +93,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { * "coap://host:port/{path}/{token}/{nameFile}" */ LwM2mTransportCoapResource otaCoapResource = new LwM2mTransportCoapResource(otaPackageDataCache, FIRMWARE_UPDATE_COAP_RESOURCE); - this.server.coap().getServer().add(otaCoapResource); +// this.server.coap().getServer().add(otaCoapResource); this.context.setServer(server); this.startLhServer(); } @@ -117,54 +122,128 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { private LeshanServer getLhServer() { LeshanServerBuilder builder = new LeshanServerBuilder(); - builder.setLocalAddress(config.getHost(), config.getPort()); - builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort()); - builder.setDecoder(new DefaultLwM2mDecoder()); - /* Use a magic converter to support bad type send by the UI. */ - builder.setEncoder(new DefaultLwM2mEncoder(LwM2mValueConverterImpl.getInstance())); - - /* Create CoAP Config */ - builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); /* Define model provider (Create Models )*/ builder.setObjectModelProvider(modelProvider); + /* Set securityStore with new registrationStore */ builder.setSecurityStore(securityStore); builder.setRegistrationStore(registrationStore); + + // Create Californium Endpoints Provider: + // ------------------ + // Create Server Endpoints Provider + CaliforniumServerEndpointsProvider.Builder endpointsBuilder = new CaliforniumServerEndpointsProvider.Builder( + // Add coap Protocol support + new CoapServerProtocolProvider(), + + // Add coaps/dtls protocol support + new CoapsServerProtocolProvider(c -> { + if (this.config.getSslCredentials() != null) { + c.setAdvancedCertificateVerifier(certificateVerifier); + c.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, RPK_OR_X509_CIPHER_SUITES); + } else { + log.info("Unable to load X509 files for LWM2MServer"); + LwM2mPskStore lwM2mPskStore = new LwM2mPskStore(securityStore, registrationStore); + c.setAdvancedPskStore(lwM2mPskStore); + c.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, PSK_CIPHER_SUITES); + } + })); + + // Create Californium Configuration + Configuration serverCoapConfig = endpointsBuilder.createDefaultConfiguration(); + getCoapConfig(serverCoapConfig, config.getPort(), config.getSecurePort(), config); + + // Set some DTLS stuff + + serverCoapConfig.setTransient(DTLS_RECOMMENDED_CURVES_ONLY); + serverCoapConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, config.isRecommendedSupportedGroups()); + + serverCoapConfig.setTransient(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); + serverCoapConfig.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, config.isRecommendedCiphers()); + + serverCoapConfig.set(DTLS_RETRANSMISSION_TIMEOUT, config.getDtlsRetransmissionTimeout(), MILLISECONDS); + serverCoapConfig.set(DTLS_ROLE, SERVER_ONLY); + serverCoapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); + /** + * "Control usage of DTLS connection ID.", // + * "- 'on' to activate Connection ID support ", // + * " (same as -cid 6)", // + * "- 'off' to deactivate it", // + * "- Positive value define the size in byte of CID generated.", // + * "- 0 value means we accept to use CID but will not generated one for foreign peer.", // + * "Default: on" + */ + int cid = 6; + serverCoapConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, cid); + + /* Create DTLS Config */ - DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(getCoapConfig(config.getPort(), config.getSecurePort(), config)); + this.setServerWithCredentials(builder); + + +// DtlsConnectorConfig dtlsConfig; +// try { +// dtlsConfig = dtlsConfigBuilder.build(); +// } catch (IllegalStateException e) { +// log.warn("Unable to create DTLS config for endpont {}.", endpointUri.toString(), e); +// return null; +// } +// +// Connector dTLSConnector = new DTLSConnector(dtlsConfig); + +// endpointsBuilder.setConnector(dTLSConnector); +// endpointsBuilder.setConfiguration(serverCoapConfig); - dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, config.isRecommendedSupportedGroups()); - dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, config.isRecommendedCiphers()); - dtlsConfig.set(DTLS_RETRANSMISSION_TIMEOUT, config.getDtlsRetransmissionTimeout(), MILLISECONDS); - dtlsConfig.set(DTLS_ROLE, SERVER_ONLY); +// endpointsBuilder.setLoggingTag(String.format("[%s]", "/" + "options.getUriPathString()")); +// endpointsBuilder.setEndpointContextMatcher(new Lwm2mEndpointContextMatcher()); /* Create credentials */ - this.setServerWithCredentials(builder, dtlsConfig); - /* Set DTLS Config */ - builder.setDtlsConfig(dtlsConfig); - /* Create LWM2M server */ + + + // Set Californium Configuration + endpointsBuilder.setConfiguration(serverCoapConfig); + + + // Create CoAP endpoint + InetSocketAddress coapAddr = new InetSocketAddress(config.getHost(), config.getPort()); + endpointsBuilder.addEndpoint(coapAddr, Protocol.COAP); + + // Create CoAP over DTLS endpoint + InetSocketAddress coapsAddr = new InetSocketAddress(config.getSecureHost(), config.getSecurePort()); + endpointsBuilder.addEndpoint(coapsAddr, Protocol.COAPS); + + + builder.setDecoder(new DefaultLwM2mDecoder(true)); + builder.setEncoder(new DefaultLwM2mEncoder(true)); +// builder.setDecoder(new DefaultLwM2mDecoder()); +// /* Use a magic converter to support bad type send by the UI. */ +// builder.setEncoder(new DefaultLwM2mEncoder(LwM2mValueConverterImpl.getInstance())); + + /* Create CoAP Config */ +// builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); + + + // Create LWM2M server + builder.setEndpointsProviders(endpointsBuilder.build()); return builder.build(); } - private void setServerWithCredentials(LeshanServerBuilder builder, DtlsConnectorConfig.Builder dtlsConfig) { + private void setServerWithCredentials(LeshanServerBuilder builder) { +// private void setServerWithCredentials(LeshanServerBuilder builder) { if (this.config.getSslCredentials() != null) { SslCredentials sslCredentials = this.config.getSslCredentials(); builder.setPublicKey(sslCredentials.getPublicKey()); builder.setPrivateKey(sslCredentials.getPrivateKey()); builder.setCertificateChain(sslCredentials.getCertificateChain()); - dtlsConfig.setAdvancedCertificateVerifier(certificateVerifier); builder.setAuthorizer(authorizer); - dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, RPK_OR_X509_CIPHER_SUITES); } else { /* by default trust all */ builder.setTrustedCertificates(new X509Certificate[0]); - log.info("Unable to load X509 files for LWM2MServer"); - dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, PSK_CIPHER_SUITES); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java index 569198f50f..eafe11d9dc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java @@ -27,8 +27,8 @@ import static org.eclipse.californium.core.config.CoapConfig.DEFAULT_BLOCKWISE_S public class LwM2MNetworkConfig { - public static Configuration getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort, LwM2MTransportServerConfig config) { - Configuration coapConfig = new Configuration(); + public static Configuration getCoapConfig(Configuration coapConfig, Integer serverPortNoSec, Integer serverSecurePort, LwM2MTransportServerConfig config) { +// Configuration coapConfig = new Configuration(); coapConfig.set(CoapConfig.COAP_PORT, serverPortNoSec); coapConfig.set(CoapConfig.COAP_SECURE_PORT, serverSecurePort); /** @@ -107,4 +107,4 @@ public class LwM2MNetworkConfig { return coapConfig; } -} \ No newline at end of file +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index b894861576..a29a37c766 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.node.LwM2mNode; +import org.eclipse.leshan.core.node.TimestampedLwM2mNodes; import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.SingleObservation; @@ -32,7 +32,6 @@ import org.eclipse.leshan.server.send.SendListener; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import java.util.Collection; -import java.util.Map; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVersionedId; @@ -128,10 +127,15 @@ public class LwM2mServerListener { public final SendListener sendListener = new SendListener() { @Override - public void dataReceived(Registration registration, Map map, SendRequest sendRequest) { + public void dataReceived(Registration registration, TimestampedLwM2mNodes data, SendRequest request) { if (registration != null) { - service.onUpdateValueWithSendRequest(registration, sendRequest); + service.onUpdateValueWithSendRequest(registration, request); } } + + @Override + public void onError(Registration registration, String errorMessage, Exception error) { + + } }; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java index f00d475a4b..91ae14108b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.Getter; import lombok.Setter; -import org.eclipse.leshan.server.californium.LeshanServer; +import org.eclipse.leshan.server.LeshanServer; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java index 3dd13cd171..c6716b6bf4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.model.DefaultDDFFileValidator; +import org.eclipse.leshan.core.LwM2m; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; @@ -107,7 +107,7 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider { @Override public ObjectModel getObjectModel(int objectId) { - String version = registration.getSupportedVersion(objectId); + String version = String.valueOf(registration.getSupportedVersion(objectId)); if (version != null) { return this.getObjectModelDynamic(objectId, version); } @@ -116,10 +116,10 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider { @Override public Collection getObjectModels() { - Map supportedObjects = this.registration.getSupportedObject(); + Map supportedObjects = this.registration.getSupportedObject(); Collection result = new ArrayList<>(supportedObjects.size()); - for (Map.Entry supportedObject : supportedObjects.entrySet()) { - ObjectModel objectModel = this.getObjectModelDynamic(supportedObject.getKey(), supportedObject.getValue()); + for (Map.Entry supportedObject : supportedObjects.entrySet()) { + ObjectModel objectModel = this.getObjectModelDynamic(supportedObject.getKey(), String.valueOf(supportedObject.getValue())); if (objectModel != null) { result.add(objectModel); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 9b63c2f2b9..b26d8f03e8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -20,7 +20,8 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.link.LinkParamValue; +import org.eclipse.leshan.core.LwM2m; +import org.eclipse.leshan.core.link.attributes.Attribute; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mMultipleResource; @@ -271,7 +272,7 @@ public class LwM2mClient { public ResourceModel getResourceModel(String pathIdVer, LwM2mModelProvider modelProvider) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); - String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); + String verSupportedObject = String.valueOf(registration.getSupportedObject().get(pathIds.getObjectId())); String verRez = getVerFromPathIdVerOrId(pathIdVer); return verRez != null && verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) .getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()) : null; @@ -289,7 +290,7 @@ public class LwM2mClient { public ObjectModel getObjectModel(String pathIdVer, LwM2mModelProvider modelProvider) { try { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); - String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); + String verSupportedObject = String.valueOf(registration.getSupportedObject().get(pathIds.getObjectId())); String verRez = getVerFromPathIdVerOrId(pathIdVer); return verRez != null && verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) .getObjectModel(pathIds.getObjectId()) : null; @@ -370,12 +371,12 @@ public class LwM2mClient { public String isValidObjectVersion(String path) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path)); - String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); + LwM2m.Version verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); if (verSupportedObject == null) { return String.format("Specified object id %s absent in the list supported objects of the client or is security object!", pathIds.getObjectId()); } else { String verRez = getVerFromPathIdVerOrId(path); - if (verRez == null || !verRez.equals(verSupportedObject)) { + if (verRez == null || !verRez.equals(verSupportedObject.toString())) { return String.format("Specified resource id %s is not valid version! Must be version: %s", path, verSupportedObject); } } @@ -432,16 +433,12 @@ public class LwM2mClient { private static Set clientSupportContentFormat(Registration registration) { Set contentFormats = new HashSet<>(); contentFormats.add(ContentFormat.DEFAULT); - LinkParamValue ct = Arrays.stream(registration.getObjectLinks()) + Attribute ct = Arrays.stream(registration.getObjectLinks()) .filter(link -> link.getUriReference().equals("/")) .findFirst() - .map(link -> link.getLinkParams().get("ct")).orElse(null); + .map(link -> link.getAttributes().get("ct")).orElse(null); if (ct != null) { - Set codes = Stream.of(ct.getUnquoted().replaceAll("\"", "").split(" ", -1)) - .map(String::trim) - .map(Integer::parseInt) - .map(ContentFormat::fromCode) - .collect(Collectors.toSet()); + Set codes = Stream.of(ct.getValue()).collect(Collectors.toSet()); contentFormats.addAll(codes); } return contentFormats; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index 09ecf500b6..e124b8142d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -15,12 +15,15 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.LwM2m; -import org.eclipse.leshan.core.attributes.Attribute; -import org.eclipse.leshan.core.attributes.AttributeSet; import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeModel; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeSet; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; @@ -75,8 +78,6 @@ import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogServic import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcWriteCompositeRequest; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; import java.util.Arrays; import java.util.Collection; import java.util.Date; @@ -86,15 +87,16 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; -import static org.eclipse.leshan.core.attributes.Attribute.GREATER_THAN; -import static org.eclipse.leshan.core.attributes.Attribute.LESSER_THAN; -import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD; -import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD; -import static org.eclipse.leshan.core.attributes.Attribute.STEP; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.GREATER_THAN; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.LESSER_THAN; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.MAXIMUM_PERIOD; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.MINIMUM_PERIOD; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.STEP; import static org.eclipse.leshan.core.model.ResourceModel.Type.OBJLNK; import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertMultiResourceValuesFromRpcBody; @@ -148,8 +150,9 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, - DownlinkRequestCallback callback, ContentFormat compositeContentFormat) { + DownlinkRequestCallback callback) { try { + ContentFormat compositeContentFormat = this.findFirstContentFormatForComposite(client.getClientSupportContentFormats()); ReadCompositeRequest downlink = new ReadCompositeRequest(compositeContentFormat, compositeContentFormat, request.getObjectIds()); sendCompositeRequest(client, downlink, this.config.getTimeout(), callback); } catch (InvalidRequestException e) { @@ -193,8 +196,8 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } @Override - public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback) { - callback.onSuccess(request, Arrays.asList(client.getRegistration().getSortedObjectLinks())); + public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback) { + callback.onSuccess(request, Arrays.stream(client.getRegistration().getSortedObjectLinks()).map(Link::toCoreLinkFormat).collect(Collectors.toList())); } @Override @@ -279,13 +282,13 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im throw new IllegalArgumentException("Attributes to write are not specified!"); } ObjectAttributes params = request.getAttributes(); - List attributes = new LinkedList<>(); + List> attributes = new LinkedList<>(); addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax()); addAttribute(attributes, MINIMUM_PERIOD, params.getPmin()); addAttribute(attributes, GREATER_THAN, params.getGt()); addAttribute(attributes, LESSER_THAN, params.getLt()); addAttribute(attributes, STEP, params.getSt()); - AttributeSet attributeSet = new AttributeSet(attributes); + LwM2mAttributeSet attributeSet = new LwM2mAttributeSet(attributes); sendSimpleRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback); } catch (InvalidRequestException e) { callback.onValidationError(request.toString(), e.getMessage()); @@ -338,9 +341,10 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest rpcWriteCompositeRequest, - DownlinkRequestCallback callback, ContentFormat contentFormatComposite) { + DownlinkRequestCallback callback) { try { - WriteCompositeRequest downlink = new WriteCompositeRequest(contentFormatComposite, rpcWriteCompositeRequest.getNodes()); + ContentFormat compositeContentFormat = this.findFirstContentFormatForComposite(client.getClientSupportContentFormats()); + WriteCompositeRequest downlink = new WriteCompositeRequest(compositeContentFormat, rpcWriteCompositeRequest.getNodes()); //TODO: replace config.getTimeout(); sendWriteCompositeRequest(client, downlink, this.config.getTimeout(), callback); } catch (InvalidRequestException e) { @@ -570,17 +574,18 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } } - private static void addAttribute(List attributes, String attributeName, T value) { - addAttribute(attributes, attributeName, value, null, null); + private static void addAttribute(List> attributes, LwM2mAttributeModel attribute, T value) { + addAttribute(attributes, attribute, value, null, null); } - private static void addAttribute(List attributes, String attributeName, T value, Function converter) { - addAttribute(attributes, attributeName, value, null, converter); + private static void addAttribute(List> attributes, LwM2mAttributeModel attribute, T value, Function converter) { + addAttribute(attributes, attribute, value, null, converter); } - private static void addAttribute(List attributes, String attributeName, T value, Predicate filter, Function converter) { + private static void addAttribute(List> attributes, LwM2mAttributeModel attributeName, T value, Predicate filter, Function converter) { if (value != null && ((filter == null) || filter.test(value))) { - attributes.add(new Attribute(attributeName, converter != null ? converter.apply(value) : value)); + T valueConvert = (T) converter != null ? (T) converter.apply(value) : value; + attributes.add(new LwM2mAttribute<>(attributeName, valueConvert)); } } @@ -620,7 +625,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } else if (OPAQUE.equals(resourceModel.type)) { return ContentFormat.OPAQUE; } else { - return findFirst(client.getClientSupportContentFormats(), client.getDefaultContentFormat(), ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON); + return findFirstContentFormatForComp(client.getClientSupportContentFormats(), client.getDefaultContentFormat(), ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON); } } else { return getContentFormatForComplex(client); @@ -634,7 +639,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im if (LwM2m.LwM2mVersion.V1_0.equals(client.getRegistration().getLwM2mVersion())) { return ContentFormat.TLV; } else if (LwM2m.LwM2mVersion.V1_1.equals(client.getRegistration().getLwM2mVersion())) { - ContentFormat result = findFirst(client.getClientSupportContentFormats(), null, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON, ContentFormat.TLV, ContentFormat.JSON); + ContentFormat result = findFirstContentFormatForComp(client.getClientSupportContentFormats(), null, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON, ContentFormat.TLV, ContentFormat.JSON); if (result != null) { return result; } else { @@ -644,16 +649,6 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im throw new RuntimeException("The version " + client.getRegistration().getLwM2mVersion() + " is not supported!"); } } - - private static ContentFormat findFirst(Set supported, ContentFormat defaultValue, ContentFormat... desiredFormats) { - for (ContentFormat contentFormat : desiredFormats) { - if (supported.contains(contentFormat)) { - return contentFormat; - } - } - return defaultValue; - } - private String toString(R request) { try { return request != null ? request.toString() : ""; @@ -662,4 +657,29 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im return request.getClass().getSimpleName(); } } + + private ContentFormat findFirstContentFormatForComposite (Set clientSupportContentFormats) { + ContentFormat contentFormat = findFirstContentFormatForComp(clientSupportContentFormats, null, ContentFormat.SENML_JSON, ContentFormat.SENML_CBOR); + if (contentFormat != null) { + return contentFormat; + } else { + throw new RuntimeException("This device does not support Composite Operation"); + } + } + private static ContentFormat findFirstContentFormatForComp(Set clientSupportContentFormats, ContentFormat defaultValue, ContentFormat... desiredFormats) { + AtomicReference compositeContentFormat = new AtomicReference<>(); + clientSupportContentFormats.forEach(c -> { + if (c instanceof Collection) { + for (ContentFormat contentFormat : desiredFormats) { + if (((Collection) c).contains(contentFormat)) { + compositeContentFormat.set(contentFormat); + break; + } + } + } else if (compositeContentFormat.get() == null && c instanceof ContentFormat) { + compositeContentFormat.set(Arrays.stream(desiredFormats).filter(f -> f.equals(c)).findFirst().get()); + } + }); + return compositeContentFormat.get() != null ? compositeContentFormat.get() : defaultValue; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java index 90d4006fb4..a9d49e6dae 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java @@ -15,8 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.eclipse.leshan.core.link.Link; -import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.request.DiscoverRequest; @@ -48,7 +46,7 @@ public interface LwM2mDownlinkMsgHandler { void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback callback); - void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback callback, ContentFormat contentFormatComposite); + void sendReadCompositeRequest(LwM2mClient client, TbLwM2MReadCompositeRequest request, DownlinkRequestCallback callback); void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback); @@ -64,13 +62,13 @@ public interface LwM2mDownlinkMsgHandler { void sendDiscoverRequest(LwM2mClient client, TbLwM2MDiscoverRequest request, DownlinkRequestCallback callback); - void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback); + void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback); void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback callback); void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback); - void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, DownlinkRequestCallback callback, ContentFormat contentFormatComposite); + void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, DownlinkRequestCallback callback); void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index 5fd20da37d..c725d4f070 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -23,7 +23,6 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; @@ -75,6 +74,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertMultiResourceValuesFromRpcBody; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertValueByTypeResource; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; @Slf4j @@ -155,22 +155,16 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); } } else if (operationType.isComposite()) { - ContentFormat contentFormatComposite = this.getCompositeContentFormat(client); - if (contentFormatComposite != null) { switch (operationType) { case READ_COMPOSITE: - sendReadCompositeRequest(client, rpcRequest, contentFormatComposite); + sendReadCompositeRequest(client, rpcRequest); break; case WRITE_COMPOSITE: - sendWriteCompositeRequest(client, rpcRequest, contentFormatComposite); + sendWriteCompositeRequest(client, rpcRequest); break; default: throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); } - } else { - this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), - ResponseCode.INTERNAL_SERVER_ERROR, "This device does not support Composite Operation"); - } } else { switch (operationType) { case OBSERVE_CANCEL_ALL: @@ -202,12 +196,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { downlinkHandler.sendReadRequest(client, request, rpcCallback); } - private void sendReadCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, ContentFormat contentFormatComposite) { + private void sendReadCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { String[] versionedIds = getIdsFromParameters(client, requestMsg); TbLwM2MReadCompositeRequest request = TbLwM2MReadCompositeRequest.builder().versionedIds(versionedIds).timeout(clientContext.getRequestTimeout(client)).build(); var mainCallback = new TbLwM2MReadCompositeCallback(uplinkHandler, logService, client, versionedIds); var rpcCallback = new RpcReadResponseCompositeCallback(transportService, client, requestMsg, mainCallback); - downlinkHandler.sendReadCompositeRequest(client, request, rpcCallback, contentFormatComposite); + downlinkHandler.sendReadCompositeRequest(client, request, rpcCallback); } private void sendObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { @@ -243,7 +237,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private void sendWriteAttributesRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { RpcWriteAttributesRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteAttributesRequest.class); - TbLwM2MWriteAttributesRequest request = TbLwM2MWriteAttributesRequest.builder().versionedId(versionedId) + TbLwM2MWriteAttributesRequest request = TbLwM2MWriteAttributesRequest.builder().versionedId(versionedId) .attributes(requestBody.getAttributes()) .timeout(clientContext.getRequestTimeout(client)).build(); var mainCallback = new TbLwM2MWriteAttributesCallback(logService, client, versionedId); @@ -298,54 +292,89 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { * nodes.put("/1/0/2", 100); * nodes.put("/5/0/1", "coap://localhost:5685"); */ - private void sendWriteCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, ContentFormat contentFormatComposite) { + private void sendWriteCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { RpcWriteCompositeRequest rpcWriteCompositeRequest = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteCompositeRequest.class); - Map validNodes = validateNodes(client, rpcWriteCompositeRequest.getNodes()); + Map validNodes = validateCompositesNodes(client, rpcWriteCompositeRequest.getNodes()); if (validNodes.size() > 0) { rpcWriteCompositeRequest.setNodes(validNodes); var mainCallback = new TbLwM2MWriteResponseCompositeCallback(uplinkHandler, logService, client, null); var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); - downlinkHandler.sendWriteCompositeRequest(client, rpcWriteCompositeRequest, rpcCallback, contentFormatComposite); + downlinkHandler.sendWriteCompositeRequest(client, rpcWriteCompositeRequest, rpcCallback); } else { throw new IllegalArgumentException(String.format("nodes: %s is not validate value", rpcWriteCompositeRequest.getNodes().toString())); } } - private Map validateNodes(LwM2mClient client, Map nodes) { + private Map validateCompositesNodes(LwM2mClient client, Map nodes) { Map newNodes = new LinkedHashMap<>(); nodes.forEach((key, value) -> { - String versionedId; + String versionedId = null; + LwM2mPath path = null; try { - LwM2mPath path = new LwM2mPath(fromVersionedIdToObjectId(key)); - if (path.isResource() || path.isResourceInstance()) { - versionedId = key; - } - else { - throw new IllegalArgumentException(String.format("nodes: %s is not validate value. " + - "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes.toString())); - } + path = new LwM2mPath(fromVersionedIdToObjectId(key)); } catch (Exception e) { versionedId = clientContext.getObjectIdByKeyNameFromProfile(client, key); } - // validate value. Must be only primitive, not JsonObject or JsonArray - try { - JsonElement element = JsonUtils.parse(value.toString()); - if (!element.isJsonNull() && !element.isJsonPrimitive()) { + + if (versionedId == null) { + if (path.isResourceInstance()) { + setValueToCompositeNodes(client, newNodes, nodes, key, value.toString()); + } else if (path.isResource()) { + validateResource(client, newNodes, nodes, key , value); + } else if (path.isObjectInstance() && value instanceof Map) { + ((Map) value).forEach((k, v) -> { + validateResource(client, newNodes, nodes, validateResourceId (key, k.toString(), nodes), v); + }); + } else { throw new IllegalArgumentException(String.format("nodes: %s is not validate value. " + - "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes.toString())); - } - else if (versionedId != null) { - newNodes.put(fromVersionedIdToObjectId(versionedId), value); - } - } catch (JsonSyntaxException jse) { - if (versionedId != null) { - newNodes.put(fromVersionedIdToObjectId(versionedId), value); + "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes)); } + } else { + setValueToCompositeNodes(client, newNodes, nodes, versionedId, value.toString()); } }); return newNodes; } + private void validateResource(LwM2mClient client, Map newNodes, Map nodes, String resourceId , Object value) { + if (value instanceof Map) { + ((Map) value).forEach((k, v) -> { + setValueToCompositeNodes(client, newNodes, nodes, validateResourceId (resourceId, k.toString(), nodes), v.toString()); + }); + } else { + setValueToCompositeNodes(client, newNodes, nodes, resourceId, value.toString()); + } + } + + private String validateResourceId (String key, String id, Map nodes) { + try { + Integer.parseInt(id); + return key + "/" + id; + } catch (NumberFormatException e) { + throw new IllegalArgumentException(String.format("nodes: %s is not validate value. " + + "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes.toString())); + } + } + + private void setValueToCompositeNodes (LwM2mClient client, Map newNodes, Map nodes, String versionedId , String value) { + // validate value. Must be only primitive, not JsonObject or JsonArray + try { + JsonElement element = JsonUtils.parse(value); + if (!element.isJsonNull() && !element.isJsonPrimitive()) { + throw new IllegalArgumentException(String.format("nodes: %s is not validate value. " + + "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes.toString())); + } + // convert value from JsonPrimitive() to resource/ResourceInstance type + ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); + Object newValue = convertValueByTypeResource(value, resourceModel.type, versionedId); + + // add new value after convert + newNodes.put(fromVersionedIdToObjectId(versionedId), newValue); + } catch (JsonSyntaxException jse) { + newNodes.put(fromVersionedIdToObjectId(versionedId), value); + } + } + private void sendCancelObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { TbLwM2MCancelObserveRequest downlink = TbLwM2MCancelObserveRequest.builder().versionedId(versionedId).timeout(clientContext.getRequestTimeout(client)).build(); var mainCallback = new TbLwM2MCancelObserveCallback(logService, client, versionedId); @@ -412,16 +441,4 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { log.info("[{}] toServerRpcResponse", toServerResponse); } - - private ContentFormat getCompositeContentFormat(LwM2mClient client) { - if (client.getClientSupportContentFormats().contains(ContentFormat.SENML_JSON)) { - return ContentFormat.SENML_JSON; - } - else if (client.getClientSupportContentFormats().contains(ContentFormat.SENML_CBOR)) { - return ContentFormat.SENML_CBOR; - } - else { - return null; - } - } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java index acb44fe55f..314d88fccb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java @@ -35,7 +35,7 @@ public class RpcDiscoverCallback extends RpcLwM2MDownlinkCallback serializeSuccessfulResponse(DiscoverResponse response) { - return Optional.of(serializer.serialize(response.getObjectLinks())); + return Optional.of(serializer.serializeCoreLinkFormat(response.getObjectLinks())); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java index cc96cea4b7..7709061e32 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.peer.OscoreIdentity; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; @@ -83,25 +84,30 @@ public class TbInMemorySecurityStore implements TbEditableSecurityStore { } } + @Override + public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { + return null; + } + @Override public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { writeLock.lock(); try { String identity = null; if (tbSecurityInfo.getSecurityInfo() != null) { - identity = tbSecurityInfo.getSecurityInfo().getIdentity(); + identity = tbSecurityInfo.getSecurityInfo().getPskIdentity(); if (identity != null) { TbLwM2MSecurityInfo infoByIdentity = securityByIdentity.get(identity); if (infoByIdentity != null && !tbSecurityInfo.getSecurityInfo().getEndpoint().equals(infoByIdentity.getEndpoint())) { throw new NonUniqueSecurityInfoException("PSK Identity " + identity + " is already used"); } - securityByIdentity.put(tbSecurityInfo.getSecurityInfo().getIdentity(), tbSecurityInfo); + securityByIdentity.put(tbSecurityInfo.getSecurityInfo().getPskIdentity(), tbSecurityInfo); } } TbLwM2MSecurityInfo previous = securityByEp.put(tbSecurityInfo.getEndpoint(), tbSecurityInfo); if (previous != null && previous.getSecurityInfo() != null) { - String previousIdentity = previous.getSecurityInfo().getIdentity(); + String previousIdentity = previous.getSecurityInfo().getPskIdentity(); if (previousIdentity != null && !previousIdentity.equals(identity)) { securityByIdentity.remove(previousIdentity); } @@ -116,8 +122,8 @@ public class TbInMemorySecurityStore implements TbEditableSecurityStore { writeLock.lock(); try { TbLwM2MSecurityInfo securityInfo = securityByEp.remove(endpoint); - if (securityInfo != null && securityInfo.getSecurityInfo() != null && securityInfo.getSecurityInfo().getIdentity() != null) { - securityByIdentity.remove(securityInfo.getSecurityInfo().getIdentity()); + if (securityInfo != null && securityInfo.getSecurityInfo() != null && securityInfo.getSecurityInfo().getPskIdentity() != null) { + securityByIdentity.remove(securityInfo.getSecurityInfo().getPskIdentity()); } } finally { writeLock.unlock(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java index 39c19c687b..40a281728b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java @@ -16,6 +16,8 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.eclipse.californium.core.coap.Token; +import org.eclipse.californium.core.network.serialization.UdpDataParser; +import org.eclipse.californium.core.network.serialization.UdpDataSerializer; import org.eclipse.californium.core.observe.ObservationStoreException; import org.eclipse.californium.elements.EndpointContext; import org.eclipse.leshan.core.Destroyable; @@ -23,18 +25,18 @@ import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Stoppable; import org.eclipse.leshan.core.californium.ObserveUtil; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.SingleObservation; -import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.peer.LwM2mIdentity; import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.core.util.Validate; -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; +import org.eclipse.leshan.server.californium.observation.ObservationSerDes; import org.eclipse.leshan.server.redis.RedisRegistrationStore; -import org.eclipse.leshan.server.redis.serialization.IdentitySerDes; -import org.eclipse.leshan.server.redis.serialization.ObservationSerDes; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; import org.eclipse.leshan.server.registration.Deregistration; import org.eclipse.leshan.server.registration.ExpirationListener; import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.UpdatedRegistration; import org.slf4j.Logger; @@ -63,7 +65,7 @@ import java.util.concurrent.locks.Lock; import static java.nio.charset.StandardCharsets.UTF_8; -public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationStore, Startable, Stoppable, Destroyable { +public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable { /** Default time in seconds between 2 cleaning tasks (used to remove expired registration). */ public static final long DEFAULT_CLEAN_PERIOD = 60; public static final int DEFAULT_CLEAN_LIMIT = 500; @@ -83,6 +85,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto private static final byte[] EXP_EP = "EXP:EP".getBytes(UTF_8); // a sorted set used for registration expiration // (expiration date, Endpoint) + private final RegistrationSerDes registrationSerDes = new RegistrationSerDes(); + private final ObservationSerDes observationSerDes = new ObservationSerDes(new UdpDataParser(), new UdpDataSerializer()); private final RedisConnectionFactory connectionFactory; // Listener use to notify when a registration expires @@ -159,7 +163,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto connection.set(regid_idx, registration.getEndpoint().getBytes(UTF_8)); byte[] addr_idx = toRegAddrKey(registration.getSocketAddress()); connection.set(addr_idx, registration.getEndpoint().getBytes(UTF_8)); - byte[] identity_idx = toRegIdentityKey(registration.getIdentity()); + byte[] identity_idx = toRegIdentityKey(registration.getClientTransportData().getIdentity()); connection.set(identity_idx, registration.getEndpoint().getBytes(UTF_8)); // Add or update expiration @@ -173,7 +177,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto if (!oldRegistration.getSocketAddress().equals(registration.getSocketAddress())) { removeAddrIndex(connection, oldRegistration); } - if (!oldRegistration.getIdentity().equals(registration.getIdentity())) { + if (!oldRegistration.getClientTransportData().getIdentity().equals(registration.getClientTransportData().getIdentity())) { removeIdentityIndex(connection, oldRegistration); } // remove old observation @@ -231,7 +235,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto if (!r.getSocketAddress().equals(updatedRegistration.getSocketAddress())) { removeAddrIndex(connection, r); } - if (!r.getIdentity().equals(updatedRegistration.getIdentity())) { + if (!r.getClientTransportData().getIdentity().equals(updatedRegistration.getClientTransportData().getIdentity())) { removeIdentityIndex(connection, r); } @@ -281,7 +285,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } @Override - public Registration getRegistrationByIdentity(Identity identity) { + public Registration getRegistrationByIdentity(LwM2mIdentity identity) { Validate.notNull(identity); try (var connection = connectionFactory.getConnection()) { byte[] ep = connection.get(toRegIdentityKey(identity)); @@ -327,6 +331,26 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } } + @Override + public Collection addObservation(String registrationId, Observation observation, boolean addIfAbsent) { + return null; + } + + @Override + public Observation getObservation(String registrationId, ObservationIdentifier observationId) { + return null; + } + + @Override + public Observation getObservation(ObservationIdentifier observationId) { + return null; + } + + @Override + public Observation removeObservation(String registrationId, ObservationIdentifier observationId) { + return null; + } + private Deregistration removeRegistration(RedisConnection connection, String registrationId, boolean removeOnlyIfNotAlive) { // fetch the client ep by registration ID index byte[] ep = connection.get(toRegIdKey(registrationId)); @@ -371,7 +395,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } private void removeIdentityIndex(RedisConnection connection, Registration r) { - removeSecondaryIndex(connection, toRegIdentityKey(r.getIdentity()), r.getEndpoint()); + removeSecondaryIndex(connection, toRegIdentityKey(r.getClientTransportData().getIdentity()), r.getEndpoint()); } //TODO: JedisCluster didn't implement Transaction, maybe should use some advanced key creation strategies @@ -410,8 +434,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto return toKey(REG_EP_ADDR_IDX, addr.getAddress().toString() + ":" + addr.getPort()); } - private byte[] toRegIdentityKey(Identity identity) { - return toKey(REG_EP_IDENTITY, IdentitySerDes.serialize(identity).toString()); + private byte[] toRegIdentityKey(LwM2mIdentity identity) { + return toKey(REG_EP_IDENTITY, identity.toString()); } private byte[] toEndpointKey(String endpoint) { @@ -423,11 +447,11 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } private byte[] serializeReg(Registration registration) { - return RegistrationSerDes.bSerialize(registration); + return registrationSerDes.bSerialize(registration); } private Registration deserializeReg(byte[] data) { - return RegistrationSerDes.deserialize(data); + return registrationSerDes.deserialize(data); } /* *************** Leshan Observation API **************** */ @@ -436,7 +460,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto * The observation is not persisted here, it is done by the Californium layer (in the implementation of the * org.eclipse.californium.core.observe.ObservationStore#add method) */ - @Override + public Collection addObservation(String registrationId, Observation observation) { List removed = new ArrayList<>(); try (var connection = connectionFactory.getConnection()) { @@ -458,9 +482,9 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto for (Observation obs : getObservations(connection, registrationId)) { //TODO: should be able to use CompositeObservation if (((SingleObservation)observation).getPath().equals(((SingleObservation)obs).getPath()) - && !Arrays.equals(observation.getId(), obs.getId())) { + && !observation.getId().equals(obs.getId())) { removed.add(obs); - unsafeRemoveObservation(connection, registrationId, obs.getId()); + unsafeRemoveObservation(connection, registrationId, obs.getId().getBytes()); } } @@ -473,7 +497,6 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto return removed; } - @Override public Observation removeObservation(String registrationId, byte[] observationId) { try (var connection = connectionFactory.getConnection()) { @@ -490,7 +513,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto lock = redisLock.obtain(lockKey); lock.lock(); - Observation observation = build(get(new Token(observationId))); +// Observation observation = build(get(new Token(observationId))); + Observation observation = build(null); if (observation != null && registrationId.equals(observation.getRegistrationId())) { unsafeRemoveObservation(connection, registrationId, observationId); return observation; @@ -505,9 +529,9 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } } - @Override public Observation getObservation(String registrationId, byte[] observationId) { - return build(get(new Token(observationId))); +// return build(get(new Token(observationId))); + return build(null); } @Override @@ -554,13 +578,11 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto /* *************** Californium ObservationStore API **************** */ - @Override public org.eclipse.californium.core.observe.Observation putIfAbsent(Token token, org.eclipse.californium.core.observe.Observation obs) throws ObservationStoreException { return add(obs, true); } - @Override public org.eclipse.californium.core.observe.Observation put(Token token, org.eclipse.californium.core.observe.Observation obs) throws ObservationStoreException { return add(obs, false); @@ -588,7 +610,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto if (previousValue == null || previousValue.length == 0) { connection.set(key, serializeObs); } else { - return deserializeObs(previousValue); +// return deserializeObs(previousValue); + return null; } } else { previousValue = connection.getSet(key, serializeObs); @@ -599,7 +622,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto // log any collisions if (previousValue != null && previousValue.length != 0) { - previousObservation = deserializeObs(previousValue); +// previousObservation = deserializeObs(previousValue); + previousObservation = null; LOG.warn( "Token collision ? observation from request [{}] will be replaced by observation from request [{}] ", previousObservation.getRequest(), obs.getRequest()); @@ -613,7 +637,6 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto return previousObservation; } - @Override public void remove(Token token) { try (var connection = connectionFactory.getConnection()) { byte[] tokenKey = toKey(OBS_TKN, token.getBytes()); @@ -623,7 +646,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto if (serializedObs == null) return; - org.eclipse.californium.core.observe.Observation obs = deserializeObs(serializedObs); +// org.eclipse.californium.core.observe.Observation obs = deserializeObs(serializedObs); + org.eclipse.californium.core.observe.Observation obs = null; String registrationId = ObserveUtil.extractRegistrationId(obs); Registration registration = getRegistration(connection, registrationId); if (registration == null) { @@ -649,14 +673,14 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } - @Override public org.eclipse.californium.core.observe.Observation get(Token token) { try (var connection = connectionFactory.getConnection()) { byte[] obs = connection.get(toKey(OBS_TKN, token.getBytes())); if (obs == null) { return null; } else { - return deserializeObs(obs); +// return deserializeObs(obs); + return null; } } } @@ -699,24 +723,26 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto return removed; } - @Override public void setContext(Token token, EndpointContext correlationContext) { // In Leshan we always set context when we send the request, so this should not be needed to implement this. } private byte[] serializeObs(org.eclipse.californium.core.observe.Observation obs) { - return ObservationSerDes.serialize(obs); + return null; +// return observationSerDes.serialize(obs); } - private org.eclipse.californium.core.observe.Observation deserializeObs(byte[] data) { - return ObservationSerDes.deserialize(data); + private Observation deserializeObs(byte[] data) { +// return observationSerDes.deserialize(data); + return null; } - private Observation build(org.eclipse.californium.core.observe.Observation cfObs) { + private Observation build(Observation cfObs) { if (cfObs == null) return null; - - return ObserveUtil.createLwM2mObservation(cfObs.getRequest()); +// String serializedObservation = observationSerDes.serialize(cfObs); +// return ObserveUtil.createLwM2mObservation(cfObs, serializedObservation); + return null; } /* *************** Expiration handling **************** */ @@ -787,7 +813,6 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto expirationListener = listener; } - @Override public void setExecutor(ScheduledExecutorService executor) { // TODO should we reuse californium executor ? } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java index 87e92c8cca..daadc893bf 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.peer.OscoreIdentity; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.data.redis.connection.RedisConnectionFactory; @@ -86,6 +87,11 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { } } + @Override + public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { + return null; + } + @Override public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { SecurityInfo info = tbSecurityInfo.getSecurityInfo(); @@ -94,21 +100,21 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { try (var connection = connectionFactory.getConnection()) { lock = redisLock.obtain(tbSecurityInfo.getEndpoint()); lock.lock(); - if (info != null && info.getIdentity() != null) { - byte[] oldEndpointBytes = connection.hGet(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); + if (info != null && info.getPskIdentity() != null) { + byte[] oldEndpointBytes = connection.hGet(PSKID_SEC.getBytes(), info.getPskIdentity().getBytes()); if (oldEndpointBytes != null) { String oldEndpoint = new String(oldEndpointBytes); if (!oldEndpoint.equals(info.getEndpoint())) { - throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used"); + throw new NonUniqueSecurityInfoException("PSK Identity " + info.getPskIdentity() + " is already used"); } - connection.hSet(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes()); + connection.hSet(PSKID_SEC.getBytes(), info.getPskIdentity().getBytes(), info.getEndpoint().getBytes()); } } byte[] previousData = connection.getSet((SEC_EP + tbSecurityInfo.getEndpoint()).getBytes(), tbSecurityInfoSerialized); if (previousData != null && info != null) { - String previousIdentity = ((TbLwM2MSecurityInfo) JavaSerDesUtil.decode(previousData)).getSecurityInfo().getIdentity(); - if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) { + String previousIdentity = ((TbLwM2MSecurityInfo) JavaSerDesUtil.decode(previousData)).getSecurityInfo().getPskIdentity(); + if (previousIdentity != null && !previousIdentity.equals(info.getPskIdentity())) { connection.hDel(PSKID_SEC.getBytes(), previousIdentity.getBytes()); } } @@ -147,8 +153,8 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { byte[] data = connection.get((SEC_EP + endpoint).getBytes()); if (data != null && data.length > 0) { SecurityInfo info = ((TbLwM2MSecurityInfo) JavaSerDesUtil.decode(data)).getSecurityInfo(); - if (info != null && info.getIdentity() != null) { - connection.hDel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); + if (info != null && info.getPskIdentity() != null) { + connection.hDel(PSKID_SEC.getBytes(), info.getPskIdentity().getBytes()); } connection.del((SEC_EP + endpoint).getBytes()); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java index 5d7e339584..0cdc75f17e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.store; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; +import org.eclipse.leshan.core.peer.OscoreIdentity; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; @@ -60,7 +61,7 @@ public class TbLwM2mSecurityStore implements TbMainSecurityStore { if (securityInfo == null) { securityInfo = fetchAndPutSecurityInfo(endpoint); } else if (securityInfo.usePSK() && securityInfo.getEndpoint().equals(SecurityMode.NO_SEC.toString()) - && securityInfo.getIdentity().equals(SecurityMode.NO_SEC.toString()) + && securityInfo.getPskIdentity().equals(SecurityMode.NO_SEC.toString()) && Arrays.equals(SecurityMode.NO_SEC.toString().getBytes(), securityInfo.getPreSharedKey())) { return null; } @@ -81,6 +82,11 @@ public class TbLwM2mSecurityStore implements TbMainSecurityStore { return securityInfo; } + @Override + public SecurityInfo getByOscoreIdentity(OscoreIdentity oscoreIdentity) { + return null; + } + public SecurityInfo fetchAndPutSecurityInfo(String credentialsId) { TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, CLIENT); doPut(securityInfo); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java index f74630fffc..9aaebaa8bc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java @@ -16,8 +16,8 @@ package org.thingsboard.server.transport.lwm2m.server.store; import lombok.RequiredArgsConstructor; -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; -import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore; +import org.eclipse.leshan.server.registration.InMemoryRegistrationStore; +import org.eclipse.leshan.server.registration.RegistrationStore; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Component; @@ -38,7 +38,7 @@ public class TbLwM2mStoreFactory { private final LwM2mCredentialsSecurityInfoValidator validator; @Bean - private CaliforniumRegistrationStore registrationStore() { + private RegistrationStore registrationStore() { return redisConfiguration.isPresent() ? new TbLwM2mRedisRegistrationStore(getConnectionFactory()) : new InMemoryRegistrationStore(config.getCleanPeriodInSec()); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java index 88663bae71..1309e9ae2c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java @@ -15,9 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.server.store.util; -import com.eclipsesource.json.Json; -import com.eclipsesource.json.JsonObject; -import com.eclipsesource.json.JsonValue; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.gson.JsonObject; import com.google.protobuf.util.JsonFormat; import lombok.SneakyThrows; import org.eclipse.leshan.core.model.ResourceModel; @@ -26,113 +25,107 @@ import org.eclipse.leshan.core.node.LwM2mNodeException; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.ObjectLink; -import org.eclipse.leshan.core.util.datatype.ULong; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; -import org.thingsboard.server.common.data.device.data.PowerMode; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; import java.lang.reflect.Field; import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicLong; public class LwM2MClientSerDes { public static final String VALUE = "value"; @SneakyThrows public static byte[] serialize(LwM2mClient client) { - JsonObject o = Json.object(); - o.add("nodeId", client.getNodeId()); - o.add("endpoint", client.getEndpoint()); + JsonObject o = new JsonObject(); + o.addProperty("nodeId", "client.getNodeId()"); + o.addProperty("endpoint", client.getEndpoint()); - JsonObject resources = Json.object(); + JsonObject resources = new JsonObject(); client.getResources().forEach((k, v) -> { - JsonObject resourceValue = Json.object(); + JsonObject resourceValue = new JsonObject(); resourceValue.add("lwM2mResource", serialize(v.getLwM2mResource())); resourceValue.add("resourceModel", serialize(v.getResourceModel())); resources.add(k, resourceValue); }); o.add("resources", resources); - JsonObject sharedAttributes = Json.object(); + JsonObject sharedAttributes = new JsonObject(); for (Map.Entry entry : client.getSharedAttributes().entrySet()) { - sharedAttributes.add(entry.getKey(), JsonFormat.printer().print(entry.getValue())); + sharedAttributes.addProperty(entry.getKey(), JsonFormat.printer().print(entry.getValue())); } o.add("sharedAttributes", sharedAttributes); - JsonObject keyTsLatestMap = Json.object(); + JsonObject keyTsLatestMap = new JsonObject(); client.getKeyTsLatestMap().forEach((k, v) -> { - keyTsLatestMap.add(k, v.get()); + keyTsLatestMap.addProperty(k, v.get()); }); o.add("keyTsLatestMap", keyTsLatestMap); - o.add("state", client.getState().toString()); + o.addProperty("state", client.getState().toString()); if (client.getSession() != null) { - o.add("session", JsonFormat.printer().print(client.getSession())); + o.addProperty("session", JsonFormat.printer().print(client.getSession())); } if (client.getTenantId() != null) { - o.add("tenantId", client.getTenantId().toString()); + o.addProperty("tenantId", client.getTenantId().toString()); } if (client.getDeviceId() != null) { - o.add("deviceId", client.getDeviceId().toString()); + o.addProperty("deviceId", client.getDeviceId().toString()); } if (client.getProfileId() != null) { - o.add("profileId", client.getProfileId().toString()); + o.addProperty("profileId", client.getProfileId().toString()); } if (client.getPowerMode() != null) { - o.add("powerMode", client.getPowerMode().toString()); + o.addProperty("powerMode", client.getPowerMode().toString()); } if (client.getEdrxCycle() != null) { - o.add("edrxCycle", client.getEdrxCycle()); + o.addProperty("edrxCycle", client.getEdrxCycle()); } if (client.getPsmActivityTimer() != null) { - o.add("psmActivityTimer", client.getPsmActivityTimer()); + o.addProperty("psmActivityTimer", client.getPsmActivityTimer()); } if (client.getPagingTransmissionWindow() != null) { - o.add("pagingTransmissionWindow", client.getPagingTransmissionWindow()); + o.addProperty("pagingTransmissionWindow", client.getPagingTransmissionWindow()); } if (client.getRegistration() != null) { - o.add("registration", RegistrationSerDes.jSerialize(client.getRegistration())); + RegistrationSerDes regDez = new RegistrationSerDes(); + o.addProperty("registration", regDez.jSerialize(client.getRegistration()).toString()); } - o.add("asleep", client.isAsleep()); - o.add("lastUplinkTime", client.getLastUplinkTime()); + o.addProperty("asleep", client.isAsleep()); + o.addProperty("lastUplinkTime", client.getLastUplinkTime()); Field firstEdrxDownlink = LwM2mClient.class.getDeclaredField("firstEdrxDownlink"); firstEdrxDownlink.setAccessible(true); - o.add("firstEdrxDownlink", (boolean) firstEdrxDownlink.get(client)); - o.add("retryAttempts", client.getRetryAttempts().get()); + o.addProperty("firstEdrxDownlink", (boolean) firstEdrxDownlink.get(client)); + o.addProperty("retryAttempts", client.getRetryAttempts().get()); if (client.getLastSentRpcId() != null) { - o.add("lastSentRpcId", client.getLastSentRpcId().toString()); + o.addProperty("lastSentRpcId", client.getLastSentRpcId().toString()); } return o.toString().getBytes(); } private static JsonObject serialize(LwM2mResource resource) { - JsonObject o = Json.object(); - o.add("id", resource.getId()); - o.add("type", resource.getType().toString()); + JsonObject o = new JsonObject(); + o.addProperty("id", resource.getId()); + o.addProperty("type", resource.getType().toString()); if (resource.isMultiInstances()) { - o.add("multiInstances", true); - JsonObject instances = Json.object(); + o.addProperty("multiInstances", true); + JsonObject instances = new JsonObject(); resource.getInstances().forEach((id, in) -> { - JsonObject instance = Json.object(); - instance.add("id", in.getId()); + JsonObject instance = new JsonObject(); + instance.addProperty("id", in.getId()); addValue(instance, in.getType(), in.getValue()); instances.add(id.toString(), instance); }); o.add("instances", instances); } else { - o.add("multiInstances", false); + o.addProperty("multiInstances", false); addValue(o, resource.getType(), resource.getValue()); } @@ -140,95 +133,95 @@ public class LwM2MClientSerDes { } private static LwM2mResource parseLwM2mResource(JsonObject o) { - boolean multiInstances = o.get("multiInstances").asBoolean(); - int id = o.get("id").asInt(); - ResourceModel.Type type = ResourceModel.Type.valueOf(o.get("type").asString()); + boolean multiInstances = o.get("multiInstances").getAsBoolean(); + int id = o.get("id").getAsInt(); + ResourceModel.Type type = ResourceModel.Type.valueOf(o.get("type").getAsString()); if (multiInstances) { Map instances = new HashMap<>(); - o.get("instances").asObject().forEach(entry -> { - instances.put(Integer.valueOf(entry.getName()), parseValue(type, entry.getValue())); + o.get("instances").getAsJsonArray().forEach(entry -> { +// instances.put(Integer.valueOf(entry.getAsJsonObject().), parseValue(type, entry.getValue())); }); return LwM2mMultipleResource.newResource(id, instances, type); } else { - return LwM2mSingleResource.newResource(id, parseValue(type, o.get(VALUE))); + return LwM2mSingleResource.newResource(id, parseValue(type, (JsonValue) o.get(VALUE))); } } private static Object parseValue(ResourceModel.Type type, JsonValue value) { switch (type) { case INTEGER: - return value.asLong(); - case FLOAT: - return value.asDouble(); - case BOOLEAN: - return value.asBoolean(); - case OPAQUE: - return Base64.getDecoder().decode(value.asString()); - case STRING: - return value.asString(); - case TIME: - return new Date(value.asLong()); - case OBJLNK: - return ObjectLink.decodeFromString(value.asString()); - case UNSIGNED_INTEGER: - return ULong.valueOf(value.asString()); + return value.value(); +// case FLOAT: +// return value.value(); +// case BOOLEAN: +// return value.asBoolean(); +// case OPAQUE: +// return Base64.getDecoder().decode(value.asString()); +// case STRING: +// return value.asString(); +// case TIME: +// return new Date(value.asLong()); +// case OBJLNK: +// return ObjectLink.decodeFromString(value.asString()); +// case UNSIGNED_INTEGER: +// return ULong.valueOf(value.asString()); default: throw new LwM2mNodeException(String.format("Type %s is not supported", type.name())); } } private static JsonObject serialize(ResourceModel resourceModel) { - JsonObject o = Json.object(); - o.add("id", resourceModel.id); - o.add("name", resourceModel.name); - o.add("operations", resourceModel.operations.toString()); - o.add("multiple", resourceModel.multiple); - o.add("mandatory", resourceModel.mandatory); - o.add("type", resourceModel.type.toString()); - o.add("rangeEnumeration", resourceModel.rangeEnumeration); - o.add("units", resourceModel.units); - o.add("description", resourceModel.description); + JsonObject o = new JsonObject(); + o.addProperty("id", resourceModel.id); + o.addProperty("name", resourceModel.name); + o.addProperty("operations", resourceModel.operations.toString()); + o.addProperty("multiple", resourceModel.multiple); + o.addProperty("mandatory", resourceModel.mandatory); + o.addProperty("type", resourceModel.type.toString()); + o.addProperty("rangeEnumeration", resourceModel.rangeEnumeration); + o.addProperty("units", resourceModel.units); + o.addProperty("description", resourceModel.description); return o; } private static ResourceModel parseResourceModel(JsonObject o) { - Integer id = o.get("id").asInt(); - String name = o.get("name").asString(); - ResourceModel.Operations operations = ResourceModel.Operations.valueOf(o.get("operations").asString()); - Boolean multiple = o.get("multiple").asBoolean(); - Boolean mandatory = o.get("mandatory").asBoolean(); - ResourceModel.Type type = ResourceModel.Type.valueOf(o.get("type").asString()); - String rangeEnumeration = o.get("rangeEnumeration").asString(); - String units = o.get("units").asString(); - String description = o.get("description").asString(); + Integer id = o.get("id").getAsInt(); + String name = o.get("name").getAsString(); + ResourceModel.Operations operations = ResourceModel.Operations.valueOf(o.get("operations").getAsString()); + Boolean multiple = o.get("multiple").getAsBoolean(); + Boolean mandatory = o.get("mandatory").getAsBoolean(); + ResourceModel.Type type = ResourceModel.Type.valueOf(o.get("type").getAsString()); + String rangeEnumeration = o.get("rangeEnumeration").getAsString(); + String units = o.get("units").getAsString(); + String description = o.get("description").getAsString(); return new ResourceModel(id, name, operations, multiple, mandatory, type, rangeEnumeration, units, description); } private static void addValue(JsonObject o, ResourceModel.Type type, Object value) { switch (type) { case INTEGER: - o.add(VALUE, (Long) value); + o.addProperty(VALUE, (Long) value); break; case FLOAT: - o.add(VALUE, (Double) value); + o.addProperty(VALUE, (Double) value); break; case BOOLEAN: - o.add(VALUE, (Boolean) value); + o.addProperty(VALUE, (Boolean) value); break; case OPAQUE: - o.add(VALUE, Base64.getEncoder().encodeToString((byte[]) value)); + o.addProperty(VALUE, Base64.getEncoder().encodeToString((byte[]) value)); break; case STRING: - o.add(VALUE, (String) value); + o.addProperty(VALUE, (String) value); break; case TIME: - o.add(VALUE, ((Date) value).getTime()); + o.addProperty(VALUE, ((Date) value).getTime()); break; case OBJLNK: - o.add(VALUE, ((ObjectLink) value).encodeToString()); + o.addProperty(VALUE, ((ObjectLink) value).encodeToString()); break; case UNSIGNED_INTEGER: - o.add(VALUE, value.toString()); + o.addProperty(VALUE, value.toString()); break; default: throw new LwM2mNodeException(String.format("Type %s is not supported", type.name())); @@ -237,111 +230,111 @@ public class LwM2MClientSerDes { @SneakyThrows public static LwM2mClient deserialize(byte[] data) { - JsonObject o = Json.parse(new String(data)).asObject(); - LwM2mClient lwM2mClient = new LwM2mClient(o.get("nodeId").asString(), o.get("endpoint").asString()); - - o.get("resources").asObject().forEach(entry -> { - JsonObject resource = entry.getValue().asObject(); - LwM2mResource lwM2mResource = parseLwM2mResource(resource.get("lwM2mResource").asObject()); - ResourceModel resourceModel = parseResourceModel(resource.get("resourceModel").asObject()); - ResourceValue resourceValue = new ResourceValue(lwM2mResource, resourceModel); - lwM2mClient.getResources().put(entry.getName(), resourceValue); - }); - - for (JsonObject.Member entry : o.get("sharedAttributes").asObject()) { - TransportProtos.TsKvProto.Builder builder = TransportProtos.TsKvProto.newBuilder(); - JsonFormat.parser().merge(entry.getValue().asString(), builder); - lwM2mClient.getSharedAttributes().put(entry.getName(), builder.build()); - } - - o.get("keyTsLatestMap").asObject().forEach(entry -> { - lwM2mClient.getKeyTsLatestMap().put(entry.getName(), new AtomicLong(entry.getValue().asLong())); - }); - - lwM2mClient.setState(LwM2MClientState.valueOf(o.get("state").asString())); - - Class lwM2mClientClass = LwM2mClient.class; - - JsonValue session = o.get("session"); - if (session != null) { - TransportProtos.SessionInfoProto.Builder builder = TransportProtos.SessionInfoProto.newBuilder(); - JsonFormat.parser().merge(session.asString(), builder); - - Field sessionField = lwM2mClientClass.getDeclaredField("session"); - sessionField.setAccessible(true); - sessionField.set(lwM2mClient, builder.build()); - } - - JsonValue tenantId = o.get("tenantId"); - if (tenantId != null) { - Field tenantIdField = lwM2mClientClass.getDeclaredField("tenantId"); - tenantIdField.setAccessible(true); - tenantIdField.set(lwM2mClient, new TenantId(UUID.fromString(tenantId.asString()))); - } - - JsonValue deviceId = o.get("deviceId"); - if (tenantId != null) { - Field deviceIdField = lwM2mClientClass.getDeclaredField("deviceId"); - deviceIdField.setAccessible(true); - deviceIdField.set(lwM2mClient, UUID.fromString(deviceId.asString())); - } - - JsonValue profileId = o.get("profileId"); - if (tenantId != null) { - Field profileIdField = lwM2mClientClass.getDeclaredField("profileId"); - profileIdField.setAccessible(true); - profileIdField.set(lwM2mClient, UUID.fromString(profileId.asString())); - } - - JsonValue powerMode = o.get("powerMode"); - if (powerMode != null) { - Field powerModeField = lwM2mClientClass.getDeclaredField("powerMode"); - powerModeField.setAccessible(true); - powerModeField.set(lwM2mClient, PowerMode.valueOf(powerMode.asString())); - } - - JsonValue edrxCycle = o.get("edrxCycle"); - if (edrxCycle != null) { - Field edrxCycleField = lwM2mClientClass.getDeclaredField("edrxCycle"); - edrxCycleField.setAccessible(true); - edrxCycleField.set(lwM2mClient, edrxCycle.asLong()); - } - - JsonValue psmActivityTimer = o.get("psmActivityTimer"); - if (psmActivityTimer != null) { - Field psmActivityTimerField = lwM2mClientClass.getDeclaredField("psmActivityTimer"); - psmActivityTimerField.setAccessible(true); - psmActivityTimerField.set(lwM2mClient, psmActivityTimer.asLong()); - } - - JsonValue pagingTransmissionWindow = o.get("pagingTransmissionWindow"); - if (pagingTransmissionWindow != null) { - Field pagingTransmissionWindowField = lwM2mClientClass.getDeclaredField("pagingTransmissionWindow"); - pagingTransmissionWindowField.setAccessible(true); - pagingTransmissionWindowField.set(lwM2mClient, pagingTransmissionWindow.asLong()); - } - - JsonValue registration = o.get("registration"); - if (registration != null) { - lwM2mClient.setRegistration(RegistrationSerDes.deserialize(registration.asObject())); - } - - lwM2mClient.setAsleep(o.get("asleep").asBoolean()); - - Field lastUplinkTimeField = lwM2mClientClass.getDeclaredField("lastUplinkTime"); - lastUplinkTimeField.setAccessible(true); - lastUplinkTimeField.setLong(lwM2mClient, o.get("lastUplinkTime").asLong()); - - Field firstEdrxDownlinkField = lwM2mClientClass.getDeclaredField("firstEdrxDownlink"); - firstEdrxDownlinkField.setAccessible(true); - firstEdrxDownlinkField.setBoolean(lwM2mClient, o.get("firstEdrxDownlink").asBoolean()); - - lwM2mClient.getRetryAttempts().set(o.get("retryAttempts").asInt()); - - JsonValue lastSentRpcId = o.get("lastSentRpcId"); - if (lastSentRpcId != null) { - lwM2mClient.setLastSentRpcId(UUID.fromString(lastSentRpcId.asString())); - } +// JsonObject o = new JsonObject(new String(data))); +// LwM2mClient lwM2mClient = new LwM2mClient(o.get("nodeId").getAsString(), o.get("endpoint").getAsString()); + LwM2mClient lwM2mClient = new LwM2mClient("nodeId", "endpoint"); +// o.get("resources").getAsJsonObject().forEach(entry -> { +// JsonObject resource = entry.getValue().asObject(); +// LwM2mResource lwM2mResource = parseLwM2mResource(resource.get("lwM2mResource").getAsJsonObject()); +// ResourceModel resourceModel = parseResourceModel(resource.get("resourceModel").asObject()); +// ResourceValue resourceValue = new ResourceValue(lwM2mResource, resourceModel); +// lwM2mClient.getResources().put(entry.getName(), resourceValue); +// }); +// +// for (JsonObject.Member entry : o.get("sharedAttributes").asObject()) { +// TransportProtos.TsKvProto.Builder builder = TransportProtos.TsKvProto.newBuilder(); +// JsonFormat.parser().merge(entry.getValue().getAsString(), builder); +// lwM2mClient.getSharedAttributes().put(entry.getName(), builder.build()); +// } +// +// o.get("keyTsLatestMap").asObject().forEach(entry -> { +// lwM2mClient.getKeyTsLatestMap().put(entry.getName(), new AtomicLong(entry.getValue().asLong())); +// }); +// +// lwM2mClient.setState(LwM2MClientState.valueOf(o.get("state").getAsString())); +// +// Class lwM2mClientClass = LwM2mClient.class; +// +// JsonValue session = o.get("session"); +// if (session != null) { +// TransportProtos.SessionInfoProto.Builder builder = TransportProtos.SessionInfoProto.newBuilder(); +// JsonFormat.parser().merge(session.asString(), builder); +// +// Field sessionField = lwM2mClientClass.getDeclaredField("session"); +// sessionField.setAccessible(true); +// sessionField.set(lwM2mClient, builder.build()); +// } +// +// JsonValue tenantId = o.get("tenantId"); +// if (tenantId != null) { +// Field tenantIdField = lwM2mClientClass.getDeclaredField("tenantId"); +// tenantIdField.setAccessible(true); +// tenantIdField.set(lwM2mClient, new TenantId(UUID.fromString(tenantId.asString()))); +// } +// +// JsonValue deviceId = o.get("deviceId"); +// if (tenantId != null) { +// Field deviceIdField = lwM2mClientClass.getDeclaredField("deviceId"); +// deviceIdField.setAccessible(true); +// deviceIdField.set(lwM2mClient, UUID.fromString(deviceId.asString())); +// } +// +// JsonValue profileId = o.get("profileId"); +// if (tenantId != null) { +// Field profileIdField = lwM2mClientClass.getDeclaredField("profileId"); +// profileIdField.setAccessible(true); +// profileIdField.set(lwM2mClient, UUID.fromString(profileId.asString())); +// } +// +// JsonValue powerMode = o.get("powerMode"); +// if (powerMode != null) { +// Field powerModeField = lwM2mClientClass.getDeclaredField("powerMode"); +// powerModeField.setAccessible(true); +// powerModeField.set(lwM2mClient, PowerMode.valueOf(powerMode.asString())); +// } +// +// JsonValue edrxCycle = o.get("edrxCycle"); +// if (edrxCycle != null) { +// Field edrxCycleField = lwM2mClientClass.getDeclaredField("edrxCycle"); +// edrxCycleField.setAccessible(true); +// edrxCycleField.set(lwM2mClient, edrxCycle.asLong()); +// } +// +// JsonValue psmActivityTimer = o.get("psmActivityTimer"); +// if (psmActivityTimer != null) { +// Field psmActivityTimerField = lwM2mClientClass.getDeclaredField("psmActivityTimer"); +// psmActivityTimerField.setAccessible(true); +// psmActivityTimerField.set(lwM2mClient, psmActivityTimer.asLong()); +// } +// +// JsonValue pagingTransmissionWindow = o.get("pagingTransmissionWindow"); +// if (pagingTransmissionWindow != null) { +// Field pagingTransmissionWindowField = lwM2mClientClass.getDeclaredField("pagingTransmissionWindow"); +// pagingTransmissionWindowField.setAccessible(true); +// pagingTransmissionWindowField.set(lwM2mClient, pagingTransmissionWindow.asLong()); +// } +// +// JsonValue registration = o.get("registration"); +// if (registration != null) { +// lwM2mClient.setRegistration(RegistrationSerDes.deserialize(registration.asObject())); +// } +// +// lwM2mClient.setAsleep(o.get("asleep").getAsBoolean()); +// +// Field lastUplinkTimeField = lwM2mClientClass.getDeclaredField("lastUplinkTime"); +// lastUplinkTimeField.setAccessible(true); +// lastUplinkTimeField.setLong(lwM2mClient, o.get("lastUplinkTime").asLong()); +// +// Field firstEdrxDownlinkField = lwM2mClientClass.getDeclaredField("firstEdrxDownlink"); +// firstEdrxDownlinkField.setAccessible(true); +// firstEdrxDownlinkField.setBoolean(lwM2mClient, o.get("firstEdrxDownlink").getAsBoolean()); +// +// lwM2mClient.getRetryAttempts().set(o.get("retryAttempts").asInt()); +// +// JsonValue lastSentRpcId = o.get("lastSentRpcId"); +// if (lastSentRpcId != null) { +// lwM2mClient.setLastSentRpcId(UUID.fromString(lastSentRpcId.asString())); +// } return lwM2mClient; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java index 4369f5f532..84e1bfa53d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java @@ -356,7 +356,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl */ @Override public void onUpdateValueWithSendRequest(Registration registration, SendRequest sendRequest) { - for(var entry : sendRequest.getNodes().entrySet()) { + for(var entry : sendRequest.getTimestampedNodes().getNodes().entrySet()) { LwM2mPath path = entry.getKey(); LwM2mNode node = entry.getValue(); LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); @@ -777,7 +777,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl this.updateResourcesValue(client, (LwM2mResource) v, k.toString(), Mode.REPLACE, code); } else { LwM2mResourceInstance resourceInstance = (LwM2mResourceInstance) v; - LwM2mMultipleResource multipleResource = new LwM2mMultipleResource(v.getId(), resourceInstance.getType(), resourceInstance); + LwM2mMultipleResource multipleResource = new LwM2mMultipleResource(((LwM2mResourceInstance) v).getId(), resourceInstance.getType(), resourceInstance); this.updateResourcesValue(client, multipleResource, k.toString(), Mode.REPLACE, code); } }); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java index 527a84df22..201d41ce61 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java @@ -15,11 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.utils; -import com.fasterxml.jackson.core.type.TypeReference; import com.google.gson.JsonElement; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.attributes.Attribute; -import org.eclipse.leshan.core.attributes.AttributeSet; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; @@ -30,8 +27,6 @@ import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.codec.CodecException; -import org.eclipse.leshan.core.request.SimpleDownlinkRequest; -import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.common.util.JacksonUtil; @@ -52,9 +47,7 @@ import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdate import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; @@ -62,13 +55,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION; -import static org.eclipse.leshan.core.attributes.Attribute.GREATER_THAN; -import static org.eclipse.leshan.core.attributes.Attribute.LESSER_THAN; -import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD; -import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD; -import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; -import static org.eclipse.leshan.core.attributes.Attribute.STEP; import static org.eclipse.leshan.core.model.ResourceModel.Type.BOOLEAN; import static org.eclipse.leshan.core.model.ResourceModel.Type.FLOAT; import static org.eclipse.leshan.core.model.ResourceModel.Type.INTEGER; @@ -83,6 +69,7 @@ import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaU import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_STATE_ID; + @Slf4j public class LwM2MTransportUtil { @@ -235,7 +222,7 @@ public class LwM2MTransportUtil { } public static String convertObjectIdToVersionedId(String path, Registration registration) { - String ver = registration.getSupportedObject().get(new LwM2mPath(path).getObjectId()); + String ver = String.valueOf(registration.getSupportedObject().get(new LwM2mPath(path).getObjectId())); ver = ver != null ? ver : TbLwM2mVersion.VERSION_1_0.getVersion().toString(); try { String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); @@ -289,28 +276,28 @@ public class LwM2MTransportUtil { * Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60"); * Attribute [] attrs = {gt, st}; */ - public static SimpleDownlinkRequest createWriteAttributeRequest(String target, Object params, LwM2mUplinkMsgHandler serviceImpl) { - AttributeSet attrSet = new AttributeSet(createWriteAttributes(params, serviceImpl, target)); - return attrSet.getAttributes().size() > 0 ? new WriteAttributesRequest(target, attrSet) : null; - } - - private static Attribute[] createWriteAttributes(Object params, LwM2mUplinkMsgHandler serviceImpl, String target) { - List attributeLists = new ArrayList<>(); - Map map = JacksonUtil.convertValue(params, new TypeReference<>() { - }); - map.forEach((k, v) -> { - if (StringUtils.trimToNull(v.toString()) != null) { - Object attrValue = convertWriteAttributes(k, v, serviceImpl, target); - if (attrValue != null) { - Attribute attribute = createAttribute(k, attrValue); - if (attribute != null) { - attributeLists.add(new Attribute(k, attrValue)); - } - } - } - }); - return attributeLists.toArray(Attribute[]::new); - } +// public static SimpleDownlinkRequest createWriteAttributeRequest(String target, Object params, LwM2mUplinkMsgHandler serviceImpl) { +// AttributeSet attrSet = new AttributeSet(createWriteAttributes(params, serviceImpl, target)); +// return attrSet.getAttributes().size() > 0 ? new WriteAttributesRequest(target, attrSet) : null; +// } + +// private static Attribute[] createWriteAttributes(Object params, LwM2mUplinkMsgHandler serviceImpl, String target) { +// List attributeLists = new ArrayList<>(); +// Map map = JacksonUtil.convertValue(params, new TypeReference<>() { +// }); +// map.forEach((k, v) -> { +// if (StringUtils.trimToNull(v.toString()) != null) { +// Object attrValue = convertWriteAttributes(k, v, serviceImpl, target); +// if (attrValue != null) { +// Attribute attribute = createAttribute(k, attrValue); +// if (attribute != null) { +// attributeLists.add(new Attribute(k, attrValue)); +// } +// } +// } +// }); +// return attributeLists.toArray(Attribute[]::new); +// } /** * "UNSIGNED_INTEGER": // Number -> Integer Example: @@ -360,46 +347,50 @@ public class LwM2MTransportUtil { public static Map convertMultiResourceValuesFromJson(JsonElement newValProto, ResourceModel.Type type, String versionedId) { Map newValues = new HashMap<>(); newValProto.getAsJsonObject().entrySet().forEach((obj) -> { - newValues.put(Integer.valueOf(obj.getKey()), LwM2mValueConverterImpl.getInstance().convertValue(obj.getValue().getAsString(), - STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId)))); + newValues.put(Integer.valueOf(obj.getKey()), convertValueByTypeResource (obj.getValue().getAsString(), type, versionedId)); }); return newValues; } - public static Object convertWriteAttributes(String type, Object value, LwM2mUplinkMsgHandler serviceImpl, String target) { - switch (type) { - /** Integer [0:255]; */ - case DIMENSION: - Long dim = (Long) serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), INTEGER, new LwM2mPath(target)); - return dim >= 0 && dim <= 255 ? dim : null; - /**String;*/ - case OBJECT_VERSION: - return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), STRING, new LwM2mPath(target)); - /**INTEGER */ - case MINIMUM_PERIOD: - case MAXIMUM_PERIOD: - return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), INTEGER, new LwM2mPath(target)); - /**Float; */ - case GREATER_THAN: - case LESSER_THAN: - case STEP: - if (value.getClass().getSimpleName().equals("String")) { - value = Double.valueOf((String) value); - } - return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), FLOAT, new LwM2mPath(target)); - default: - return null; - } + public static Object convertValueByTypeResource (String value, ResourceModel.Type type, String versionedId) { + return LwM2mValueConverterImpl.getInstance().convertValue(value, + STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId))); } - private static Attribute createAttribute(String key, Object attrValue) { - try { - return new Attribute(key, attrValue); - } catch (Exception e) { - log.error("CreateAttribute, not valid parameter key: [{}], attrValue: [{}], error: [{}]", key, attrValue, e.getMessage()); - return null; - } - } +// public static Object convertWriteAttributes(String type, Object value, LwM2mUplinkMsgHandler serviceImpl, String target) { +// switch (type) { +// /** Integer [0:255]; */ +// case DIMENSION: +// Long dim = (Long) serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), INTEGER, new LwM2mPath(target)); +// return dim >= 0 && dim <= 255 ? dim : null; +// /**String;*/ +// case OBJECT_VERSION: +// return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), STRING, new LwM2mPath(target)); +// /**INTEGER */ +// case MINIMUM_PERIOD: +// case MAXIMUM_PERIOD: +// return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), INTEGER, new LwM2mPath(target)); +// /**Float; */ +// case GREATER_THAN: +// case LESSER_THAN: +// case STEP: +// if (value.getClass().getSimpleName().equals("String")) { +// value = Double.valueOf((String) value); +// } +// return serviceImpl.getConverter().convertValue(value, equalsResourceTypeGetSimpleName(value), FLOAT, new LwM2mPath(target)); +// default: +// return null; +// } +// } + +// private static Attribute createAttribute(String key, Object attrValue) { +// try { +// return new Attribute(key, attrValue); +// } catch (Exception e) { +// log.error("CreateAttribute, not valid parameter key: [{}], attrValue: [{}], error: [{}]", key, attrValue, e.getMessage()); +// return null; +// } +// } /** * @param lwM2MClient - diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 89510c6076..9e56d36209 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -327,10 +327,13 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator 65534) { - throw new DeviceCredentialsValidationException(server + " ShortServerId must not be less than 1 and more than 65534!"); + if (!serverConfig.isBootstrapServerIs() && (serverConfig.getShortServerId() < 1 || serverConfig.getShortServerId() > 65534)) { + throw new DeviceCredentialsValidationException("LwM2M Server ShortServerId must not be less than 1 and more than 65534!"); } + if (serverConfig.isBootstrapServerIs() && !(serverConfig.getShortServerId() == null || serverConfig.getShortServerId() ==0)) { + throw new DeviceCredentialsValidationException("Bootstrap Server ShortServerId must be null or '0'!"); + } + String server = serverConfig.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server"; if (!shortServerIds.add(serverConfig.getShortServerId())) { throw new DeviceCredentialsValidationException(server + " \"Short server Id\" value = " + serverConfig.getShortServerId() + ". This value must be a unique value for all servers!"); } diff --git a/pom.xml b/pom.xml index 64ed92bbc6..528ae6353a 100755 --- a/pom.xml +++ b/pom.xml @@ -40,7 +40,7 @@ /usr/share/${pkg.name} 2.1.1 4.0.0 - 2.3.1 + 2.4.0-b180830.0359 4.0.2 3.1.0 3.1.0 @@ -67,13 +67,13 @@ 4.5.13 4.4.14 2.8.1 - 2.13.4 - 2.13.4.2 + 2.15.3 + 2.15.3 1.3.4 4.2.1 2.2.6 - 3.0.0 - 2.0.0-M5 + 3.9.1 + 2.0.0-M14 2.9.0 2.3.30 2.0.1 diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts index ef592b50cd..65b7906463 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts @@ -25,7 +25,7 @@ export const ATTRIBUTE = 'attribute'; export const TELEMETRY = 'telemetry'; export const KEY_NAME = 'keyName'; export const DEFAULT_ID_SERVER = 123; -export const DEFAULT_ID_BOOTSTRAP = 111; +export const DEFAULT_ID_BOOTSTRAP = 0; export const DEFAULT_LOCAL_HOST_NAME = 'localhost'; export const DEFAULT_PORT_SERVER_NO_SEC = 5685; export const DEFAULT_PORT_BOOTSTRAP_NO_SEC = 5687; From 1fc704bae1ac2c8cbbc7a7bd486017171f764dd0 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 26 Dec 2023 11:08:06 +0100 Subject: [PATCH 070/209] test fixes and minor refactoring --- .../thingsboard/server/queue/pubsub/TbPubSubSettings.java | 2 +- .../java/org/thingsboard/script/api/tbel/TbDateTest.java | 2 +- pom.xml | 5 ----- .../org/thingsboard/rule/engine/rest/TbHttpClient.java | 2 +- .../rule/engine/profile/TbDeviceProfileNodeTest.java | 8 ++++---- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSettings.java index a0bd43c80f..b77fdec5aa 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSettings.java @@ -27,7 +27,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.common.util.ThingsBoardThreadFactory; import jakarta.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.concurrent.Executors; diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index e8701be3ce..4988243a53 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -832,4 +832,4 @@ class TbDateTest { Assert.assertNotNull(serializedTbDate); Assert.assertEquals(expectedDate.toString(), serializedTbDate); } -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index a5e05f695c..1204d02abf 100755 --- a/pom.xml +++ b/pom.xml @@ -1971,11 +1971,6 @@ hibernate-validator ${hibernate-validator.version} - - io.hypersistence - hypersistence-utils-hibernate-55 - ${hypersistence-utils.version} - io.hypersistence hypersistence-utils-hibernate-62 diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 6085abd859..4f1784d79a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -393,4 +393,4 @@ public class TbHttpClient { } } -} \ No newline at end of file +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index ad23b7492b..0c60c3f975 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -211,7 +211,7 @@ public class TbDeviceProfileNodeTest { registerCreateAlarmMock(alarmService.updateAlarm(any()), false); - + Thread.sleep(1); TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -644,7 +644,7 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); - Thread.sleep(halfOfAlarmDelay); + Thread.sleep(halfOfAlarmDelay + 1); TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); @@ -768,7 +768,7 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); - Thread.sleep(halfOfAlarmDelay); + Thread.sleep(halfOfAlarmDelay + 1); TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); @@ -1089,7 +1089,7 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); - Thread.sleep(halfOfAlarmDelay); + Thread.sleep(halfOfAlarmDelay + 1); TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); From 03560f05577c7c73f708271c98eb2418d2df52e0 Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 26 Dec 2023 17:26:51 +0200 Subject: [PATCH 071/209] lwm2ml: add tests attributes --- ...pcLwm2mIntegrationWriteAttributesTest.java | 37 +++++++++++++-- .../profile/lwm2m/ObjectAttributes.java | 15 ++++++- .../DefaultLwM2mDownlinkMsgHandler.java | 45 +++++++++++++++---- 3 files changed, 83 insertions(+), 14 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java index 6275a657b5..c2d87149de 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.rpc.sql; import com.fasterxml.jackson.databind.node.ObjectNode; import org.eclipse.leshan.core.ResponseCode; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; @@ -30,22 +29,47 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MIntegrationTest { - @Ignore /** * WriteAttributes {"id":"/3/0/14","attributes":{"pmax":100, "pmin":10}} * if not implemented: - * {"result":"BAD_REQUEST","error":"Content Format is mandatory"} + * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} * if implemented: * {"result":"CHANGED"} */ @Test public void testWriteAttributesResourceWithParametersById_Result_INTERNAL_SERVER_ERROR() throws Exception { String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; + sendRPCReadById(expectedPath); String expectedValue = "{\"pmax\":100, \"pmin\":10}"; String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); + String expected = "not implemented"; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + @Test + public void testWriteAttributesResourceVerWithParametersById_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectIdVer_3; + String actualResult = sendRPCReadById(expectedPath); + String expectedValue = "{\"ver\":1.3}"; + actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Content Format is mandatory"; + String expected = "Attribute ver is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + @Test + public void testWriteAttributesResourceServerUriWithParametersById_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectInstanceIdVer_1; + String actualResult = sendRPCReadById(expectedPath); + String expectedValue = "{\"uri\":\"coaps://localhost:5690\"}"; + actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Attribute uri is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; String actual = rpcActualResult.get("error").asText(); assertTrue(actual.equals(expected)); } @@ -55,4 +79,9 @@ public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MInte return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); } + private String sendRPCReadById(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java index b195068b62..f22ffe4104 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile.lwm2m; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; +import org.eclipse.leshan.core.LwM2m; import java.io.Serializable; @@ -25,13 +26,25 @@ import java.io.Serializable; public class ObjectAttributes implements Serializable { private static final long serialVersionUID = 4765123984733721312L; - private Long dim; + private Long ssid; + private String uri; private String ver; + private String lwm2m; private Long pmin; private Long pmax; private Double gt; private Double lt; private Double st; + private Long epmin; + private Long epmax; + + public LwM2m.Version getVer(){ + return ver != null ? new LwM2m.Version(ver) : null; + } + + public LwM2m.LwM2mVersion getLwm2m(){ + return lwm2m != null ? LwM2m.LwM2mVersion.get(lwm2m) : null; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index e124b8142d..3f2f8c598e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -92,10 +92,17 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.DIMENSION; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.ENABLER_VERSION; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.EVALUATE_MAXIMUM_PERIOD; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.EVALUATE_MINIMUM_PERIOD; import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.GREATER_THAN; import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.LESSER_THAN; import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.MAXIMUM_PERIOD; import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.MINIMUM_PERIOD; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.OBJECT_VERSION; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.SERVER_URI; +import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.SHORT_SERVER_ID; import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.STEP; import static org.eclipse.leshan.core.model.ResourceModel.Type.OBJLNK; import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; @@ -281,20 +288,40 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im if (request.getAttributes() == null) { throw new IllegalArgumentException("Attributes to write are not specified!"); } - ObjectAttributes params = request.getAttributes(); - List> attributes = new LinkedList<>(); - addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax()); - addAttribute(attributes, MINIMUM_PERIOD, params.getPmin()); - addAttribute(attributes, GREATER_THAN, params.getGt()); - addAttribute(attributes, LESSER_THAN, params.getLt()); - addAttribute(attributes, STEP, params.getSt()); - LwM2mAttributeSet attributeSet = new LwM2mAttributeSet(attributes); - sendSimpleRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback); + sendSimpleRequest(client, new WriteAttributesRequest(request.getObjectId(), getAttributesSet(request.getAttributes())), request.getTimeout(), callback); } catch (InvalidRequestException e) { callback.onValidationError(request.toString(), e.getMessage()); } } + private LwM2mAttributeSet getAttributesSet(ObjectAttributes params) { + List> attributes = new LinkedList<>(); + /** + * Only: AttributeClass.NOTIFICATION -> RW + */ + addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax()); + addAttribute(attributes, MINIMUM_PERIOD, params.getPmin()); + addAttribute(attributes, GREATER_THAN, params.getGt()); + addAttribute(attributes, LESSER_THAN, params.getLt()); + addAttribute(attributes, STEP, params.getSt()); + addAttribute(attributes, EVALUATE_MAXIMUM_PERIOD, params.getEpmax()); + addAttribute(attributes, EVALUATE_MINIMUM_PERIOD, params.getEpmin()); + /** + * Only: AttributeClass.PROPERTIES -> R + */ + addAttribute(attributes, DIMENSION, params.getDim()); // Attachment.RESOURCE + addAttribute(attributes, SHORT_SERVER_ID, params.getSsid()); // Attachment.OBJECT_INSTANCE + addAttribute(attributes, SERVER_URI, params.getUri()); // Attachment.OBJECT_INSTANCE + if (params.getLwm2m() != null) { + addAttribute(attributes, ENABLER_VERSION, params.getLwm2m()); // attachment.ROOT + } + if (params.getVer() != null) { + addAttribute(attributes, OBJECT_VERSION, params.getVer()); // Attachment.OBJECT + } + + return new LwM2mAttributeSet(attributes); + } + @Override public void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback) { LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); From a97f52c6984b2296088fdf62498dfd165f277eb0 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 28 Dec 2023 15:50:47 +0200 Subject: [PATCH 072/209] lwm2m: comments (why full import?) --- .../server/transport/lwm2m/client/FwLwM2MDevice.java | 7 ++++--- .../lwm2m/client/LwM2mBinaryAppDataContainer.java | 5 +++-- .../server/transport/lwm2m/client/LwM2mLocation.java | 3 ++- .../transport/lwm2m/client/LwM2mTemperatureSensor.java | 5 +++-- .../server/transport/lwm2m/client/Lwm2mServer.java | 7 ++++--- .../server/transport/lwm2m/client/SimpleLwM2MDevice.java | 7 ++++--- .../server/transport/lwm2m/client/SwLwM2MDevice.java | 7 ++++--- 7 files changed, 24 insertions(+), 17 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java index 86124ccb5f..444158b956 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.request.argument.Arguments; @@ -45,7 +46,7 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { private final AtomicInteger updateResult = new AtomicInteger(0); @Override - public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { + public ReadResponse read(LwM2mServer identity, int resourceId) { if (!identity.isSystem()) log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { @@ -65,7 +66,7 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { } @Override - public ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId, Arguments arguments) { + public ExecuteResponse execute(LwM2mServer identity, int resourceId, Arguments arguments) { String withArguments = ""; if (!arguments.isEmpty()) withArguments = " with arguments " + arguments; @@ -82,7 +83,7 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { } @Override - public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { + public WriteResponse write(LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java index 35866dea0f..5b4dedbba5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mMultipleResource; @@ -92,7 +93,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements } @Override - public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { + public ReadResponse read(LwM2mServer identity, int resourceId) { try { switch (resourceId) { case 0: @@ -117,7 +118,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements } @Override - public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { + public WriteResponse write(LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { log.info("Write on Device resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); switch (resourceId) { case 0: diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java index 4599f7c0ee..8d76498ccf 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.response.ReadResponse; @@ -84,7 +85,7 @@ public class LwM2mLocation extends BaseInstanceEnabler implements Destroyable { } @Override - public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { + public ReadResponse read(LwM2mServer identity, int resourceId) { log.info("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); switch (resourceId) { case 0: diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java index ae0297e02b..bd7703dbb3 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; @@ -56,7 +57,7 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr } @Override - public synchronized ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { + public synchronized ReadResponse read(LwM2mServer identity, int resourceId) { log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); switch (resourceId) { case 5601: @@ -73,7 +74,7 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr } @Override - public synchronized ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId, Arguments arguments) { + public synchronized ExecuteResponse execute(LwM2mServer identity, int resourceId, Arguments arguments) { log.info("Execute on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); switch (resourceId) { case 5605: diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java index 1cddbb17f8..9ab30b3dde 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; +import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mResource; @@ -70,7 +71,7 @@ public class Lwm2mServer extends BaseInstanceEnabler { } @Override - public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceid) { + public ReadResponse read(LwM2mServer identity, int resourceid) { if (!identity.isSystem()) LOG.debug("Read on Server resource /{}/{}/{}", getModel().id, getId(), resourceid); @@ -108,7 +109,7 @@ public class Lwm2mServer extends BaseInstanceEnabler { } @Override - public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceid, LwM2mResource value) { + public WriteResponse write(LwM2mServer identity, boolean replace, int resourceid, LwM2mResource value) { if (!identity.isSystem()) log.debug("Write on Server resource /{}/{}/{}", getModel().id, getId(), resourceid); @@ -195,7 +196,7 @@ public class Lwm2mServer extends BaseInstanceEnabler { } } - public ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceid, Arguments arguments) { + public ExecuteResponse execute(LwM2mServer identity, int resourceid, Arguments arguments) { log.info("Execute on Server resource /{}/{}/{}", getModel().id, getId(), resourceid); if (resourceid == 8) { getLwM2mClient().triggerRegistrationUpdate(identity); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java index 8a32092572..e9bc2c9d48 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; @@ -66,7 +67,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl @Override - public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { + public ReadResponse read(LwM2mServer identity, int resourceId) { if (!identity.isSystem()) log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { @@ -108,7 +109,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl } @Override - public ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId, Arguments arguments) { + public ExecuteResponse execute(LwM2mServer identity, int resourceId, Arguments arguments) { String withArguments = ""; if (!arguments.isEmpty()) withArguments = " with arguments " + arguments; @@ -117,7 +118,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl } @Override - public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { + public WriteResponse write(LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java index 340f9ffcb2..da6bbcd379 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.request.argument.Arguments; @@ -45,7 +46,7 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { private final AtomicInteger updateResult = new AtomicInteger(0); @Override - public ReadResponse read(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId) { + public ReadResponse read(LwM2mServer identity, int resourceId) { if (!identity.isSystem()) log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { @@ -63,7 +64,7 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { } @Override - public ExecuteResponse execute(org.eclipse.leshan.client.servers.LwM2mServer identity, int resourceId, Arguments arguments) { + public ExecuteResponse execute(LwM2mServer identity, int resourceId, Arguments arguments) { String withArguments = ""; if (!arguments.isEmpty()) withArguments = " with arguments " + arguments; @@ -81,7 +82,7 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { } @Override - public WriteResponse write(org.eclipse.leshan.client.servers.LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { + public WriteResponse write(LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { From 992767d7b102e3560e3682e4630a29c5843ede01 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 28 Dec 2023 16:44:31 +0200 Subject: [PATCH 073/209] lwm2m: redis --- .../store/TbLwM2mRedisRegistrationStore.java | 92 +++++++++---------- 1 file changed, 41 insertions(+), 51 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java index 40a281728b..fa44c2a93b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java @@ -333,12 +333,50 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab @Override public Collection addObservation(String registrationId, Observation observation, boolean addIfAbsent) { - return null; + List removed = new ArrayList<>(); + try (var connection = connectionFactory.getConnection()) { + + // fetch the client ep by registration ID index + byte[] ep = connection.get(toRegIdKey(registrationId)); + if (ep == null) { + return null; + } + + Lock lock = null; + String lockKey = toLockKey(ep); + + try { + lock = redisLock.obtain(lockKey); + lock.lock(); + + // cancel existing observations for the same path and registration id. + for (Observation obs : getObservations(connection, registrationId)) { + //TODO: should be able to use CompositeObservation + if (((SingleObservation)observation).getPath().equals(((SingleObservation)obs).getPath()) + && !observation.getId().equals(obs.getId())) { + removed.add(obs); + unsafeRemoveObservation(connection, registrationId, obs.getId().getBytes()); + } + } + + } finally { + if (lock != null) { + lock.unlock(); + } + } + } + return removed; } + @Override + public Collection getObservations(String registrationId) { + try (var connection = connectionFactory.getConnection()) { + return getObservations(connection, registrationId); + } + } @Override public Observation getObservation(String registrationId, ObservationIdentifier observationId) { - return null; + return getObservations(registrationId).stream().filter(o -> o.getId()==observationId).findFirst().get(); } @Override @@ -348,7 +386,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab @Override public Observation removeObservation(String registrationId, ObservationIdentifier observationId) { - return null; + return removeObservation(registrationId, observationId.getBytes()); } private Deregistration removeRegistration(RedisConnection connection, String registrationId, boolean removeOnlyIfNotAlive) { @@ -461,42 +499,6 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab * org.eclipse.californium.core.observe.ObservationStore#add method) */ - public Collection addObservation(String registrationId, Observation observation) { - List removed = new ArrayList<>(); - try (var connection = connectionFactory.getConnection()) { - - // fetch the client ep by registration ID index - byte[] ep = connection.get(toRegIdKey(registrationId)); - if (ep == null) { - return null; - } - - Lock lock = null; - String lockKey = toLockKey(ep); - - try { - lock = redisLock.obtain(lockKey); - lock.lock(); - - // cancel existing observations for the same path and registration id. - for (Observation obs : getObservations(connection, registrationId)) { - //TODO: should be able to use CompositeObservation - if (((SingleObservation)observation).getPath().equals(((SingleObservation)obs).getPath()) - && !observation.getId().equals(obs.getId())) { - removed.add(obs); - unsafeRemoveObservation(connection, registrationId, obs.getId().getBytes()); - } - } - - } finally { - if (lock != null) { - lock.unlock(); - } - } - } - return removed; - } - public Observation removeObservation(String registrationId, byte[] observationId) { try (var connection = connectionFactory.getConnection()) { @@ -529,18 +531,6 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab } } - public Observation getObservation(String registrationId, byte[] observationId) { -// return build(get(new Token(observationId))); - return build(null); - } - - @Override - public Collection getObservations(String registrationId) { - try (var connection = connectionFactory.getConnection()) { - return getObservations(connection, registrationId); - } - } - private Collection getObservations(RedisConnection connection, String registrationId) { Collection result = new ArrayList<>(); for (byte[] token : connection.lRange(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, -1)) { From dd711989083af958ab8784c87f84a5665229722a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 28 Dec 2023 16:32:10 +0100 Subject: [PATCH 074/209] added clean cache for the upgrade --- .../thingsboard/server/install/ThingsboardInstallService.java | 2 ++ .../service/install/update/DefaultCacheCleanupService.java | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 5ab3b7b28e..95391fa0c0 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -276,6 +276,8 @@ public class ThingsboardInstallService { } else { log.info("Skipping images migration. Run the upgrade with fromVersion as '3.6.2-images' to migrate"); } + case "3.6.3": + log.info("Upgrading ThingsBoard from version 3.6.3 to 3.7.0 ..."); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache break; default: diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java index 350005536a..fd0edef626 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java @@ -94,6 +94,10 @@ public class DefaultCacheCleanupService implements CacheCleanupService { clearCacheByName(SECURITY_SETTINGS_CACHE); clearCacheByName(RESOURCE_INFO_CACHE); break; + case "3.6.3": + log.info("Clearing cache to upgrade from version 3.6.3 to 3.7.0"); + clearAll(); + break; default: //Do nothing, since cache cleanup is optional. } From c92a7ecf3a5d18b2f56c14a984ebe8e2bf7cf31e Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 2 Jan 2024 17:46:29 +0200 Subject: [PATCH 075/209] deleted attribute_kv_dictionary table (used ts_kv_dictionary instead), renamed ts_kv_dictionary to key_dictionary --- .../schema_update_attribute_kv.sql | 27 +++++++----- .../install/ThingsboardInstallService.java | 6 +-- .../install/SqlDatabaseUpgradeService.java | 6 +-- .../CassandraTsLatestToSqlMigrateService.java | 24 +++++------ .../model/sql/AttributeKvDictionaryEntry.java | 41 ------------------- ...ey.java => KeyDictionaryCompositeKey.java} | 2 +- ...ictionary.java => KeyDictionaryEntry.java} | 6 +-- .../AttributeKvDictionaryRepository.java | 27 ------------ .../dao/sql/attributes/JpaAttributeDao.java | 22 +++++----- .../dao/sql/query/EntityKeyMapping.java | 6 +-- .../sqlts/BaseAbstractSqlTimeseriesDao.java | 24 +++++------ ...tory.java => KeyDictionaryRepository.java} | 11 +++-- .../latest/SearchTsKvLatestRepository.java | 4 +- .../sqlts/latest/TsKvLatestRepository.java | 18 ++++---- .../main/resources/sql/schema-entities.sql | 10 +---- .../main/resources/sql/schema-timescale.sql | 6 +-- dao/src/main/resources/sql/schema-ts-psql.sql | 12 +++--- .../sql/schema-views-and-functions.sql | 8 ++-- .../profile/TbDeviceProfileNodeTest.java | 3 -- .../tools/migrator/DictionaryParser.java | 2 +- 20 files changed, 97 insertions(+), 168 deletions(-) rename application/src/main/data/upgrade/{3.6.2 => 3.6.3}/schema_update_attribute_kv.sql (88%) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionaryEntry.java rename dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/{TsKvDictionaryCompositeKey.java => KeyDictionaryCompositeKey.java} (93%) rename dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/{TsKvDictionary.java => KeyDictionaryEntry.java} (91%) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvDictionaryRepository.java rename dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/{TsKvDictionaryRepository.java => KeyDictionaryRepository.java} (65%) diff --git a/application/src/main/data/upgrade/3.6.2/schema_update_attribute_kv.sql b/application/src/main/data/upgrade/3.6.3/schema_update_attribute_kv.sql similarity index 88% rename from application/src/main/data/upgrade/3.6.2/schema_update_attribute_kv.sql rename to application/src/main/data/upgrade/3.6.3/schema_update_attribute_kv.sql index 8a13029310..509a94bf20 100644 --- a/application/src/main/data/upgrade/3.6.2/schema_update_attribute_kv.sql +++ b/application/src/main/data/upgrade/3.6.3/schema_update_attribute_kv.sql @@ -46,13 +46,18 @@ $$ END; $$; --- create attribute_kv_dictionary table -CREATE TABLE IF NOT EXISTS attribute_kv_dictionary -( - key varchar(255) NOT NULL, - key_id serial UNIQUE, - CONSTRAINT attribute_key_id_pkey PRIMARY KEY (key) -); +-- rename ts_kv_dictionary table to key_dictionary +DO +$$ + BEGIN + IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'ts_kv_dictionary') THEN + ALTER TABLE ts_kv_dictionary + RENAME CONSTRAINT ts_key_id_pkey TO key_id_pkey; + ALTER TABLE ts_kv_dictionary + RENAME TO key_dictionary; + END IF; + END; +$$; -- create to_attribute_type_id CREATE OR REPLACE FUNCTION to_attribute_type_id(IN attribute_type varchar, OUT attribute_type_id int) AS @@ -70,7 +75,7 @@ END; $$ LANGUAGE plpgsql; --- insert keys into attribute_kv_dictionary +-- insert keys into key_dictionary DO $$ DECLARE @@ -84,8 +89,8 @@ BEGIN LOOP FETCH key_cursor INTO insert_record; EXIT WHEN NOT FOUND; - IF NOT EXISTS(SELECT key FROM attribute_kv_dictionary WHERE key = insert_record.attribute_key) THEN - INSERT INTO attribute_kv_dictionary(key) VALUES (insert_record.attribute_key); + IF NOT EXISTS(SELECT key FROM key_dictionary WHERE key = insert_record.attribute_key) THEN + INSERT INTO key_dictionary(key) VALUES (insert_record.attribute_key); END IF; END LOOP; CLOSE key_cursor; @@ -122,7 +127,7 @@ BEGIN dbl_v, json_v, last_update_ts - FROM attribute_kv_old INNER JOIN attribute_kv_dictionary ON (attribute_kv_old.attribute_key = attribute_kv_dictionary.key) + FROM attribute_kv_old INNER JOIN key_dictionary ON (attribute_kv_old.attribute_key = key_dictionary.key) WHERE attribute_type= ANY(%L)) AS records) TO %L;', attribute_scope_array, path_to_file); EXECUTE format('COPY attribute_kv FROM %L', path_to_file); SELECT COUNT(*) INTO row_num_old FROM attribute_kv_old; diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index f9fcf77001..383d22c21f 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -265,9 +265,9 @@ public class ThingsboardInstallService { case "3.6.0": log.info("Upgrading ThingsBoard from version 3.6.0 to 3.6.1 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.6.0"); - case "3.6.2": - log.info("Upgrading ThingsBoard from version 3.6.2 to 3.7.0 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.6.2"); + case "3.6.3": + log.info("Upgrading ThingsBoard from version 3.6.3 to 3.7.0 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.6.3"); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache break; default: diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 1a22b6e0e2..1ad5c9afe4 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -799,11 +799,11 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.error("Failed updating schema!!!", e); } break; - case "3.6.2": + case "3.6.3": try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - if (isOldSchema(conn, 3006002)) { + if (isOldSchema(conn, 3006003)) { log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.6.2", LOAD_ATTRIBUTE_KV_FUNCTIONS_SQL); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.6.3", LOAD_ATTRIBUTE_KV_FUNCTIONS_SQL); loadSql(schemaUpdateFile, conn); Path pathToTempAttributeKvFile; diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java index 4357e6be63..083d513e6d 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java @@ -24,10 +24,10 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.dao.cassandra.CassandraCluster; -import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; -import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; +import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryEntry; +import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryCompositeKey; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; -import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository; +import org.thingsboard.server.dao.sqlts.dictionary.KeyDictionaryRepository; import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository; import org.thingsboard.server.dao.util.NoSqlTsDao; import org.thingsboard.server.dao.util.SqlTsLatestDao; @@ -71,7 +71,7 @@ public class CassandraTsLatestToSqlMigrateService implements TsLatestMigrateServ protected CassandraCluster cluster; @Autowired - protected TsKvDictionaryRepository dictionaryRepository; + protected KeyDictionaryRepository dictionaryRepository; @Autowired private InstallScripts installScripts; @@ -192,22 +192,22 @@ public class CassandraTsLatestToSqlMigrateService implements TsLatestMigrateServ Integer keyId = tsKvDictionaryMap.get(strKey); if (keyId == null) { - Optional tsKvDictionaryOptional; - tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + Optional tsKvDictionaryOptional; + tsKvDictionaryOptional = dictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); if (!tsKvDictionaryOptional.isPresent()) { tsCreationLock.lock(); try { - tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + tsKvDictionaryOptional = dictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); if (!tsKvDictionaryOptional.isPresent()) { - TsKvDictionary tsKvDictionary = new TsKvDictionary(); - tsKvDictionary.setKey(strKey); + KeyDictionaryEntry keyDictionaryEntry = new KeyDictionaryEntry(); + keyDictionaryEntry.setKey(strKey); try { - TsKvDictionary saved = dictionaryRepository.save(tsKvDictionary); + KeyDictionaryEntry saved = dictionaryRepository.save(keyDictionaryEntry); tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId()); keyId = saved.getKeyId(); } catch (ConstraintViolationException e) { - tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); - TsKvDictionary dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); + tsKvDictionaryOptional = dictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); + KeyDictionaryEntry dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); keyId = dictionary.getKeyId(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionaryEntry.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionaryEntry.java deleted file mode 100644 index 98e3a0f5ab..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvDictionaryEntry.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.model.sql; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import lombok.Data; -import org.hibernate.annotations.Generated; - -import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.KEY_ID_COLUMN; - -@Data -@Entity -@Table(name = "attribute_kv_dictionary") -public final class AttributeKvDictionaryEntry { - - @Id - @Column(name = KEY_COLUMN) - private String key; - - @Column(name = KEY_ID_COLUMN, unique = true, columnDefinition="int") - @Generated - private int keyId; - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionaryCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/KeyDictionaryCompositeKey.java similarity index 93% rename from dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionaryCompositeKey.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/KeyDictionaryCompositeKey.java index 49ab8b6822..599c0e5eb7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionaryCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/KeyDictionaryCompositeKey.java @@ -25,7 +25,7 @@ import java.io.Serializable; @Data @NoArgsConstructor @AllArgsConstructor -public class TsKvDictionaryCompositeKey implements Serializable{ +public class KeyDictionaryCompositeKey implements Serializable{ @Transient private static final long serialVersionUID = -4089175869616037523L; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/KeyDictionaryEntry.java similarity index 91% rename from dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/KeyDictionaryEntry.java index 5f1ff6a9c1..6063cbf70a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/KeyDictionaryEntry.java @@ -28,9 +28,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.KEY_ID_COLUMN; @Data @Entity -@Table(name = "ts_kv_dictionary") -@IdClass(TsKvDictionaryCompositeKey.class) -public final class TsKvDictionary { +@Table(name = "key_dictionary") +@IdClass(KeyDictionaryCompositeKey.class) +public final class KeyDictionaryEntry { @Id @Column(name = KEY_COLUMN) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvDictionaryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvDictionaryRepository.java deleted file mode 100644 index aa0ca5b8a5..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvDictionaryRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.sql.attributes; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.thingsboard.server.dao.model.sql.AttributeKvDictionaryEntry; - -import java.util.Optional; - -public interface AttributeKvDictionaryRepository extends JpaRepository { - - Optional findByKeyId(int keyId); - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index 50ee8c462c..01149d3d49 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -36,12 +36,14 @@ import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.attributes.AttributesDao; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; -import org.thingsboard.server.dao.model.sql.AttributeKvDictionaryEntry; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; +import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryCompositeKey; +import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryEntry; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper; +import org.thingsboard.server.dao.sqlts.dictionary.KeyDictionaryRepository; import org.thingsboard.server.dao.util.SqlDao; import java.util.ArrayList; @@ -64,7 +66,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl ScheduledLogExecutorComponent logExecutor; @Autowired - private AttributeKvDictionaryRepository dictionaryRepository; + private KeyDictionaryRepository keyDictionaryRepository; @Autowired private AttributeKvRepository attributeKvRepository; @@ -215,21 +217,21 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl private Integer getOrSaveKeyId(String attributeKey) { Integer keyId = attributeDictionaryMap.get(attributeKey); if (keyId == null) { - Optional byIdOptional = dictionaryRepository.findById(attributeKey); + Optional byIdOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(attributeKey)); if (byIdOptional.isEmpty()) { attributeCreationLock.lock(); try { - byIdOptional = dictionaryRepository.findById(attributeKey); + byIdOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(attributeKey)); if (byIdOptional.isEmpty()) { - AttributeKvDictionaryEntry attributeKvDictionaryEntry = new AttributeKvDictionaryEntry(); + KeyDictionaryEntry attributeKvDictionaryEntry = new KeyDictionaryEntry(); attributeKvDictionaryEntry.setKey(attributeKey); try { - AttributeKvDictionaryEntry saved = dictionaryRepository.save(attributeKvDictionaryEntry); + KeyDictionaryEntry saved = keyDictionaryRepository.save(attributeKvDictionaryEntry); attributeDictionaryMap.put(saved.getKey(), saved.getKeyId()); keyId = saved.getKeyId(); } catch (DataIntegrityViolationException | ConstraintViolationException e) { - byIdOptional = dictionaryRepository.findById(attributeKey); - AttributeKvDictionaryEntry dictionary = byIdOptional.orElseThrow(() -> new RuntimeException("Failed to get AttributeKvDictionary entity from DB!")); + byIdOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(attributeKey)); + KeyDictionaryEntry dictionary = byIdOptional.orElseThrow(() -> new RuntimeException("Failed to get AttributeKvDictionary entity from DB!")); attributeDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); keyId = dictionary.getKeyId(); } @@ -247,7 +249,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl return keyId; } private String getKey(Integer attributeKey) { - Optional byKeyId = dictionaryRepository.findByKeyId(attributeKey); - return byKeyId.map(AttributeKvDictionaryEntry::getKey).orElse(null); + Optional byKeyId = keyDictionaryRepository.findByKeyId(attributeKey); + return byKeyId.map(KeyDictionaryEntry::getKey).orElse(null); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 200e93765d..23d8e972b2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -256,13 +256,13 @@ public class EntityKeyMapping { } if (entityKey.getType().equals(EntityKeyType.TIME_SERIES)) { String join = (hasFilter() && hasFilterValues(ctx)) ? "inner join" : "left join"; - return String.format("%s ts_kv_latest %s ON %s.entity_id=entities.id AND %s.key = (select key_id from ts_kv_dictionary where key = :%s_key_id) %s", + return String.format("%s ts_kv_latest %s ON %s.entity_id=entities.id AND %s.key = (select key_id from key_dictionary where key = :%s_key_id) %s", join, alias, alias, alias, alias, filterQuery); } else { String query; if (!entityKey.getType().equals(EntityKeyType.ATTRIBUTE)) { String join = (hasFilter() && hasFilterValues(ctx)) ? "inner join" : "left join"; - query = String.format("%s attribute_kv %s ON %s.entity_id=entities.id AND %s.attribute_key=(select key_id from attribute_kv_dictionary where key = :%s_key_id) ", + query = String.format("%s attribute_kv %s ON %s.entity_id=entities.id AND %s.attribute_key=(select key_id from key_dictionary where key = :%s_key_id) ", join, alias, alias, alias, alias); int scope; if (entityKey.getType().equals(EntityKeyType.CLIENT_ATTRIBUTE)) { @@ -275,7 +275,7 @@ public class EntityKeyMapping { query = String.format("%s AND %s.attribute_type=%s %s", query, alias, scope, filterQuery); } else { String join = (hasFilter() && hasFilterValues(ctx)) ? "join LATERAL" : "left join LATERAL"; - query = String.format("%s (select * from attribute_kv %s WHERE %s.entity_id=entities.id AND %s.attribute_key=(select key_id from attribute_kv_dictionary where key = :%s_key_id) %s " + + query = String.format("%s (select * from attribute_kv %s WHERE %s.entity_id=entities.id AND %s.attribute_key=(select key_id from key_dictionary where key = :%s_key_id) %s " + "ORDER BY %s.last_update_ts DESC limit 1) as %s ON true", join, alias, alias, alias, alias, filterQuery, alias, alias); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java index 52c4216c69..9a33cba81e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java @@ -26,10 +26,10 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; -import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; -import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; +import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryEntry; +import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryCompositeKey; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; -import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository; +import org.thingsboard.server.dao.sqlts.dictionary.KeyDictionaryRepository; import jakarta.annotation.Nullable; import java.util.List; @@ -46,13 +46,13 @@ public abstract class BaseAbstractSqlTimeseriesDao extends JpaAbstractDaoListeni private final ConcurrentMap tsKvDictionaryMap = new ConcurrentHashMap<>(); protected static final ReentrantLock tsCreationLock = new ReentrantLock(); @Autowired - protected TsKvDictionaryRepository dictionaryRepository; + protected KeyDictionaryRepository dictionaryRepository; protected Integer getOrSaveKeyId(String strKey) { Integer keyId = tsKvDictionaryMap.get(strKey); if (keyId == null) { - Optional tsKvDictionaryOptional; - tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + Optional tsKvDictionaryOptional; + tsKvDictionaryOptional = dictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); if (tsKvDictionaryOptional.isEmpty()) { tsCreationLock.lock(); try { @@ -60,17 +60,17 @@ public abstract class BaseAbstractSqlTimeseriesDao extends JpaAbstractDaoListeni if (keyId != null) { return keyId; } - tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + tsKvDictionaryOptional = dictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); if (tsKvDictionaryOptional.isEmpty()) { - TsKvDictionary tsKvDictionary = new TsKvDictionary(); - tsKvDictionary.setKey(strKey); + KeyDictionaryEntry keyDictionaryEntry = new KeyDictionaryEntry(); + keyDictionaryEntry.setKey(strKey); try { - TsKvDictionary saved = dictionaryRepository.save(tsKvDictionary); + KeyDictionaryEntry saved = dictionaryRepository.save(keyDictionaryEntry); tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId()); keyId = saved.getKeyId(); } catch (DataIntegrityViolationException | ConstraintViolationException e) { - tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); - TsKvDictionary dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); + tsKvDictionaryOptional = dictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); + KeyDictionaryEntry dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); keyId = dictionary.getKeyId(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/KeyDictionaryRepository.java similarity index 65% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/KeyDictionaryRepository.java index 41e165b92f..12c2422094 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/KeyDictionaryRepository.java @@ -16,15 +16,14 @@ package org.thingsboard.server.dao.sqlts.dictionary; import org.springframework.data.jpa.repository.JpaRepository; -import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; -import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; -import org.thingsboard.server.dao.util.SqlTsOrTsLatestAnyDao; +import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryEntry; +import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryCompositeKey; import java.util.Optional; -@SqlTsOrTsLatestAnyDao -public interface TsKvDictionaryRepository extends JpaRepository { +public interface KeyDictionaryRepository extends JpaRepository { + + Optional findByKeyId(int keyId); - Optional findByKeyId(int keyId); } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java index beaa4a5f0d..fc7b2d568f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java @@ -30,9 +30,9 @@ public class SearchTsKvLatestRepository { public static final String FIND_ALL_BY_ENTITY_ID = "findAllByEntityId"; - public static final String FIND_ALL_BY_ENTITY_ID_QUERY = "SELECT ts_kv_latest.entity_id AS entityId, ts_kv_latest.key AS key, ts_kv_dictionary.key AS strKey, ts_kv_latest.str_v AS strValue," + + public static final String FIND_ALL_BY_ENTITY_ID_QUERY = "SELECT ts_kv_latest.entity_id AS entityId, ts_kv_latest.key AS key, key_dictionary.key AS strKey, ts_kv_latest.str_v AS strValue," + " ts_kv_latest.bool_v AS boolValue, ts_kv_latest.long_v AS longValue, ts_kv_latest.dbl_v AS doubleValue, ts_kv_latest.json_v AS jsonValue, ts_kv_latest.ts AS ts FROM ts_kv_latest " + - "INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id WHERE ts_kv_latest.entity_id = cast(:id AS uuid)"; + "INNER JOIN key_dictionary ON ts_kv_latest.key = key_dictionary.key_id WHERE ts_kv_latest.entity_id = cast(:id AS uuid)"; @PersistenceContext private EntityManager entityManager; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java index 835ce3a11b..abcae06e19 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java @@ -26,19 +26,19 @@ import java.util.UUID; public interface TsKvLatestRepository extends JpaRepository { - @Query(value = "SELECT DISTINCT ts_kv_dictionary.key AS strKey FROM ts_kv_latest " + - "INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id " + - "WHERE ts_kv_latest.entity_id IN (SELECT id FROM device WHERE device_profile_id = :device_profile_id AND tenant_id = :tenant_id limit 100) ORDER BY ts_kv_dictionary.key", nativeQuery = true) + @Query(value = "SELECT DISTINCT key_dictionary.key AS strKey FROM ts_kv_latest " + + "INNER JOIN key_dictionary ON ts_kv_latest.key = key_dictionary.key_id " + + "WHERE ts_kv_latest.entity_id IN (SELECT id FROM device WHERE device_profile_id = :device_profile_id AND tenant_id = :tenant_id limit 100) ORDER BY key_dictionary.key", nativeQuery = true) List getKeysByDeviceProfileId(@Param("tenant_id") UUID tenantId, @Param("device_profile_id") UUID deviceProfileId); - @Query(value = "SELECT DISTINCT ts_kv_dictionary.key AS strKey FROM ts_kv_latest " + - "INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id " + - "WHERE ts_kv_latest.entity_id IN (SELECT id FROM device WHERE tenant_id = :tenant_id limit 100) ORDER BY ts_kv_dictionary.key", nativeQuery = true) + @Query(value = "SELECT DISTINCT key_dictionary.key AS strKey FROM ts_kv_latest " + + "INNER JOIN key_dictionary ON ts_kv_latest.key = key_dictionary.key_id " + + "WHERE ts_kv_latest.entity_id IN (SELECT id FROM device WHERE tenant_id = :tenant_id limit 100) ORDER BY key_dictionary.key", nativeQuery = true) List getKeysByTenantId(@Param("tenant_id") UUID tenantId); - @Query(value = "SELECT DISTINCT ts_kv_dictionary.key AS strKey FROM ts_kv_latest " + - "INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id " + - "WHERE ts_kv_latest.entity_id IN :entityIds ORDER BY ts_kv_dictionary.key", nativeQuery = true) + @Query(value = "SELECT DISTINCT key_dictionary.key AS strKey FROM ts_kv_latest " + + "INNER JOIN key_dictionary ON ts_kv_latest.key = key_dictionary.key_id " + + "WHERE ts_kv_latest.entity_id IN :entityIds ORDER BY key_dictionary.key", nativeQuery = true) List findAllKeysByEntityIds(@Param("entityIds") List entityIds); } diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index f376a777d3..9ce78bd56f 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -115,12 +115,6 @@ CREATE TABLE IF NOT EXISTS attribute_kv ( CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_id, attribute_type, attribute_key) ); -CREATE TABLE IF NOT EXISTS attribute_kv_dictionary ( - key varchar(255) NOT NULL, - key_id serial UNIQUE, - CONSTRAINT attribute_key_id_pkey PRIMARY KEY (key) -); - CREATE TABLE IF NOT EXISTS component_descriptor ( id uuid NOT NULL CONSTRAINT component_descriptor_pkey PRIMARY KEY, created_time bigint NOT NULL, @@ -552,11 +546,11 @@ CREATE TABLE IF NOT EXISTS ts_kv_latest CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) ); -CREATE TABLE IF NOT EXISTS ts_kv_dictionary +CREATE TABLE IF NOT EXISTS key_dictionary ( key varchar(255) NOT NULL, key_id serial UNIQUE, - CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) + CONSTRAINT key_id_pkey PRIMARY KEY (key) ); CREATE TABLE IF NOT EXISTS oauth2_params ( diff --git a/dao/src/main/resources/sql/schema-timescale.sql b/dao/src/main/resources/sql/schema-timescale.sql index 0a60b64a50..f547a72cd1 100644 --- a/dao/src/main/resources/sql/schema-timescale.sql +++ b/dao/src/main/resources/sql/schema-timescale.sql @@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS ts_kv ( CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts) ); -CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( +CREATE TABLE IF NOT EXISTS key_dictionary ( key varchar(255) NOT NULL, key_id serial UNIQUE, CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) @@ -104,7 +104,7 @@ BEGIN WHILE FOUND LOOP EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from key_dictionary where key = %L)', tenant_id_record, 'TTL') INTO tenant_ttl; if tenant_ttl IS NULL THEN tenant_ttl := system_ttl; @@ -122,7 +122,7 @@ BEGIN SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record LOOP EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from key_dictionary where key = %L)', customer_id_record, 'TTL') INTO customer_ttl; IF customer_ttl IS NULL THEN customer_ttl_ts := tenant_ttl_ts; diff --git a/dao/src/main/resources/sql/schema-ts-psql.sql b/dao/src/main/resources/sql/schema-ts-psql.sql index 3f8f380b03..023e1b544d 100644 --- a/dao/src/main/resources/sql/schema-ts-psql.sql +++ b/dao/src/main/resources/sql/schema-ts-psql.sql @@ -27,7 +27,7 @@ CREATE TABLE IF NOT EXISTS ts_kv CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts) ) PARTITION BY RANGE (ts); -CREATE TABLE IF NOT EXISTS ts_kv_dictionary +CREATE TABLE IF NOT EXISTS key_dictionary ( key varchar(255) NOT NULL, key_id serial UNIQUE, @@ -75,7 +75,7 @@ BEGIN WHERE schemaname = 'public' AND tablename like 'ts_kv_' || '%' AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' + AND tablename != 'key_dictionary' AND tablename != 'ts_kv_indefinite' AND tablename != partition_by_max_ttl_date LOOP @@ -96,7 +96,7 @@ BEGIN WHERE schemaname = 'public' AND tablename like 'ts_kv_' || '%' AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' + AND tablename != 'key_dictionary' AND tablename != 'ts_kv_indefinite' AND tablename != partition_by_max_ttl_date LOOP @@ -138,7 +138,7 @@ BEGIN WHERE schemaname = 'public' AND tablename like 'ts_kv_' || '%' AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' + AND tablename != 'key_dictionary' AND tablename != 'ts_kv_indefinite' AND tablename != partition_by_max_ttl_date LOOP @@ -272,7 +272,7 @@ BEGIN WHILE FOUND LOOP EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from key_dictionary where key = %L)', tenant_id_record, 'TTL') INTO tenant_ttl; if tenant_ttl IS NULL THEN tenant_ttl := system_ttl; @@ -290,7 +290,7 @@ BEGIN SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record LOOP EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from key_dictionary where key = %L)', customer_id_record, 'TTL') INTO customer_ttl; IF customer_ttl IS NULL THEN customer_ttl_ts := tenant_ttl_ts; diff --git a/dao/src/main/resources/sql/schema-views-and-functions.sql b/dao/src/main/resources/sql/schema-views-and-functions.sql index 4d684c6406..bcb34a030a 100644 --- a/dao/src/main/resources/sql/schema-views-and-functions.sql +++ b/dao/src/main/resources/sql/schema-views-and-functions.sql @@ -23,7 +23,7 @@ SELECT d.* , COALESCE(da.bool_v, FALSE) as active FROM device d LEFT JOIN customer c ON c.id = d.customer_id - LEFT JOIN attribute_kv da ON da.entity_id = d.id AND da.attribute_type = 2 AND da.attribute_key = (select key_id from attribute_kv_dictionary where key = 'active'); + LEFT JOIN attribute_kv da ON da.entity_id = d.id AND da.attribute_type = 2 AND da.attribute_key = (select key_id from key_dictionary where key = 'active'); DROP VIEW IF EXISTS device_info_active_ts_view CASCADE; CREATE OR REPLACE VIEW device_info_active_ts_view AS @@ -34,7 +34,7 @@ SELECT d.* , COALESCE(dt.bool_v, FALSE) as active FROM device d LEFT JOIN customer c ON c.id = d.customer_id - LEFT JOIN ts_kv_latest dt ON dt.entity_id = d.id and dt.key = (select key_id from ts_kv_dictionary where key = 'active'); + LEFT JOIN ts_kv_latest dt ON dt.entity_id = d.id and dt.key = (select key_id from key_dictionary where key = 'active'); DROP VIEW IF EXISTS device_info_view CASCADE; CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_attribute_view; @@ -312,7 +312,7 @@ BEGIN WHILE FOUND LOOP EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from key_dictionary where key = %L)', tenant_id_record, 'TTL') INTO tenant_ttl; if tenant_ttl IS NULL THEN tenant_ttl := system_ttl; @@ -330,7 +330,7 @@ BEGIN SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record LOOP EXECUTE format( - 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from attribute_kv_dictionary where key = %L)', + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = (select key_id from key_dictionary where key = %L)', customer_id_record, 'TTL') INTO customer_ttl; IF customer_ttl IS NULL THEN customer_ttl_ts := tenant_ttl_ts; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index 0b94f07255..2aafcc0381 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -30,10 +30,8 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.AttributeScope; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -60,7 +58,6 @@ import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValueSourceType; -import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.NumericFilterPredicate; diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/DictionaryParser.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/DictionaryParser.java index 3b1164e95b..b85171d6ac 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/DictionaryParser.java +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/DictionaryParser.java @@ -40,7 +40,7 @@ public class DictionaryParser { } private boolean isBlockStarted(String line) { - return line.startsWith("COPY public.ts_kv_dictionary ("); + return line.startsWith("COPY public.key_dictionary ("); } private void parseDictionaryDump(LineIterator iterator) throws IOException { From 8c5210b3871929d5d1567505c936a67e423593f2 Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 2 Jan 2024 19:31:25 +0200 Subject: [PATCH 076/209] lwm2m: add test, 'dim' Attributes implemented --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 6 +- .../transport/lwm2m/Lwm2mTestHelper.java | 1 + .../DefaultLwM2mLinkParserTest.java | 4 +- .../lwm2m/client/LwM2MTestClient.java | 3 +- .../lwm2m/client/SimpleLwM2MDevice.java | 12 +- .../lwm2m/client/TbLwm2mObjectEnabler.java | 642 ++++++++++++++++++ .../lwm2m/client/TbObjectsInitializer.java | 56 ++ .../sql/RpcLwm2mIntegrationDiscoverTest.java | 8 +- ...pcLwm2mIntegrationWriteAttributesTest.java | 50 +- application/src/test/resources/lwm2m/3.xml | 2 +- 10 files changed, 768 insertions(+), 16 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index deb8b8230f..4378c3758e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -62,7 +62,6 @@ import org.thingsboard.server.common.data.query.SingleEntityFilter; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; @@ -75,7 +74,6 @@ import java.io.IOException; import java.net.ServerSocket; import java.util.ArrayList; import java.util.Arrays; -import java.util.Base64; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -148,13 +146,13 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte " {\n" + " \"keyName\": {\n" + - " \"/3_1.0/0/9\": \"batteryLevel\"\n" + + " \"/3_1.2/0/9\": \"batteryLevel\"\n" + " },\n" + " \"observe\": [],\n" + " \"attribute\": [\n" + " ],\n" + " \"telemetry\": [\n" + - " \"/3_1.0/0/9\"\n" + + " \"/3_1.2/0/9\"\n" + " ],\n" + " \"attributeLwm2m\": {}\n" + " }"; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java index 9e47eab80b..6eb8dab73f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java @@ -34,6 +34,7 @@ public class Lwm2mTestHelper { public static final int RESOURCE_ID_2 = 2; public static final int RESOURCE_ID_3 = 3; public static final int RESOURCE_ID_4 = 4; + public static final int RESOURCE_ID_6 = 6; public static final int RESOURCE_ID_7 = 7; public static final int RESOURCE_ID_8 = 8; public static final int RESOURCE_ID_9 = 9; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mLinkParserTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mLinkParserTest.java index b5d8fecf10..6f872432ec 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mLinkParserTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/DefaultLwM2mLinkParserTest.java @@ -34,7 +34,7 @@ public class DefaultLwM2mLinkParserTest { @Test public void check_invalid_values() throws LinkParseException { - // first check it's OK with valid value + // first check it's OK with valid value (3/0/11 - "errorCodes") LwM2mLink[] parsed = parser.parseLwM2mLinkFromCoreLinkFormat(";dim=255".getBytes(), null); assertEquals(new LwM2mPath(3, 0, 11), parsed[0].getPath()); AttributeSet attResult = new LwM2mAttributeSet(LwM2mAttributes.create(LwM2mAttributes.DIMENSION, 255l)); @@ -69,7 +69,7 @@ public class DefaultLwM2mLinkParserTest { // then check an invalid one assertThrowsExactly(LinkParseException.class, () -> { - // dim should be between 0-255 + // pmin should be with value parser.parseLwM2mLinkFromCoreLinkFormat(";pmin".getBytes(), null); }); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 6e6fa12cf7..19f32d175f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -36,7 +36,6 @@ import org.eclipse.leshan.client.observer.LwM2mClientObserver; import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; -import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter; import org.eclipse.leshan.client.send.ManualDataSender; import org.eclipse.leshan.client.servers.LwM2mServer; @@ -133,7 +132,7 @@ public class LwM2MTestClient { models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); } LwM2mModel model = new StaticModel(models); - ObjectsInitializer initializer = new ObjectsInitializer(model); + TbObjectsInitializer initializer = new TbObjectsInitializer(model); if (securityBs != null && security != null) { // SECURITY diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java index e9bc2c9d48..e7e2320b38 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -47,7 +47,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl private static final int min = 5; private static final int max = 50; private static final PrimitiveIterator.OfInt randomIterator = new Random().ints(min,max + 1).iterator(); - private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21); + private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 6, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21); public SimpleLwM2MDevice() { @@ -79,6 +79,8 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl return ReadResponse.success(resourceId, getSerialNumber()); case 3: return ReadResponse.success(resourceId, getFirmwareVersion()); + case 6: + return ReadResponse.success(resourceId, getAvailablePowerSources(), ResourceModel.Type.INTEGER); case 9: return ReadResponse.success(resourceId, getBatteryLevel()); case 10: @@ -157,6 +159,14 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl return 0; } + private Map getAvailablePowerSources() { + Map availablePowerSources = new HashMap<>(); + availablePowerSources.put(0, 1L); + availablePowerSources.put(1, 2L); + availablePowerSources.put(2, 5L); + return availablePowerSources; + } + private int getBatteryLevel() { return randomIterator.nextInt(); // return 42; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java new file mode 100644 index 0000000000..a0c1333069 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java @@ -0,0 +1,642 @@ +package org.thingsboard.server.transport.lwm2m.client; + +import org.eclipse.leshan.client.LwM2mClient; +import org.eclipse.leshan.client.resource.BaseObjectEnabler; +import org.eclipse.leshan.client.resource.DummyInstanceEnabler; +import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; +import org.eclipse.leshan.client.resource.LwM2mInstanceEnablerFactory; +import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; +import org.eclipse.leshan.client.resource.listener.ResourceListener; +import org.eclipse.leshan.client.servers.LwM2mServer; +import org.eclipse.leshan.client.servers.ServersInfoExtractor; +import org.eclipse.leshan.client.util.LinkFormatHelper; +import org.eclipse.leshan.core.Destroyable; +import org.eclipse.leshan.core.LwM2mId; +import org.eclipse.leshan.core.Startable; +import org.eclipse.leshan.core.Stoppable; +import org.eclipse.leshan.core.link.lwm2m.LwM2mLink; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; +import org.eclipse.leshan.core.node.LwM2mObject; +import org.eclipse.leshan.core.node.LwM2mObjectInstance; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.node.LwM2mResourceInstance; +import org.eclipse.leshan.core.request.BootstrapDeleteRequest; +import org.eclipse.leshan.core.request.BootstrapReadRequest; +import org.eclipse.leshan.core.request.BootstrapWriteRequest; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.CreateRequest; +import org.eclipse.leshan.core.request.DeleteRequest; +import org.eclipse.leshan.core.request.DiscoverRequest; +import org.eclipse.leshan.core.request.DownlinkRequest; +import org.eclipse.leshan.core.request.ExecuteRequest; +import org.eclipse.leshan.core.request.ObserveRequest; +import org.eclipse.leshan.core.request.ReadRequest; +import org.eclipse.leshan.core.request.WriteAttributesRequest; +import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.core.request.WriteRequest.Mode; +import org.eclipse.leshan.core.response.BootstrapDeleteResponse; +import org.eclipse.leshan.core.response.BootstrapReadResponse; +import org.eclipse.leshan.core.response.BootstrapWriteResponse; +import org.eclipse.leshan.core.response.CreateResponse; +import org.eclipse.leshan.core.response.DeleteResponse; +import org.eclipse.leshan.core.response.DiscoverResponse; +import org.eclipse.leshan.core.response.ExecuteResponse; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.response.WriteAttributesResponse; +import org.eclipse.leshan.core.response.WriteResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyable, Startable, Stoppable { + + private static Logger LOG = LoggerFactory.getLogger(DummyInstanceEnabler.class); + + protected Map instances; + protected LwM2mInstanceEnablerFactory instanceFactory; + protected ContentFormat defaultContentFormat; + + private LinkFormatHelper tbLinkFormatHelper; + + public TbLwm2mObjectEnabler(int id, ObjectModel objectModel, Map instances, + LwM2mInstanceEnablerFactory instanceFactory, ContentFormat defaultContentFormat) { + super(id, objectModel); + this.instances = new HashMap<>(instances); + this.instanceFactory = instanceFactory; + this.defaultContentFormat = defaultContentFormat; + for (Entry entry : this.instances.entrySet()) { + instances.put(entry.getKey(), entry.getValue()); + listenInstance(entry.getValue(), entry.getKey()); + } + } + + public TbLwm2mObjectEnabler(int id, ObjectModel objectModel) { + super(id, objectModel); + } + + @Override + public synchronized List getAvailableInstanceIds() { + List ids = new ArrayList<>(instances.keySet()); + Collections.sort(ids); + return ids; + } + + @Override + public synchronized List getAvailableResourceIds(int instanceId) { + LwM2mInstanceEnabler instanceEnabler = instances.get(instanceId); + if (instanceEnabler != null) { + return instanceEnabler.getAvailableResourceIds(getObjectModel()); + } else { + return Collections.emptyList(); + } + } + + public synchronized void addInstance(int instanceId, LwM2mInstanceEnabler newInstance) { + instances.put(instanceId, newInstance); + listenInstance(newInstance, instanceId); + fireInstancesAdded(instanceId); + } + + public synchronized LwM2mInstanceEnabler getInstance(int instanceId) { + return instances.get(instanceId); + } + + public synchronized LwM2mInstanceEnabler removeInstance(int instanceId) { + LwM2mInstanceEnabler removedInstance = instances.remove(instanceId); + if (removedInstance != null) { + fireInstancesRemoved(removedInstance.getId()); + } + return removedInstance; + } + + @Override + protected CreateResponse doCreate(LwM2mServer server, CreateRequest request) { + if (!getObjectModel().multiple && instances.size() > 0) { + return CreateResponse.badRequest("an instance already exist for this single instance object"); + } + + if (request.unknownObjectInstanceId()) { + // create instance + LwM2mInstanceEnabler newInstance = createInstance(server, getObjectModel().multiple ? null : 0, + request.getResources()); + + // add new instance to this object + instances.put(newInstance.getId(), newInstance); + listenInstance(newInstance, newInstance.getId()); + fireInstancesAdded(newInstance.getId()); + + return CreateResponse + .success(new LwM2mPath(request.getPath().getObjectId(), newInstance.getId()).toString()); + } else { + List instanceNodes = request.getObjectInstances(); + + // checks single object instances + if (!getObjectModel().multiple) { + if (request.getObjectInstances().size() > 1) { + return CreateResponse.badRequest("can not create several instances on this single instance object"); + } + if (request.getObjectInstances().get(0).getId() != 0) { + return CreateResponse.badRequest("single instance object must use 0 as ID"); + } + } + // ensure instance does not already exists + for (LwM2mObjectInstance instance : instanceNodes) { + if (instances.containsKey(instance.getId())) { + return CreateResponse.badRequest(String.format("instance %d already exists", instance.getId())); + } + } + + // create the new instances + int[] instanceIds = new int[request.getObjectInstances().size()]; + int i = 0; + for (LwM2mObjectInstance instance : request.getObjectInstances()) { + // create instance + LwM2mInstanceEnabler newInstance = createInstance(server, instance.getId(), + instance.getResources().values()); + + // add new instance to this object + instances.put(newInstance.getId(), newInstance); + listenInstance(newInstance, newInstance.getId()); + + // store instance ids + instanceIds[i] = newInstance.getId(); + i++; + } + fireInstancesAdded(instanceIds); + return CreateResponse.success(); + } + } + + protected LwM2mInstanceEnabler createInstance(LwM2mServer server, Integer instanceId, + Collection resources) { + // create the new instance + LwM2mInstanceEnabler newInstance = instanceFactory.create(getObjectModel(), instanceId, instances.keySet()); + newInstance.setLwM2mClient(getLwm2mClient()); + + // add/write resource + for (LwM2mResource resource : resources) { + newInstance.write(server, true, resource.getId(), resource); + } + + return newInstance; + } + + @Override + protected ReadResponse doRead(LwM2mServer server, ReadRequest request) { + LwM2mPath path = request.getPath(); + + // Manage Object case + if (path.isObject()) { + List lwM2mObjectInstances = new ArrayList<>(); + for (LwM2mInstanceEnabler instance : instances.values()) { + ReadResponse response = instance.read(server); + if (response.isSuccess()) { + lwM2mObjectInstances.add((LwM2mObjectInstance) response.getContent()); + } + } + return ReadResponse.success(new LwM2mObject(getId(), lwM2mObjectInstances)); + } + + // Manage Instance case + LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); + if (instance == null) + return ReadResponse.notFound(); + + if (path.getResourceId() == null) { + return instance.read(server); + } + + // Manage Resource case + if (path.getResourceInstanceId() == null) { + return instance.read(server, path.getResourceId()); + } + + // Manage Resource Instance case + return instance.read(server, path.getResourceId(), path.getResourceInstanceId()); + } + + @Override + protected BootstrapReadResponse doRead(LwM2mServer server, BootstrapReadRequest request) { + // Basic implementation we delegate to classic Read Request + ReadResponse response = doRead(server, + new ReadRequest(request.getContentFormat(), request.getPath(), request.getCoapRequest())); + return new BootstrapReadResponse(response.getCode(), response.getContent(), response.getErrorMessage()); + } + + @Override + protected ObserveResponse doObserve(final LwM2mServer server, final ObserveRequest request) { + final LwM2mPath path = request.getPath(); + + // Manage Object case + if (path.isObject()) { + List lwM2mObjectInstances = new ArrayList<>(); + for (LwM2mInstanceEnabler instance : instances.values()) { + ReadResponse response = instance.observe(server); + if (response.isSuccess()) { + lwM2mObjectInstances.add((LwM2mObjectInstance) response.getContent()); + } + } + return ObserveResponse.success(new LwM2mObject(getId(), lwM2mObjectInstances)); + } + + // Manage Instance case + final LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); + if (instance == null) + return ObserveResponse.notFound(); + + if (path.getResourceId() == null) { + return instance.observe(server); + } + + // Manage Resource case + if (path.getResourceInstanceId() == null) { + return instance.observe(server, path.getResourceId()); + } + + // Manage Resource Instance case + return instance.observe(server, path.getResourceId(), path.getResourceInstanceId()); + } + + @Override + protected WriteResponse doWrite(LwM2mServer server, WriteRequest request) { + LwM2mPath path = request.getPath(); + + // Manage Instance case + LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); + if (instance == null) + return WriteResponse.notFound(); + + if (path.isObjectInstance()) { + return instance.write(server, request.isReplaceRequest(), (LwM2mObjectInstance) request.getNode()); + } + + // Manage Resource case + if (path.getResourceInstanceId() == null) { + return instance.write(server, request.isReplaceRequest(), path.getResourceId(), + (LwM2mResource) request.getNode()); + } + + // Manage Resource Instance case + return instance.write(server, false, path.getResourceId(), path.getResourceInstanceId(), + ((LwM2mResourceInstance) request.getNode())); + } + + @Override + protected BootstrapWriteResponse doWrite(LwM2mServer server, BootstrapWriteRequest request) { + LwM2mPath path = request.getPath(); + + // Manage Object case + if (path.isObject()) { + for (LwM2mObjectInstance instanceNode : ((LwM2mObject) request.getNode()).getInstances().values()) { + LwM2mInstanceEnabler instanceEnabler = instances.get(instanceNode.getId()); + if (instanceEnabler == null) { + doCreate(server, new CreateRequest(path.getObjectId(), instanceNode)); + } else { + doWrite(server, new WriteRequest(Mode.REPLACE, path.getObjectId(), instanceEnabler.getId(), + instanceNode.getResources().values())); + } + } + return BootstrapWriteResponse.success(); + } + + // Manage Instance case + if (path.isObjectInstance()) { + LwM2mObjectInstance instanceNode = (LwM2mObjectInstance) request.getNode(); + LwM2mInstanceEnabler instanceEnabler = instances.get(path.getObjectInstanceId()); + if (instanceEnabler == null) { + doCreate(server, new CreateRequest(path.getObjectId(), instanceNode)); + } else { + doWrite(server, new WriteRequest(Mode.REPLACE, request.getContentFormat(), path.getObjectId(), + path.getObjectInstanceId(), instanceNode.getResources().values())); + } + return BootstrapWriteResponse.success(); + } + + // Manage resource case + LwM2mResource resource = (LwM2mResource) request.getNode(); + LwM2mInstanceEnabler instanceEnabler = instances.get(path.getObjectInstanceId()); + if (instanceEnabler == null) { + doCreate(server, new CreateRequest(path.getObjectId(), + new LwM2mObjectInstance(path.getObjectInstanceId(), resource))); + } else { + instanceEnabler.write(server, true, path.getResourceId(), resource); + } + return BootstrapWriteResponse.success(); + } + + @Override + protected ExecuteResponse doExecute(LwM2mServer server, ExecuteRequest request) { + LwM2mPath path = request.getPath(); + LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); + if (instance == null) { + return ExecuteResponse.notFound(); + } + return instance.execute(server, path.getResourceId(), request.getArguments()); + } + + @Override + protected DeleteResponse doDelete(LwM2mServer server, DeleteRequest request) { + LwM2mInstanceEnabler deletedInstance = instances.remove(request.getPath().getObjectInstanceId()); + if (deletedInstance != null) { + deletedInstance.onDelete(server); + fireInstancesRemoved(deletedInstance.getId()); + return DeleteResponse.success(); + } + return DeleteResponse.notFound(); + } + + @Override + public BootstrapDeleteResponse doDelete(LwM2mServer server, BootstrapDeleteRequest request) { + if (request.getPath().isRoot() || request.getPath().isObject()) { + if (id == LwM2mId.SECURITY) { + // For security object, we clean everything except bootstrap Server account. + + // Get bootstrap account and store removed instances ids + Entry bootstrapServerAccount = null; + int[] instanceIds = new int[instances.size()]; + int i = 0; + for (Entry instance : instances.entrySet()) { + if (ServersInfoExtractor.isBootstrapServer(instance.getValue())) { + bootstrapServerAccount = instance; + } else { + // Store instance ids + instanceIds[i] = instance.getKey(); + i++; + } + } + // Clear everything + instances.clear(); + + // Put bootstrap account again + if (bootstrapServerAccount != null) { + instances.put(bootstrapServerAccount.getKey(), bootstrapServerAccount.getValue()); + } + + fireInstancesRemoved(instanceIds); + return BootstrapDeleteResponse.success(); + } else if (id == LwM2mId.OSCORE) { + // For OSCORE object, we clean everything except OSCORE object link to bootstrap Server account. + + // Get bootstrap account + LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance( + getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY)); + // Get OSCORE instance ID associated to it + Integer bootstrapOscoreInstanceId = bootstrapInstance != null + ? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance) + : null; + + // if bootstrap server use OSCORE, + // search the OSCORE instance for this ID and store removed instances ids + if (bootstrapOscoreInstanceId != null) { + Entry bootstrapServerOscore = null; + int[] instanceIds = new int[instances.size()]; + int i = 0; + for (Entry instance : instances.entrySet()) { + if (bootstrapOscoreInstanceId.equals(instance.getKey())) { + bootstrapServerOscore = instance; + } else { + // Store instance ids + instanceIds[i] = instance.getKey(); + i++; + } + } + + // Clear everything + instances.clear(); + + // Put bootstrap OSCORE instance again + if (bootstrapServerOscore != null) { + instances.put(bootstrapServerOscore.getKey(), bootstrapServerOscore.getValue()); + } + fireInstancesRemoved(instanceIds); + return BootstrapDeleteResponse.success(); + } + // else delete everything. + } + + // In all other cases, just delete everything + instances.clear(); + // fired instances removed + int[] instanceIds = new int[instances.size()]; + int i = 0; + for (Entry instance : instances.entrySet()) { + instanceIds[i] = instance.getKey(); + i++; + } + fireInstancesRemoved(instanceIds); + + return BootstrapDeleteResponse.success(); + } else if (request.getPath().isObjectInstance()) { + if (id == LwM2mId.SECURITY) { + // For security object, deleting bootstrap Server account is not allowed + LwM2mInstanceEnabler instance = instances.get(request.getPath().getObjectInstanceId()); + if (instance == null) { + return BootstrapDeleteResponse + .badRequest(String.format("Instance %s not found", request.getPath())); + } else if (ServersInfoExtractor.isBootstrapServer(instance)) { + return BootstrapDeleteResponse.badRequest("bootstrap server can not be deleted"); + } + } else if (id == LwM2mId.OSCORE) { + // For OSCORE object, deleting instance linked to Bootstrap account is not allowed + + // Get bootstrap instance + LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance( + getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY)); + // Get OSCORE instance ID associated to it + Integer bootstrapOscoreInstanceId = bootstrapInstance != null + ? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance) + : null; + + if (bootstrapOscoreInstanceId != null + && bootstrapOscoreInstanceId.equals(request.getPath().getObjectInstanceId())) { + return BootstrapDeleteResponse + .badRequest("OSCORE instance linked to bootstrap server can not be deleted"); + } + } + if (null != instances.remove(request.getPath().getObjectInstanceId())) { + fireInstancesRemoved(request.getPath().getObjectInstanceId()); + return BootstrapDeleteResponse.success(); + } else { + return BootstrapDeleteResponse.badRequest(String.format("Instance %s not found", request.getPath())); + } + } + return BootstrapDeleteResponse.badRequest(String.format("unexcepted path %s", request.getPath())); + } + + protected void listenInstance(LwM2mInstanceEnabler instance, final int instanceId) { + instance.addResourceListener(new ResourceListener() { + @Override + public void resourceChanged(LwM2mPath... paths) { + for (LwM2mPath path : paths) { + if (!isValid(instanceId, path)) { + LOG.warn("InstanceEnabler ({}) of object ({}) try to raise a change of {} which seems invalid.", + instanceId, getId(), path); + } + } + fireResourcesChanged(paths); + } + }); + } + + protected boolean isValid(int instanceId, LwM2mPath pathToValidate) { + if (!(pathToValidate.isResource() || pathToValidate.isResourceInstance())) + return false; + + if (pathToValidate.getObjectId() != getId()) { + return false; + } + + if (pathToValidate.getObjectInstanceId() != instanceId) { + return false; + } + + return true; + } + + @Override + public ContentFormat getDefaultEncodingFormat(DownlinkRequest request) { + return defaultContentFormat; + } + + @Override + public void init(LwM2mClient client, LinkFormatHelper linkFormatHelper) { + super.init(client, linkFormatHelper); + this.tbLinkFormatHelper = linkFormatHelper; + for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { + instanceEnabler.setLwM2mClient(client); + } + } + + @Override + public void destroy() { + for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { + if (instanceEnabler instanceof Destroyable) { + ((Destroyable) instanceEnabler).destroy(); + } else if (instanceEnabler instanceof Stoppable) { + ((Stoppable) instanceEnabler).stop(); + } + } + } + + @Override + public void start() { + for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { + if (instanceEnabler instanceof Startable) { + ((Startable) instanceEnabler).start(); + } + } + } + + @Override + public void stop() { + for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { + if (instanceEnabler instanceof Stoppable) { + ((Stoppable) instanceEnabler).stop(); + } + } + } + + @Override + public synchronized WriteAttributesResponse writeAttributes(LwM2mServer server, WriteAttributesRequest request) { + // execute is not supported for bootstrap + if (server.isLwm2mBootstrapServer()) { + return WriteAttributesResponse.methodNotAllowed(); + } + // TODO should be implemented here to be available for all object enabler + // This should be a not implemented error, but this is not defined in the spec. + return WriteAttributesResponse.internalServerError("not implemented"); + } + + + @Override + public synchronized DiscoverResponse discover(LwM2mServer server, DiscoverRequest request) { + + if (server.isLwm2mBootstrapServer()) { + // discover is not supported for bootstrap + return DiscoverResponse.methodNotAllowed(); + } + + if (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE) { + return DiscoverResponse.notFound(); + } + return doDiscover(server, request); + + } + + protected DiscoverResponse doDiscover(LwM2mServer server, DiscoverRequest request) { + + LwM2mPath path = request.getPath(); + if (path.isObject()) { + // Manage discover on object + LwM2mLink[] ObjectLinks = this.tbLinkFormatHelper.getObjectDescription(this, null); + return DiscoverResponse.success(ObjectLinks); + + } else if (path.isObjectInstance()) { + // Manage discover on instance + if (!getAvailableInstanceIds().contains(path.getObjectInstanceId())) + return DiscoverResponse.notFound(); + + LwM2mLink[] instanceLink = this.tbLinkFormatHelper.getInstanceDescription(this, path.getObjectInstanceId(), null); + return DiscoverResponse.success(instanceLink); + + } else if (path.isResource()) { + // Manage discover on resource + if (!getAvailableInstanceIds().contains(path.getObjectInstanceId())) + return DiscoverResponse.notFound(); + + ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId()); + if (resourceModel == null) + return DiscoverResponse.notFound(); + + if (!getAvailableResourceIds(path.getObjectInstanceId()).contains(path.getResourceId())) + return DiscoverResponse.notFound(); + LwM2mLink resourceLink = this.getResourceAttributes(server, path, resourceModel, this); + if (resourceLink == null) { + resourceLink = this.tbLinkFormatHelper.getResourceDescription(this, path.getObjectInstanceId(), path.getResourceId(), null); + } + return DiscoverResponse.success(new LwM2mLink[] { resourceLink }); + } + return DiscoverResponse.badRequest(null); + } + + protected LwM2mLink getResourceAttributes (LwM2mServer server, LwM2mPath path, ResourceModel resourceModel, LwM2mObjectEnabler objectEnabler) { + LwM2mAttribute attrResource = null; + if (resourceModel.multiple) { + attrResource = getResourceAttributeDim(path, server); + + } + + return attrResource != null ? new LwM2mLink(null, new LwM2mPath(objectEnabler.getId(), path.getObjectInstanceId(), path.getResourceId()), attrResource) : null; + } + + protected LwM2mAttribute getResourceAttributeDim(LwM2mPath path, LwM2mServer server) { + LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); + try { + ReadResponse readResponse = instance.read(server, path.getResourceId()); + if (readResponse.getCode().getCode()==205 && readResponse.getContent() instanceof LwM2mMultipleResource) { + long valueDim = ((LwM2mMultipleResource)readResponse.getContent()).getInstances().size(); + return LwM2mAttributes.create(LwM2mAttributes.DIMENSION, valueDim); + } else { + return null; + } + } catch (Exception e ){ + return null; + } + + } + +} + diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java new file mode 100644 index 0000000000..33cfe5e4ef --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java @@ -0,0 +1,56 @@ +package org.thingsboard.server.transport.lwm2m.client; + +import org.eclipse.leshan.client.resource.BaseInstanceEnablerFactory; +import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; +import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; +import org.eclipse.leshan.client.resource.ObjectsInitializer; +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.model.ObjectModel; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TbObjectsInitializer extends ObjectsInitializer { + + + public TbObjectsInitializer(LwM2mModel model) { + super(model); + } + + public List create(int... objectId) { + List enablers = new ArrayList<>(); + for (int anObjectId : objectId) { + LwM2mObjectEnabler objectEnabler = create(anObjectId); + if (objectEnabler != null) + enablers.add(objectEnabler); + } + return enablers; + } + + public LwM2mObjectEnabler create(int objectId) { + ObjectModel objectModel = model.getObjectModel(objectId); + if (objectModel == null) { + throw new IllegalArgumentException( + "Cannot create object for id " + objectId + " because no model is defined for this id."); + } + return createNodeEnabler(objectModel); + } + + protected LwM2mObjectEnabler createNodeEnabler(ObjectModel objectModel) { + Map instances = new HashMap<>(); + LwM2mInstanceEnabler[] newInstances = createInstances(objectModel); + for (LwM2mInstanceEnabler instance : newInstances) { + // set id if not already set + if (instance.getId() == null) { + int id = BaseInstanceEnablerFactory.generateNewInstanceId(instances.keySet()); + instance.setId(id); + } + instance.setModel(objectModel); + instances.put(instance.getId(), instance); + } + return new TbLwm2mObjectEnabler(objectModel.id, objectModel, instances, getFactoryFor(objectModel), + getContentFormat(objectModel.id)); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java index ea0a1ad4ca..6da48b0a79 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java @@ -99,8 +99,8 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration * {"result":"CONTENT","value":",,,,,,,,,,,,,,,,,,,,,,,"} * If WriteAttributes implemented and WriteAttributes saved - * Discover {"id":"19/0"} - * {"result":"CONTENT","value":"[;dim=2;pmin=10;pmax=60;gt=50;lt=42.2,;pmax=120, , , , , ;lt=45]"} + * Discover {"id":"19"} + * {"result":"CONTENT","value":"[;ver=1.1,;dim=2;pmin=10;pmax=60;gt=50;lt=42.2,;pmax=120, , , , , ;lt=45]"} */ @Test public void testDiscoverInstance_Return_CONTENT_LinksResourcesOnLyExpectedInstance() throws Exception { @@ -117,12 +117,12 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration /** * Discover {"id":"3/0/14"} * If WriteAttributes implemented: - * {"result":"CONTENT","value":";pmax=100, "pmin":10, "ver"=1.0"} + * {"result":"CONTENT","value":";pmax=100, "pmin":10} * If WriteAttributes not implemented: * {"result":"CONTENT","value":""} * Discover {"id":"19_1.1/0/0"} * If WriteAttributes implemented: - * {"result":"CONTENT","value":";pmax=100, "pmin":10, "ver"=1.1"} + * {"result":"CONTENT","value":";pmax=100, "pmin":10} * If WriteAttributes not implemented: * {"result":"CONTENT","value":""} */ diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java index c2d87149de..1e5c9b593a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java @@ -24,7 +24,8 @@ import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTes import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_15; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_6; public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MIntegrationTest { @@ -38,7 +39,7 @@ public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MInte */ @Test public void testWriteAttributesResourceWithParametersById_Result_INTERNAL_SERVER_ERROR() throws Exception { - String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; + String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_15; sendRPCReadById(expectedPath); String expectedValue = "{\"pmax\":100, \"pmin\":10}"; String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); @@ -74,6 +75,46 @@ public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MInte assertTrue(actual.equals(expected)); } + /** + * Class Attributes + * Dimension dim Integer [0:255] + * Number of instances existing for a Multiple-Instance Resource + * 3 + * + * Available Power Sources + * R + * Multiple + * Integer + * 0..7 + * WriteAttributes implemented: Discover {"id":"3/0/6"} -> 'dim' = 3 + * "ver" only for objectId + */ + @Test + public void testDIM_3_0_6_Only_R () throws Exception { + String path = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; + String actualResult = sendDiscover(path); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expected = ";dim=3"; + assertTrue(rpcActualResult.get("value").asText().equals(expected)); + + } + /** + * Class Attributes + * Minimum/Maximum Period + * Notes: The Minimum Period Attribute: + * -- indicates the minimum time in seconds the LwM2M Client MUST wait between two notifications. If a notification of an observed Resource is supposed to be generated but it is before pmin expiry, notification MUST be sent as soon as pmin expires. In the absence of this parameter, the Minimum Period is defined by the Default Minimum Period set in the LwM2M Server Account. + * Notes: The Maximum Period Attribute: + * -- indicates the maximum time in seconds the LwM2M Client MAY wait between two notifications. When this "Maximum Period" expires after the last notification, a new notification MUST be sent. In the absence of this parameter, the "Maximum Period" is defined by the Default Maximum Period when set in the LwM2M Server Account or considered as 0 otherwise. The value of 0, means pmax MUST be ignored. The maximum period parameter MUST be greater than the minimum period parameter otherwise pmax will be ignored for the Resource to which such inconsistent timing conditions are applied. + * Object Id = 1 + * Default Minimum Period Id = 2 300 + * Default Maximum Period Id = 3 6000 + */ + @Test + public void testPeriod () { + + } + private String sendRPCExecuteWithValueById(String path, String value) throws Exception { String setRpcRequest = "{\"method\": \"WriteAttributes\", \"params\": {\"id\": \"" + path + "\", \"attributes\": " + value + " }}"; return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); @@ -84,4 +125,9 @@ public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MInte return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); } + private String sendDiscover(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + } diff --git a/application/src/test/resources/lwm2m/3.xml b/application/src/test/resources/lwm2m/3.xml index 724fc4cb33..e71c2045c2 100644 --- a/application/src/test/resources/lwm2m/3.xml +++ b/application/src/test/resources/lwm2m/3.xml @@ -64,7 +64,7 @@ LEGAL DISCLAIMER 3 urn:oma:lwm2m:oma:3:1.0 1.1 - 1.0 + 1.2 Single Mandatory From 395a278fb4dc2d3432c6e9557a846928c1b802c0 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 3 Jan 2024 12:24:23 +0200 Subject: [PATCH 077/209] Fix test after merge with master --- .../server/service/edge/EdgeEventSourcingListener.java | 3 +-- .../service/entitiy/EntityStateSourcingListener.java | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 47ac9e3567..cb5a4d98cf 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -92,7 +92,7 @@ public class EdgeEventSourcingListener { public void handleEvent(DeleteEntityEvent event) { EntityType entityType = event.getEntityId().getEntityType(); try { - if (EntityType.EDGE.equals(entityType) || EntityType.TENANT.equals(entityType) || EntityType.TB_RESOURCE.equals(entityType)) { + if (EntityType.EDGE.equals(entityType) || EntityType.TENANT.equals(entityType)) { return; } log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event); @@ -179,7 +179,6 @@ public class EdgeEventSourcingListener { case TENANT: return !event.getAdded(); case API_USAGE_STATE: - case TB_RESOURCE: case EDGE: return false; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java index fc24ab5324..80d2647078 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java @@ -180,13 +180,18 @@ public class EntityStateSourcingListener { @TransactionalEventListener(fallbackExecution = true) public void handleEvent(ActionEntityEvent event) { log.trace("[{}] ActionEntityEvent called: {}", event.getTenantId(), event); - if (ActionType.CREDENTIALS_UPDATED.equals(event.getActionType()) && EntityType.DEVICE.equals(event.getEntityId().getEntityType()) + if (ActionType.CREDENTIALS_UPDATED.equals(event.getActionType()) && + EntityType.DEVICE.equals(event.getEntityId().getEntityType()) && event.getEntity() instanceof DeviceCredentials) { tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(event.getTenantId(), (DeviceId) event.getEntityId(), (DeviceCredentials) event.getEntity()), null); } else if (ActionType.ASSIGNED_TO_TENANT.equals(event.getActionType()) && event.getEntity() instanceof Device) { + Device device = (Device) event.getEntity(); Tenant tenant = JacksonUtil.fromString(event.getBody(), Tenant.class); - pushAssignedFromNotification(tenant, event.getTenantId(), (Device) event.getEntity()); + if (tenant != null) { + tbClusterService.onDeviceAssignedToTenant(tenant.getId(), device); + } + pushAssignedFromNotification(tenant, event.getTenantId(), device); } } From 8def73cde1be18c222a88badc21ff02cde3e7f1f Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 3 Jan 2024 14:09:28 +0200 Subject: [PATCH 078/209] Fix TenantServiceImpl eventPublish --- .../org/thingsboard/server/dao/tenant/TenantServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 9d9cea4085..54d5776e83 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -197,7 +197,7 @@ public class TenantServiceImpl extends AbstractCachedEntityService Date: Wed, 3 Jan 2024 16:21:24 +0200 Subject: [PATCH 079/209] Renaming to match the correct type --- .../server/service/transport/DefaultTransportApiService.java | 4 ++-- .../org/thingsboard/server/dao/resource/ResourceService.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 23de57ae74..6e594ce5f3 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -382,7 +382,7 @@ public class DefaultTransportApiService implements TransportApiService { || !gatewayId.toString().equals(deviceAdditionalInfo.get(DataConstants.LAST_CONNECTED_GATEWAY).asText()))) { ObjectNode newDeviceAdditionalInfo = (ObjectNode) deviceAdditionalInfo; newDeviceAdditionalInfo.put(DataConstants.LAST_CONNECTED_GATEWAY, gatewayId.toString()); - Device savedDevice = deviceService.saveDevice(device); + deviceService.saveDevice(device); } } GetOrCreateDeviceFromGatewayResponseMsg.Builder builder = GetOrCreateDeviceFromGatewayResponseMsg.newBuilder() @@ -412,7 +412,7 @@ public class DefaultTransportApiService implements TransportApiService { } private ListenableFuture handle(ProvisionDeviceRequestMsg requestMsg) { - ListenableFuture provisionResponseFuture = null; + ListenableFuture provisionResponseFuture; try { provisionResponseFuture = Futures.immediateFuture(deviceProvisionService.provisionDevice( new ProvisionRequest( diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java index fe39b86f07..b4cda2d733 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java @@ -54,9 +54,9 @@ public interface ResourceService extends EntityDaoService { PageData findTenantResourcesByResourceTypeAndPageLink(TenantId tenantId, ResourceType lwm2mModel, PageLink pageLink); - void deleteResource(TenantId tenantId, TbResourceId resource); + void deleteResource(TenantId tenantId, TbResourceId resourceId); - void deleteResource(TenantId tenantId, TbResourceId resource, boolean force); + void deleteResource(TenantId tenantId, TbResourceId resourceId, boolean force); void deleteResourcesByTenantId(TenantId tenantId); From 6bcb00b6b90f392270a4709f93d1846233131385 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 4 Jan 2024 12:16:06 +0200 Subject: [PATCH 080/209] lwm2m: add test, Write Attributes implemented and read Attributes (Discover) --- .../lwm2m/client/SimpleLwM2MDevice.java | 2 +- .../lwm2m/client/TbLwm2mObjectEnabler.java | 126 ++++++++-- .../lwm2m/client/TbObjectsInitializer.java | 15 ++ .../rpc/AbstractRpcLwM2MIntegrationTest.java | 4 + ...ntegrationDiscoverWriteAttributesTest.java | 226 ++++++++++++++++++ .../sql/RpcLwm2mIntegrationObserveTest.java | 9 +- ...pcLwm2mIntegrationWriteAttributesTest.java | 133 ----------- 7 files changed, 359 insertions(+), 156 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java index e7e2320b38..89833a43a6 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -47,7 +47,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl private static final int min = 5; private static final int max = 50; private static final PrimitiveIterator.OfInt randomIterator = new Random().ints(min,max + 1).iterator(); - private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 6, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21); + private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21); public SimpleLwM2MDevice() { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java index a0c1333069..5fa55f24a3 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.transport.lwm2m.client; import org.eclipse.leshan.client.LwM2mClient; @@ -5,7 +20,6 @@ import org.eclipse.leshan.client.resource.BaseObjectEnabler; import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mInstanceEnablerFactory; -import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.listener.ResourceListener; import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.client.servers.ServersInfoExtractor; @@ -16,6 +30,7 @@ import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Stoppable; import org.eclipse.leshan.core.link.lwm2m.LwM2mLink; import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeSet; import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; @@ -54,6 +69,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -66,21 +82,24 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab private static Logger LOG = LoggerFactory.getLogger(DummyInstanceEnabler.class); protected Map instances; + protected LwM2mInstanceEnablerFactory instanceFactory; protected ContentFormat defaultContentFormat; private LinkFormatHelper tbLinkFormatHelper; - + protected Map lwM2mAttributes; public TbLwm2mObjectEnabler(int id, ObjectModel objectModel, Map instances, LwM2mInstanceEnablerFactory instanceFactory, ContentFormat defaultContentFormat) { super(id, objectModel); this.instances = new HashMap<>(instances); + ; this.instanceFactory = instanceFactory; this.defaultContentFormat = defaultContentFormat; for (Entry entry : this.instances.entrySet()) { instances.put(entry.getKey(), entry.getValue()); listenInstance(entry.getValue(), entry.getKey()); } + this.lwM2mAttributes = new HashMap<>(); } public TbLwm2mObjectEnabler(int id, ObjectModel objectModel) { @@ -555,11 +574,55 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab if (server.isLwm2mBootstrapServer()) { return WriteAttributesResponse.methodNotAllowed(); } - // TODO should be implemented here to be available for all object enabler - // This should be a not implemented error, but this is not defined in the spec. - return WriteAttributesResponse.internalServerError("not implemented"); +// return WriteAttributesResponse.internalServerError("not implemented"); + return doWriteAttributes(server, request); } + /** + * Class Attributes + * - pmin (def = 0(sec)) Integer Resource/Object Instance/Object Readable Resource + * - pmax (def = -- ) Integer Resource/Object Instance/Object Readable Resource + * - Greater Than gt (def = -- ) Float Resource Numerical&Readable Resource + * - Less Than lt (def = -- ) Float Resource Numerical&Readable Resource + * - Step st (def = -- ) Float Resource Numerical&Readable Resource + */ + public WriteAttributesResponse doWriteAttributes(LwM2mServer server, WriteAttributesRequest request) { + LwM2mPath lwM2mPath = request.getPath(); + LwM2mAttributeSet attributeSet = lwM2mAttributes.get(lwM2mPath); + Map > attributes = new HashMap<>(); + + for (LwM2mAttribute attr : request.getAttributes().getLwM2mAttributes()) { + if (attr.getName().equals("pmax") || attr.getName().equals("pmin")) { + if (lwM2mPath.isObject() || lwM2mPath.isObjectInstance() || lwM2mPath.isResource()) { + attributes.put(attr.getName(), attr); + } else { + return WriteAttributesResponse.badRequest("Attribute " + attr.getName() + " can be used for only Resource/Object Instance/Object."); + } + } else if (attr.getName().equals("gt") || attr.getName().equals("lt") || attr.getName().equals("st")) { + if (lwM2mPath.isResource()) { + attributes.put(attr.getName(), attr); + } else { + return WriteAttributesResponse.badRequest("Attribute " + attr.getName() + " can be used for only Resource."); + } + } + } + if (attributes.size()>0){ + if (attributeSet == null) { + attributeSet = new LwM2mAttributeSet(attributes.values()); + } else { + Iterable> lwM2mAttributeIterable = attributeSet.getLwM2mAttributes(); + Map > attributesOld = new HashMap<>(); + for (LwM2mAttribute attr : lwM2mAttributeIterable) { + attributesOld.put(attr.getName(), attr); + } + attributesOld.putAll(attributes); + attributeSet = new LwM2mAttributeSet(attributesOld.values()); + } + lwM2mAttributes.put(lwM2mPath, attributeSet); + return WriteAttributesResponse.success(); + } + return WriteAttributesResponse.internalServerError("not implemented"); + } @Override public synchronized DiscoverResponse discover(LwM2mServer server, DiscoverRequest request) { @@ -580,8 +643,7 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab LwM2mPath path = request.getPath(); if (path.isObject()) { - // Manage discover on object - LwM2mLink[] ObjectLinks = this.tbLinkFormatHelper.getObjectDescription(this, null); + LwM2mLink[] ObjectLinks = linkUpdateAttributes(this.tbLinkFormatHelper.getObjectDescription(this, null), server); return DiscoverResponse.success(ObjectLinks); } else if (path.isObjectInstance()) { @@ -589,7 +651,7 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab if (!getAvailableInstanceIds().contains(path.getObjectInstanceId())) return DiscoverResponse.notFound(); - LwM2mLink[] instanceLink = this.tbLinkFormatHelper.getInstanceDescription(this, path.getObjectInstanceId(), null); + LwM2mLink[] instanceLink = linkUpdateAttributes(this.tbLinkFormatHelper.getInstanceDescription(this, path.getObjectInstanceId(), null), server); return DiscoverResponse.success(instanceLink); } else if (path.isResource()) { @@ -603,23 +665,52 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab if (!getAvailableResourceIds(path.getObjectInstanceId()).contains(path.getResourceId())) return DiscoverResponse.notFound(); - LwM2mLink resourceLink = this.getResourceAttributes(server, path, resourceModel, this); - if (resourceLink == null) { - resourceLink = this.tbLinkFormatHelper.getResourceDescription(this, path.getObjectInstanceId(), path.getResourceId(), null); - } + + LwM2mLink resourceLink = linkAddAttribute( + this.tbLinkFormatHelper.getResourceDescription(this, path.getObjectInstanceId(), path.getResourceId(), null), + server); return DiscoverResponse.success(new LwM2mLink[] { resourceLink }); } return DiscoverResponse.badRequest(null); } - protected LwM2mLink getResourceAttributes (LwM2mServer server, LwM2mPath path, ResourceModel resourceModel, LwM2mObjectEnabler objectEnabler) { - LwM2mAttribute attrResource = null; - if (resourceModel.multiple) { - attrResource = getResourceAttributeDim(path, server); + private LwM2mLink[] linkUpdateAttributes(LwM2mLink[] links, LwM2mServer server) { + return Arrays.stream(links) + .map(link -> linkAddAttribute(link, server)) + .toArray(LwM2mLink[]::new); + } + + private LwM2mLink linkAddAttribute(LwM2mLink link, LwM2mServer server) { + + LwM2mAttributeSet lwM2mAttributeSetDop = null; + if (this.lwM2mAttributes.get(link.getPath())!= null){ + lwM2mAttributeSetDop = this.lwM2mAttributes.get(link.getPath()); + } + LwM2mAttribute resourceAttributeDim = getResourceAttributes (server, link.getPath()); + Map > attributes = new HashMap<>(); + if (link.getAttributes() != null) { + for (LwM2mAttribute attr : link.getAttributes().getLwM2mAttributes()) { + attributes.put(attr.getName(), attr); + } + } + if (lwM2mAttributeSetDop != null) { + for (LwM2mAttribute attr : lwM2mAttributeSetDop.getLwM2mAttributes()) { + attributes.put(attr.getName(), attr); + } + } + if (resourceAttributeDim != null) { + attributes.put(resourceAttributeDim.getName(), resourceAttributeDim); } + return new LwM2mLink(link.getRootPath(), link.getPath(), attributes.values()); + } - return attrResource != null ? new LwM2mLink(null, new LwM2mPath(objectEnabler.getId(), path.getObjectInstanceId(), path.getResourceId()), attrResource) : null; + protected LwM2mAttribute getResourceAttributes (LwM2mServer server, LwM2mPath path) { + ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId()); + if (path.isResource() && resourceModel.multiple) { + return getResourceAttributeDim(path, server); + } + return null; } protected LwM2mAttribute getResourceAttributeDim(LwM2mPath path, LwM2mServer server) { @@ -635,7 +726,6 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab } catch (Exception e ){ return null; } - } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java index 33cfe5e4ef..78322bcd7c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.transport.lwm2m.client; import org.eclipse.leshan.client.resource.BaseInstanceEnablerFactory; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index ddd1329254..e3c0c680cf 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -37,6 +37,7 @@ import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; @@ -62,6 +63,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg protected String objectInstanceIdVer_1; protected String objectIdVer_0; + protected String objectIdVer_1; protected String objectIdVer_2; private static final Predicate PREDICATE_3 = path -> (!((String) path).startsWith("/" + TEMPERATURE_SENSOR) && ((String) path).startsWith("/" + DEVICE)); protected String objectIdVer_3; @@ -106,7 +108,9 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg } }); String ver_Id_0 = lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_0).version; + String ver_Id_1 = lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_1).version; objectIdVer_0 = "/" + OBJECT_ID_0 + "_" + ver_Id_0; + objectIdVer_1 = "/" + OBJECT_ID_1 + "_" + ver_Id_1; objectIdVer_2 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + ACCESS_CONTROL)).findFirst().get(); objectIdVer_3 = (String) expectedObjectIdVers.stream().filter(PREDICATE_3).findFirst().get(); objectIdVer_19 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java new file mode 100644 index 0000000000..536770cfd3 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java @@ -0,0 +1,226 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.leshan.core.ResponseCode; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_6; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_7; + + +public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcLwM2MIntegrationTest { + + /** + * WriteAttributes {"id":"/3_1.2/0/6","attributes":{"pmax":100, "pmin":10}} + * if not implemented: + * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} + * if implemented: + * {"result":"BAD_REQUEST","error":"Attribute pmax can be used for only Resource/Object Instance/Object."} + */ + @Test + public void testWriteAttributesResourceWithParametersByResourceInstanceId_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6 + "/1"; + String expectedValue = "{\"pmax\":100, \"pmin\":10}"; + String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Attribute pmax can be used for only Resource/Object Instance/Object."; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + /** + * WriteAttributes {"id":"/3_1.2/0/6","attributes":{"pmax":100, "pmin":10}} + * if not implemented: + * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} + * if implemented: + * {"result":"BAD_REQUEST","error":"Attribute pmax can be used for only Resource/Object Instance/Object."} + */ + @Test + public void testWriteAttributeResourceDimWithParametersByResourceId_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; + String expectedValue = "{\"dim\":3}"; + String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Attribute dim is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + @Test + public void testWriteAttributesResourceVerWithParametersById_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectIdVer_3; + String expectedValue = "{\"ver\":1.3}"; + String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Attribute ver is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + @Test + public void testWriteAttributesResourceServerUriWithParametersById_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectInstanceIdVer_1; + String actualResult = sendRPCReadById(expectedPath); + String expectedValue = "{\"uri\":\"coaps://localhost:5690\"}"; + actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Attribute uri is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + /** + * Class Attributes + * Dimension dim Integer [0:255] + * Number of instances existing for a Multiple-Instance Resource + * 3 + * + * Available Power Sources + * R + * Multiple + * Integer + * 0..7 + * WriteAttributes implemented: Discover {"id":"3/0/6"} -> 'dim' = 3 + * "ver" only for objectId + */ + @Test + public void testReadDIM_3_0_6_Only_R () throws Exception { + String path = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; + String actualResult = sendDiscover(path); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expected = ";dim=3"; + assertTrue(rpcActualResult.get("value").asText().equals(expected)); + + } + + + /** + * Class Attributes + * Object Version ver Object + * Provide the version of the associated Object. + * "ver" only for objectId + */ + @Test + public void testReadVer () throws Exception { + String path = objectIdVer_3; + String actualResult = sendDiscover(path); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + String expected = ";ver=1.2"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + } + + /** + * Class Attributes + * Minimum/Maximum Period pmin/pmax + * Notes: The Minimum Period Attribute: + * -- indicates the minimum time in seconds the LwM2M Client MUST wait between two notifications. If a notification of an observed Resource is supposed to be generated but it is before pmin expiry, notification MUST be sent as soon as pmin expires. In the absence of this parameter, the Minimum Period is defined by the Default Minimum Period set in the LwM2M Server Account. + * Notes: The Maximum Period Attribute: + * -- indicates the maximum time in seconds the LwM2M Client MAY wait between two notifications. When this "Maximum Period" expires after the last notification, a new notification MUST be sent. In the absence of this parameter, the "Maximum Period" is defined by the Default Maximum Period when set in the LwM2M Server Account or considered as 0 otherwise. The value of 0, means pmax MUST be ignored. The maximum period parameter MUST be greater than the minimum period parameter otherwise pmax will be ignored for the Resource to which such inconsistent timing conditions are applied. + * Greater Than gt Resource + * Less Than lt Resource + * Step st Resource + * + * Object Id = 1 + * Default Minimum Period Id = 2 300 or 0 + * Default Maximum Period Id = 3 6000 or "-" + * ;pmax=65, , <3/0/2>, , , + * <3/0/6>;dim=8,<3/0/7>;gt=50;lt=42.2;st=0.5,<3/0/8>;... + */ + @Test + public void testWriteAttributesPeriodLtGt () throws Exception { + String expectedPath = objectInstanceIdVer_3; + String expectedValue = "{\"pmax\":60}"; + String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + expectedPath = objectInstanceIdVer_3; + expectedValue = "{\"pmax\":65}"; + actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_7; + expectedValue ="{\"gt\":50, \"lt\":42.2, \"st\":0.5}"; + actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + // ObjectId + expectedPath = objectIdVer_3; + actualResult = sendDiscover(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + // String expected = ";ver=1.2,;pmax=60,,,,,;dim=3,;st=0.5;lt=42.2;gt=50.0,,,,;dim=1,,,,,,,,,"; + String expected = ";ver=1.2,;pmax=65"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + expected = ";dim=3,;st=0.5;lt=42.2;gt=50.0"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + // ObjectInstanceId + expectedPath = objectInstanceIdVer_3; + actualResult = sendDiscover(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + expected = ";pmax=65"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + expected = ";dim=3,;st=0.5;lt=42.2;gt=50.0"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + // ResourceId + expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; + actualResult = sendDiscover(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + expected = ";dim=3"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_7; + actualResult = sendDiscover(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + expected = ";st=0.5;lt=42.2;gt=50.0"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + // ResourceInstanceId + expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6+ "/1"; + actualResult = sendDiscover(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); + expected = "InvalidRequestException: Discover request cannot target resource instance path: /3/0/6/1"; + assertTrue(rpcActualResult.get("error").asText().contains(expected)); + } + + private String sendRPCExecuteWithValueById(String path, String value) throws Exception { + String setRpcRequest = "{\"method\": \"WriteAttributes\", \"params\": {\"id\": \"" + path + "\", \"attributes\": " + value + " }}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + + private String sendRPCReadById(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } + + private String sendDiscover(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java index 9a17978e83..4d8f58f6f0 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java @@ -66,7 +66,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT */ @Test public void testObserveSingleResourceWithout_IdVer_1_0_Result_CONTENT_Value_SingleResource() throws Exception { - String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; + String expectedId = objectInstanceIdVer_9 + "/" + RESOURCE_ID_0; String actualResult = sendRpcObserve("Observe", fromVersionedIdToObjectId(expectedId)); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); @@ -147,16 +147,17 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT /** * Repeated request on Observe - * Observe {"id":"/3/0/9"} + * Observe {"id":"/3_1.2/0/9"} * @throws Exception */ @Test public void testObserveRepeatedRequestObserveOnDevice_Result_BAD_REQUEST_ErrorMsg_AlreadyRegistered() throws Exception { String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; - sendRpcObserve("Observe", fromVersionedIdToObjectId(idVer_3_0_0)); - sendRpcObserve("ObserveReadAll", null); String actualResult = sendRpcObserve("Observe", idVer_3_0_0); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRpcObserve("Observe", idVer_3_0_0); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); String expected = "Observation is already registered!"; assertEquals(expected, rpcActualResult.get("error").asText()); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java deleted file mode 100644 index 1e5c9b593a..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteAttributesTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.lwm2m.rpc.sql; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.eclipse.leshan.core.ResponseCode; -import org.junit.Test; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_15; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_6; - - -public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MIntegrationTest { - - /** - * WriteAttributes {"id":"/3/0/14","attributes":{"pmax":100, "pmin":10}} - * if not implemented: - * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} - * if implemented: - * {"result":"CHANGED"} - */ - @Test - public void testWriteAttributesResourceWithParametersById_Result_INTERNAL_SERVER_ERROR() throws Exception { - String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_15; - sendRPCReadById(expectedPath); - String expectedValue = "{\"pmax\":100, \"pmin\":10}"; - String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); - String expected = "not implemented"; - String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); - } - @Test - public void testWriteAttributesResourceVerWithParametersById_Result_BAD_REQUEST() throws Exception { - String expectedPath = objectIdVer_3; - String actualResult = sendRPCReadById(expectedPath); - String expectedValue = "{\"ver\":1.3}"; - actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Attribute ver is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; - String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); - } - - @Test - public void testWriteAttributesResourceServerUriWithParametersById_Result_BAD_REQUEST() throws Exception { - String expectedPath = objectInstanceIdVer_1; - String actualResult = sendRPCReadById(expectedPath); - String expectedValue = "{\"uri\":\"coaps://localhost:5690\"}"; - actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Attribute uri is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; - String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); - } - - /** - * Class Attributes - * Dimension dim Integer [0:255] - * Number of instances existing for a Multiple-Instance Resource - * 3 - * - * Available Power Sources - * R - * Multiple - * Integer - * 0..7 - * WriteAttributes implemented: Discover {"id":"3/0/6"} -> 'dim' = 3 - * "ver" only for objectId - */ - @Test - public void testDIM_3_0_6_Only_R () throws Exception { - String path = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; - String actualResult = sendDiscover(path); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String expected = ";dim=3"; - assertTrue(rpcActualResult.get("value").asText().equals(expected)); - - } - /** - * Class Attributes - * Minimum/Maximum Period - * Notes: The Minimum Period Attribute: - * -- indicates the minimum time in seconds the LwM2M Client MUST wait between two notifications. If a notification of an observed Resource is supposed to be generated but it is before pmin expiry, notification MUST be sent as soon as pmin expires. In the absence of this parameter, the Minimum Period is defined by the Default Minimum Period set in the LwM2M Server Account. - * Notes: The Maximum Period Attribute: - * -- indicates the maximum time in seconds the LwM2M Client MAY wait between two notifications. When this "Maximum Period" expires after the last notification, a new notification MUST be sent. In the absence of this parameter, the "Maximum Period" is defined by the Default Maximum Period when set in the LwM2M Server Account or considered as 0 otherwise. The value of 0, means pmax MUST be ignored. The maximum period parameter MUST be greater than the minimum period parameter otherwise pmax will be ignored for the Resource to which such inconsistent timing conditions are applied. - * Object Id = 1 - * Default Minimum Period Id = 2 300 - * Default Maximum Period Id = 3 6000 - */ - @Test - public void testPeriod () { - - } - - private String sendRPCExecuteWithValueById(String path, String value) throws Exception { - String setRpcRequest = "{\"method\": \"WriteAttributes\", \"params\": {\"id\": \"" + path + "\", \"attributes\": " + value + " }}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); - } - - private String sendRPCReadById(String path) throws Exception { - String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); - } - - private String sendDiscover(String path) throws Exception { - String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); - } - -} From 2ad19106f369dbf6b2b2251c56a1569d5eec0e2c Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 4 Jan 2024 14:52:12 +0200 Subject: [PATCH 081/209] removed redundant code, fixed tests --- .../service/install/TimescaleTsDatabaseUpgradeService.java | 5 ----- .../main/java/org/thingsboard/server/dao/JpaDaoConfig.java | 6 +++--- .../org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java | 1 - .../timeseries/nosql/TimeseriesServiceNoSqlTest.java | 1 - 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java index 4477bef1c4..205ec14591 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java @@ -184,11 +184,6 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr break; case "3.2.2": break; - case "3.6.2": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "3.6.2"); - } - break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java index 37ca4aecbc..40a45c3444 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java @@ -27,9 +27,9 @@ import org.thingsboard.server.dao.util.TbAutoConfiguration; */ @Configuration @TbAutoConfiguration -@ComponentScan({"org.thingsboard.server.dao.sql", "org.thingsboard.server.dao.sqlts", "org.thingsboard.server.dao.attributes", "org.thingsboard.server.dao.cache", "org.thingsboard.server.cache"}) -@EnableJpaRepositories({"org.thingsboard.server.dao.sql", "org.thingsboard.server.dao.sqlts"}) -@EntityScan({"org.thingsboard.server.dao.model.sql", "org.thingsboard.server.dao.model.sqlts"}) +@ComponentScan({"org.thingsboard.server.dao.sql", "org.thingsboard.server.dao.attributes", "org.thingsboard.server.dao.cache", "org.thingsboard.server.cache"}) +@EnableJpaRepositories("org.thingsboard.server.dao.sql") +@EntityScan("org.thingsboard.server.dao.model.sql") @EnableTransactionManagement public class JpaDaoConfig { diff --git a/dao/src/main/java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java index 44658f114f..9693e2d10b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java @@ -27,7 +27,6 @@ import org.thingsboard.server.dao.util.TbAutoConfiguration; @EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.dictionary"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.dictionary"}) @EnableTransactionManagement -@SqlTsOrTsLatestAnyDao public class SqlTimeseriesDaoConfig { } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java index af1b11174a..efcf62be20 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.service.timeseries.nosql; -import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.service.DaoNoSqlTest; import org.thingsboard.server.dao.service.timeseries.BaseTimeseriesServiceTest; From 237568997491b967e4b9b73be8e0c1ba9761b3ca Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 4 Jan 2024 18:55:00 +0200 Subject: [PATCH 082/209] refactoring: moved common method for retrieving key dictionary id to abstract class --- .../install/ThingsboardInstallService.java | 1 - .../install/SqlTsDatabaseUpgradeService.java | 2 - .../update/DefaultCacheCleanupService.java | 5 ++ ...paAbstractDaoListeningExecutorService.java | 55 +++++++++++++++++++ .../dao/sql/attributes/JpaAttributeDao.java | 47 ---------------- .../sqlts/BaseAbstractSqlTimeseriesDao.java | 54 ------------------ 6 files changed, 60 insertions(+), 104 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index ef5f3edd58..730ec0ec97 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -276,7 +276,6 @@ public class ThingsboardInstallService { } else { log.info("Skipping images migration. Run the upgrade with fromVersion as '3.6.2-images' to migrate"); } - //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache case "3.6.3": log.info("Upgrading ThingsBoard from version 3.6.3 to 3.7.0 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.6.3"); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java index b61da45631..6400463e11 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java @@ -213,8 +213,6 @@ public class SqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSer loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3"); } break; - case "3.6.2": - break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java index 350005536a..e401ec2360 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java @@ -27,6 +27,7 @@ import org.springframework.stereotype.Service; import java.util.Objects; import java.util.Optional; +import static org.thingsboard.server.common.data.CacheConstants.ATTRIBUTES_CACHE; import static org.thingsboard.server.common.data.CacheConstants.RESOURCE_INFO_CACHE; import static org.thingsboard.server.common.data.CacheConstants.SECURITY_SETTINGS_CACHE; @@ -94,6 +95,10 @@ public class DefaultCacheCleanupService implements CacheCleanupService { clearCacheByName(SECURITY_SETTINGS_CACHE); clearCacheByName(RESOURCE_INFO_CACHE); break; + case "3.6.3": + log.info("Clearing cache to upgrade from version 3.6.3 to 3.7.0"); + clearCacheByName(ATTRIBUTES_CACHE); + break; default: //Do nothing, since cache cleanup is optional. } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java index 5646aa1b6c..3a10b7f619 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java @@ -16,17 +16,29 @@ package org.thingsboard.server.dao.sql; import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.jdbc.core.JdbcTemplate; +import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryCompositeKey; +import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryEntry; +import org.thingsboard.server.dao.sqlts.dictionary.KeyDictionaryRepository; import javax.sql.DataSource; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Statement; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantLock; @Slf4j public abstract class JpaAbstractDaoListeningExecutorService { + private final ConcurrentMap keyDictionaryMap = new ConcurrentHashMap<>(); + protected static final ReentrantLock creationLock = new ReentrantLock(); + @Autowired protected JpaExecutorService service; @@ -36,6 +48,49 @@ public abstract class JpaAbstractDaoListeningExecutorService { @Autowired protected JdbcTemplate jdbcTemplate; + @Autowired + protected KeyDictionaryRepository keyDictionaryRepository; + + protected Integer getOrSaveKeyId(String strKey) { + Integer keyId = keyDictionaryMap.get(strKey); + if (keyId == null) { + Optional tsKvDictionaryOptional; + tsKvDictionaryOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); + if (tsKvDictionaryOptional.isEmpty()) { + creationLock.lock(); + try { + keyId = keyDictionaryMap.get(strKey); + if (keyId != null) { + return keyId; + } + tsKvDictionaryOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); + if (tsKvDictionaryOptional.isEmpty()) { + KeyDictionaryEntry keyDictionaryEntry = new KeyDictionaryEntry(); + keyDictionaryEntry.setKey(strKey); + try { + KeyDictionaryEntry saved = keyDictionaryRepository.save(keyDictionaryEntry); + keyDictionaryMap.put(saved.getKey(), saved.getKeyId()); + keyId = saved.getKeyId(); + } catch (DataIntegrityViolationException | ConstraintViolationException e) { + tsKvDictionaryOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); + KeyDictionaryEntry dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get KeyDictionaryEntry entity from DB!")); + keyDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); + keyId = dictionary.getKeyId(); + } + } else { + keyId = tsKvDictionaryOptional.get().getKeyId(); + } + } finally { + creationLock.unlock(); + } + } else { + keyId = tsKvDictionaryOptional.get().getKeyId(); + keyDictionaryMap.put(strKey, keyId); + } + } + return keyId; + } + protected void printWarnings(Statement statement) throws SQLException { SQLWarning warnings = statement.getWarnings(); if (warnings != null) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index 01149d3d49..54d0bf9495 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -22,10 +22,8 @@ import com.google.common.util.concurrent.MoreExecutors; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; -import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -37,13 +35,11 @@ import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.attributes.AttributesDao; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; -import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryCompositeKey; import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryEntry; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper; -import org.thingsboard.server.dao.sqlts.dictionary.KeyDictionaryRepository; import org.thingsboard.server.dao.util.SqlDao; import java.util.ArrayList; @@ -51,9 +47,6 @@ import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import java.util.stream.Collectors; @@ -65,9 +58,6 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @Autowired ScheduledLogExecutorComponent logExecutor; - @Autowired - private KeyDictionaryRepository keyDictionaryRepository; - @Autowired private AttributeKvRepository attributeKvRepository; @@ -92,9 +82,6 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @Value("${sql.batch_sort:true}") private boolean batchSortEnabled; - private final ConcurrentMap attributeDictionaryMap = new ConcurrentHashMap<>(); - private static final ReentrantLock attributeCreationLock = new ReentrantLock(); - private TbSqlBlockingQueueWrapper queue; @PostConstruct @@ -214,40 +201,6 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl attributeKey); } - private Integer getOrSaveKeyId(String attributeKey) { - Integer keyId = attributeDictionaryMap.get(attributeKey); - if (keyId == null) { - Optional byIdOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(attributeKey)); - if (byIdOptional.isEmpty()) { - attributeCreationLock.lock(); - try { - byIdOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(attributeKey)); - if (byIdOptional.isEmpty()) { - KeyDictionaryEntry attributeKvDictionaryEntry = new KeyDictionaryEntry(); - attributeKvDictionaryEntry.setKey(attributeKey); - try { - KeyDictionaryEntry saved = keyDictionaryRepository.save(attributeKvDictionaryEntry); - attributeDictionaryMap.put(saved.getKey(), saved.getKeyId()); - keyId = saved.getKeyId(); - } catch (DataIntegrityViolationException | ConstraintViolationException e) { - byIdOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(attributeKey)); - KeyDictionaryEntry dictionary = byIdOptional.orElseThrow(() -> new RuntimeException("Failed to get AttributeKvDictionary entity from DB!")); - attributeDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); - keyId = dictionary.getKeyId(); - } - } else { - keyId = byIdOptional.get().getKeyId(); - } - } finally { - attributeCreationLock.unlock(); - } - } else { - keyId = byIdOptional.get().getKeyId(); - attributeDictionaryMap.put(attributeKey, keyId); - } - } - return keyId; - } private String getKey(Integer attributeKey) { Optional byKeyId = keyDictionaryRepository.findByKeyId(attributeKey); return byKeyId.map(KeyDictionaryEntry::getKey).orElse(null); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java index e018cc1ef9..56f21aee39 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java @@ -19,75 +19,21 @@ import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.hibernate.exception.ConstraintViolationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.DataIntegrityViolationException; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; -import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryEntry; -import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryCompositeKey; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; -import org.thingsboard.server.dao.sqlts.dictionary.KeyDictionaryRepository; import jakarta.annotation.Nullable; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; @Slf4j public abstract class BaseAbstractSqlTimeseriesDao extends JpaAbstractDaoListeningExecutorService { - private final ConcurrentMap tsKvDictionaryMap = new ConcurrentHashMap<>(); - protected static final ReentrantLock tsCreationLock = new ReentrantLock(); - @Autowired - protected KeyDictionaryRepository keyDictionaryRepository; - - protected Integer getOrSaveKeyId(String strKey) { - Integer keyId = tsKvDictionaryMap.get(strKey); - if (keyId == null) { - Optional tsKvDictionaryOptional; - tsKvDictionaryOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); - if (tsKvDictionaryOptional.isEmpty()) { - tsCreationLock.lock(); - try { - keyId = tsKvDictionaryMap.get(strKey); - if (keyId != null) { - return keyId; - } - tsKvDictionaryOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); - if (tsKvDictionaryOptional.isEmpty()) { - KeyDictionaryEntry keyDictionaryEntry = new KeyDictionaryEntry(); - keyDictionaryEntry.setKey(strKey); - try { - KeyDictionaryEntry saved = keyDictionaryRepository.save(keyDictionaryEntry); - tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId()); - keyId = saved.getKeyId(); - } catch (DataIntegrityViolationException | ConstraintViolationException e) { - tsKvDictionaryOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); - KeyDictionaryEntry dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); - tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); - keyId = dictionary.getKeyId(); - } - } else { - keyId = tsKvDictionaryOptional.get().getKeyId(); - } - } finally { - tsCreationLock.unlock(); - } - } else { - keyId = tsKvDictionaryOptional.get().getKeyId(); - tsKvDictionaryMap.put(strKey, keyId); - } - } - return keyId; - } - protected ListenableFuture getReadTsKvQueryResultFuture(ReadTsKvQuery query, ListenableFuture>> future) { return Futures.transform(future, new Function<>() { @Nullable From 6b7ae41c72ce95dd9a4b37fd324a5d066e8e651e Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 4 Jan 2024 19:23:43 +0200 Subject: [PATCH 083/209] refactoring: created KeyDictionaryDao --- .../dao/dictionary/KeyDictionaryDao.java | 25 +++++ ...paAbstractDaoListeningExecutorService.java | 55 ----------- .../dao/sql/attributes/JpaAttributeDao.java | 28 +++--- ...stractChunkedAggregationTimeseriesDao.java | 10 +- .../dao/sqlts/SqlTimeseriesLatestDao.java | 10 +- .../sqlts/dictionary/JpaKeyDictionaryDao.java | 92 +++++++++++++++++++ .../dao/sqlts/sql/JpaSqlTimeseriesDao.java | 5 +- .../timescale/TimescaleTimeseriesDao.java | 12 ++- 8 files changed, 156 insertions(+), 81 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/dictionary/KeyDictionaryDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/JpaKeyDictionaryDao.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/dictionary/KeyDictionaryDao.java b/dao/src/main/java/org/thingsboard/server/dao/dictionary/KeyDictionaryDao.java new file mode 100644 index 0000000000..aa663bf208 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/dictionary/KeyDictionaryDao.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.dictionary; + + +public interface KeyDictionaryDao { + + Integer getOrSaveKeyId(String strKey); + + String getKey(Integer attributeKey); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java index 3a10b7f619..5646aa1b6c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java @@ -16,29 +16,17 @@ package org.thingsboard.server.dao.sql; import lombok.extern.slf4j.Slf4j; -import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.jdbc.core.JdbcTemplate; -import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryCompositeKey; -import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryEntry; -import org.thingsboard.server.dao.sqlts.dictionary.KeyDictionaryRepository; import javax.sql.DataSource; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Statement; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.locks.ReentrantLock; @Slf4j public abstract class JpaAbstractDaoListeningExecutorService { - private final ConcurrentMap keyDictionaryMap = new ConcurrentHashMap<>(); - protected static final ReentrantLock creationLock = new ReentrantLock(); - @Autowired protected JpaExecutorService service; @@ -48,49 +36,6 @@ public abstract class JpaAbstractDaoListeningExecutorService { @Autowired protected JdbcTemplate jdbcTemplate; - @Autowired - protected KeyDictionaryRepository keyDictionaryRepository; - - protected Integer getOrSaveKeyId(String strKey) { - Integer keyId = keyDictionaryMap.get(strKey); - if (keyId == null) { - Optional tsKvDictionaryOptional; - tsKvDictionaryOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); - if (tsKvDictionaryOptional.isEmpty()) { - creationLock.lock(); - try { - keyId = keyDictionaryMap.get(strKey); - if (keyId != null) { - return keyId; - } - tsKvDictionaryOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); - if (tsKvDictionaryOptional.isEmpty()) { - KeyDictionaryEntry keyDictionaryEntry = new KeyDictionaryEntry(); - keyDictionaryEntry.setKey(strKey); - try { - KeyDictionaryEntry saved = keyDictionaryRepository.save(keyDictionaryEntry); - keyDictionaryMap.put(saved.getKey(), saved.getKeyId()); - keyId = saved.getKeyId(); - } catch (DataIntegrityViolationException | ConstraintViolationException e) { - tsKvDictionaryOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); - KeyDictionaryEntry dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get KeyDictionaryEntry entity from DB!")); - keyDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); - keyId = dictionary.getKeyId(); - } - } else { - keyId = tsKvDictionaryOptional.get().getKeyId(); - } - } finally { - creationLock.unlock(); - } - } else { - keyId = tsKvDictionaryOptional.get().getKeyId(); - keyDictionaryMap.put(strKey, keyId); - } - } - return keyId; - } - protected void printWarnings(Statement statement) throws SQLException { SQLWarning warnings = statement.getWarnings(); if (warnings != null) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index 54d0bf9495..7114d6db3e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -33,9 +33,9 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.attributes.AttributesDao; +import org.thingsboard.server.dao.dictionary.KeyDictionaryDao; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; -import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryEntry; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; @@ -67,6 +67,9 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @Autowired private StatsFactory statsFactory; + @Autowired + private KeyDictionaryDao keyDictionaryDao; + @Value("${sql.attributes.batch_size:1000}") private int batchSize; @@ -114,7 +117,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @Override public Optional find(TenantId tenantId, EntityId entityId, AttributeScope attributeScope, String attributeKey) { AttributeKvCompositeKey compositeKey = - getAttributeKvCompositeKey(entityId, attributeScope.getId(), getOrSaveKeyId(attributeKey)); + getAttributeKvCompositeKey(entityId, attributeScope.getId(), keyDictionaryDao.getOrSaveKeyId(attributeKey)); Optional attributeKvEntityOptional = attributeKvRepository.findById(compositeKey); if (attributeKvEntityOptional.isPresent()) { AttributeKvEntity attributeKvEntity = attributeKvEntityOptional.get(); @@ -130,10 +133,10 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl attributeKeys .stream() .map(attributeKey -> - getAttributeKvCompositeKey(entityId, attributeScope.getId(), getOrSaveKeyId(attributeKey))) + getAttributeKvCompositeKey(entityId, attributeScope.getId(), keyDictionaryDao.getOrSaveKeyId(attributeKey))) .collect(Collectors.toList()); List attributes = attributeKvRepository.findAllById(compositeKeys); - attributes.forEach(attributeKvEntity -> attributeKvEntity.setStrKey(getKey(attributeKvEntity.getId().getAttributeKey()))); + attributes.forEach(attributeKvEntity -> attributeKvEntity.setStrKey(keyDictionaryDao.getKey(attributeKvEntity.getId().getAttributeKey()))); return DaoUtil.convertDataList(Lists.newArrayList(attributes)); } @@ -142,7 +145,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl List attributes = attributeKvRepository.findAllEntityIdAndAttributeType( entityId.getId(), attributeScope.getId()); - attributes.forEach(attributeKvEntity -> attributeKvEntity.setStrKey(getKey(attributeKvEntity.getId().getAttributeKey()))); + attributes.forEach(attributeKvEntity -> attributeKvEntity.setStrKey(keyDictionaryDao.getKey(attributeKvEntity.getId().getAttributeKey()))); return DaoUtil.convertDataList(Lists.newArrayList( attributes)); } @@ -151,10 +154,10 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl public List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) { if (deviceProfileId != null) { return attributeKvRepository.findAllKeysByDeviceProfileId(tenantId.getId(), deviceProfileId.getId()) - .stream().map(this::getKey).collect(Collectors.toList()); + .stream().map(id -> keyDictionaryDao.getKey(id)).collect(Collectors.toList()); } else { return attributeKvRepository.findAllKeysByTenantId(tenantId.getId()) - .stream().map(this::getKey).collect(Collectors.toList()); + .stream().map(id -> keyDictionaryDao.getKey(id)).collect(Collectors.toList()); } } @@ -162,13 +165,13 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl public List findAllKeysByEntityIds(TenantId tenantId, List entityIds) { return attributeKvRepository .findAllKeysByEntityIds(entityIds.stream().map(EntityId::getId).collect(Collectors.toList())) - .stream().map(this::getKey).collect(Collectors.toList()); + .stream().map(id -> keyDictionaryDao.getKey(id)).collect(Collectors.toList()); } @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, AttributeScope attributeScope, AttributeKvEntry attribute) { AttributeKvEntity entity = new AttributeKvEntity(); - entity.setId(new AttributeKvCompositeKey(entityId.getId(), attributeScope.getId(), getOrSaveKeyId(attribute.getKey()))); + entity.setId(new AttributeKvCompositeKey(entityId.getId(), attributeScope.getId(), keyDictionaryDao.getOrSaveKeyId(attribute.getKey()))); entity.setLastUpdateTs(attribute.getLastUpdateTs()); entity.setStrValue(attribute.getStrValue().orElse(null)); entity.setDoubleValue(attribute.getDoubleValue().orElse(null)); @@ -187,7 +190,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl List> futuresList = new ArrayList<>(keys.size()); for (String key : keys) { futuresList.add(service.submit(() -> { - attributeKvRepository.delete(entityId.getId(), attributeScope.getId(), getOrSaveKeyId(key)); + attributeKvRepository.delete(entityId.getId(), attributeScope.getId(), keyDictionaryDao.getOrSaveKeyId(key)); return key; })); } @@ -200,9 +203,4 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl attributeType, attributeKey); } - - private String getKey(Integer attributeKey) { - Optional byKeyId = keyDictionaryRepository.findByKeyId(attributeKey); - return byKeyId.map(KeyDictionaryEntry::getKey).orElse(null); - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java index 991043db88..c023b2ede3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.dictionary.KeyDictionaryDao; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; @@ -61,6 +62,9 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq @Autowired private StatsFactory statsFactory; + @Autowired + private KeyDictionaryDao keyDictionaryDao; + @PostConstruct protected void init() { TbSqlBlockingQueueParams tsParams = TbSqlBlockingQueueParams.builder() @@ -93,7 +97,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq return service.submit(() -> { tsKvRepository.delete( entityId.getId(), - getOrSaveKeyId(query.getKey()), + keyDictionaryDao.getOrSaveKeyId(query.getKey()), query.getStartTs(), query.getEndTs()); return null; @@ -132,7 +136,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq } private ReadTsKvQueryResult findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { - Integer keyId = getOrSaveKeyId(query.getKey()); + Integer keyId = keyDictionaryDao.getOrSaveKeyId(query.getKey()); List tsKvEntities = tsKvRepository.findAllWithLimit( entityId.getId(), keyId, @@ -160,7 +164,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq } protected TsKvEntity switchAggregation(EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation) { - var keyId = getOrSaveKeyId(key); + var keyId = keyDictionaryDao.getOrSaveKeyId(key); switch (aggregation) { case AVG: return tsKvRepository.findAvg(entityId.getId(), keyId, startTs, endTs); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java index 404855edf4..b7e871e3f5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.dictionary.KeyDictionaryDao; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; @@ -103,6 +104,9 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme @Autowired private StatsFactory statsFactory; + @Autowired + private KeyDictionaryDao keyDictionaryDao; + @PostConstruct protected void init() { TbSqlBlockingQueueParams tsLatestParams = TbSqlBlockingQueueParams.builder() @@ -209,7 +213,7 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme TsKvLatestCompositeKey compositeKey = new TsKvLatestCompositeKey( entityId.getId(), - getOrSaveKeyId(key)); + keyDictionaryDao.getOrSaveKeyId(key)); Optional entry = tsKvLatestRepository.findById(compositeKey); if (entry.isPresent()) { TsKvLatestEntity tsKvLatestEntity = entry.get(); @@ -232,7 +236,7 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme if (ts >= query.getStartTs() && ts < query.getEndTs()) { TsKvLatestEntity latestEntity = new TsKvLatestEntity(); latestEntity.setEntityId(entityId.getId()); - latestEntity.setKey(getOrSaveKeyId(query.getKey())); + latestEntity.setKey(keyDictionaryDao.getOrSaveKeyId(query.getKey())); removedLatestFuture = service.submit(() -> { tsKvLatestRepository.delete(latestEntity); return true; @@ -259,7 +263,7 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme TsKvLatestEntity latestEntity = new TsKvLatestEntity(); latestEntity.setEntityId(entityId.getId()); latestEntity.setTs(tsKvEntry.getTs()); - latestEntity.setKey(getOrSaveKeyId(tsKvEntry.getKey())); + latestEntity.setKey(keyDictionaryDao.getOrSaveKeyId(tsKvEntry.getKey())); latestEntity.setStrValue(tsKvEntry.getStrValue().orElse(null)); latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/JpaKeyDictionaryDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/JpaKeyDictionaryDao.java new file mode 100644 index 0000000000..3c2a4cd228 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/JpaKeyDictionaryDao.java @@ -0,0 +1,92 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sqlts.dictionary; + +import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Component; +import org.thingsboard.server.dao.dictionary.KeyDictionaryDao; +import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryCompositeKey; +import org.thingsboard.server.dao.model.sqlts.dictionary.KeyDictionaryEntry; +import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantLock; + +@Component +@Slf4j +@SqlDao +public class JpaKeyDictionaryDao extends JpaAbstractDaoListeningExecutorService implements KeyDictionaryDao { + + private final ConcurrentMap keyDictionaryMap = new ConcurrentHashMap<>(); + protected static final ReentrantLock creationLock = new ReentrantLock(); + + @Autowired + private KeyDictionaryRepository keyDictionaryRepository; + + @Override + public Integer getOrSaveKeyId(String strKey) { + Integer keyId = keyDictionaryMap.get(strKey); + if (keyId == null) { + Optional tsKvDictionaryOptional; + tsKvDictionaryOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); + if (tsKvDictionaryOptional.isEmpty()) { + creationLock.lock(); + try { + keyId = keyDictionaryMap.get(strKey); + if (keyId != null) { + return keyId; + } + tsKvDictionaryOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); + if (tsKvDictionaryOptional.isEmpty()) { + KeyDictionaryEntry keyDictionaryEntry = new KeyDictionaryEntry(); + keyDictionaryEntry.setKey(strKey); + try { + KeyDictionaryEntry saved = keyDictionaryRepository.save(keyDictionaryEntry); + keyDictionaryMap.put(saved.getKey(), saved.getKeyId()); + keyId = saved.getKeyId(); + } catch (DataIntegrityViolationException | ConstraintViolationException e) { + tsKvDictionaryOptional = keyDictionaryRepository.findById(new KeyDictionaryCompositeKey(strKey)); + KeyDictionaryEntry dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get KeyDictionaryEntry entity from DB!")); + keyDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); + keyId = dictionary.getKeyId(); + } + } else { + keyId = tsKvDictionaryOptional.get().getKeyId(); + } + } finally { + creationLock.unlock(); + } + } else { + keyId = tsKvDictionaryOptional.get().getKeyId(); + keyDictionaryMap.put(strKey, keyId); + } + } + return keyId; + } + + @Override + public String getKey(Integer attributeKey) { + Optional byKeyId = keyDictionaryRepository.findByKeyId(attributeKey); + return byKeyId.map(KeyDictionaryEntry::getKey).orElse(null); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/sql/JpaSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/sql/JpaSqlTimeseriesDao.java index 15a7e99129..4b0d4d9428 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/sql/JpaSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/sql/JpaSqlTimeseriesDao.java @@ -27,6 +27,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.dao.dictionary.KeyDictionaryDao; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.sqlts.AbstractChunkedAggregationTimeseriesDao; import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; @@ -59,6 +60,8 @@ public class JpaSqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao @Autowired private SqlPartitioningRepository partitioningRepository; + @Autowired + private KeyDictionaryDao keyDictionaryDao; private SqlTsPartitionDate tsFormat; @@ -83,7 +86,7 @@ public class JpaSqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao int dataPointDays = getDataPointDays(tsKvEntry, computeTtl(ttl)); savePartitionIfNotExist(tsKvEntry.getTs()); String strKey = tsKvEntry.getKey(); - Integer keyId = getOrSaveKeyId(strKey); + Integer keyId = keyDictionaryDao.getOrSaveKeyId(strKey); TsKvEntity entity = new TsKvEntity(); entity.setEntityId(entityId.getId()); entity.setTs(tsKvEntry.getTs()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java index 128073323c..d7a6b45423 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.dictionary.KeyDictionaryDao; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; @@ -69,6 +70,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements @Autowired protected InsertTsRepository insertRepository; + @Autowired + protected KeyDictionaryDao keyDictionaryDao; + protected TbSqlBlockingQueueWrapper tsQueue; @PostConstruct @@ -108,7 +112,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { int dataPointDays = getDataPointDays(tsKvEntry, computeTtl(ttl)); String strKey = tsKvEntry.getKey(); - Integer keyId = getOrSaveKeyId(strKey); + Integer keyId = keyDictionaryDao.getOrSaveKeyId(strKey); TimescaleTsKvEntity entity = new TimescaleTsKvEntity(); entity.setEntityId(entityId.getId()); entity.setTs(tsKvEntry.getTs()); @@ -130,7 +134,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements @Override public ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { String strKey = query.getKey(); - Integer keyId = getOrSaveKeyId(strKey); + Integer keyId = keyDictionaryDao.getOrSaveKeyId(strKey); return service.submit(() -> { tsKvRepository.delete( entityId.getId(), @@ -161,7 +165,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements private ReadTsKvQueryResult findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { String strKey = query.getKey(); - Integer keyId = getOrSaveKeyId(strKey); + Integer keyId = keyDictionaryDao.getOrSaveKeyId(strKey); List timescaleTsKvEntities = tsKvRepository.findAllWithLimit( entityId.getId(), keyId, @@ -205,7 +209,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } private List switchAggregation(String key, long startTs, long endTs, long timeBucket, Aggregation aggregation, UUID entityId) { - Integer keyId = getOrSaveKeyId(key); + Integer keyId = keyDictionaryDao.getOrSaveKeyId(key); switch (aggregation) { case AVG: return aggregationRepository.findAvg(entityId, keyId, timeBucket, startTs, endTs); From 4c77a75ef63df33768dbb10ac4c38d3750ea05ca Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 5 Jan 2024 10:49:33 +0200 Subject: [PATCH 084/209] fixed tests --- .../java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java | 2 ++ .../org/thingsboard/server/dao/dictionary/KeyDictionaryDao.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java index 9693e2d10b..9b3818794e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao; import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; @@ -24,6 +25,7 @@ import org.thingsboard.server.dao.util.TbAutoConfiguration; @Configuration @TbAutoConfiguration +@ComponentScan({"org.thingsboard.server.dao.sqlts.dictionary"}) @EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.dictionary"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.dictionary"}) @EnableTransactionManagement diff --git a/dao/src/main/java/org/thingsboard/server/dao/dictionary/KeyDictionaryDao.java b/dao/src/main/java/org/thingsboard/server/dao/dictionary/KeyDictionaryDao.java index aa663bf208..64da0dd55d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dictionary/KeyDictionaryDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dictionary/KeyDictionaryDao.java @@ -20,6 +20,6 @@ public interface KeyDictionaryDao { Integer getOrSaveKeyId(String strKey); - String getKey(Integer attributeKey); + String getKey(Integer keyId); } From d59bab4f0f93fdfab24140b94965eba06c7fee5a Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 5 Jan 2024 12:34:55 +0200 Subject: [PATCH 085/209] lwm2m: add to Client Write Attributes implemented and read Attributes (Discover) only for WriteAttributes test --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 29 ++++++++++++------- .../lwm2m/client/LwM2MTestClient.java | 6 ++-- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 5 ++-- .../sql/RpcLwm2mIntegrationObserveTest.java | 22 ++++++-------- .../AbstractSecurityLwM2MIntegrationTest.java | 1 - 5 files changed, 33 insertions(+), 30 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 4378c3758e..9cd6a66cb5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -17,6 +17,8 @@ package org.thingsboard.server.transport.lwm2m; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.eclipse.californium.elements.config.Configuration; @@ -61,6 +63,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.SingleEntityFilter; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; @@ -87,6 +90,7 @@ import static org.eclipse.californium.core.config.CoapConfig.COAP_PORT; import static org.eclipse.californium.core.config.CoapConfig.COAP_SECURE_PORT; import static org.eclipse.leshan.client.object.Security.noSec; import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.Assert.assertEquals; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS; @@ -177,6 +181,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte protected ScheduledExecutorService executor; protected LwM2MTestClient lwM2MTestClient; private String[] resources; + protected String deviceId; + protected boolean isWriteAttribute = false; @Before public void startInit() throws Exception { @@ -232,7 +238,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte getWsClient().registerWaitForUpdate(); createNewClient(security, null, coapConfig, false, endpoint); - awaitObserveReadAll(0, device.getId().getId().toString()); + deviceId = device.getId().getId().toString(); + awaitObserveReadAll(0, deviceId); String msg = getWsClient().waitForUpdate(); EntityDataUpdate update = JacksonUtil.fromString(msg, EntityDataUpdate.class); @@ -305,7 +312,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte try (ServerSocket socket = new ServerSocket(0)) { int clientPort = socket.getLocalPort(); lwM2MTestClient.init(security, securityBs, coapConfig, clientPort, isRpc, - this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest); + this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, isWriteAttribute); } } @@ -398,15 +405,15 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception { await("ObserveReadAll after start client: countObserve " + cntObserve) .atMost(40, TimeUnit.SECONDS) - .until(() -> { - String actualResultReadAll = sendObserve("ObserveReadAll", null, deviceIdStr); - ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - Assert.assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValuesReadAll = rpcActualResultReadAll.get("value").asText(); - log.warn("ObserveReadAll: [{}]", actualValuesReadAll); - int actualCntObserve = "[]".equals(actualValuesReadAll) ? 0 : actualValuesReadAll.split(",").length; - return cntObserve == actualCntObserve; - }); + .until(() -> cntObserve == getCntObserveAll(deviceIdStr)); + } + + protected int getCntObserveAll(String deviceIdStr) throws Exception { + String actualResultBefore = sendObserve("ObserveReadAll", null, deviceIdStr); + ObjectNode rpcActualResultBefore = JacksonUtil.fromString(actualResultBefore, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText()); + JsonElement element = JsonUtils.parse(rpcActualResultBefore.get("value").asText()); + return element.isJsonArray() ? ((JsonArray)element).size() : 0; } protected String sendObserve(String method, String params, String deviceIdStr) throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 19f32d175f..59631945f2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -36,6 +36,7 @@ import org.eclipse.leshan.client.observer.LwM2mClientObserver; import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; +import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter; import org.eclipse.leshan.client.send.ManualDataSender; import org.eclipse.leshan.client.servers.LwM2mServer; @@ -123,7 +124,7 @@ public class LwM2MTestClient { public void init(Security security, Security securityBs,Configuration coapConfig, int port, boolean isRpc, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, - LwM2mClientContext clientContext) throws InvalidDDFFileException, IOException { + LwM2mClientContext clientContext, boolean isWriteAttribute) throws InvalidDDFFileException, IOException { Assert.assertNull("client already initialized", leshanClient); this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler; this.clientContext = clientContext; @@ -132,8 +133,7 @@ public class LwM2MTestClient { models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); } LwM2mModel model = new StaticModel(models); - TbObjectsInitializer initializer = new TbObjectsInitializer(model); - + ObjectsInitializer initializer = isWriteAttribute ? new TbObjectsInitializer(model) : new ObjectsInitializer(model); if (securityBs != null && security != null) { // SECURITY security.setId(serverId); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index e3c0c680cf..9d6a9d7a1d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -55,7 +55,6 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg protected final LinkParser linkParser = new DefaultLwM2mLinkParser(); protected String OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC; - protected String deviceId; public Set expectedObjects; public Set expectedObjectIdVers; public Set expectedInstances; @@ -84,6 +83,9 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg @Before public void startInitRPC() throws Exception { + if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationDiscoverWriteAttributesTest")){ + isWriteAttribute = true; + } initRpc(); } @@ -154,7 +156,6 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg deviceId = device.getId().getId().toString(); lwM2MTestClient.start(true); -// awaitObserveReadAll(2, true, device.getId().getId().toString()); } protected String pathIdVerToObjectId(String pathIdVer) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java index 4d8f58f6f0..d9679c92c4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java @@ -36,28 +36,24 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fr public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest { /** - * ObserveReadAll&ObserveReadAll + * ObserveReadAll&ObserveCancelAll * @throws Exception */ @Test public void testObserveReadAllNothingObservation_Result_CONTENT_Value_Count_0() throws Exception { + awaitObserveReadAll(2, deviceId); String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; - sendRpcObserve("Observe", fromVersionedIdToObjectId(idVer_3_0_0)); - String actualResultBefore = sendRpcObserve("ObserveReadAll", null); - ObjectNode rpcActualResultBefore = JacksonUtil.fromString(actualResultBefore, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText()); - int cntObserveBefore = rpcActualResultBefore.get("value").asText().split(",").length; - assertTrue(cntObserveBefore > 0); - String actualResult = sendRpcObserve("ObserveCancelAll", null); + String actualResult = sendRpcObserve("Observe", idVer_3_0_0); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource")); + assertEquals(3, getCntObserveAll(deviceId)); + actualResult = sendRpcObserve("ObserveCancelAll", null); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); int cntObserveCancelAll = Integer.parseInt(rpcActualResult.get("value").asText()); assertTrue(cntObserveCancelAll > 0); - String actualResultAfter = sendRpcObserve("ObserveReadAll", null); - ObjectNode rpcActualResultAfter = JacksonUtil.fromString(actualResultAfter, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultAfter.get("result").asText()); - String expectResultAfter = "[]"; - assertEquals( expectResultAfter, rpcActualResultAfter.get("value").asText()); + assertEquals(0, getCntObserveAll(deviceId)); } /** diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java index b97adf035c..7b62023c63 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -196,7 +196,6 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M boolean isStartLw) throws Exception { createDeviceProfile(transportConfiguration); final Device device = createDevice(deviceCredentials, endpoint); - device.getId().getId().toString(); createNewClient(security, securityBs, coapConfig, true, endpoint); lwM2MTestClient.start(isStartLw); if (isAwaitObserveReadAll) { From a89985efdd4bad3ed6b81cf8113f74dda5974a90 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 5 Jan 2024 14:20:00 +0200 Subject: [PATCH 086/209] fixed AttributeKvRepository --- .../server/dao/sql/attributes/AttributeKvRepository.java | 4 ++-- .../server/dao/sqlts/dictionary/JpaKeyDictionaryDao.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java index a43be3e49d..f5487971c9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java @@ -42,12 +42,12 @@ public interface AttributeKvRepository extends JpaRepository findAllKeysByDeviceProfileId(@Param("tenantId") UUID tenantId, @Param("deviceProfileId") UUID deviceProfileId); - @Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE entity_type = 'DEVICE' " + + @Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE " + "AND entity_id in (SELECT id FROM device WHERE tenant_id = :tenantId limit 100) ORDER BY attribute_key", nativeQuery = true) List findAllKeysByTenantId(@Param("tenantId") UUID tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/JpaKeyDictionaryDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/JpaKeyDictionaryDao.java index 3c2a4cd228..90bb3bbed0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/JpaKeyDictionaryDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/JpaKeyDictionaryDao.java @@ -84,8 +84,8 @@ public class JpaKeyDictionaryDao extends JpaAbstractDaoListeningExecutorService } @Override - public String getKey(Integer attributeKey) { - Optional byKeyId = keyDictionaryRepository.findByKeyId(attributeKey); + public String getKey(Integer keyId) { + Optional byKeyId = keyDictionaryRepository.findByKeyId(keyId); return byKeyId.map(KeyDictionaryEntry::getKey).orElse(null); } From 9619fb7fc5df1ec4cc0f348b2993cf43905d3c70 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 5 Jan 2024 14:23:12 +0200 Subject: [PATCH 087/209] fixed AttributeKvRepository --- .../server/dao/sql/attributes/AttributeKvRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java index f5487971c9..064fa1fdc7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java @@ -43,12 +43,12 @@ public interface AttributeKvRepository extends JpaRepository findAllKeysByDeviceProfileId(@Param("tenantId") UUID tenantId, @Param("deviceProfileId") UUID deviceProfileId); @Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE " + - "AND entity_id in (SELECT id FROM device WHERE tenant_id = :tenantId limit 100) ORDER BY attribute_key", nativeQuery = true) + "entity_id in (SELECT id FROM device WHERE tenant_id = :tenantId limit 100) ORDER BY attribute_key", nativeQuery = true) List findAllKeysByTenantId(@Param("tenantId") UUID tenantId); @Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE " + From 2b82768225415545e20e0db15af0af114b4ae7b9 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 8 Jan 2024 16:00:05 +0200 Subject: [PATCH 088/209] lwm2m: add Redis --- .../lwm2m/client/LwM2MTestClient.java | 1 - .../LwM2MTransportBootstrapService.java | 60 ----- .../server/DefaultLwM2mTransportService.java | 30 --- .../store/TbLwM2mRedisRegistrationStore.java | 224 ++++++++++-------- 4 files changed, 124 insertions(+), 191 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 59631945f2..911a47d7b4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -255,7 +255,6 @@ public class LwM2MTestClient { */ boolean reconnectOnUpdate = false; engineFactory.setReconnectOnUpdate(reconnectOnUpdate); -// engineFactory.setReconnectOnUpdate(false); // old engineFactory.setResumeOnConnect(true); // new /** diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 96a02bdf97..1f1382ecf5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -128,26 +128,6 @@ public class LwM2MTransportBootstrapService { /* Create DTLS Config */ this.setServerWithCredentials(builder); -// DtlsConnectorConfig dtlsConfig; -// try { -// dtlsConfig = dtlsConfigBuilder.build(); -// } catch (IllegalStateException e) { -// log.warn("Unable to create DTLS config for endpont {}.", endpointUri.toString(), e); -// return null; -// } -// -// Connector dTLSConnector = new DTLSConnector(dtlsConfig); - -// endpointsBuilder.setConnector(dTLSConnector); -// endpointsBuilder.setConfiguration(serverCoapConfig); - -// endpointsBuilder.setLoggingTag(String.format("[%s]", "/" + "options.getUriPathString()")); -// endpointsBuilder.setEndpointContextMatcher(new Lwm2mEndpointContextMatcher()); - - /* Create credentials */ - - - // Set Californium Configuration endpointsBuilder.setConfiguration(serverCoapConfig); @@ -161,46 +141,6 @@ public class LwM2MTransportBootstrapService { InetSocketAddress coapsAddr = new InetSocketAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort()); endpointsBuilder.addEndpoint(coapsAddr, Protocol.COAPS); - -// builder.setLocalAddress(config.getHost(), config.getPort()); -// builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort()); -// builder.setDecoder(new DefaultLwM2mDecoder()); - /* Use a magic converter to support bad type send by the UI. */ -// builder.setEncoder(new DefaultLwM2mEncoder(LwM2mValueConverterImpl.getInstance())); - - /* Create CoAP Config */ -// builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); - -// -// /* Set securityStore with new registrationStore */ -// builder.setSecurityStore(securityStore); -// builder.setRegistrationStore(registrationStore); -// -// -// // Create LWM2M server -// builder.setEndpointsProviders(endpointsBuilder.build()); -// -// -// -// builder.setLocalAddress(bootstrapConfig.getHost(), bootstrapConfig.getPort()); -// builder.setLocalSecureAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort()); -// -// /* Create CoAP Config */ -// builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); -// -// -// /* Create and Set DTLS Config */ -// DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); -// -// dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups()); -// dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, serverConfig.isRecommendedCiphers()); -// dtlsConfig.set(DTLS_RETRANSMISSION_TIMEOUT, serverConfig.getDtlsRetransmissionTimeout(), MILLISECONDS); -// dtlsConfig.set(DTLS_ROLE, SERVER_ONLY); -// setServerWithCredentials(builder, dtlsConfig); -// -// /* Set DTLS Config */ -// builder.setDtlsConfig(dtlsConfig); - /* Set securityStore with new ConfigStore */ builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 5c2045f61a..ab94157ec1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -182,29 +182,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { /* Create DTLS Config */ this.setServerWithCredentials(builder); - - -// DtlsConnectorConfig dtlsConfig; -// try { -// dtlsConfig = dtlsConfigBuilder.build(); -// } catch (IllegalStateException e) { -// log.warn("Unable to create DTLS config for endpont {}.", endpointUri.toString(), e); -// return null; -// } -// -// Connector dTLSConnector = new DTLSConnector(dtlsConfig); - -// endpointsBuilder.setConnector(dTLSConnector); -// endpointsBuilder.setConfiguration(serverCoapConfig); - -// endpointsBuilder.setLoggingTag(String.format("[%s]", "/" + "options.getUriPathString()")); -// endpointsBuilder.setEndpointContextMatcher(new Lwm2mEndpointContextMatcher()); - - /* Create credentials */ - - - - // Set Californium Configuration endpointsBuilder.setConfiguration(serverCoapConfig); @@ -220,13 +197,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { builder.setDecoder(new DefaultLwM2mDecoder(true)); builder.setEncoder(new DefaultLwM2mEncoder(true)); -// builder.setDecoder(new DefaultLwM2mDecoder()); -// /* Use a magic converter to support bad type send by the UI. */ -// builder.setEncoder(new DefaultLwM2mEncoder(LwM2mValueConverterImpl.getInstance())); - - /* Create CoAP Config */ -// builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); - // Create LWM2M server builder.setEndpointsProviders(endpointsBuilder.build()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java index fa44c2a93b..d8be30f86d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java @@ -15,11 +15,11 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; +import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.Token; import org.eclipse.californium.core.network.serialization.UdpDataParser; import org.eclipse.californium.core.network.serialization.UdpDataSerializer; import org.eclipse.californium.core.observe.ObservationStoreException; -import org.eclipse.californium.elements.EndpointContext; import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Stoppable; @@ -30,8 +30,8 @@ import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.peer.LwM2mIdentity; import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.core.util.Validate; -import org.eclipse.leshan.server.californium.observation.ObservationSerDes; import org.eclipse.leshan.server.redis.RedisRegistrationStore; +import org.eclipse.leshan.server.redis.serialization.ObservationSerDes; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; import org.eclipse.leshan.server.registration.Deregistration; import org.eclipse.leshan.server.registration.ExpirationListener; @@ -65,6 +65,7 @@ import java.util.concurrent.locks.Lock; import static java.nio.charset.StandardCharsets.UTF_8; +@Slf4j public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable { /** Default time in seconds between 2 cleaning tasks (used to remove expired registration). */ public static final long DEFAULT_CLEAN_PERIOD = 60; @@ -86,7 +87,9 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab // (expiration date, Endpoint) private final RegistrationSerDes registrationSerDes = new RegistrationSerDes(); - private final ObservationSerDes observationSerDes = new ObservationSerDes(new UdpDataParser(), new UdpDataSerializer()); + private final ObservationSerDes observationSerDes = new ObservationSerDes(); + private final org.eclipse.leshan.server.californium.observation.ObservationSerDes observationSerDesCoap = + new org.eclipse.leshan.server.californium.observation.ObservationSerDes(new UdpDataParser(), new UdpDataSerializer()); private final RedisConnectionFactory connectionFactory; // Listener use to notify when a registration expires @@ -255,6 +258,18 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab return getRegistration(connection, registrationId); } } + private Registration getRegistration(RedisConnection connection, String registrationId) { + byte[] ep = connection.get(toRegIdKey(registrationId)); + if (ep == null) { + return null; + } + byte[] data = connection.get(toEndpointKey(ep)); + if (data == null) { + return null; + } + + return deserializeReg(data); + } @Override public Registration getRegistrationByEndpoint(String endpoint) { @@ -331,63 +346,6 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab } } - @Override - public Collection addObservation(String registrationId, Observation observation, boolean addIfAbsent) { - List removed = new ArrayList<>(); - try (var connection = connectionFactory.getConnection()) { - - // fetch the client ep by registration ID index - byte[] ep = connection.get(toRegIdKey(registrationId)); - if (ep == null) { - return null; - } - - Lock lock = null; - String lockKey = toLockKey(ep); - - try { - lock = redisLock.obtain(lockKey); - lock.lock(); - - // cancel existing observations for the same path and registration id. - for (Observation obs : getObservations(connection, registrationId)) { - //TODO: should be able to use CompositeObservation - if (((SingleObservation)observation).getPath().equals(((SingleObservation)obs).getPath()) - && !observation.getId().equals(obs.getId())) { - removed.add(obs); - unsafeRemoveObservation(connection, registrationId, obs.getId().getBytes()); - } - } - - } finally { - if (lock != null) { - lock.unlock(); - } - } - } - return removed; - } - - @Override - public Collection getObservations(String registrationId) { - try (var connection = connectionFactory.getConnection()) { - return getObservations(connection, registrationId); - } - } - @Override - public Observation getObservation(String registrationId, ObservationIdentifier observationId) { - return getObservations(registrationId).stream().filter(o -> o.getId()==observationId).findFirst().get(); - } - - @Override - public Observation getObservation(ObservationIdentifier observationId) { - return null; - } - - @Override - public Observation removeObservation(String registrationId, ObservationIdentifier observationId) { - return removeObservation(registrationId, observationId.getBytes()); - } private Deregistration removeRegistration(RedisConnection connection, String registrationId, boolean removeOnlyIfNotAlive) { // fetch the client ep by registration ID index @@ -499,6 +457,91 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab * org.eclipse.californium.core.observe.ObservationStore#add method) */ + @Override + public Collection addObservation(String registrationId, Observation observation, boolean addIfAbsent) { + List removed = new ArrayList<>(); + try (var connection = connectionFactory.getConnection()) { + + // fetch the client ep by registration ID index + byte[] ep = connection.get(toRegIdKey(registrationId)); + if (ep == null) { + throw new IllegalStateException(String.format( + "can not add observation %s there is no registration with id %s", observation, registrationId)); + } + + Lock lock = null; + String lockKey = toLockKey(ep); + + try { + lock = redisLock.obtain(lockKey); + lock.lock(); + + // Add and Get previous observation + byte[] previousValue; + byte[] key = toKey(OBS_TKN, observation.getId().getBytes()); + byte[] serializeObs = serializeObs(observation); + if (addIfAbsent) { + previousValue = connection.get(key); + if (previousValue == null || previousValue.length == 0) { + connection.set(key, serializeObs); + } + } else { + previousValue = connection.getSet(key, serializeObs); + } + + // secondary index to get the list by registrationId + connection.lPush(toKey(OBS_TKNS_REGID_IDX, registrationId), observation.getId().getBytes()); + + // log any collisions + Observation previousObservation; + if (previousValue != null && previousValue.length != 0) { + previousObservation = deserializeObs(previousValue); + LOG.warn("Token collision ? observation [{}] will be replaced by observation [{}] ", + previousObservation, observation); + } + // cancel existing observations for the same path and registration id. + for (Observation obs : getObservations(connection, registrationId)) { + //TODO: should be able to use CompositeObservation + if (((SingleObservation)observation).getPath().equals(((SingleObservation)obs).getPath()) + && !observation.getId().equals(obs.getId())) { + removed.add(obs); + unsafeRemoveObservation(connection, registrationId, obs.getId().getBytes()); + } + } + } finally { + if (lock != null) { + lock.unlock(); + } + } + } + return removed; + } + + @Override + public Collection getObservations(String registrationId) { + try (var connection = connectionFactory.getConnection()) { + return getObservations(connection, registrationId); + } + } + @Override + public Observation getObservation(String registrationId, ObservationIdentifier observationId) { + return getObservations(registrationId).stream().filter( + o -> o.getId().getAsHexString().equals(observationId.getAsHexString())).findFirst().get(); + } + + @Override + public Observation getObservation(ObservationIdentifier observationId) { + try (var connection = connectionFactory.getConnection()) { + byte[] observationValue = connection.get(toKey(OBS_TKN, observationId.getBytes())); + return deserializeObs(observationValue); + } + } + + @Override + public Observation removeObservation(String registrationId, ObservationIdentifier observationId) { + return removeObservation(registrationId, observationId.getBytes()); + } + public Observation removeObservation(String registrationId, byte[] observationId) { try (var connection = connectionFactory.getConnection()) { @@ -515,8 +558,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab lock = redisLock.obtain(lockKey); lock.lock(); -// Observation observation = build(get(new Token(observationId))); - Observation observation = build(null); + Observation observation = get(new Token(observationId)); if (observation != null && registrationId.equals(observation.getRegistrationId())) { unsafeRemoveObservation(connection, registrationId, observationId); return observation; @@ -536,7 +578,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab for (byte[] token : connection.lRange(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, -1)) { byte[] obs = connection.get(toKey(OBS_TKN, token)); if (obs != null) { - result.add(build(deserializeObs(obs))); + result.add(deserializeObs(obs)); } } return result; @@ -593,15 +635,16 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab if (!connection.exists(toRegIdKey(registrationId))) throw new ObservationStoreException("no registration for this Id"); byte[] key = toKey(OBS_TKN, obs.getRequest().getToken().getBytes()); - byte[] serializeObs = serializeObs(obs); + Observation obsLeshan = buildLwM2mObservationFromCfToLeshanCore(obs); + byte[] serializeObs = serializeObs(obsLeshan); byte[] previousValue; if (ifAbsent) { previousValue = connection.get(key); if (previousValue == null || previousValue.length == 0) { connection.set(key, serializeObs); + previousValue = connection.getSet(key, serializeObs); } else { -// return deserializeObs(previousValue); - return null; + return buildCoapObservationFromLeshanCoreToCfCore(deserializeObs(previousValue)); } } else { previousValue = connection.getSet(key, serializeObs); @@ -612,8 +655,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab // log any collisions if (previousValue != null && previousValue.length != 0) { -// previousObservation = deserializeObs(previousValue); - previousObservation = null; + previousObservation = buildCoapObservationFromLeshanCoreToCfCore(deserializeObs(previousValue)); LOG.warn( "Token collision ? observation from request [{}] will be replaced by observation from request [{}] ", previousObservation.getRequest(), obs.getRequest()); @@ -663,33 +705,19 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab } - public org.eclipse.californium.core.observe.Observation get(Token token) { + public Observation get(Token token) { try (var connection = connectionFactory.getConnection()) { byte[] obs = connection.get(toKey(OBS_TKN, token.getBytes())); if (obs == null) { return null; } else { -// return deserializeObs(obs); - return null; + return deserializeObs(obs); } } } /* *************** Observation utility functions **************** */ - private Registration getRegistration(RedisConnection connection, String registrationId) { - byte[] ep = connection.get(toRegIdKey(registrationId)); - if (ep == null) { - return null; - } - byte[] data = connection.get(toEndpointKey(ep)); - if (data == null) { - return null; - } - - return deserializeReg(data); - } - private void unsafeRemoveObservation(RedisConnection connection, String registrationId, byte[] observationId) { if (connection.del(toKey(OBS_TKN, observationId)) > 0L) { connection.lRem(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, observationId); @@ -704,7 +732,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab for (byte[] token : connection.lRange(regIdKey, 0, -1)) { byte[] obs = connection.get(toKey(OBS_TKN, token)); if (obs != null) { - removed.add(build(deserializeObs(obs))); + removed.add(deserializeObs(obs)); } connection.del(toKey(OBS_TKN, token)); } @@ -713,26 +741,22 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab return removed; } - public void setContext(Token token, EndpointContext correlationContext) { - // In Leshan we always set context when we send the request, so this should not be needed to implement this. + private byte[] serializeObs(Observation obs) { + return observationSerDes.serialize(obs); } - private byte[] serializeObs(org.eclipse.californium.core.observe.Observation obs) { - return null; -// return observationSerDes.serialize(obs); + private org.eclipse.leshan.core.observation.Observation buildLwM2mObservationFromCfToLeshanCore( + org.eclipse.californium.core.observe.Observation observation) { + String serializedObservation = observationSerDesCoap.serialize(observation); + return serializedObservation == null ? null : ObserveUtil.createLwM2mObservation(observation, serializedObservation); } - - private Observation deserializeObs(byte[] data) { -// return observationSerDes.deserialize(data); - return null; + private org.eclipse.californium.core.observe.Observation buildCoapObservationFromLeshanCoreToCfCore(Observation obs) { + String serializedObservation = ObserveUtil.extractSerializedObservation(obs); + return serializedObservation == null ? null : observationSerDesCoap.deserialize(serializedObservation); } - private Observation build(Observation cfObs) { - if (cfObs == null) - return null; -// String serializedObservation = observationSerDes.serialize(cfObs); -// return ObserveUtil.createLwM2mObservation(cfObs, serializedObservation); - return null; + private Observation deserializeObs(byte[] data) { + return data == null ? null : observationSerDes.deserialize(data); } /* *************** Expiration handling **************** */ From 56f9dce24244b4e8c6d7b516854bc19a65529100 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 9 Jan 2024 12:27:40 +0200 Subject: [PATCH 089/209] updated BaseAttributeService, InternalTelemetryService, RuleEngineTelemetryService to store old methods for backward compatibility --- .../main/data/upgrade/3.6.3/schema_update.sql | 100 +++++------------- .../controller/TelemetryController.java | 2 +- .../device/ClaimDevicesServiceImpl.java | 5 +- .../service/edge/rpc/EdgeGrpcService.java | 5 +- .../telemetry/BaseTelemetryProcessor.java | 2 +- .../DefaultTbEntityViewService.java | 5 +- .../install/SqlDatabaseUpgradeService.java | 60 +---------- .../ota/DefaultOtaPackageStateService.java | 5 +- .../state/DefaultDeviceStateService.java | 4 +- .../DefaultTbLocalSubscriptionService.java | 4 +- .../TbAttributeSubscriptionScope.java | 31 +++++- .../csv/AbstractBulkImportService.java | 3 +- .../DefaultTelemetrySubscriptionService.java | 95 ++++++++++++++++- .../telemetry/InternalTelemetryService.java | 7 ++ .../state/DefaultDeviceStateServiceTest.java | 23 ++-- .../dao/attributes/AttributesService.java | 22 ++++ .../server/dao/attributes/AttributeUtils.java | 6 ++ .../dao/attributes/BaseAttributesService.java | 57 +++++++++- .../attributes/CachedAttributesService.java | 36 +++++++ .../main/resources/sql/schema-entities.sql | 2 +- .../main/resources/sql/schema-timescale.sql | 2 +- dao/src/main/resources/sql/schema-ts-psql.sql | 2 +- .../api/RuleEngineTelemetryService.java | 37 +++++++ .../TbCopyAttributesToEntityViewNode.java | 13 +-- .../rule/engine/math/TbMathNode.java | 5 +- .../engine/telemetry/TbMsgAttributesNode.java | 18 ++-- .../telemetry/TbMsgDeleteAttributesNode.java | 16 +-- .../rule/engine/math/TbMathNodeTest.java | 4 +- .../rule/engine/profile/DeviceStateTest.java | 3 +- .../telemetry/TbMsgAttributesNodeTest.java | 9 +- .../TbMsgDeleteAttributesNodeTest.java | 3 +- 31 files changed, 380 insertions(+), 206 deletions(-) diff --git a/application/src/main/data/upgrade/3.6.3/schema_update.sql b/application/src/main/data/upgrade/3.6.3/schema_update.sql index 30d9fa3adb..e11c52f77f 100644 --- a/application/src/main/data/upgrade/3.6.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.6.3/schema_update.sql @@ -21,14 +21,11 @@ $$ BEGIN -- in case of running the upgrade script a second time: IF EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name = 'attribute_kv' and column_name='entity_type') THEN - IF EXISTS(SELECT 1 FROM pg_indexes WHERE indexname = 'idx_attribute_kv_by_key_and_last_update_ts') THEN - ALTER INDEX idx_attribute_kv_by_key_and_last_update_ts RENAME TO idx_attribute_kv_by_key_and_last_update_ts_old; - END IF; + ALTER INDEX IF EXISTS idx_attribute_kv_by_key_and_last_update_ts RENAME TO idx_attribute_kv_by_key_and_last_update_ts_old; IF EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'attribute_kv_pkey') THEN ALTER TABLE attribute_kv RENAME CONSTRAINT attribute_kv_pkey TO attribute_kv_pkey_old; END IF; - ALTER TABLE attribute_kv - RENAME TO attribute_kv_old; + ALTER TABLE attribute_kv RENAME TO attribute_kv_old; CREATE TABLE IF NOT EXISTS attribute_kv ( entity_id uuid, @@ -43,6 +40,8 @@ $$ CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_id, attribute_type, attribute_key) ); END IF; + DROP VIEW IF EXISTS device_info_view; + DROP VIEW IF EXISTS device_info_active_attribute_view; END; $$; @@ -51,14 +50,12 @@ DO $$ BEGIN IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'ts_kv_dictionary') THEN - ALTER TABLE ts_kv_dictionary - RENAME CONSTRAINT ts_key_id_pkey TO key_id_pkey; - ALTER TABLE ts_kv_dictionary - RENAME TO key_dictionary; + ALTER TABLE ts_kv_dictionary RENAME CONSTRAINT ts_key_id_pkey TO key_dictionary_id_pkey; + ALTER TABLE ts_kv_dictionary RENAME TO key_dictionary; ELSE CREATE TABLE IF NOT EXISTS key_dictionary( key varchar(255) NOT NULL, key_id serial UNIQUE, - CONSTRAINT key_id_pkey PRIMARY KEY (key) + CONSTRAINT key_dictionary_id_pkey PRIMARY KEY (key) ); END IF; END; @@ -83,83 +80,40 @@ $$ LANGUAGE plpgsql; -- insert keys into key_dictionary DO $$ -DECLARE - insert_record RECORD; - key_cursor refcursor; -BEGIN - IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'attribute_kv_old') THEN - OPEN key_cursor FOR SELECT DISTINCT attribute_key - FROM attribute_kv_old - ORDER BY attribute_key; - LOOP - FETCH key_cursor INTO insert_record; - EXIT WHEN NOT FOUND; - IF NOT EXISTS(SELECT key FROM key_dictionary WHERE key = insert_record.attribute_key) THEN - INSERT INTO key_dictionary(key) VALUES (insert_record.attribute_key); - END IF; - END LOOP; - CLOSE key_cursor; - END IF; -END; + BEGIN + IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'attribute_kv_old') THEN + INSERT INTO key_dictionary(key) SELECT DISTINCT attribute_key FROM attribute_kv_old ON CONFLICT DO NOTHING; + END IF; + END; $$; --- create procedure to migrate all rows from attribute_kv_old to attribute_kv -CREATE OR REPLACE PROCEDURE insert_into_attribute_kv(IN path_to_file varchar) - LANGUAGE plpgsql AS +-- migrate attributes from attribute_kv_old to attribute_kv +DO $$ DECLARE row_num_old integer; row_num integer; - attribute_scope_array text[]; BEGIN - attribute_scope_array := ARRAY['SERVER_SCOPE', 'CLIENT_SCOPE', 'SHARED_SCOPE']; IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'attribute_kv_old') THEN - EXECUTE format('COPY (SELECT records.entity_id AS entity_id, - to_attribute_type_id(records.attribute_type) AS attribute_type, - records.attribute_key AS attribute_key, - records.bool_v AS bool_v, - records.str_v AS str_v, - records.long_v AS long_v, - records.dbl_v AS dbl_v, - records.json_v AS json_v, - records.last_update_ts AS last_update_ts - FROM (SELECT entity_id, - attribute_type, - key_id AS attribute_key, - bool_v, - str_v, - long_v, - dbl_v, - json_v, - last_update_ts - FROM attribute_kv_old INNER JOIN key_dictionary ON (attribute_kv_old.attribute_key = key_dictionary.key) - WHERE attribute_type= ANY(%L)) AS records) TO %L;', attribute_scope_array, path_to_file); - EXECUTE format('COPY attribute_kv FROM %L', path_to_file); + INSERT INTO attribute_kv(entity_id, attribute_type, attribute_key, bool_v, str_v, long_v, dbl_v, json_v, last_update_ts) + SELECT a.entity_id, to_attribute_type_id(a.attribute_type), k.key_id, a.bool_v, a.str_v, a.long_v, a.dbl_v, a.json_v, a.last_update_ts + FROM attribute_kv_old a INNER JOIN key_dictionary k ON (a.attribute_key = k.key) + WHERE a.attribute_type IN ('SERVER_SCOPE', 'CLIENT_SCOPE', 'SHARED_SCOPE'); SELECT COUNT(*) INTO row_num_old FROM attribute_kv_old; SELECT COUNT(*) INTO row_num FROM attribute_kv; RAISE NOTICE 'Migrated % of % rows', row_num, row_num_old; + + IF row_num != 0 THEN + DROP TABLE IF EXISTS attribute_kv_old; + ELSE + RAISE EXCEPTION 'Table attribute_kv is empty'; + END IF; + + CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribute_kv(entity_id, attribute_key, last_update_ts desc); END IF; EXCEPTION WHEN others THEN ROLLBACK; RAISE EXCEPTION 'Error during COPY: %', SQLERRM; END -$$; - -CREATE OR REPLACE PROCEDURE drop_attribute_kv_old_table() - LANGUAGE plpgsql AS -$$ -DECLARE - row_num integer; -BEGIN - SELECT COUNT(*) INTO row_num FROM attribute_kv; - IF row_num != 0 then - DROP TABLE IF EXISTS attribute_kv_old; - DROP PROCEDURE IF EXISTS insert_into_attribute_kv(IN path_to_file varchar); - ELSE - RAISE EXCEPTION 'Table attribute_kv is empty'; - END IF; - RETURN; -END; -$$; - +$$; \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index e041975a15..3bde00c5f9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -631,7 +631,7 @@ public class TelemetryController extends BaseController { } SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { - tsSubService.saveAndNotify(tenantId, entityId, scope.name(), attributes, new FutureCallback() { + tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { logAttributesUpdated(user, entityId, scope, attributes, null); diff --git a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java index 08852be7fa..882f5fade2 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java @@ -31,7 +31,6 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; @@ -187,7 +186,7 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { } SettableFuture result = SettableFuture.create(); telemetryService.saveAndNotify( - tenantId, savedDevice.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList( + tenantId, savedDevice.getId(), AttributeScope.SERVER_SCOPE, Collections.singletonList( new BaseAttributeKvEntry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true), System.currentTimeMillis()) ), new FutureCallback<>() { @@ -230,7 +229,7 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { } SettableFuture result = SettableFuture.create(); telemetryService.deleteAndNotify(device.getTenantId(), - device.getId(), DataConstants.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME), new FutureCallback<>() { + device.getId(), AttributeScope.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME), new FutureCallback<>() { @Override public void onSuccess(@Nullable Void tmp) { result.set(tmp); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index 89cae58264..b57c85d982 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -29,6 +29,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.ResourceUtils; import org.thingsboard.server.common.data.edge.Edge; @@ -412,7 +413,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))), new AttributeSaveCallback(tenantId, edgeId, key, value)); } else { - tsSubService.saveAttrAndNotify(tenantId, edgeId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(tenantId, edgeId, key, value)); + tsSubService.saveAttrAndNotify(tenantId, edgeId, AttributeScope.SERVER_SCOPE, key, value, new AttributeSaveCallback(tenantId, edgeId, key, value)); } } @@ -424,7 +425,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))), new AttributeSaveCallback(tenantId, edgeId, key, value)); } else { - tsSubService.saveAttrAndNotify(tenantId, edgeId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(tenantId, edgeId, key, value)); + tsSubService.saveAttrAndNotify(tenantId, edgeId, AttributeScope.SERVER_SCOPE, key, value, new AttributeSaveCallback(tenantId, edgeId, key, value)); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index 51f3fac9f2..327048f27a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -252,7 +252,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); List attributes = new ArrayList<>(JsonConverter.convertToAttributes(json)); String scope = metaData.getValue("scope"); - tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback() { + tsSubService.saveAndNotify(tenantId, entityId, AttributeScope.valueOf(scope), attributes, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java index 665735cc22..a214a88d3e 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java @@ -26,7 +26,6 @@ import org.springframework.stereotype.Service; import org.springframework.util.ConcurrentReferenceHashMap; import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.User; @@ -288,7 +287,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen (startTime == 0 && endTime > lastUpdateTs) || (startTime < lastUpdateTs && endTime > lastUpdateTs); }).collect(Collectors.toList()); - tsSubService.saveAndNotify(entityView.getTenantId(), entityId, scope.name(), attributes, new FutureCallback() { + tsSubService.saveAndNotify(entityView.getTenantId(), entityId, scope, attributes, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { try { @@ -356,7 +355,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen EntityViewId entityId = entityView.getId(); SettableFuture resultFuture = SettableFuture.create(); if (keys != null && !keys.isEmpty()) { - tsSubService.deleteAndNotify(entityView.getTenantId(), entityId, scope.name(), keys, new FutureCallback() { + tsSubService.deleteAndNotify(entityView.getTenantId(), entityId, scope, keys, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { try { diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index e3927b376a..cbd8686918 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -779,31 +779,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService }); break; case "3.6.3": - updateSchema("3.6.3", 3006003, "3.7.0", 3007000, connection -> { - try { - Path pathToTempAttributeKvFile; - if (SystemUtils.IS_OS_WINDOWS) { - pathToTempAttributeKvFile = createTempFileWindows("attribute_kv_temp",".sql"); - } else { - pathToTempAttributeKvFile = createTempFile("attribute_kv", "attribute_kv_temp.sql"); - } - executeQuery(connection, "call insert_into_attribute_kv('" + pathToTempAttributeKvFile + "')"); - - // remove attribute_kv_old - executeQuery(connection, "call drop_attribute_kv_old_table()"); - - //create index for new table attribute_kv - executeQuery(connection, "CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribute_kv(entity_id, attribute_key, last_update_ts desc);"); - - // remove temp files - boolean deleteTsKvFile = Files.deleteIfExists(pathToTempAttributeKvFile); - if (deleteTsKvFile) { - log.info("Successfully deleted the temp file for attribute_kv table upgrade!"); - } - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } - }); + updateSchema("3.6.3", 3006003, "3.7.0", 3007000, null); break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); @@ -829,40 +805,6 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService } } - private static Path createTempFile(String tempDirectoryName, String tempFileName) throws IOException { - Path pathToTempAttributeKvFile; - Path tempDirPath = Files.createTempDirectory(tempDirectoryName); - File tempDirAsFile = tempDirPath.toFile(); - boolean writable = tempDirAsFile.setWritable(true, false); - boolean readable = tempDirAsFile.setReadable(true, false); - boolean executable = tempDirAsFile.setExecutable(true, false); - pathToTempAttributeKvFile = tempDirPath.resolve(tempFileName).toAbsolutePath(); - - if (!(writable && readable && executable)) { - throw new RuntimeException("Failed to grant write permissions for the: " + tempDirPath + "folder!"); - } - return pathToTempAttributeKvFile; - } - - private static Path createTempFileWindows(String prefix, String suffix) throws IOException { - Path pathToTempAttributeKvFile; - log.info("Lookup for environment variable: {} ...", THINGSBOARD_WINDOWS_UPGRADE_DIR); - Path pathToDir; - String thingsboardWindowsUpgradeDir = System.getenv("THINGSBOARD_WINDOWS_UPGRADE_DIR"); - if (StringUtils.isNotEmpty(thingsboardWindowsUpgradeDir)) { - log.info("Environment variable: {} was found!", THINGSBOARD_WINDOWS_UPGRADE_DIR); - pathToDir = Paths.get(thingsboardWindowsUpgradeDir); - } else { - log.info("Failed to lookup environment variable: {}", THINGSBOARD_WINDOWS_UPGRADE_DIR); - pathToDir = Paths.get(PATH_TO_USERS_PUBLIC_FOLDER); - } - log.info("Directory: {} will be used for creation temporary upgrade files!", pathToDir); - - Path attributeKvFile = Files.createTempFile(pathToDir, prefix, suffix); - pathToTempAttributeKvFile = attributeKvFile.toAbsolutePath(); - return pathToTempAttributeKvFile; - } - private void runSchemaUpdateScript(Connection connection, String version) throws Exception { Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, SCHEMA_UPDATE_SQL); loadSql(schemaUpdateFile, connection); diff --git a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java index 70c6655620..adaa6012de 100644 --- a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java @@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.msg.rule.engine.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DataConstants; @@ -335,7 +336,7 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { remove(device, otaPackageType, attrToRemove); - telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() { + telemetryService.saveAndNotify(tenantId, deviceId, AttributeScope.SHARED_SCOPE, attributes, new FutureCallback<>() { @Override public void onSuccess(@Nullable Void tmp) { log.trace("[{}] Success save attributes with target firmware!", deviceId); @@ -353,7 +354,7 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { } private void remove(Device device, OtaPackageType otaPackageType, List attributesKeys) { - telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, attributesKeys, + telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), AttributeScope.SHARED_SCOPE, attributesKeys, new FutureCallback<>() { @Override public void onSuccess(@Nullable Void tmp) { diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 0affb51fc4..d127259099 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -806,7 +806,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService(deviceId, key, value)); } else { - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, AttributeScope.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); } } @@ -817,7 +817,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService(deviceId, key, value)); } else { - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, AttributeScope.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); } } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index a6d5716d2b..c2b218197d 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -449,8 +449,8 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer } final Map keyStates = subscription.getKeyStates(); AttributeScope scope; - if (subscription.getScope() != null && !TbAttributeSubscriptionScope.ANY_SCOPE.equals(subscription.getScope())) { - scope = AttributeScope.valueOf(subscription.getScope().name()); + if (subscription.getScope() != null && subscription.getScope().getAttributeScope() != null) { + scope = subscription.getScope().getAttributeScope(); } else { scope = AttributeScope.CLIENT_SCOPE; } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscriptionScope.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscriptionScope.java index b3874e28fb..ed2558dd42 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscriptionScope.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscriptionScope.java @@ -15,8 +15,37 @@ */ package org.thingsboard.server.service.subscription; +import org.thingsboard.server.common.data.AttributeScope; + public enum TbAttributeSubscriptionScope { - ANY_SCOPE, CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE + ANY_SCOPE(), + CLIENT_SCOPE(AttributeScope.CLIENT_SCOPE), + SHARED_SCOPE(AttributeScope.SHARED_SCOPE), + SERVER_SCOPE(AttributeScope.SERVER_SCOPE); + + private final AttributeScope attributeScope; + + TbAttributeSubscriptionScope() { + this.attributeScope = null; + } + + TbAttributeSubscriptionScope(AttributeScope attributeScope) { + this.attributeScope = attributeScope; + } + + public AttributeScope getAttributeScope() { + return attributeScope; + } + + public static TbAttributeSubscriptionScope of(AttributeScope attributeScope) { + for (TbAttributeSubscriptionScope scope : TbAttributeSubscriptionScope.values()) { + if (attributeScope == scope.getAttributeScope()) { + return scope; + } + } + throw new IllegalArgumentException("Unknown AttributeScope: " + attributeScope.name()); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java index 68c0347b40..5b2e458c59 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java @@ -29,6 +29,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasAdditionalInfo; import org.thingsboard.server.common.data.HasTenantId; @@ -230,7 +231,7 @@ public abstract class AbstractBulkImportService attributes = new ArrayList<>(JsonConverter.convertToAttributes(kvsEntry.getValue())); accessValidator.validateEntityAndCallback(user, Operation.WRITE_ATTRIBUTES, entity.getId(), (result, tenantId, entityId) -> { - tsSubscriptionService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback<>() { + tsSubscriptionService.saveAndNotify(tenantId, entityId, AttributeScope.valueOf(scope), attributes, new FutureCallback<>() { @Override public void onSuccess(Void unused) { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index b01b103007..531f518579 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -242,19 +242,38 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer saveAndNotify(tenantId, entityId, scope, attributes, true, callback); } + @Override + + public void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes, FutureCallback callback) { + saveAndNotify(tenantId, entityId, scope, attributes, true, callback); + } + @Override public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback) { checkInternalEntity(entityId); saveAndNotifyInternal(tenantId, entityId, scope, attributes, notifyDevice, callback); } + @Override + public void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes, boolean notifyDevice, FutureCallback callback) { + checkInternalEntity(entityId); + saveAndNotifyInternal(tenantId, entityId, scope, attributes, notifyDevice, callback); + } + @Override public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback) { - ListenableFuture> saveFuture = attrService.save(tenantId, entityId, AttributeScope.valueOf(scope), attributes); + ListenableFuture> saveFuture = attrService.save(tenantId, entityId, scope, attributes); addVoidCallback(saveFuture, callback); addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice)); } + @Override + public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes, boolean notifyDevice, FutureCallback callback) { + ListenableFuture> saveFuture = attrService.save(tenantId, entityId, scope, attributes); + addVoidCallback(saveFuture, callback); + addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope.name(), attributes, notifyDevice)); + } + @Override public void saveLatestAndNotify(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback) { checkInternalEntity(entityId); @@ -274,19 +293,38 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer deleteAndNotifyInternal(tenantId, entityId, scope, keys, false, callback); } + @Override + public void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List keys, FutureCallback callback) { + checkInternalEntity(entityId); + deleteAndNotifyInternal(tenantId, entityId, scope, keys, false, callback); + } + @Override public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, FutureCallback callback) { checkInternalEntity(entityId); deleteAndNotifyInternal(tenantId, entityId, scope, keys, notifyDevice, callback); } + @Override + public void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List keys, boolean notifyDevice, FutureCallback callback) { + checkInternalEntity(entityId); + deleteAndNotifyInternal(tenantId, entityId, scope, keys, notifyDevice, callback); + } + @Override public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, FutureCallback callback) { - ListenableFuture> deleteFuture = attrService.removeAll(tenantId, entityId, AttributeScope.valueOf(scope), keys); + ListenableFuture> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys); addVoidCallback(deleteFuture, callback); addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice)); } + @Override + public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List keys, boolean notifyDevice, FutureCallback callback) { + ListenableFuture> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys); + addVoidCallback(deleteFuture, callback); + addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope.name(), keys, notifyDevice)); + } + @Override public void deleteLatest(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback) { checkInternalEntity(entityId); @@ -328,24 +366,49 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer , System.currentTimeMillis())), callback); } + + @Override + public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value, FutureCallback callback) { + saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value) + , System.currentTimeMillis())), callback); + } + @Override public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value, FutureCallback callback) { saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(key, value) , System.currentTimeMillis())), callback); } + @Override + public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value, FutureCallback callback) { + saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(key, value) + , System.currentTimeMillis())), callback); + } + @Override public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value, FutureCallback callback) { saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new DoubleDataEntry(key, value) , System.currentTimeMillis())), callback); } + @Override + public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value, FutureCallback callback) { + saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new DoubleDataEntry(key, value) + , System.currentTimeMillis())), callback); + } + @Override public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value, FutureCallback callback) { saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value) , System.currentTimeMillis())), callback); } + @Override + public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value, FutureCallback callback) { + saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value) + , System.currentTimeMillis())), callback); + } + @Override public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value) { SettableFuture future = SettableFuture.create(); @@ -353,6 +416,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer return future; } + @Override + public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value) { + SettableFuture future = SettableFuture.create(); + saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future)); + return future; + } + @Override public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value) { SettableFuture future = SettableFuture.create(); @@ -360,6 +430,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer return future; } + @Override + public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value) { + SettableFuture future = SettableFuture.create(); + saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future)); + return future; + } + @Override public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value) { SettableFuture future = SettableFuture.create(); @@ -367,6 +444,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer return future; } + @Override + public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value) { + SettableFuture future = SettableFuture.create(); + saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future)); + return future; + } + @Override public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value) { SettableFuture future = SettableFuture.create(); @@ -374,6 +458,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer return future; } + @Override + public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value) { + SettableFuture future = SettableFuture.create(); + saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future)); + return future; + } + private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice) { forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { subscriptionManagerService.onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java index 301376b16a..5d451d4b32 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.telemetry; import com.google.common.util.concurrent.FutureCallback; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -33,12 +34,18 @@ public interface InternalTelemetryService extends RuleEngineTelemetryService { void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback); + @Deprecated(since = "3.7.0") void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback); + void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes, boolean notifyDevice, FutureCallback callback); + void saveLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback); + @Deprecated(since = "3.7.0") void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, FutureCallback callback); + void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List keys, boolean notifyDevice, FutureCallback callback); + void deleteLatestInternal(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback); } diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java index 05624b8741..8bba931a0a 100644 --- a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java @@ -26,6 +26,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -251,7 +252,7 @@ public class DefaultDeviceStateServiceTest { long newTimeout = System.currentTimeMillis() - deviceState.getLastActivityTime() + increase; service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); - verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(), eq(ACTIVITY_STATE), any(), any()); + verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any()); Thread.sleep(defaultTimeout + increase); service.checkStates(); activityVerify(false); @@ -290,7 +291,7 @@ public class DefaultDeviceStateServiceTest { long newTimeout = 1; Thread.sleep(newTimeout); - verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(), eq(ACTIVITY_STATE), any(), any()); + verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any()); } @Test @@ -311,7 +312,7 @@ public class DefaultDeviceStateServiceTest { service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); activityVerify(true); - verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(), eq(ACTIVITY_STATE), any(), any()); + verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any()); long newTimeout = 1; Thread.sleep(newTimeout); @@ -352,11 +353,11 @@ public class DefaultDeviceStateServiceTest { long newTimeout = 1; service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); - verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(), eq(ACTIVITY_STATE), any(), any()); + verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any()); } private void activityVerify(boolean isActive) { - verify(telemetrySubscriptionService, times(1)).saveAttrAndNotify(any(), eq(deviceId), any(), eq(ACTIVITY_STATE), eq(isActive), any()); + verify(telemetrySubscriptionService, times(1)).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), eq(isActive), any()); } @Test @@ -403,19 +404,19 @@ public class DefaultDeviceStateServiceTest { assertThat(deviceState.isActive()).isEqualTo(true); assertThat(deviceState.getLastActivityTime()).isEqualTo(lastReportedActivity); then(telemetrySubscriptionService).should().saveAttrAndNotify( - any(), eq(deviceId), any(), eq(LAST_ACTIVITY_TIME), eq(lastReportedActivity), any() + any(), eq(deviceId), any(AttributeScope.class), eq(LAST_ACTIVITY_TIME), eq(lastReportedActivity), any() ); assertThat(deviceState.getLastInactivityAlarmTime()).isEqualTo(expectedInactivityAlarmTime); if (shouldSetInactivityAlarmTimeToZero) { then(telemetrySubscriptionService).should().saveAttrAndNotify( - any(), eq(deviceId), any(), eq(INACTIVITY_ALARM_TIME), eq(0L), any() + any(), eq(deviceId), any(AttributeScope.class), eq(INACTIVITY_ALARM_TIME), eq(0L), any() ); } if (shouldUpdateActivityStateToActive) { then(telemetrySubscriptionService).should().saveAttrAndNotify( - eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any() + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any() ); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -497,7 +498,7 @@ public class DefaultDeviceStateServiceTest { assertThat(deviceState.isActive()).isEqualTo(expectedActivityState); if (activityState && !expectedActivityState) { then(telemetrySubscriptionService).should().saveAttrAndNotify( - any(), eq(deviceId), any(), eq(ACTIVITY_STATE), eq(false), any() + any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), eq(false), any() ); } } @@ -594,7 +595,7 @@ public class DefaultDeviceStateServiceTest { if (shouldUpdateActivityStateToInactive) { then(telemetrySubscriptionService).should().saveAttrAndNotify( - eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any() + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any() ); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -611,7 +612,7 @@ public class DefaultDeviceStateServiceTest { assertThat(actualNotification.isActive()).isFalse(); then(telemetrySubscriptionService).should().saveAttrAndNotify( - eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(SERVER_SCOPE), + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(INACTIVITY_ALARM_TIME), eq(expectedLastInactivityAlarmTime), any() ); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java index ccbc90825a..d7c6f5981e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.attributes; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.AttributeScope; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -31,20 +32,41 @@ import java.util.Optional; */ public interface AttributesService { + @Deprecated(since = "3.7.0") + ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, String attributeKey); + ListenableFuture> find(TenantId tenantId, EntityId entityId, AttributeScope scope, String attributeKey); + @Deprecated(since = "3.7.0") + ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, Collection attributeKeys); + ListenableFuture> find(TenantId tenantId, EntityId entityId, AttributeScope scope, Collection attributeKeys); + @Deprecated(since = "3.7.0") + ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String scope); + ListenableFuture> findAll(TenantId tenantId, EntityId entityId, AttributeScope scope); + @Deprecated(since = "3.7.0") + ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes); + ListenableFuture> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes); + @Deprecated(since = "3.7.0") + ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute); + ListenableFuture save(TenantId tenantId, EntityId entityId, AttributeScope scope, AttributeKvEntry attribute); + @Deprecated(since = "3.7.0") + ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys); + ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributeKeys); List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); + @Deprecated(since = "3.7.0") + List findAllKeysByEntityIds(TenantId tenantId, EntityType entityType, List entityIds); + List findAllKeysByEntityIds(TenantId tenantId, List entityIds); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java index 983661ff6e..7077edac40 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java @@ -26,6 +26,12 @@ import java.util.List; public class AttributeUtils { + @Deprecated(since = "3.7.0") + public static void validate(EntityId id, String scope) { + Validator.validateId(id.getId(), "Incorrect id " + id); + Validator.validateString(scope, "Incorrect scope " + scope); + } + public static void validate(EntityId id, AttributeScope scope) { Validator.validateId(id.getId(), "Incorrect id " + id); Validator.checkNotNull(scope, "Incorrect scope " + scope); diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java index 3f8c88d609..84b504ca6e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.AttributeScope; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -53,6 +54,13 @@ public class BaseAttributesService implements AttributesService { this.attributesDao = attributesDao; } + @Override + public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, String attributeKey) { + validate(entityId, scope); + Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey); + return Futures.immediateFuture(attributesDao.find(tenantId, entityId, AttributeScope.valueOf(scope), attributeKey)); + } + @Override public ListenableFuture> find(TenantId tenantId, EntityId entityId, AttributeScope scope, String attributeKey) { validate(entityId, scope); @@ -60,17 +68,30 @@ public class BaseAttributesService implements AttributesService { return Futures.immediateFuture(attributesDao.find(tenantId, entityId, scope, attributeKey)); } + @Override + public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, Collection attributeKeys) { + validate(entityId, scope); + attributeKeys.forEach(attributeKey -> Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey)); + return Futures.immediateFuture(attributesDao.find(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys)); + } + @Override public ListenableFuture> find(TenantId tenantId, EntityId entityId, AttributeScope scope, Collection attributeKeys) { validate(entityId, scope); attributeKeys.forEach(attributeKey -> Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey)); - return Futures.immediateFuture(attributesDao.find(tenantId, entityId, scope, attributeKeys)); + return Futures.immediateFuture(attributesDao.find(tenantId, entityId, scope, attributeKeys)); + } + + @Override + public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String scope) { + validate(entityId, scope); + return Futures.immediateFuture(attributesDao.findAll(tenantId, entityId, AttributeScope.valueOf(scope))); } @Override public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, AttributeScope scope) { validate(entityId, scope); - return Futures.immediateFuture(attributesDao.findAll(tenantId, entityId, scope)); + return Futures.immediateFuture(attributesDao.findAll(tenantId, entityId, scope)); } @Override @@ -78,29 +99,55 @@ public class BaseAttributesService implements AttributesService { return attributesDao.findAllKeysByDeviceProfileId(tenantId, deviceProfileId); } + @Override + public List findAllKeysByEntityIds(TenantId tenantId, EntityType entityType, List entityIds) { + return attributesDao.findAllKeysByEntityIds(tenantId, entityIds); + } + @Override public List findAllKeysByEntityIds(TenantId tenantId, List entityIds) { return attributesDao.findAllKeysByEntityIds(tenantId, entityIds); } + @Override + public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) { + validate(entityId, scope); + AttributeUtils.validate(attribute, valueNoXssValidation); + return attributesDao.save(tenantId, entityId, AttributeScope.valueOf(scope), attribute); + } + @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, AttributeScope scope, AttributeKvEntry attribute) { validate(entityId, scope); AttributeUtils.validate(attribute, valueNoXssValidation); - return attributesDao.save(tenantId, entityId, scope, attribute); + return attributesDao.save(tenantId, entityId, scope, attribute); + } + + @Override + public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { + validate(entityId, scope); + AttributeUtils.validate(attributes, valueNoXssValidation); + List> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, AttributeScope.valueOf(scope), attribute)).collect(Collectors.toList()); + return Futures.allAsList(saveFutures); } @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes) { validate(entityId, scope); AttributeUtils.validate(attributes, valueNoXssValidation); - List> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, scope, attribute)).collect(Collectors.toList()); + List> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, scope, attribute)).collect(Collectors.toList()); return Futures.allAsList(saveFutures); } + @Override + public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys) { + validate(entityId, scope); + return Futures.allAsList(attributesDao.removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys)); + } + @Override public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributeKeys) { validate(entityId, scope); - return Futures.allAsList(attributesDao.removeAll(tenantId, entityId, scope, attributeKeys)); + return Futures.allAsList(attributesDao.removeAll(tenantId, entityId, scope, attributeKeys)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java index 6029be94c9..f87827b535 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java @@ -27,6 +27,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.TbCacheValueWrapper; import org.thingsboard.server.cache.TbTransactionalCache; import org.thingsboard.server.common.data.AttributeScope; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; @@ -108,6 +109,11 @@ public class CachedAttributesService implements AttributesService { } + @Override + public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, String attributeKey) { + return find(tenantId, entityId, AttributeScope.valueOf(scope), attributeKey); + } + @Override public ListenableFuture> find(TenantId tenantId, EntityId entityId, AttributeScope scope, String attributeKey) { validate(entityId, scope); @@ -137,6 +143,11 @@ public class CachedAttributesService implements AttributesService { } } + @Override + public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, Collection attributeKeys) { + return find(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys); + } + @Override public ListenableFuture> find(TenantId tenantId, EntityId entityId, AttributeScope scope, final Collection attributeKeysNonUnique) { validate(entityId, scope); @@ -204,6 +215,11 @@ public class CachedAttributesService implements AttributesService { return cachedAttributes; } + @Override + public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String scope) { + return findAll(tenantId, entityId, AttributeScope.valueOf(scope)); + } + @Override public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, AttributeScope scope) { validate(entityId, scope); @@ -215,11 +231,21 @@ public class CachedAttributesService implements AttributesService { return attributesDao.findAllKeysByDeviceProfileId(tenantId, deviceProfileId); } + @Override + public List findAllKeysByEntityIds(TenantId tenantId, EntityType entityType, List entityIds) { + return findAllKeysByEntityIds(tenantId, entityIds); + } + @Override public List findAllKeysByEntityIds(TenantId tenantId, List entityIds) { return attributesDao.findAllKeysByEntityIds(tenantId, entityIds); } + @Override + public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) { + return save(tenantId, entityId, AttributeScope.valueOf(scope), attribute); + } + @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, AttributeScope scope, AttributeKvEntry attribute) { validate(entityId, scope); @@ -228,6 +254,11 @@ public class CachedAttributesService implements AttributesService { return Futures.transform(future, key -> evict(entityId, scope, attribute, key), cacheExecutor); } + @Override + public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { + return save(tenantId, entityId, scope, attributes); + } + @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes) { validate(entityId, scope); @@ -249,6 +280,11 @@ public class CachedAttributesService implements AttributesService { return key; } + @Override + public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys) { + return removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys); + } + @Override public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributeKeys) { validate(entityId, scope); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index c0a3815dc2..056201a56e 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -551,7 +551,7 @@ CREATE TABLE IF NOT EXISTS key_dictionary ( key varchar(255) NOT NULL, key_id serial UNIQUE, - CONSTRAINT key_id_pkey PRIMARY KEY (key) + CONSTRAINT key_dictionary_id_pkey PRIMARY KEY (key) ); CREATE TABLE IF NOT EXISTS oauth2_params ( diff --git a/dao/src/main/resources/sql/schema-timescale.sql b/dao/src/main/resources/sql/schema-timescale.sql index 380f813680..caeadbe12e 100644 --- a/dao/src/main/resources/sql/schema-timescale.sql +++ b/dao/src/main/resources/sql/schema-timescale.sql @@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS ts_kv ( CREATE TABLE IF NOT EXISTS key_dictionary ( key varchar(255) NOT NULL, key_id serial UNIQUE, - CONSTRAINT key_id_pkey PRIMARY KEY (key) + CONSTRAINT key_dictionary_id_pkey PRIMARY KEY (key) ); CREATE TABLE IF NOT EXISTS ts_kv_latest ( diff --git a/dao/src/main/resources/sql/schema-ts-psql.sql b/dao/src/main/resources/sql/schema-ts-psql.sql index bd9713c104..69932e130f 100644 --- a/dao/src/main/resources/sql/schema-ts-psql.sql +++ b/dao/src/main/resources/sql/schema-ts-psql.sql @@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS key_dictionary ( key varchar(255) NOT NULL, key_id serial UNIQUE, - CONSTRAINT key_id_pkey PRIMARY KEY (key) + CONSTRAINT key_dictionary_id_pkey PRIMARY KEY (key) ); CREATE OR REPLACE PROCEDURE drop_partitions_by_system_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint) diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java index a61e83f48f..c54c6aed04 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.api; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -40,32 +41,68 @@ public interface RuleEngineTelemetryService { void saveWithoutLatestAndNotify(TenantId tenantId, CustomerId id, EntityId entityId, List ts, long ttl, FutureCallback callback); + @Deprecated(since = "3.7.0") void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List attributes, FutureCallback callback); + void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes, FutureCallback callback); + + @Deprecated(since = "3.7.0") void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback); + void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes, boolean notifyDevice, FutureCallback callback); + void saveLatestAndNotify(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback); + @Deprecated(since = "3.7.0") ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value); + ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value); + + @Deprecated(since = "3.7.0") ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value); + ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value); + + @Deprecated(since = "3.7.0") ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value); + ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value); + + @Deprecated(since = "3.7.0") ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value); + ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value); + + @Deprecated(since = "3.7.0") void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback callback); + void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value, FutureCallback callback); + + @Deprecated(since = "3.7.0") void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value, FutureCallback callback); + void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value, FutureCallback callback); + + @Deprecated(since = "3.7.0") void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value, FutureCallback callback); + void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value, FutureCallback callback); + + @Deprecated(since = "3.7.0") void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value, FutureCallback callback); + void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value, FutureCallback callback); + + @Deprecated(since = "3.7.0") void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List keys, FutureCallback callback); + void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List keys, FutureCallback callback); + + @Deprecated(since = "3.7.0") void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, FutureCallback callback); + void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List keys, boolean notifyDevice, FutureCallback callback); + void deleteLatest(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback); void deleteAllLatest(TenantId tenantId, EntityId entityId, FutureCallback> callback); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index b1248c0856..14b368e8dc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -79,8 +80,8 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { ACTIVITY_EVENT, INACTIVITY_EVENT, POST_ATTRIBUTES_REQUEST)) { if (!msg.getMetaData().getData().isEmpty()) { long now = System.currentTimeMillis(); - String scope = msg.isTypeOf(POST_ATTRIBUTES_REQUEST) ? - DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue(DataConstants.SCOPE); + AttributeScope scope = msg.isTypeOf(POST_ATTRIBUTES_REQUEST) ? + AttributeScope.CLIENT_SCOPE : AttributeScope.valueOf(msg.getMetaData().getValue(DataConstants.SCOPE)); ListenableFuture> entityViewsFuture = ctx.getEntityViewService().findEntityViewsByTenantIdAndEntityIdAsync(ctx.getTenantId(), msg.getOriginator()); @@ -145,17 +146,17 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { ctx.enqueueForTellNext(ctx.newMsg(msg.getQueueName(), msg.getType(), entityView.getId(), msg.getCustomerId(), msg.getMetaData(), msg.getData()), SUCCESS); } - private boolean attributeContainsInEntityView(String scope, String attrKey, EntityView entityView) { + private boolean attributeContainsInEntityView(AttributeScope scope, String attrKey, EntityView entityView) { AttributesEntityView attributesEntityView = entityView.getKeys().getAttributes(); List keys = null; switch (scope) { - case DataConstants.CLIENT_SCOPE: + case CLIENT_SCOPE: keys = attributesEntityView.getCs(); break; - case DataConstants.SERVER_SCOPE: + case SERVER_SCOPE: keys = attributesEntityView.getSs(); break; - case DataConstants.SHARED_SCOPE: + case SHARED_SCOPE: keys = attributesEntityView.getSh(); break; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java index 455e5c8da7..4f25e0760f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java @@ -34,7 +34,6 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.AttributeScope; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; @@ -213,11 +212,11 @@ public class TbMathNode implements TbNode { if (isIntegerResult(mathResultDef, config.getOperation())) { var value = toIntValue(result); return ctx.getTelemetryService().saveAttrAndNotify( - ctx.getTenantId(), msg.getOriginator(), attributeScope.name(), mathResultDef.getKey(), value); + ctx.getTenantId(), msg.getOriginator(), attributeScope, mathResultDef.getKey(), value); } else { var value = toDoubleValue(mathResultDef, result); return ctx.getTelemetryService().saveAttrAndNotify( - ctx.getTenantId(), msg.getOriginator(), attributeScope.name(), mathResultDef.getKey(), value); + ctx.getTenantId(), msg.getOriginator(), attributeScope, mathResultDef.getKey(), value); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 5fa7f91b9f..c29dd67915 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -90,7 +90,7 @@ public class TbMsgAttributesNode implements TbNode { ctx.tellSuccess(msg); return; } - String scope = getScope(msg.getMetaData().getValue(SCOPE)); + AttributeScope scope = getScope(msg.getMetaData().getValue(SCOPE)); boolean sendAttributesUpdateNotification = checkSendNotification(scope); if (!config.isUpdateAttributesOnlyOnValueChange()) { @@ -99,7 +99,7 @@ public class TbMsgAttributesNode implements TbNode { } List keys = newAttributes.stream().map(KvEntry::getKey).collect(Collectors.toList()); - ListenableFuture> findFuture = ctx.getAttributesService().find(ctx.getTenantId(), msg.getOriginator(), AttributeScope.valueOf(scope), keys); + ListenableFuture> findFuture = ctx.getAttributesService().find(ctx.getTenantId(), msg.getOriginator(), scope, keys); DonAsynchron.withCallback(findFuture, currentAttributes -> { @@ -110,7 +110,7 @@ public class TbMsgAttributesNode implements TbNode { MoreExecutors.directExecutor()); } - void saveAttr(List attributes, TbContext ctx, TbMsg msg, String scope, boolean sendAttributesUpdateNotification) { + void saveAttr(List attributes, TbContext ctx, TbMsg msg, AttributeScope scope, boolean sendAttributesUpdateNotification) { if (attributes.isEmpty()) { ctx.tellSuccess(msg); return; @@ -122,7 +122,7 @@ public class TbMsgAttributesNode implements TbNode { attributes, config.isNotifyDevice() || checkNotifyDeviceMdValue(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY)), sendAttributesUpdateNotification ? - new AttributesUpdateNodeCallback(ctx, msg, scope, attributes) : + new AttributesUpdateNodeCallback(ctx, msg, scope.name(), attributes) : new TelemetryNodeCallback(ctx, msg) ); } @@ -145,8 +145,8 @@ public class TbMsgAttributesNode implements TbNode { .collect(Collectors.toList()); } - private boolean checkSendNotification(String scope) { - return config.isSendAttributesUpdatedNotification() && !CLIENT_SCOPE.equals(scope); + private boolean checkSendNotification(AttributeScope scope) { + return config.isSendAttributesUpdatedNotification() && AttributeScope.CLIENT_SCOPE != scope; } private boolean checkNotifyDeviceMdValue(String notifyDeviceMdValue) { @@ -154,11 +154,11 @@ public class TbMsgAttributesNode implements TbNode { return StringUtils.isEmpty(notifyDeviceMdValue) || Boolean.parseBoolean(notifyDeviceMdValue); } - private String getScope(String mdScopeValue) { + private AttributeScope getScope(String mdScopeValue) { if (StringUtils.isNotEmpty(mdScopeValue)) { - return mdScopeValue; + return AttributeScope.valueOf(mdScopeValue); } - return config.getScope(); + return AttributeScope.valueOf(config.getScope()); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNode.java index 112a3c26dd..7d61d7782e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNode.java @@ -22,6 +22,7 @@ import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -32,7 +33,6 @@ import java.util.stream.Collectors; import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY; import static org.thingsboard.server.common.data.DataConstants.SCOPE; -import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; @Slf4j @RuleNode( @@ -69,7 +69,7 @@ public class TbMsgDeleteAttributesNode implements TbNode { if (keysToDelete.isEmpty()) { ctx.tellSuccess(msg); } else { - String scope = getScope(msg.getMetaData().getValue(SCOPE)); + AttributeScope scope = getScope(msg.getMetaData().getValue(SCOPE)); ctx.getTelemetryService().deleteAndNotify( ctx.getTenantId(), msg.getOriginator(), @@ -77,21 +77,21 @@ public class TbMsgDeleteAttributesNode implements TbNode { keysToDelete, checkNotifyDevice(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY), scope), config.isSendAttributesDeletedNotification() ? - new AttributesDeleteNodeCallback(ctx, msg, scope, keysToDelete) : + new AttributesDeleteNodeCallback(ctx, msg, scope.name(), keysToDelete) : new TelemetryNodeCallback(ctx, msg) ); } } - private String getScope(String mdScopeValue) { + private AttributeScope getScope(String mdScopeValue) { if (StringUtils.isNotEmpty(mdScopeValue)) { - return mdScopeValue; + return AttributeScope.valueOf(mdScopeValue); } - return config.getScope(); + return AttributeScope.valueOf(config.getScope()); } - private boolean checkNotifyDevice(String notifyDeviceMdValue, String scope) { - return SHARED_SCOPE.equals(scope) && (config.isNotifyDevice() || Boolean.parseBoolean(notifyDeviceMdValue)); + private boolean checkNotifyDevice(String notifyDeviceMdValue, AttributeScope scope) { + return (AttributeScope.SHARED_SCOPE == scope) && (config.isNotifyDevice() || Boolean.parseBoolean(notifyDeviceMdValue)); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java index 8ee70f23d7..c4e7c46f3e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java @@ -438,14 +438,14 @@ public class TbMathNodeTest { TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); - when(telemetryService.saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble())) + when(telemetryService.saveAttrAndNotify(any(), any(), any(AttributeScope.class), anyString(), anyDouble())) .thenReturn(Futures.immediateFuture(null)); node.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture()); - verify(telemetryService, times(1)).saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble()); + verify(telemetryService, times(1)).saveAttrAndNotify(any(), any(), any(AttributeScope.class), anyString(), anyDouble()); TbMsg resultMsg = msgCaptor.getValue(); assertNotNull(resultMsg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java index eef77a5304..e523313a63 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java @@ -22,6 +22,7 @@ import org.mockito.ArgumentCaptor; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; @@ -75,7 +76,7 @@ public class DeviceStateTest { when(ctx.getDeviceService()).thenReturn(mock(DeviceService.class)); AttributesService attributesService = mock(AttributesService.class); - when(attributesService.find(any(), any(), any(), anyCollection())).thenReturn(Futures.immediateFuture(Collections.emptyList())); + when(attributesService.find(any(), any(), any(AttributeScope.class), anyCollection())).thenReturn(Futures.immediateFuture(Collections.emptyList())); when(ctx.getAttributesService()).thenReturn(attributesService); RuleEngineAlarmService alarmService = mock(RuleEngineAlarmService.class); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java index fe2edc941b..3efbb2f009 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java @@ -29,7 +29,7 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -54,7 +54,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.willCallRealMethod; import static org.mockito.Mockito.mock; @@ -157,7 +156,7 @@ class TbMsgAttributesNodeTest { when(ctxMock.getTenantId()).thenReturn(tenantId); when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock); willCallRealMethod().given(node).init(any(TbContext.class), any(TbNodeConfiguration.class)); - willCallRealMethod().given(node).saveAttr(any(), eq(ctxMock), any(TbMsg.class), anyString(), anyBoolean()); + willCallRealMethod().given(node).saveAttr(any(), eq(ctxMock), any(TbMsg.class), any(AttributeScope.class), anyBoolean()); node.init(ctxMock, tbNodeConfiguration); @@ -169,12 +168,12 @@ class TbMsgAttributesNodeTest { var testTbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, md, TbMsg.EMPTY_STRING); List testAttrList = List.of(new BaseAttributeKvEntry(0L, new StringDataEntry("testKey", "testValue"))); - node.saveAttr(testAttrList, ctxMock, testTbMsg, DataConstants.SHARED_SCOPE, false); + node.saveAttr(testAttrList, ctxMock, testTbMsg, AttributeScope.SHARED_SCOPE, false); ArgumentCaptor notifyDeviceCaptor = ArgumentCaptor.forClass(Boolean.class); verify(telemetryServiceMock, times(1)).saveAndNotify( - eq(tenantId), eq(deviceId), eq(DataConstants.SHARED_SCOPE), + eq(tenantId), eq(deviceId), eq(AttributeScope.SHARED_SCOPE), eq(testAttrList), notifyDeviceCaptor.capture(), any() ); boolean notifyDevice = notifyDeviceCaptor.getValue(); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java index 632243676f..f3fc7e6163 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java @@ -25,6 +25,7 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.msg.TbMsgType; @@ -81,7 +82,7 @@ public class TbMsgDeleteAttributesNodeTest { callBack.onSuccess(null); return null; }).given(telemetryService).deleteAndNotify( - any(), any(), anyString(), anyList(), anyBoolean(), any()); + any(), any(), any(AttributeScope.class), anyList(), anyBoolean(), any()); } @AfterEach From 720afebeebf2be4df7f9a0a90b462997c5bd3e98 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 9 Jan 2024 12:31:23 +0100 Subject: [PATCH 090/209] Used Java serialization for NotificationRuleTrigger --- .../queue/DefaultTbCoreConsumerService.java | 3 ++- common/proto/src/main/proto/queue.proto | 1 - .../RemoteNotificationRuleProcessor.java | 4 ++-- .../dao/service/TenantProfileServiceTest.java | 15 ++++++++------- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index ded333d42e..8b58126a39 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -28,6 +28,7 @@ import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.data.JavaSerDesUtil; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.event.ErrorEvent; import org.thingsboard.server.common.data.event.Event; @@ -383,7 +384,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService { TopicPartitionInfo tpi = topicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/TenantProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/TenantProfileServiceTest.java index ae7fd9dd8e..dd76955927 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/TenantProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/TenantProfileServiceTest.java @@ -24,7 +24,6 @@ import org.junit.jupiter.api.Assertions; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityInfo; -import org.thingsboard.server.common.data.JavaSerDesUtil; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; @@ -38,12 +37,15 @@ import org.thingsboard.server.common.data.queue.SubmitStrategyType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; +import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.tenant.TenantProfileService; +import org.thingsboard.server.gen.transport.TransportProtos; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; @@ -300,19 +302,18 @@ public class TenantProfileServiceTest extends AbstractServiceTest { } @Test - public void testTenantProfileSerialization_fst() { + public void testTenantProfileSerialization_proto() { TenantProfile tenantProfile = new TenantProfile(); + tenantProfile.setId(new TenantProfileId(UUID.randomUUID())); + tenantProfile.setName("testProfile"); TenantProfileData profileData = new TenantProfileData(); tenantProfile.setProfileData(profileData); profileData.setConfiguration(new DefaultTenantProfileConfiguration()); addMainQueueConfig(tenantProfile); - byte[] serialized = assertDoesNotThrow(() -> JavaSerDesUtil.encode(tenantProfile)); - assertDoesNotThrow(() -> { - JavaSerDesUtil.encode(profileData); - }); + byte[] serialized = assertDoesNotThrow(() -> ProtoUtils.toProto(tenantProfile).toByteArray()); - TenantProfile deserialized = assertDoesNotThrow(() -> JavaSerDesUtil.decode(serialized)); + TenantProfile deserialized = assertDoesNotThrow(() -> ProtoUtils.fromProto(TransportProtos.TenantProfileProto.parseFrom(serialized))); assertThat(deserialized).isEqualTo(tenantProfile); assertThat(deserialized.getProfileData()).isNotNull(); assertThat(deserialized.getProfileData().getQueueConfiguration()).isNotEmpty(); From 5ecd9b623844093095a537e2c4ad380fbde5271c Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 9 Jan 2024 14:47:01 +0200 Subject: [PATCH 091/209] fixed tests --- .../service/telemetry/DefaultTelemetrySubscriptionService.java | 1 - .../rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 531f518579..2424bd8676 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -243,7 +243,6 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer } @Override - public void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List attributes, FutureCallback callback) { saveAndNotify(tenantId, entityId, scope, attributes, true, callback); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java index f3fc7e6163..be25c71cee 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java @@ -153,6 +153,6 @@ public class TbMsgDeleteAttributesNodeTest { } verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); verify(ctx, never()).tellFailure(any(), any()); - verify(telemetryService, times(1)).deleteAndNotify(any(), any(), anyString(), anyList(), eq(notifyDevice || notifyDeviceMetadata), any()); + verify(telemetryService, times(1)).deleteAndNotify(any(), any(), any(AttributeScope.class), anyList(), eq(notifyDevice || notifyDeviceMetadata), any()); } } From e94a7e6cf74f23143e5e76deb9f014fe06536af9 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 9 Jan 2024 18:43:07 +0200 Subject: [PATCH 092/209] fixed test --- .../org/thingsboard/server/controller/WebsocketApiTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java b/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java index 47ce021dae..59fa730d2a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java @@ -700,7 +700,7 @@ public class WebsocketApiTest extends AbstractControllerTest { private void sendAttributes(TenantId tenantId, EntityId entityId, TbAttributeSubscriptionScope scope, List attrData) throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); - tsService.saveAndNotify(tenantId, entityId, scope.name(), attrData, new FutureCallback() { + tsService.saveAndNotify(tenantId, entityId, scope.getAttributeScope(), attrData, new FutureCallback() { @Override public void onSuccess(@Nullable Void result) { log.debug("sendAttributes callback onSuccess"); From 192951d4eb4302f915999b1f1db3701c6f4cd6eb Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 9 Jan 2024 19:10:59 +0200 Subject: [PATCH 093/209] minor refactoring --- .../server/dao/attributes/CachedAttributesService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java index f87827b535..c5270cc830 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java @@ -144,8 +144,8 @@ public class CachedAttributesService implements AttributesService { } @Override - public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, Collection attributeKeys) { - return find(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys); + public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, final Collection attributeKeysNonUnique) { + return find(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeysNonUnique); } @Override From 5b513b8d8e9a7f7def2aa8a5ce940cb4a9601a38 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 10 Jan 2024 12:06:42 +0200 Subject: [PATCH 094/209] deleted redundant method --- .../service/install/SqlDatabaseUpgradeService.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index cbd8686918..7045280f0c 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -819,18 +819,6 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService Thread.sleep(5000); } - private void executeQuery(Connection conn, String query) { - try { - Statement statement = conn.createStatement(); - statement.execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - printWarnings(statement); - log.info("Successfully executed query: {}", query); - } catch (SQLException e) { - log.error("Failed to execute query: {} due to: {}", query, e.getMessage()); - throw new RuntimeException("Failed to execute query:" + query + " due to: ", e); - } - } - protected void printWarnings(Statement statement) throws SQLException { SQLWarning warnings = statement.getWarnings(); if (warnings != null) { From 44b5e91676e9b84112f80299c195b8b58327826c Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 10 Jan 2024 15:56:24 +0200 Subject: [PATCH 095/209] updated upgrade script: inlined function, moved view dropping before table rename --- .../main/data/upgrade/3.6.3/schema_update.sql | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/application/src/main/data/upgrade/3.6.3/schema_update.sql b/application/src/main/data/upgrade/3.6.3/schema_update.sql index e11c52f77f..19e0d44839 100644 --- a/application/src/main/data/upgrade/3.6.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.6.3/schema_update.sql @@ -21,6 +21,8 @@ $$ BEGIN -- in case of running the upgrade script a second time: IF EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name = 'attribute_kv' and column_name='entity_type') THEN + DROP VIEW IF EXISTS device_info_view; + DROP VIEW IF EXISTS device_info_active_attribute_view; ALTER INDEX IF EXISTS idx_attribute_kv_by_key_and_last_update_ts RENAME TO idx_attribute_kv_by_key_and_last_update_ts_old; IF EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'attribute_kv_pkey') THEN ALTER TABLE attribute_kv RENAME CONSTRAINT attribute_kv_pkey TO attribute_kv_pkey_old; @@ -40,8 +42,6 @@ $$ CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_id, attribute_type, attribute_key) ); END IF; - DROP VIEW IF EXISTS device_info_view; - DROP VIEW IF EXISTS device_info_active_attribute_view; END; $$; @@ -61,22 +61,6 @@ $$ END; $$; --- create to_attribute_type_id -CREATE OR REPLACE FUNCTION to_attribute_type_id(IN attribute_type varchar, OUT attribute_type_id int) AS -$$ -BEGIN - CASE - WHEN attribute_type = 'CLIENT_SCOPE' THEN - attribute_type_id := 1; - WHEN attribute_type = 'SERVER_SCOPE' THEN - attribute_type_id := 2; - WHEN attribute_type = 'SHARED_SCOPE' THEN - attribute_type_id := 3; - END CASE; -END; -$$ LANGUAGE plpgsql; - - -- insert keys into key_dictionary DO $$ @@ -96,7 +80,13 @@ DECLARE BEGIN IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'attribute_kv_old') THEN INSERT INTO attribute_kv(entity_id, attribute_type, attribute_key, bool_v, str_v, long_v, dbl_v, json_v, last_update_ts) - SELECT a.entity_id, to_attribute_type_id(a.attribute_type), k.key_id, a.bool_v, a.str_v, a.long_v, a.dbl_v, a.json_v, a.last_update_ts + SELECT a.entity_id, CASE + WHEN a.attribute_type = 'CLIENT_SCOPE' THEN 1 + WHEN a.attribute_type = 'SERVER_SCOPE' THEN 2 + WHEN a.attribute_type = 'SHARED_SCOPE' THEN 3 + ELSE 0 + END, + k.key_id, a.bool_v, a.str_v, a.long_v, a.dbl_v, a.json_v, a.last_update_ts FROM attribute_kv_old a INNER JOIN key_dictionary k ON (a.attribute_key = k.key) WHERE a.attribute_type IN ('SERVER_SCOPE', 'CLIENT_SCOPE', 'SHARED_SCOPE'); SELECT COUNT(*) INTO row_num_old FROM attribute_kv_old; From d77b735de39f13d8aacc2190cb60519fe6df269c Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 10 Jan 2024 16:31:03 +0200 Subject: [PATCH 096/209] updated upgrade script: deleted redundant WHERE --- application/src/main/data/upgrade/3.6.3/schema_update.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/application/src/main/data/upgrade/3.6.3/schema_update.sql b/application/src/main/data/upgrade/3.6.3/schema_update.sql index 19e0d44839..9888ee0304 100644 --- a/application/src/main/data/upgrade/3.6.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.6.3/schema_update.sql @@ -87,8 +87,7 @@ BEGIN ELSE 0 END, k.key_id, a.bool_v, a.str_v, a.long_v, a.dbl_v, a.json_v, a.last_update_ts - FROM attribute_kv_old a INNER JOIN key_dictionary k ON (a.attribute_key = k.key) - WHERE a.attribute_type IN ('SERVER_SCOPE', 'CLIENT_SCOPE', 'SHARED_SCOPE'); + FROM attribute_kv_old a INNER JOIN key_dictionary k ON (a.attribute_key = k.key); SELECT COUNT(*) INTO row_num_old FROM attribute_kv_old; SELECT COUNT(*) INTO row_num FROM attribute_kv; RAISE NOTICE 'Migrated % of % rows', row_num, row_num_old; From 71bd6f2bd2e9f380e86bef123da105adf9d9b1c0 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 10 Jan 2024 21:47:39 +0100 Subject: [PATCH 097/209] added tests for proto entities --- common/proto/pom.xml | 6 ++ .../server/common/util/ProtoUtils.java | 20 ++++- common/proto/src/main/proto/queue.proto | 31 ++++--- .../server/common/util/ProtoUtilsTest.java | 82 +++++++++++++++++++ pom.xml | 7 ++ 5 files changed, 130 insertions(+), 16 deletions(-) diff --git a/common/proto/pom.xml b/common/proto/pom.xml index 745b4f105f..84460e8cd0 100644 --- a/common/proto/pom.xml +++ b/common/proto/pom.xml @@ -76,6 +76,12 @@ awaitility test + + org.jeasy + easy-random-core + test + + diff --git a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java index 3ee6751370..71ee64c36a 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.DeviceCredentialsId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EdgeId; @@ -624,7 +625,7 @@ public class ProtoUtils { } if (isNotNull(deviceProfile.getDefaultRuleChainId())) { builder.setDefaultRuleChainIdMSB(getMsb(deviceProfile.getDefaultRuleChainId())) - .setDefaultRuleChainIdLSB(getMsb(deviceProfile.getDefaultRuleChainId())); + .setDefaultRuleChainIdLSB(getLsb(deviceProfile.getDefaultRuleChainId())); } if (isNotNull(deviceProfile.getDefaultDashboardId())) { builder.setDefaultDashboardIdMSB(getMsb(deviceProfile.getDefaultDashboardId())) @@ -744,7 +745,7 @@ public class ProtoUtils { Tenant tenant = new Tenant(getEntityId(proto.getTenantIdMSB(), proto.getTenantIdLSB(), TenantId::new)); tenant.setCreatedTime(proto.getCreatedTime()); tenant.setTenantProfileId(getEntityId(proto.getTenantProfileIdMSB(), proto.getTenantProfileIdLSB(), TenantProfileId::new)); - tenant.setTitle(tenant.getTitle()); + tenant.setTitle(proto.getTitle()); if (proto.hasRegion()) { tenant.setRegion(proto.getRegion()); @@ -822,8 +823,12 @@ public class ProtoUtils { .setTitle(resource.getTitle()) .setResourceType(resource.getResourceType().name()) .setResourceKey(resource.getResourceKey()) + .setIsPublic(resource.isPublic()) .setSearchText(resource.getSearchText()) .setFileName(resource.getFileName()); + if (isNotNull(resource.getPublicResourceKey())) { + builder.setPublicResourceKey(resource.getPublicResourceKey()); + } if (isNotNull(resource.getEtag())) { builder.setEtag(resource.getEtag()); } @@ -850,8 +855,12 @@ public class ProtoUtils { resource.setTitle(proto.getTitle()); resource.setResourceType(ResourceType.valueOf(proto.getResourceType())); resource.setResourceKey(proto.getResourceKey()); + resource.setPublic(proto.getIsPublic()); resource.setSearchText(proto.getSearchText()); resource.setFileName(proto.getFileName()); + if (proto.hasPublicResourceKey()) { + resource.setPublicResourceKey(proto.getPublicResourceKey()); + } if (proto.hasEtag()) { resource.setEtag(proto.getEtag()); } @@ -963,6 +972,9 @@ public class ProtoUtils { public static TransportProtos.DeviceCredentialsProto toProto(DeviceCredentials deviceCredentials) { TransportProtos.DeviceCredentialsProto.Builder builder = TransportProtos.DeviceCredentialsProto.newBuilder() + .setCredentialsIdMSB(deviceCredentials.getId().getId().getMostSignificantBits()) + .setCredentialsIdLSB(deviceCredentials.getId().getId().getLeastSignificantBits()) + .setCreatedTime(deviceCredentials.getCreatedTime()) .setDeviceIdMSB(getMsb(deviceCredentials.getDeviceId())) .setDeviceIdLSB(getLsb(deviceCredentials.getDeviceId())) .setCredentialsId(deviceCredentials.getCredentialsId()) @@ -975,7 +987,9 @@ public class ProtoUtils { } public static DeviceCredentials fromProto(TransportProtos.DeviceCredentialsProto proto) { - DeviceCredentials deviceCredentials = new DeviceCredentials(); + DeviceCredentials deviceCredentials = + new DeviceCredentials(new DeviceCredentialsId(new UUID(proto.getCredentialsIdMSB(), proto.getCredentialsIdLSB()))); + deviceCredentials.setCreatedTime(proto.getCreatedTime()); deviceCredentials.setDeviceId(getEntityId(proto.getDeviceIdMSB(), proto.getDeviceIdLSB(), DeviceId::new)); deviceCredentials.setCredentialsId(proto.getCredentialsId()); deviceCredentials.setCredentialsType(DeviceCredentialsType.valueOf(proto.getCredentialsType().name())); diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index c2eb1ab56f..dd21c1730c 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -275,14 +275,16 @@ message TbResourceProto { string title = 6; string resourceType = 7; string resourceKey = 8; - string searchText = 9; - optional string etag = 10; - string fileName = 11; - optional string resourceDescriptor = 12; - optional int64 externalIdMSB = 13; - optional int64 externalIdLSB = 14; - optional bytes data = 15; - optional bytes preview = 16; + bool isPublic = 9; + optional string publicResourceKey = 10; + string searchText = 11; + optional string etag = 12; + string fileName = 13; + optional string resourceDescriptor = 14; + optional int64 externalIdMSB = 15; + optional int64 externalIdLSB = 16; + optional bytes data = 17; + optional bytes preview = 18; } message ApiUsageStateProto { @@ -637,11 +639,14 @@ message ClaimDeviceMsg { } message DeviceCredentialsProto { - int64 deviceIdMSB = 1; - int64 deviceIdLSB = 2; - CredentialsType credentialsType = 3; - string credentialsId = 4; - optional string credentialsValue = 5; + int64 credentialsIdMSB = 1; + int64 credentialsIdLSB = 2; + int64 createdTime = 3; + int64 deviceIdMSB = 4; + int64 deviceIdLSB = 5; + CredentialsType credentialsType = 6; + string credentialsId = 7; + optional string credentialsValue = 8; } message CredentialsDataProto { diff --git a/common/proto/src/test/java/org/thingsboard/server/common/util/ProtoUtilsTest.java b/common/proto/src/test/java/org/thingsboard/server/common/util/ProtoUtilsTest.java index a41b2be208..3d3eee8e77 100644 --- a/common/proto/src/test/java/org/thingsboard/server/common/util/ProtoUtilsTest.java +++ b/common/proto/src/test/java/org/thingsboard/server/common/util/ProtoUtilsTest.java @@ -15,9 +15,25 @@ */ package org.thingsboard.server.common.util; +import com.fasterxml.jackson.databind.JsonNode; +import org.jeasy.random.EasyRandom; +import org.jeasy.random.EasyRandomParameters; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; +import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.data.DeviceConfiguration; +import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; @@ -34,6 +50,9 @@ import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; import org.thingsboard.server.common.msg.edge.EdgeEventUpdateMsg; import org.thingsboard.server.common.msg.edge.FromEdgeSyncResponse; import org.thingsboard.server.common.msg.edge.ToEdgeSyncRequest; @@ -62,6 +81,19 @@ class ProtoUtilsTest { DeviceId deviceId = new DeviceId(UUID.fromString("ceebb9e5-4239-437c-a507-dc5f71f1232d")); EdgeId edgeId = new EdgeId(UUID.fromString("364be452-2183-459b-af93-1ddb325feac1")); UUID id = UUID.fromString("31a07d85-6ed5-46f8-83c0-6715cb0a8782"); + static EasyRandom easyRandom; + + @BeforeAll + static void init() { + EasyRandomParameters parameters = new EasyRandomParameters() + .randomize(DeviceConfiguration.class, DefaultDeviceConfiguration::new) + .randomize(DeviceTransportConfiguration.class, DefaultDeviceTransportConfiguration::new) + .randomize(JsonNode.class, JacksonUtil::newObjectNode) + .randomize(DeviceProfileData.class, DeviceProfileData::new) + .randomize(TenantProfileConfiguration.class, DefaultTenantProfileConfiguration::new) + .randomize(EntityId.class, () -> new DeviceId(UUID.randomUUID())); + easyRandom = new EasyRandom(parameters); + } @Test void protoComponentLifecycleSerialization() { @@ -179,4 +211,54 @@ class ProtoUtilsTest { Assertions.assertNotNull(serializedMsg); assertThat(ProtoUtils.fromProto(serializedMsg)).as("deserialized").isEqualTo(msg); } + + private static final String description = "Failed to deserialize %s, because found some new fields which absent in %sProto!!!"; + + @Test + void protoSerializationDeserializationEntities() { + Device expectedDevice = easyRandom.nextObject(Device.class); + TransportProtos.DeviceProto deviceProto = ProtoUtils.toProto(expectedDevice); + Device actualDevice = ProtoUtils.fromProto(deviceProto); + assertEqualDeserializedEntity(expectedDevice, actualDevice, "Device"); + + DeviceCredentials expectedCredentials = easyRandom.nextObject(DeviceCredentials.class); + TransportProtos.DeviceCredentialsProto credentialsProto = ProtoUtils.toProto(expectedCredentials); + DeviceCredentials actualCredentials = ProtoUtils.fromProto(credentialsProto); + assertEqualDeserializedEntity(expectedCredentials, actualCredentials, "DeviceCredentials"); + + DeviceProfile expectedDeviceProfile = easyRandom.nextObject(DeviceProfile.class); + TransportProtos.DeviceProfileProto deviceProfileProto = ProtoUtils.toProto(expectedDeviceProfile); + DeviceProfile actualDeviceProfile = ProtoUtils.fromProto(deviceProfileProto); + assertEqualDeserializedEntity(expectedDeviceProfile, actualDeviceProfile, "DeviceProfile"); + + Tenant expectedTenant = easyRandom.nextObject(Tenant.class); + TransportProtos.TenantProto tenantProto = ProtoUtils.toProto(expectedTenant); + Tenant actualTenant = ProtoUtils.fromProto(tenantProto); + assertEqualDeserializedEntity(expectedTenant, actualTenant, "Tenant"); + + TenantProfile expectedTenantProfile = easyRandom.nextObject(TenantProfile.class); + TransportProtos.TenantProfileProto tenantProfileProto = ProtoUtils.toProto(expectedTenantProfile); + TenantProfile actualTenantProfile = ProtoUtils.fromProto(tenantProfileProto); + assertEqualDeserializedEntity(expectedTenantProfile, actualTenantProfile, "TenantProfile"); + + TbResource expectedResource = easyRandom.nextObject(TbResource.class); + TransportProtos.TbResourceProto resourceProto = ProtoUtils.toProto(expectedResource); + TbResource actualResource = ProtoUtils.fromProto(resourceProto); + assertEqualDeserializedEntity(expectedResource, actualResource, "TbResource"); + + ApiUsageState expectedState = easyRandom.nextObject(ApiUsageState.class); + TransportProtos.ApiUsageStateProto stateProto = ProtoUtils.toProto(expectedState); + ApiUsageState actualState = ProtoUtils.fromProto(stateProto); + assertEqualDeserializedEntity(expectedState, actualState, "ApiUsageState"); + + RepositorySettings expectedSettings = easyRandom.nextObject(RepositorySettings.class); + TransportProtos.RepositorySettingsProto settingsProto = ProtoUtils.toProto(expectedSettings); + RepositorySettings actualSettings = ProtoUtils.fromProto(settingsProto); + assertEqualDeserializedEntity(expectedSettings, actualSettings, "RepositorySettings"); + } + + private void assertEqualDeserializedEntity(Object expected, Object actual, String entityName) { + assertThat(actual).as(String.format(description, entityName, entityName)).isEqualTo(expected); + } + } diff --git a/pom.xml b/pom.xml index 1204d02abf..9ebe900497 100755 --- a/pom.xml +++ b/pom.xml @@ -139,6 +139,7 @@ 5.15.0 1.3.0 1.2.7 + 5.0.0 7.6.1 3.23.1 @@ -2036,6 +2037,12 @@ ${mock-server.version} test + + org.jeasy + easy-random-core + ${jeasy.version} + test + org.opensmpp opensmpp-core From 7b3b8de7ce1dfdef725324217683e4dc2497bebc Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 11 Jan 2024 16:19:01 +0200 Subject: [PATCH 098/209] Code review refactoring --- .../entitiy/EntityStateSourcingListener.java | 15 ++++---- .../tenant/DefaultTbTenantService.java | 7 ---- .../service/install/InstallScripts.java | 4 +-- .../oauth2/AbstractOAuth2ClientMapper.java | 7 ---- .../dao/entity/EntityStateSyncManager.java | 23 ------------- .../server/dao/rule/RuleChainService.java | 4 +++ .../server/dao/device/DeviceServiceImpl.java | 5 ++- .../entity/DefaultEntityStateSyncManager.java | 34 ------------------- .../DefaultNotificationRequestService.java | 4 +-- .../server/dao/rule/BaseRuleChainService.java | 23 ++++++++++--- 10 files changed, 36 insertions(+), 90 deletions(-) delete mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityStateSyncManager.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityStateSyncManager.java diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java index 80d2647078..98bd890fab 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; @@ -47,7 +48,6 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.rule.engine.DeviceCredentialsUpdateNotificationMsg; -import org.thingsboard.server.dao.entity.EntityStateSyncManager; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; @@ -61,7 +61,6 @@ import java.util.Set; public class EntityStateSourcingListener { private final TbClusterService tbClusterService; - private final EntityStateSyncManager entityStateSyncManager; @PostConstruct public void init() { @@ -70,9 +69,6 @@ public class EntityStateSourcingListener { @TransactionalEventListener(fallbackExecution = true) public void handleEvent(SaveEntityEvent event) { - if (entityStateSyncManager.isSync()) { - return; - } log.trace("[{}] SaveEntityEvent called: {}", event.getTenantId(), event); TenantId tenantId = event.getTenantId(); EntityId entityId = event.getEntityId(); @@ -138,13 +134,18 @@ public class EntityStateSourcingListener { case CUSTOMER: case EDGE: case NOTIFICATION_RULE: - case NOTIFICATION_REQUEST: tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); break; + case NOTIFICATION_REQUEST: + NotificationRequest request = (NotificationRequest) event.getEntity(); + if (request.isScheduled()) { + tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); + } + break; case RULE_CHAIN: RuleChain ruleChain = (RuleChain) event.getEntity(); - Set referencingRuleChainIds = JacksonUtil.fromString(event.getBody(), new TypeReference<>() {}); if (RuleChainType.CORE.equals(ruleChain.getType())) { + Set referencingRuleChainIds = JacksonUtil.fromString(event.getBody(), new TypeReference<>() {}); if (referencingRuleChainIds != null) { referencingRuleChainIds.forEach(referencingRuleChainId -> tbClusterService.broadcastEntityStateChangeEvent(tenantId, referencingRuleChainId, ComponentLifecycleEvent.UPDATED)); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java index 5d8a477f5a..8201ded840 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java @@ -21,7 +21,6 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.entity.EntityStateSyncManager; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; @@ -47,17 +46,12 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T private final TenantProfileService tenantProfileService; private final EntitiesVersionControlService versionControlService; private final ApplicationEventPublisher eventPublisher; - private final EntityStateSyncManager entityStateSyncManager; @Override public Tenant save(Tenant tenant) throws Exception { boolean created = tenant.getId() == null; Tenant oldTenant = !created ? tenantService.findTenantById(tenant.getId()) : null; - if (created) { - entityStateSyncManager.getSync().set(true); - } - Tenant savedTenant = checkNotNull(tenantService.saveTenant(tenant)); if (created) { installScripts.createDefaultRuleChains(savedTenant.getId()); @@ -67,7 +61,6 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T tenantProfileCache.evict(savedTenant.getId()); if (created) { - entityStateSyncManager.getSync().remove(); eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(TenantId.SYS_TENANT_ID).entityId(savedTenant.getId()).entity(savedTenant).added(true).build()); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 223fa39f3a..41392f242f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -193,10 +193,10 @@ public class InstallScripts { if (!StringUtils.isEmpty(newRuleChainName)) { ruleChain.setName(newRuleChainName); } - ruleChain = ruleChainService.saveRuleChain(ruleChain); + ruleChain = ruleChainService.saveRuleChain(ruleChain, false); ruleChainMetaData.setRuleChainId(ruleChain.getId()); - ruleChainService.saveRuleChainMetaData(TenantId.SYS_TENANT_ID, ruleChainMetaData, Function.identity()); + ruleChainService.saveRuleChainMetaData(TenantId.SYS_TENANT_ID, ruleChainMetaData, Function.identity(), false); return ruleChain; } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index 49d33122ef..deee755cd3 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -42,7 +42,6 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; -import org.thingsboard.server.dao.entity.EntityStateSyncManager; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; @@ -90,9 +89,6 @@ public abstract class AbstractOAuth2ClientMapper { @Autowired private ApplicationEventPublisher eventPublisher; - @Autowired - private EntityStateSyncManager entityStateSyncManager; - @Value("${edges.enabled}") @Getter private boolean edgesEnabled; @@ -179,8 +175,6 @@ public abstract class AbstractOAuth2ClientMapper { List tenants = tenantService.findTenants(new PageLink(1, 0, tenantName)).getData(); Tenant tenant; if (tenants == null || tenants.isEmpty()) { - entityStateSyncManager.getSync().set(true); - tenant = new Tenant(); tenant.setTitle(tenantName); tenant = tenantService.saveTenant(tenant); @@ -188,7 +182,6 @@ public abstract class AbstractOAuth2ClientMapper { installScripts.createDefaultEdgeRuleChains(tenant.getId()); tenantProfileCache.evict(tenant.getId()); - entityStateSyncManager.getSync().remove(); eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(TenantId.SYS_TENANT_ID).entityId(tenant.getId()).entity(tenant).added(true).build()); } else { tenant = tenants.get(0); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityStateSyncManager.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityStateSyncManager.java deleted file mode 100644 index 69a4004498..0000000000 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityStateSyncManager.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.entity; - -public interface EntityStateSyncManager { - - ThreadLocal getSync(); - - boolean isSync(); -} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java index 532da4ac00..85f341b95e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -44,10 +44,14 @@ public interface RuleChainService extends EntityDaoService { RuleChain saveRuleChain(RuleChain ruleChain); + RuleChain saveRuleChain(RuleChain ruleChain, boolean publishSaveEvent); + boolean setRootRuleChain(TenantId tenantId, RuleChainId ruleChainId); RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater); + RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater, boolean publishSaveEvent); + RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId); RuleChain findRuleChainById(TenantId tenantId, RuleChainId ruleChainId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 59b0da9dae..68b77ce149 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -81,7 +81,6 @@ import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.tenant.TenantService; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -465,7 +464,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService customerDeviceUnasigner = new PaginatedRemover<>() { + private final PaginatedRemover customerDevicesRemover = new PaginatedRemover<>() { @Override protected PageData findEntities(TenantId tenantId, CustomerId id, PageLink pageLink) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityStateSyncManager.java b/dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityStateSyncManager.java deleted file mode 100644 index 5004b29995..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityStateSyncManager.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.entity; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -@Component -@Slf4j -public class DefaultEntityStateSyncManager implements EntityStateSyncManager { - - @Getter - private final ThreadLocal sync = new ThreadLocal<>(); - - @Override - public boolean isSync() { - Boolean sync = this.sync.get(); - return sync != null && sync; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java index 9f63cc71cf..740849826b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java @@ -90,9 +90,7 @@ public class DefaultNotificationRequestService implements NotificationRequestSer public void deleteNotificationRequest(TenantId tenantId, NotificationRequest request) { notificationRequestDao.removeById(tenantId, request.getUuidId()); notificationDao.deleteByRequestId(tenantId, request.getId()); - if (request.isScheduled()) { - eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(request.getId()).build()); - } + eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entity(request).entityId(request.getId()).build()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 13ee23e9d5..d07102b80b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -111,14 +111,22 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Override @Transactional public RuleChain saveRuleChain(RuleChain ruleChain) { + return saveRuleChain(ruleChain, true); + } + + @Override + @Transactional + public RuleChain saveRuleChain(RuleChain ruleChain, boolean publishSaveEvent) { ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); try { RuleChain savedRuleChain = ruleChainDao.save(ruleChain.getTenantId(), ruleChain); if (ruleChain.getId() == null) { entityCountService.publishCountEntityEvictEvent(ruleChain.getTenantId(), EntityType.RULE_CHAIN); } - eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedRuleChain.getTenantId()) - .entity(savedRuleChain).entityId(savedRuleChain.getId()).added(ruleChain.getId() == null).build()); + if (publishSaveEvent) { + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedRuleChain.getTenantId()) + .entity(savedRuleChain).entityId(savedRuleChain.getId()).added(ruleChain.getId() == null).build()); + } return savedRuleChain; } catch (Exception e) { checkConstraintViolation(e, "rule_chain_external_id_unq_key", "Rule Chain with such external id already exists!"); @@ -155,6 +163,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Override public RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater) { + return saveRuleChainMetaData(tenantId, ruleChainMetaData, ruleNodeUpdater, true); + } + + + @Override + public RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater, boolean publishSaveEvent) { Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id."); RuleChain ruleChain = findRuleChainById(tenantId, ruleChainMetaData.getRuleChainId()); if (ruleChain == null) { @@ -268,8 +282,9 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC if (!relations.isEmpty()) { relationService.saveRelations(tenantId, relations); } - eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entity(ruleChain).entityId(ruleChain.getId()).build()); - + if (publishSaveEvent) { + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entity(ruleChain).entityId(ruleChain.getId()).build()); + } return RuleChainUpdateResult.successful(updatedRuleNodes); } From eda943dcfbab221a3e48bc59b4bc5074633066d2 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 11 Jan 2024 17:58:26 +0200 Subject: [PATCH 099/209] Fix tests --- .../profile/BaseAssetProfileProcessor.java | 2 +- .../profile/BaseDeviceProfileProcessor.java | 2 +- .../entitiy/tenant/DefaultTbTenantService.java | 2 +- .../install/SqlDatabaseUpgradeService.java | 6 ------ .../server/dao/asset/AssetProfileService.java | 4 ++-- .../dao/device/DeviceProfileService.java | 4 ++-- .../server/dao/tenant/TenantService.java | 2 ++ .../dao/asset/AssetProfileServiceImpl.java | 18 ++++++++++-------- .../dao/device/DeviceProfileServiceImpl.java | 18 ++++++++++-------- .../server/dao/tenant/TenantServiceImpl.java | 12 ++++++++++-- 10 files changed, 39 insertions(+), 31 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java index 2fe6cb36c2..47addb5481 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java @@ -67,7 +67,7 @@ public abstract class BaseAssetProfileProcessor extends BaseEdgeProcessor { if (created) { assetProfile.setId(assetProfileId); } - assetProfileService.saveAssetProfile(assetProfile, false); + assetProfileService.saveAssetProfile(assetProfile, false, true); } catch (Exception e) { log.error("[{}] Failed to process asset profile update msg [{}]", tenantId, assetProfileUpdateMsg, e); throw e; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java index 7104764a7b..ff85dc4e62 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java @@ -66,7 +66,7 @@ public abstract class BaseDeviceProfileProcessor extends BaseEdgeProcessor { if (created) { deviceProfile.setId(deviceProfileId); } - deviceProfileService.saveDeviceProfile(deviceProfile, false); + deviceProfileService.saveDeviceProfile(deviceProfile, false, true); } catch (Exception e) { log.error("[{}] Failed to process device profile update msg [{}]", tenantId, deviceProfileUpdateMsg, e); throw e; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java index 8201ded840..3575ba1109 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java @@ -52,7 +52,7 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T boolean created = tenant.getId() == null; Tenant oldTenant = !created ? tenantService.findTenantById(tenant.getId()) : null; - Tenant savedTenant = checkNotNull(tenantService.saveTenant(tenant)); + Tenant savedTenant = checkNotNull(tenantService.saveTenant(tenant, false)); if (created) { installScripts.createDefaultRuleChains(savedTenant.getId()); installScripts.createDefaultEdgeRuleChains(savedTenant.getId()); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 7045280f0c..368c11107c 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -19,14 +19,12 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang3.SystemUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -50,8 +48,6 @@ import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; import org.thingsboard.server.service.install.sql.SqlDbHelper; import org.thingsboard.server.service.install.update.DefaultDataUpdateService; -import java.io.File; -import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; @@ -69,8 +65,6 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import static org.thingsboard.server.service.install.AbstractSqlTsDatabaseUpgradeService.PATH_TO_USERS_PUBLIC_FOLDER; -import static org.thingsboard.server.service.install.AbstractSqlTsDatabaseUpgradeService.THINGSBOARD_WINDOWS_UPGRADE_DIR; import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO; import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS; import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION; diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java index 7d7769411e..6835985d1e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java @@ -38,10 +38,10 @@ public interface AssetProfileService extends EntityDaoService { AssetProfileInfo findAssetProfileInfoById(TenantId tenantId, AssetProfileId assetProfileId); - AssetProfile saveAssetProfile(AssetProfile assetProfile, boolean doValidate); - AssetProfile saveAssetProfile(AssetProfile assetProfile); + AssetProfile saveAssetProfile(AssetProfile assetProfile, boolean doValidate, boolean publishSaveEvent); + void deleteAssetProfile(TenantId tenantId, AssetProfileId assetProfileId); PageData findAssetProfiles(TenantId tenantId, PageLink pageLink); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java index 066254d28d..d37e4b7436 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java @@ -38,10 +38,10 @@ public interface DeviceProfileService extends EntityDaoService { DeviceProfileInfo findDeviceProfileInfoById(TenantId tenantId, DeviceProfileId deviceProfileId); - DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile, boolean doValidate); - DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile); + DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile, boolean doValidate, boolean publishSaveEvent); + void deleteDeviceProfile(TenantId tenantId, DeviceProfileId deviceProfileId); PageData findDeviceProfiles(TenantId tenantId, PageLink pageLink); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java index 18c5eadfee..f17a34da9e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java @@ -36,6 +36,8 @@ public interface TenantService extends EntityDaoService { Tenant saveTenant(Tenant tenant); + Tenant saveTenant(Tenant tenant, boolean publishSaveEvent); + boolean tenantExists(TenantId tenantId); void deleteTenant(TenantId tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java index 63e5c39c51..e2fbd7b973 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java @@ -130,11 +130,11 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService Date: Fri, 12 Jan 2024 09:47:01 +0200 Subject: [PATCH 100/209] Add logic to handle update process tenant on TbTenantService --- .../server/service/entitiy/tenant/DefaultTbTenantService.java | 2 +- .../java/org/thingsboard/server/dao/device/DeviceService.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java index 3575ba1109..3b92d53883 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java @@ -52,7 +52,7 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T boolean created = tenant.getId() == null; Tenant oldTenant = !created ? tenantService.findTenantById(tenant.getId()) : null; - Tenant savedTenant = checkNotNull(tenantService.saveTenant(tenant, false)); + Tenant savedTenant = checkNotNull(tenantService.saveTenant(tenant, !created)); if (created) { installScripts.createDefaultRuleChains(savedTenant.getId()); installScripts.createDefaultEdgeRuleChains(savedTenant.getId()); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index 0bb0dbf50e..cccf54d10a 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -49,10 +49,10 @@ public interface DeviceService extends EntityDaoService { Device findDeviceByTenantIdAndName(TenantId tenantId, String name); - Device saveDevice(Device device, boolean doValidate); - Device saveDevice(Device device); + Device saveDevice(Device device, boolean doValidate); + Device saveDeviceWithAccessToken(Device device, String accessToken); Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials); From 5acf527f1a5b01a66882282d9fec8beb90e7d1f8 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 16 Jan 2024 11:43:44 +0200 Subject: [PATCH 101/209] Version set to 3.7.0-SNAPSHOT --- application/pom.xml | 2 +- common/actor/pom.xml | 2 +- common/cache/pom.xml | 2 +- common/cluster-api/pom.xml | 2 +- common/coap-server/pom.xml | 2 +- common/dao-api/pom.xml | 2 +- common/data/pom.xml | 2 +- common/edge-api/pom.xml | 2 +- common/message/pom.xml | 2 +- common/pom.xml | 2 +- common/proto/pom.xml | 2 +- common/queue/pom.xml | 2 +- common/script/pom.xml | 2 +- common/script/remote-js-client/pom.xml | 2 +- common/script/script-api/pom.xml | 2 +- common/stats/pom.xml | 2 +- common/transport/coap/pom.xml | 2 +- common/transport/http/pom.xml | 2 +- common/transport/lwm2m/pom.xml | 2 +- common/transport/mqtt/pom.xml | 2 +- common/transport/pom.xml | 2 +- common/transport/snmp/pom.xml | 2 +- common/transport/transport-api/pom.xml | 2 +- common/util/pom.xml | 2 +- common/version-control/pom.xml | 2 +- dao/pom.xml | 2 +- monitoring/pom.xml | 2 +- msa/black-box-tests/pom.xml | 2 +- msa/js-executor/package.json | 2 +- msa/js-executor/pom.xml | 2 +- msa/monitoring/pom.xml | 2 +- msa/pom.xml | 2 +- msa/tb-node/pom.xml | 2 +- msa/tb/pom.xml | 2 +- msa/transport/coap/pom.xml | 2 +- msa/transport/http/pom.xml | 2 +- msa/transport/lwm2m/pom.xml | 2 +- msa/transport/mqtt/pom.xml | 2 +- msa/transport/pom.xml | 2 +- msa/transport/snmp/pom.xml | 2 +- msa/vc-executor-docker/pom.xml | 2 +- msa/vc-executor/pom.xml | 2 +- msa/web-ui/package.json | 2 +- msa/web-ui/pom.xml | 2 +- netty-mqtt/pom.xml | 4 ++-- pom.xml | 2 +- rest-client/pom.xml | 2 +- rule-engine/pom.xml | 2 +- rule-engine/rule-engine-api/pom.xml | 2 +- rule-engine/rule-engine-components/pom.xml | 2 +- tools/pom.xml | 2 +- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/lwm2m/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- transport/pom.xml | 2 +- transport/snmp/pom.xml | 2 +- ui-ngx/package.json | 2 +- ui-ngx/pom.xml | 2 +- 59 files changed, 60 insertions(+), 60 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index bae63d437e..a565e4a29d 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT thingsboard application diff --git a/common/actor/pom.xml b/common/actor/pom.xml index c0e13c2cbe..b00cdb7bc7 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/cache/pom.xml b/common/cache/pom.xml index 32cab29341..ba0823bb00 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml index c550b9a11e..dc4a829504 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml index 036ab0908d..675f030305 100644 --- a/common/coap-server/pom.xml +++ b/common/coap-server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 1509dfbdee..dbc9251ba3 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/data/pom.xml b/common/data/pom.xml index 06bcf79617..63a4959253 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml index 2c5f26ef55..cf3134f09b 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index 90932261b4..e6b61916b0 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index d66c00ab6c..b12140012d 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT thingsboard common diff --git a/common/proto/pom.xml b/common/proto/pom.xml index aab7f95e1d..71327a9019 100644 --- a/common/proto/pom.xml +++ b/common/proto/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 3c6526573c..8d1bc0c800 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/script/pom.xml b/common/script/pom.xml index 05f2e78980..b307d328d2 100644 --- a/common/script/pom.xml +++ b/common/script/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml index 5e85aad623..b62d9f7f07 100644 --- a/common/script/remote-js-client/pom.xml +++ b/common/script/remote-js-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT script org.thingsboard.common.script diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml index f01581872b..8f2d39a5b7 100644 --- a/common/script/script-api/pom.xml +++ b/common/script/script-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT script org.thingsboard.common.script diff --git a/common/stats/pom.xml b/common/stats/pom.xml index 672731852e..5c9bad7459 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 8e9d5ba0db..4e2a9a5b5d 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index a2038a2299..963bde07a4 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index 0f00bbb2c9..e69e4d54ac 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index f8d1999a2a..7f3d80aae1 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index 8bb5073638..0c5c7a02c9 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index 04273222f2..8ad570fceb 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 490a057392..255b9ad8e4 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/util/pom.xml b/common/util/pom.xml index b2eb84af9e..1952e5a7ad 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml index c9c603120d..696739d948 100644 --- a/common/version-control/pom.xml +++ b/common/version-control/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index 40950b0671..ab62d8f013 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT thingsboard dao diff --git a/monitoring/pom.xml b/monitoring/pom.xml index c948961ab9..2dd8e54f83 100644 --- a/monitoring/pom.xml +++ b/monitoring/pom.xml @@ -21,7 +21,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT thingsboard diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 2d3c2ce18f..d13bb54557 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index c7edb2bbd7..f0869beffa 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-js-executor", "private": true, - "version": "3.6.3", + "version": "3.7.0", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 5eb3a34c49..4c465b377b 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/monitoring/pom.xml b/msa/monitoring/pom.xml index 8d99011751..60ff2e3f4a 100644 --- a/msa/monitoring/pom.xml +++ b/msa/monitoring/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT msa diff --git a/msa/pom.xml b/msa/pom.xml index 4602bc210d..71ddc91412 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 3103fd2676..708af73ae0 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index 52e4978303..ad0fa66cb9 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index caf06056ea..5c11fd7adf 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 7db412c8ff..b7cdae9c95 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml index 1005963af0..19ea2a022c 100644 --- a/msa/transport/lwm2m/pom.xml +++ b/msa/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index bbd7cc752b..f06777265e 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index f9cc2f08dc..13ee09c3a7 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index ef55db19c5..35b3809aae 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT org.thingsboard.msa.transport diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml index 7b9b34b8a7..e70656c49b 100644 --- a/msa/vc-executor-docker/pom.xml +++ b/msa/vc-executor-docker/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml index d0884e6302..34340ab08c 100644 --- a/msa/vc-executor/pom.xml +++ b/msa/vc-executor/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index f078f9af5f..6e7cdd4bc1 100644 --- a/msa/web-ui/package.json +++ b/msa/web-ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-web-ui", "private": true, - "version": "3.6.3", + "version": "3.7.0", "description": "ThingsBoard Web UI Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index f60c73b8d1..99981ca9f3 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index c745f6d2c1..6834bb1aca 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT thingsboard netty-mqtt - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index ee5cedb664..2a5054ecd7 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT pom Thingsboard diff --git a/rest-client/pom.xml b/rest-client/pom.xml index 1bdd531fe2..e4a959a7ec 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT thingsboard rest-client diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index a0cbca16ba..8902ef0cee 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 258dfa354b..82462132bd 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 1c01ca2b9c..895da628b9 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/tools/pom.xml b/tools/pom.xml index 9a2cfe21d2..38309f35b0 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 46fa896663..e894528e0b 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 8b37976941..373518134f 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index 97e653c0cc..2540b57383 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 94e7b5129c..47fa56d50e 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index ac9858e2da..49009b83b0 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT thingsboard transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index d7a0096a09..5f236adb9a 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT transport diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 4bbe817fcf..761c802fa8 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -1,6 +1,6 @@ { "name": "thingsboard", - "version": "3.6.3", + "version": "3.7.0", "scripts": { "ng": "ng", "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open", diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index a13d364052..afdf9400c4 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.6.3-SNAPSHOT + 3.7.0-SNAPSHOT thingsboard org.thingsboard From 09fc025e12b15352f18d11484c841897f2007410 Mon Sep 17 00:00:00 2001 From: Ivan Raznatovskyi Date: Tue, 16 Jan 2024 15:39:47 +0200 Subject: [PATCH 102/209] Initial commit from another fork --- .github/release.yml | 2 +- .../workflows/check-configuration-files.yml | 2 +- application/pom.xml | 4 +- application/src/main/conf/logback.xml | 2 +- application/src/main/conf/thingsboard.conf | 2 +- .../data/json/demo/dashboards/gateways.json | 1315 ---- .../docker/localhost_warning.md | 3 - .../install}/centos/instructions.md | 24 +- .../install}/docker/instructions.md | 35 +- .../install}/ubuntu/instructions.md | 4 +- .../upgrade/centos/instructions.md | 15 + .../upgrade/docker/instructions.md | 10 + .../upgrade/docker/start_service.md | 23 + .../instructions/upgrade/docker/upgrade_db.md | 61 + .../upgrade/docker/upgrade_preparing.md | 95 + .../instructions/upgrade/start_service.md | 6 + .../upgrade/ubuntu/instructions.md | 15 + .../edge/instructions/upgrade/upgrade_db.md | 8 + .../instructions/upgrade/upgrade_preparing.md | 36 + .../rule_chains/edge_root_rule_chain.json | 8 +- .../system/widget_bundles/air_quality.json | 44 +- .../json/system/widget_bundles/charts.json | 5 +- .../widget_bundles/indoor_environment.json | 3 + .../widget_bundles/industial_widgets.json | 84 + .../widget_bundles/outdoor_environment.json | 3 + .../widget_types/bar_chart_with_labels.json | 30 + .../system/widget_types/battery_level.json | 2 +- .../carbon_monoxide__co__card.json | 37 + ...on_monoxide__co__card_with_background.json | 37 + .../carbon_monoxide__co__chart_card.json | 39 + ...oxide__co__chart_card_with_background.json | 39 + .../json/system/widget_types/doughnut.json | 2 +- .../system/widget_types/efficiency_card.json | 29 + .../efficiency_card_with_background.json | 29 + .../widget_types/efficiency_chart_card.json | 31 + ...efficiency_chart_card_with_background.json | 31 + .../widget_types/efficiency_progress_bar.json | 29 + ...ficiency_progress_bar_with_background.json | 29 + .../widget_types/efficiency_range_chart.json | 32 + ...fficiency_range_chart_with_background.json | 32 + .../system/widget_types/flow_rate_card.json | 32 + .../flow_rate_card_with_background.json | 32 + .../widget_types/flow_rate_chart_card.json | 34 + .../flow_rate_chart_card_with_background.json | 34 + .../system/widget_types/flow_rate_gauge.json | 33 + .../widget_types/flow_rate_progress_bar.json | 32 + ...low_rate_progress_bar_with_background.json | 32 + .../widget_types/flow_rate_range_chart.json | 35 + ...flow_rate_range_chart_with_background.json | 35 + .../widget_types/fluid_pressure_card.json | 34 + .../fluid_pressure_card_with_background.json | 34 + .../fluid_pressure_chart_card.json | 36 + ...d_pressure_chart_card_with_background.json | 36 + .../widget_types/fluid_pressure_gauge.json | 35 + .../fluid_pressure_progress_bar.json | 34 + ...pressure_progress_bar_with_background.json | 34 + .../fluid_pressure_range_chart.json | 37 + ..._pressure_range_chart_with_background.json | 37 + .../horizontal_carbon_monoxide__co__card.json | 37 + ...on_monoxide__co__card_with_background.json | 37 + .../widget_types/horizontal_doughnut.json | 2 +- .../horizontal_efficiency_card.json | 29 + ...ontal_efficiency_card_with_background.json | 29 + .../horizontal_flow_rate_card.json | 32 + ...zontal_flow_rate_card_with_background.json | 32 + .../horizontal_fluid_pressure_card.json | 34 + ...l_fluid_pressure_card_with_background.json | 34 + ...l_individual_allergy_index__iai__card.json | 32 + ...ergy_index__iai__card_with_background.json | 32 + ...orizontal_nitrogen_dioxide__no2__card.json | 36 + ...en_dioxide__no2__card_with_background.json | 37 + .../horizontal_ozone__o3__card.json | 37 + ...ontal_ozone__o3__card_with_background.json | 37 + .../horizontal_power_consumption_card.json | 32 + ...ower_consumption_card_with_background.json | 32 + .../horizontal_pump_vibration_card.json | 33 + ...l_pump_vibration_card_with_background.json | 33 + .../horizontal_rotational_speed_card.json | 30 + ...rotational_speed_card_with_background.json | 30 + .../horizontal_sulfur_dioxide__so2__card.json | 38 + ...ur_dioxide__so2__card_with_background.json | 38 + .../json/system/widget_types/image_map.json | 2 +- .../individual_allergy_index__iai__card.json | 24 + ...ergy_index__iai__card_with_background.json | 24 + ...vidual_allergy_index__iai__chart_card.json | 34 + ...ndex__iai__chart_card_with_background.json | 34 + .../indoor_temperature_gauge.json | 29 + .../indoor_temperature_range_chart.json | 31 + ...mperature_range_chart_with_background.json | 31 + .../system/widget_types/label_widget.json | 2 +- .../markers_placement___image_map.json | 2 +- .../nitrogen_dioxide__no2__card.json | 37 + ...en_dioxide__no2__card_with_background.json | 37 + .../nitrogen_dioxide__no2__chart_card.json | 39 + ...xide__no2__chart_card_with_background.json | 39 + .../system/widget_types/ozone__o3__card.json | 37 + .../ozone__o3__card_with_background.json | 37 + .../widget_types/ozone__o3__chart_card.json | 39 + ...ozone__o3__chart_card_with_background.json | 39 + .../widget_types/power_consumption_card.json | 32 + ...ower_consumption_card_with_background.json | 32 + .../power_consumption_chart_card.json | 34 + ...onsumption_chart_card_with_background.json | 34 + .../power_consumption_range_chart.json | 35 + ...nsumption_range_chart_with_background.json | 35 + .../widget_types/pump_vibration_card.json | 33 + .../pump_vibration_card_with_background.json | 33 + .../pump_vibration_chart_card.json | 35 + ..._vibration_chart_card_with_background.json | 35 + .../pump_vibration_range_chart.json | 36 + ...vibration_range_chart_with_background.json | 36 + .../json/system/widget_types/range_chart.json | 31 + .../widget_types/rotational_speed_card.json | 30 + ...rotational_speed_card_with_background.json | 30 + .../rotational_speed_chart_card.json | 32 + ...onal_speed_chart_card_with_background.json | 32 + .../widget_types/rotational_speed_gauge.json | 31 + .../rotational_speed_progress_bar.json | 30 + ...al_speed_progress_bar_with_background.json | 30 + .../rotational_speed_range_chart.json | 33 + ...nal_speed_range_chart_with_background.json | 33 + ...imple_carbon_monoxide__co__chart_card.json | 40 + ...oxide__co__chart_card_with_background.json | 40 + .../simple_efficiency_chart_card.json | 32 + ...efficiency_chart_card_with_background.json | 32 + .../simple_flow_rate_chart_card.json | 35 + ..._flow_rate_chart_card_with_background.json | 35 + .../simple_fluid_pressure_chart_card.json | 37 + ...d_pressure_chart_card_with_background.json | 37 + ...vidual_allergy_index__iai__chart_card.json | 35 + ...ndex__iai__chart_card_with_background.json | 35 + ...ple_nitrogen_dioxide__no2__chart_card.json | 40 + ...xide__no2__chart_card_with_background.json | 40 + .../simple_ozone__o3__chart_card.json | 40 + ...ozone__o3__chart_card_with_background.json | 40 + .../simple_power_consumption_chart_card.json | 35 + ...onsumption_chart_card_with_background.json | 35 + .../simple_pump_vibration_chart_card.json | 36 + ..._vibration_chart_card_with_background.json | 36 + .../simple_rotational_speed_chart_card.json | 33 + ...onal_speed_chart_card_with_background.json | 33 + ...imple_sulfur_dioxide__so2__chart_card.json | 40 + ...xide__so2__chart_card_with_background.json | 40 + .../json/system/widget_types/speed_gauge.json | 3 +- .../sulfur_dioxide__so2__card.json | 37 + ...ur_dioxide__so2__card_with_background.json | 37 + .../sulfur_dioxide__so2__chart_card.json | 39 + ...xide__so2__chart_card_with_background.json | 39 + .../widget_types/temperature_gauge.json | 29 + .../temperature_radial_gauge.json | 3 +- .../widget_types/temperature_range_chart.json | 31 + ...mperature_range_chart_with_background.json | 31 + .../widget_types/thermometer_scale.json | 2 +- .../update_location_timeseries.json | 2 +- .../update_server_location_attribute.json | 2 +- .../update_shared_location_attribute.json | 2 +- .../dashboards/gateways.json} | 175 +- .../device_profile/rule_chain_template.json | 8 +- .../tenant/rule_chains/root_rule_chain.json | 8 +- .../main/data/upgrade/1.3.0/schema_update.cql | 2 +- .../main/data/upgrade/1.3.1/schema_update.sql | 2 +- .../main/data/upgrade/1.4.0/schema_update.cql | 2 +- .../main/data/upgrade/1.4.0/schema_update.sql | 2 +- .../main/data/upgrade/2.0.0/schema_update.cql | 2 +- .../main/data/upgrade/2.0.0/schema_update.sql | 2 +- .../main/data/upgrade/2.1.1/schema_update.cql | 2 +- .../main/data/upgrade/2.1.1/schema_update.sql | 2 +- .../main/data/upgrade/2.1.2/schema_update.cql | 2 +- .../main/data/upgrade/2.1.2/schema_update.sql | 2 +- .../main/data/upgrade/2.2.0/schema_update.sql | 2 +- .../main/data/upgrade/2.3.1/schema_update.sql | 2 +- .../main/data/upgrade/2.4.0/schema_update.sql | 2 +- .../main/data/upgrade/2.4.2/schema_update.sql | 2 +- .../schema_update_psql_drop_partitions.sql | 2 +- .../upgrade/2.4.3/schema_update_psql_ts.sql | 2 +- .../2.4.3/schema_update_timescale_ts.sql | 2 +- .../data/upgrade/2.4.3/schema_update_ttl.sql | 2 +- .../data/upgrade/3.0.1/schema_ts_latest.sql | 2 +- .../upgrade/3.0.1/schema_update_to_uuid.sql | 2 +- .../main/data/upgrade/3.1.0/schema_update.sql | 2 +- .../upgrade/3.1.1/schema_update_after.sql | 2 +- .../upgrade/3.1.1/schema_update_before.sql | 2 +- .../main/data/upgrade/3.2.1/schema_update.sql | 2 +- .../data/upgrade/3.2.1/schema_update_ttl.sql | 2 +- .../main/data/upgrade/3.2.2/schema_update.sql | 2 +- .../upgrade/3.2.2/schema_update_event.sql | 2 +- .../data/upgrade/3.2.2/schema_update_ttl.sql | 2 +- .../main/data/upgrade/3.3.2/schema_update.sql | 2 +- .../3.3.2/schema_update_lwm2m_bootstrap.sql | 2 +- .../3.3.3/schema_event_ttl_procedure.sql | 2 +- .../main/data/upgrade/3.3.3/schema_update.sql | 2 +- .../main/data/upgrade/3.3.4/schema_update.sql | 2 +- .../main/data/upgrade/3.4.0/schema_update.sql | 2 +- .../main/data/upgrade/3.4.1/schema_update.sql | 2 +- .../upgrade/3.4.1/schema_update_after.sql | 2 +- .../upgrade/3.4.1/schema_update_before.sql | 2 +- .../main/data/upgrade/3.4.4/schema_update.sql | 2 +- .../main/data/upgrade/3.5.0/schema_update.sql | 2 +- .../main/data/upgrade/3.5.1/schema_update.sql | 2 +- .../main/data/upgrade/3.6.0/schema_update.sql | 2 +- .../3.6.1/save_attributes_node_update.sql | 26 + .../main/data/upgrade/3.6.1/schema_update.sql | 59 + .../main/data/upgrade/3.6.2/schema_update.sql | 30 + .../server/ThingsboardInstallApplication.java | 2 +- .../server/ThingsboardServerApplication.java | 2 +- .../server/actors/ActorSystemContext.java | 14 +- .../actors/TbEntityTypeActorIdPredicate.java | 2 +- .../server/actors/app/AppActor.java | 2 +- .../server/actors/app/AppInitMsg.java | 2 +- .../server/actors/device/DeviceActor.java | 5 +- .../actors/device/DeviceActorCreator.java | 2 +- .../device/DeviceActorMessageProcessor.java | 18 +- .../server/actors/device/SessionInfo.java | 2 +- .../actors/device/SessionInfoMetaData.java | 2 +- .../actors/device/SessionTimeoutCheckMsg.java | 2 +- .../device/ToDeviceRpcRequestMetadata.java | 2 +- .../device/ToServerRpcRequestMetadata.java | 2 +- .../actors/ruleChain/DefaultTbContext.java | 35 +- .../actors/ruleChain/RuleChainActor.java | 2 +- .../RuleChainActorMessageProcessor.java | 2 +- .../actors/ruleChain/RuleChainInputMsg.java | 2 +- .../ruleChain/RuleChainManagerActor.java | 2 +- .../actors/ruleChain/RuleChainOutputMsg.java | 2 +- .../ruleChain/RuleChainToRuleChainMsg.java | 2 +- .../ruleChain/RuleChainToRuleNodeMsg.java | 2 +- .../ruleChain/RuleEngineComponentActor.java | 2 +- .../actors/ruleChain/RuleNodeActor.java | 2 +- .../RuleNodeActorMessageProcessor.java | 2 +- .../server/actors/ruleChain/RuleNodeCtx.java | 2 +- .../actors/ruleChain/RuleNodeRelation.java | 2 +- .../RuleNodeToRuleChainTellNextMsg.java | 2 +- .../actors/ruleChain/RuleNodeToSelfMsg.java | 2 +- .../ruleChain/TbToRuleChainActorMsg.java | 2 +- .../ruleChain/TbToRuleNodeActorMsg.java | 2 +- .../server/actors/service/ActorService.java | 2 +- .../server/actors/service/ComponentActor.java | 2 +- .../actors/service/ContextAwareActor.java | 2 +- .../actors/service/ContextBasedCreator.java | 2 +- .../actors/service/DefaultActorService.java | 2 +- .../AbstractContextAwareMsgProcessor.java | 2 +- .../actors/shared/ActorTerminationMsg.java | 2 +- .../actors/shared/ComponentMsgProcessor.java | 2 +- .../actors/shared/RuleChainErrorActor.java | 2 +- .../server/actors/stats/StatsActor.java | 2 +- .../server/actors/stats/StatsPersistMsg.java | 2 +- .../server/actors/stats/StatsPersistTick.java | 2 +- .../actors/tenant/DebugTbRateLimits.java | 2 +- .../server/actors/tenant/TenantActor.java | 23 +- .../server/config/CryptoConfig.java | 2 +- ...tomOAuth2AuthorizationRequestResolver.java | 4 +- .../server/config/MvcCorsProperties.java | 2 +- .../config/RateLimitProcessingFilter.java | 2 +- .../config/SchedulingConfiguration.java | 2 +- .../server/config/SwaggerConfiguration.java | 4 +- .../TbRuleEngineSecurityConfiguration.java | 44 + .../ThingsboardMessageConfiguration.java | 2 +- .../ThingsboardSecurityConfiguration.java | 28 +- .../thingsboard/server/config/WebConfig.java | 2 +- .../server/config/WebSocketConfiguration.java | 53 +- .../controller/AbstractRpcController.java | 2 +- .../server/controller/AdminController.java | 6 +- .../controller/AlarmCommentController.java | 2 +- .../server/controller/AlarmController.java | 2 +- .../server/controller/AssetController.java | 6 +- .../controller/AssetProfileController.java | 36 +- .../server/controller/AuditLogController.java | 2 +- .../server/controller/AuthController.java | 10 +- .../controller/AutoCommitController.java | 2 +- .../server/controller/BaseController.java | 27 +- .../ComponentDescriptorController.java | 2 +- .../controller/ControllerConstants.java | 7 +- .../server/controller/CustomerController.java | 2 +- .../controller/DashboardController.java | 16 +- .../DeviceConnectivityController.java | 57 +- .../server/controller/DeviceController.java | 21 +- .../controller/DeviceProfileController.java | 34 +- .../server/controller/EdgeController.java | 58 +- .../controller/EdgeEventController.java | 2 +- .../EntitiesVersionControlController.java | 10 +- .../controller/EntityQueryController.java | 2 +- .../controller/EntityRelationController.java | 2 +- .../controller/EntityViewController.java | 2 +- .../server/controller/EventController.java | 2 +- .../controller/HttpValidationCallback.java | 2 +- .../server/controller/ImageController.java | 346 + .../server/controller/Lwm2mController.java | 2 +- .../MailConfigTemplateController.java | 2 +- .../controller/NotificationController.java | 2 +- .../NotificationRuleController.java | 2 +- .../NotificationTargetController.java | 2 +- .../NotificationTemplateController.java | 2 +- .../OAuth2ConfigTemplateController.java | 2 +- .../server/controller/OAuth2Controller.java | 2 +- .../controller/OtaPackageController.java | 2 +- .../server/controller/QueueController.java | 2 +- .../server/controller/RpcV1Controller.java | 2 +- .../server/controller/RpcV2Controller.java | 2 +- .../controller/RuleChainController.java | 2 +- .../controller/SystemInfoController.java | 7 +- .../controller/TbResourceController.java | 130 +- .../server/controller/TbUrlConstants.java | 2 +- .../controller/TelemetryController.java | 25 +- .../server/controller/TenantController.java | 2 +- .../controller/TenantProfileController.java | 3 +- .../TwoFactorAuthConfigController.java | 2 +- .../controller/TwoFactorAuthController.java | 2 +- .../controller/UiSettingsController.java | 2 +- .../controller/UsageInfoController.java | 2 +- .../server/controller/UserController.java | 2 +- .../controller/WidgetTypeController.java | 40 +- .../controller/WidgetsBundleController.java | 20 +- .../controller/plugin/TbWebSocketHandler.java | 322 +- .../controller/plugin/TbWebSocketMsg.java | 2 +- .../controller/plugin/TbWebSocketMsgType.java | 2 +- .../controller/plugin/TbWebSocketPingMsg.java | 2 +- .../controller/plugin/TbWebSocketTextMsg.java | 2 +- .../exception/AccessDeniedException.java | 2 +- .../exception/EntityNotFoundException.java | 2 +- .../exception/InternalErrorException.java | 2 +- .../exception/InvalidParametersException.java | 2 +- ...ThingsboardCredentialsExpiredResponse.java | 2 +- ...ingsboardCredentialsViolationResponse.java | 33 + .../exception/ThingsboardErrorResponse.java | 2 +- .../ThingsboardErrorResponseHandler.java | 6 +- .../exception/ToErrorResponseEntity.java | 2 +- .../exception/UnauthorizedException.java | 2 +- .../exception/UncheckedApiException.java | 2 +- .../ThingsboardInstallConfiguration.java | 2 +- .../install/ThingsboardInstallException.java | 2 +- .../install/ThingsboardInstallService.java | 20 +- .../service/action/EntityActionService.java | 2 +- .../service/apiusage/BaseApiUsageState.java | 2 +- .../apiusage/CustomerApiUsageState.java | 2 +- .../DefaultTbApiUsageStateService.java | 5 +- .../apiusage/TbApiUsageStateService.java | 2 +- .../service/apiusage/TenantApiUsageState.java | 2 +- .../service/asset/AssetBulkImportService.java | 2 +- .../AnnotationComponentDiscoveryService.java | 3 +- .../component/ComponentDiscoveryService.java | 2 +- .../service/component/RuleNodeClassInfo.java | 2 +- .../device/ClaimDevicesServiceImpl.java | 2 +- .../device/DeviceBulkImportService.java | 2 +- .../device/DeviceProvisionServiceImpl.java | 2 +- .../edge/DefaultEdgeNotificationService.java | 36 +- .../service/edge/EdgeBulkImportService.java | 2 +- .../service/edge/EdgeContextComponent.java | 44 +- .../edge/EdgeEventSourcingListener.java | 49 +- .../service/edge/EdgeNotificationService.java | 2 +- ...efaultEdgeInstallInstructionsService.java} | 40 +- ...DefaultEdgeUpgradeInstructionsService.java | 193 + ...va => EdgeInstallInstructionsService.java} | 11 +- .../EdgeUpgradeInstructionsService.java | 34 + .../edge/rpc/EdgeEventStorageSettings.java | 2 +- .../service/edge/rpc/EdgeGrpcService.java | 2 +- .../service/edge/rpc/EdgeGrpcSession.java | 90 +- .../service/edge/rpc/EdgeRpcService.java | 2 +- .../service/edge/rpc/EdgeSessionState.java | 2 +- .../service/edge/rpc/EdgeSyncCursor.java | 4 +- .../rpc/constructor/AssetMsgConstructor.java | 60 - .../BaseMsgConstructorFactory.java | 46 + .../DeviceProfileMsgConstructor.java | 90 - .../rpc/constructor/MsgConstructor.java} | 8 +- .../TenantProfileMsgConstructor.java | 52 - .../WidgetsBundleMsgConstructor.java | 65 - .../alarm/AlarmMsgConstructor.java | 30 + .../alarm/AlarmMsgConstructorFactory.java | 26 + .../AlarmMsgConstructorV1.java} | 13 +- .../alarm/AlarmMsgConstructorV2.java | 36 + .../alarm/BaseAlarmMsgConstructor.java | 29 + .../asset/AssetMsgConstructor.java | 36 + .../asset/AssetMsgConstructorFactory.java | 26 + .../AssetMsgConstructorV1.java} | 50 +- .../asset/AssetMsgConstructorV2.java | 44 + .../asset/BaseAssetMsgConstructor.java | 41 + .../customer/BaseCustomerMsgConstructor.java | 31 + .../customer/CustomerMsgConstructor.java | 29 + .../CustomerMsgConstructorFactory.java | 26 + .../CustomerMsgConstructorV1.java} | 15 +- .../customer/CustomerMsgConstructorV2.java | 35 + .../BaseDashboardMsgConstructor.java | 31 + .../dashboard/DashboardMsgConstructor.java | 29 + .../DashboardMsgConstructorFactory.java | 26 + .../DashboardMsgConstructorV1.java} | 22 +- .../dashboard/DashboardMsgConstructorV2.java | 35 + .../device/BaseDeviceMsgConstructor.java | 102 + .../device/DeviceMsgConstructor.java | 46 + .../device/DeviceMsgConstructorFactory.java | 26 + .../DeviceMsgConstructorV1.java} | 107 +- .../device/DeviceMsgConstructorV2.java | 51 + .../{ => edge}/EdgeMsgConstructor.java | 4 +- .../BaseEntityViewMsgConstructor.java | 31 + .../entityview/EntityViewMsgConstructor.java | 29 + .../EntityViewMsgConstructorFactory.java | 26 + .../EntityViewMsgConstructorV1.java} | 37 +- .../EntityViewMsgConstructorV2.java | 35 + .../ota/BaseOtaPackageMsgConstructor.java | 31 + .../ota/OtaPackageMsgConstructor.java | 29 + .../ota/OtaPackageMsgConstructorFactory.java | 26 + .../OtaPackageMsgConstructorV1.java} | 16 +- .../ota/OtaPackageMsgConstructorV2.java | 35 + .../queue/BaseQueueMsgConstructor.java | 31 + .../queue/QueueMsgConstructor.java | 29 + .../queue/QueueMsgConstructorFactory.java | 26 + .../QueueMsgConstructorV1.java} | 13 +- .../queue/QueueMsgConstructorV2.java | 35 + .../relation/RelationMsgConstructor.java | 26 + .../RelationMsgConstructorFactory.java | 26 + .../RelationMsgConstructorV1.java} | 7 +- .../relation/RelationMsgConstructorV2.java | 34 + .../resource/BaseResourceMsgConstructor.java | 31 + .../resource/ResourceMsgConstructor.java | 29 + .../ResourceMsgConstructorFactory.java | 26 + .../ResourceMsgConstructorV1.java} | 25 +- .../resource/ResourceMsgConstructorV2.java | 35 + ... => BaseRuleChainMetadataConstructor.java} | 23 +- .../BaseRuleChainMsgConstructor.java} | 49 +- .../rule/RuleChainMetadataConstructor.java | 6 +- .../RuleChainMetadataConstructorFactory.java | 8 +- .../RuleChainMetadataConstructorV330.java | 6 +- .../RuleChainMetadataConstructorV340.java | 6 +- .../RuleChainMetadataConstructorV362.java | 29 + .../rule/RuleChainMsgConstructor.java | 38 + .../rule/RuleChainMsgConstructorFactory.java | 26 + .../rule/RuleChainMsgConstructorV1.java | 45 + .../rule/RuleChainMsgConstructorV2.java | 39 + .../settings/AdminSettingsMsgConstructor.java | 25 + .../AdminSettingsMsgConstructorFactory.java | 26 + .../AdminSettingsMsgConstructorV1.java} | 11 +- .../AdminSettingsMsgConstructorV2.java | 32 + .../EntityDataMsgConstructor.java | 9 +- .../tenant/TenantMsgConstructor.java | 31 + .../tenant/TenantMsgConstructorFactory.java | 26 + .../TenantMsgConstructorV1.java} | 35 +- .../tenant/TenantMsgConstructorV2.java | 41 + .../user/BaseUserMsgConstructor.java | 31 + .../constructor/user/UserMsgConstructor.java | 33 + .../user/UserMsgConstructorFactory.java | 26 + .../UserMsgConstructorV1.java} | 21 +- .../user/UserMsgConstructorV2.java | 42 + .../widget/BaseWidgetMsgConstructor.java | 43 + .../widget/WidgetMsgConstructor.java | 39 + .../widget/WidgetMsgConstructorFactory.java | 26 + .../WidgetMsgConstructorV1.java} | 52 +- .../widget/WidgetMsgConstructorV2.java | 48 + .../fetch/AdminSettingsEdgeEventFetcher.java | 2 +- .../fetch/AssetProfilesEdgeEventFetcher.java | 2 +- .../rpc/fetch/AssetsEdgeEventFetcher.java | 2 +- .../fetch/BasePageableEdgeEventFetcher.java | 2 +- .../fetch/BaseResourceEdgeEventFetcher.java | 49 - .../rpc/fetch/BaseUsersEdgeEventFetcher.java | 2 +- .../BaseWidgetTypesEdgeEventFetcher.java | 2 +- .../BaseWidgetsBundlesEdgeEventFetcher.java | 2 +- .../rpc/fetch/CustomerEdgeEventFetcher.java | 2 +- .../fetch/CustomerUsersEdgeEventFetcher.java | 2 +- .../rpc/fetch/DashboardsEdgeEventFetcher.java | 2 +- .../DefaultProfilesEdgeEventFetcher.java | 2 +- .../fetch/DeviceProfilesEdgeEventFetcher.java | 2 +- .../rpc/fetch/DevicesEdgeEventFetcher.java | 2 +- .../edge/rpc/fetch/EdgeEventFetcher.java | 2 +- .../fetch/EntityViewsEdgeEventFetcher.java | 2 +- .../rpc/fetch/GeneralEdgeEventFetcher.java | 2 +- .../fetch/OtaPackagesEdgeEventFetcher.java | 2 +- .../rpc/fetch/QueuesEdgeEventFetcher.java | 2 +- .../rpc/fetch/RuleChainsEdgeEventFetcher.java | 2 +- .../SystemResourcesEdgeEventFetcher.java | 34 - .../SystemWidgetTypesEdgeEventFetcher.java | 2 +- .../SystemWidgetsBundlesEdgeEventFetcher.java | 2 +- .../TenantAdminUsersEdgeEventFetcher.java | 2 +- .../rpc/fetch/TenantEdgeEventFetcher.java | 2 +- .../TenantResourcesEdgeEventFetcher.java | 23 +- .../TenantWidgetTypesEdgeEventFetcher.java | 2 +- .../TenantWidgetsBundlesEdgeEventFetcher.java | 2 +- .../edge/rpc/processor/BaseEdgeProcessor.java | 131 +- .../processor/BaseEdgeProcessorFactory.java | 46 + .../edge/rpc/processor/EdgeProcessor.java | 19 + .../processor/alarm/AlarmEdgeProcessor.java | 143 +- .../alarm/AlarmEdgeProcessorFactory.java | 26 + .../processor/alarm/AlarmEdgeProcessorV1.java | 72 + .../processor/alarm/AlarmEdgeProcessorV2.java | 43 + .../rpc/processor/alarm/AlarmProcessor.java | 37 + .../processor/alarm/BaseAlarmProcessor.java | 118 +- .../processor/asset/AssetEdgeProcessor.java | 26 +- .../asset/AssetEdgeProcessorFactory.java | 26 + .../processor/asset/AssetEdgeProcessorV1.java | 59 + .../processor/asset/AssetEdgeProcessorV2.java | 43 + .../rpc/processor/asset/AssetProcessor.java | 33 + .../processor/asset/BaseAssetProcessor.java | 39 +- .../AssetProfileEdgeProcessor.java | 40 +- .../AssetProfileEdgeProcessorFactory.java | 26 + .../profile/AssetProfileEdgeProcessorV1.java | 65 + .../profile/AssetProfileEdgeProcessorV2.java | 57 + .../asset/profile/AssetProfileProcessor.java | 33 + .../BaseAssetProfileProcessor.java | 48 +- .../customer/CustomerEdgeProcessor.java | 14 +- .../dashboard/BaseDashboardProcessor.java | 59 +- .../dashboard/DashboardEdgeProcessor.java | 27 +- .../DashboardEdgeProcessorFactory.java | 26 + .../dashboard/DashboardEdgeProcessorV1.java | 54 + .../dashboard/DashboardEdgeProcessorV2.java | 36 + .../dashboard/DashboardProcessor.java | 32 + .../processor/device/BaseDeviceProcessor.java | 82 +- .../device/BaseDeviceProfileProcessor.java | 115 - .../processor/device/DeviceEdgeProcessor.java | 50 +- .../device/DeviceEdgeProcessorFactory.java | 26 + .../device/DeviceEdgeProcessorV1.java | 87 + .../device/DeviceEdgeProcessorV2.java | 50 + .../rpc/processor/device/DeviceProcessor.java | 40 + .../profile/BaseDeviceProfileProcessor.java | 86 + .../DeviceProfileEdgeProcessor.java | 40 +- .../DeviceProfileEdgeProcessorFactory.java | 26 + .../profile/DeviceProfileEdgeProcessorV1.java | 98 + .../profile/DeviceProfileEdgeProcessorV2.java | 57 + .../profile/DeviceProfileProcessor.java | 33 + .../rpc/processor/edge/EdgeProcessor.java | 4 +- .../entityview/BaseEntityViewProcessor.java | 43 +- .../entityview/EntityViewEdgeProcessor.java | 25 +- .../entityview/EntityViewProcessor.java | 32 + .../EntityViewProcessorFactory.java | 26 + .../entityview/EntityViewProcessorV1.java | 65 + .../entityview/EntityViewProcessorV2.java | 43 + .../ota/OtaPackageEdgeProcessor.java | 14 +- .../processor/queue/QueueEdgeProcessor.java | 17 +- .../relation/BaseRelationProcessor.java | 33 +- .../relation/RelationEdgeProcessor.java | 18 +- .../RelationEdgeProcessorFactory.java | 26 + .../relation/RelationEdgeProcessorV1.java | 52 + .../relation/RelationEdgeProcessorV2.java | 34 + .../processor/relation/RelationProcessor.java | 32 + .../resource/BaseResourceProcessor.java | 34 +- .../resource/ResourceEdgeProcessor.java | 28 +- .../ResourceEdgeProcessorFactory.java | 26 + .../resource/ResourceEdgeProcessorV1.java | 48 + .../resource/ResourceEdgeProcessorV2.java | 36 + .../processor/resource/ResourceProcessor.java | 32 + .../rule/RuleChainEdgeProcessor.java | 20 +- .../settings/AdminSettingsEdgeProcessor.java | 12 +- .../telemetry/BaseTelemetryProcessor.java | 10 +- .../telemetry/TelemetryEdgeProcessor.java | 7 +- .../processor/tenant/TenantEdgeProcessor.java | 13 +- .../tenant/TenantProfileEdgeProcessor.java | 10 +- .../rpc/processor/user/UserEdgeProcessor.java | 15 +- .../widget/WidgetBundleEdgeProcessor.java | 10 +- .../widget/WidgetTypeEdgeProcessor.java | 10 +- .../rpc/sync/DefaultEdgeRequestsService.java | 4 +- .../edge/rpc/sync/EdgeRequestsService.java | 2 +- .../edge/rpc/utils/EdgeVersionUtils.java | 2 +- .../entitiy/AbstractTbEntityService.java | 2 +- .../DefaultTbNotificationEntityService.java | 43 +- .../entitiy/SimpleTbEntityService.java | 2 +- .../entitiy/TbNotificationEntityService.java | 2 +- .../alarm/DefaultTbAlarmCommentService.java | 2 +- .../entitiy/alarm/DefaultTbAlarmService.java | 2 +- .../entitiy/alarm/TbAlarmCommentService.java | 3 +- .../service/entitiy/alarm/TbAlarmService.java | 2 +- .../entitiy/asset/DefaultTbAssetService.java | 2 +- .../service/entitiy/asset/TbAssetService.java | 2 +- .../profile/DefaultTbAssetProfileService.java | 2 +- .../asset/profile/TbAssetProfileService.java | 2 +- .../customer/DefaultTbCustomerService.java | 2 +- .../entitiy/customer/TbCustomerService.java | 2 +- .../dashboard/DefaultTbDashboardService.java | 3 +- .../entitiy/dashboard/TbDashboardService.java | 2 +- .../device/DefaultTbDeviceService.java | 2 +- .../entitiy/device/TbDeviceService.java | 2 +- .../DefaultTbDeviceProfileService.java | 2 +- .../profile/TbDeviceProfileService.java | 2 +- .../entitiy/edge/DefaultTbEdgeService.java | 2 +- .../service/entitiy/edge/TbEdgeService.java | 2 +- .../DefaultTbEntityRelationService.java | 2 +- .../relation/TbEntityRelationService.java | 2 +- .../DefaultTbEntityViewService.java | 2 +- .../entityview/TbEntityViewService.java | 2 +- .../ota/DefaultTbOtaPackageService.java | 2 +- .../entitiy/ota/TbOtaPackageService.java | 2 +- .../entitiy/queue/DefaultTbQueueService.java | 2 +- .../service/entitiy/queue/TbQueueService.java | 2 +- .../tenant/DefaultTbTenantService.java | 3 +- .../entitiy/tenant/TbTenantService.java | 2 +- .../DefaultTbTenantProfileService.java | 2 +- .../profile/TbTenantProfileService.java | 2 +- .../user/DefaultTbUserSettingsService.java | 2 +- .../entitiy/user/DefaultUserService.java | 2 +- .../service/entitiy/user/TbUserService.java | 2 +- .../entitiy/user/TbUserSettingsService.java | 2 +- .../bundle/DefaultWidgetsBundleService.java | 2 +- .../bundle/TbWidgetsBundleService.java | 2 +- .../type/DefaultWidgetTypeService.java | 5 +- .../widgets/type/TbWidgetTypeService.java | 2 +- .../executors/DbCallbackExecutorService.java | 2 +- .../ExternalCallExecutorService.java | 2 +- .../GrpcCallbackExecutorService.java | 2 +- .../NotificationExecutorService.java | 2 +- .../PubSubRuleNodeExecutorProvider.java | 63 + .../SharedEventLoopGroupService.java | 2 +- .../executors/VersionControlExecutor.java | 32 + .../DefaultGatewayNotificationsService.java | 2 +- .../GatewayNotificationsService.java | 2 +- .../InMemoryHouseKeeperServiceService.java | 2 +- ...stractCassandraDatabaseUpgradeService.java | 2 +- .../AbstractSqlTsDatabaseUpgradeService.java | 2 +- ...assandraAbstractDatabaseSchemaService.java | 2 +- .../install/CassandraKeyspaceService.java | 2 +- .../CassandraTsDatabaseSchemaService.java | 2 +- .../CassandraTsDatabaseUpgradeService.java | 2 +- ...assandraTsLatestDatabaseSchemaService.java | 2 +- .../DatabaseEntitiesUpgradeService.java | 2 +- .../service/install/DatabaseHelper.java | 2 +- .../install/DatabaseSchemaService.java | 2 +- .../install/DatabaseTsUpgradeService.java | 2 +- .../install/DbUpgradeExecutorService.java | 2 +- .../DefaultSystemDataLoaderService.java | 51 +- .../install/EntityDatabaseSchemaService.java | 2 +- .../service/install/InstallScripts.java | 75 +- .../service/install/NoSqlKeyspaceService.java | 2 +- .../SqlAbstractDatabaseSchemaService.java | 2 +- .../install/SqlDatabaseUpgradeService.java | 134 +- .../SqlEntityDatabaseSchemaService.java | 2 +- .../install/SqlTsDatabaseSchemaService.java | 2 +- .../install/SqlTsDatabaseUpgradeService.java | 2 +- .../install/SystemDataLoaderService.java | 2 +- .../TbRuleEngineQueueConfigService.java | 2 +- .../TimescaleTsDatabaseSchemaService.java | 2 +- .../TimescaleTsDatabaseUpgradeService.java | 2 +- .../install/TsDatabaseSchemaService.java | 2 +- .../TsLatestDatabaseSchemaService.java | 2 +- .../install/cql/CQLStatementsParser.java | 2 +- .../install/cql/CassandraDbHelper.java | 2 +- .../CassandraEntitiesToSqlMigrateService.java | 2 +- .../install/migrate/CassandraToSqlColumn.java | 2 +- .../migrate/CassandraToSqlColumnData.java | 2 +- .../migrate/CassandraToSqlColumnType.java | 2 +- .../migrate/CassandraToSqlEventTsColumn.java | 2 +- .../install/migrate/CassandraToSqlTable.java | 2 +- .../CassandraTsLatestToSqlMigrateService.java | 2 +- .../migrate/EntitiesMigrateService.java | 2 +- .../migrate/TsLatestMigrateService.java | 2 +- .../service/install/sql/SqlDbHelper.java | 2 +- .../install/update/CacheCleanupService.java | 2 +- .../install/update/DataUpdateService.java | 2 +- .../update/DefaultCacheCleanupService.java | 10 +- .../update/DefaultDataUpdateService.java | 2 +- .../service/install/update/ImagesUpdater.java | 170 + .../install/update/PaginatedUpdater.java | 2 +- .../install/update/RateLimitsUpdater.java | 2 +- .../server/service/lwm2m/LwM2MService.java | 2 +- .../service/lwm2m/LwM2MServiceImpl.java | 2 +- .../service/mail/DefaultMailService.java | 2 +- .../DefaultTbMailConfigTemplateService.java | 2 +- .../service/mail/MailExecutorService.java | 2 +- .../mail/PasswordResetExecutorService.java | 2 +- .../mail/RefreshTokenExpCheckService.java | 2 +- .../mail/TbMailConfigTemplateService.java | 2 +- .../service/mail/TbMailContextComponent.java | 2 +- .../server/service/mail/TbMailSender.java | 2 +- .../DefaultNotificationCenter.java | 38 +- .../DefaultNotificationSchedulerService.java | 18 +- .../NotificationProcessingContext.java | 2 +- .../NotificationSchedulerService.java | 2 +- .../channels/EmailNotificationChannel.java | 2 +- .../MicrosoftTeamsNotificationChannel.java | 2 +- .../channels/NotificationChannel.java | 2 +- .../channels/SlackNotificationChannel.java | 2 +- .../channels/SmsNotificationChannel.java | 2 +- .../DefaultNotificationRuleProcessor.java | 2 +- .../cache/DefaultNotificationRulesCache.java | 2 +- .../rule/cache/NotificationRulesCache.java | 2 +- .../AlarmAssignmentTriggerProcessor.java | 2 +- .../trigger/AlarmCommentTriggerProcessor.java | 2 +- .../rule/trigger/AlarmTriggerProcessor.java | 2 +- .../ApiUsageLimitTriggerProcessor.java | 2 +- .../DeviceActivityTriggerProcessor.java | 2 +- .../EntitiesLimitTriggerProcessor.java | 2 +- .../trigger/EntityActionTriggerProcessor.java | 2 +- .../NewPlatformVersionTriggerProcessor.java | 2 +- .../NotificationRuleTriggerProcessor.java | 2 +- .../trigger/RateLimitsTriggerProcessor.java | 2 +- ...mponentLifecycleEventTriggerProcessor.java | 2 +- .../ota/DefaultOtaPackageStateService.java | 2 +- .../service/ota/OtaPackageStateService.java | 2 +- .../AbstractPartitionBasedService.java | 2 +- .../partition/TbCoreStartupService.java | 2 +- .../profile/DefaultTbAssetProfileCache.java | 2 +- .../profile/DefaultTbDeviceProfileCache.java | 2 +- .../service/profile/TbAssetProfileCache.java | 2 +- .../service/profile/TbDeviceProfileCache.java | 2 +- .../query/DefaultEntityQueryService.java | 2 +- .../service/query/EntityQueryService.java | 2 +- .../queue/DefaultQueueRoutingInfoService.java | 2 +- .../queue/DefaultTbClusterService.java | 34 +- .../queue/DefaultTbCoreConsumerService.java | 28 +- .../DefaultTbRuleEngineConsumerService.java | 3 +- .../DefaultTenantRoutingInfoService.java | 2 +- .../service/queue/TbCoreConsumerService.java | 2 +- .../service/queue/TbCoreConsumerStats.java | 2 +- .../service/queue/TbMsgPackCallback.java | 2 +- .../queue/TbMsgPackProcessingContext.java | 2 +- .../service/queue/TbMsgProfilerInfo.java | 2 +- .../server/service/queue/TbPackCallback.java | 2 +- .../queue/TbPackProcessingContext.java | 2 +- .../queue/TbRuleEngineConsumerService.java | 2 +- .../queue/TbRuleEngineConsumerStats.java | 2 +- .../service/queue/TbRuleNodeProfilerInfo.java | 2 +- .../queue/TbTenantRuleEngineStats.java | 2 +- .../TbTopicWithConsumerPerPartition.java | 2 +- .../processing/AbstractConsumerService.java | 2 +- .../AbstractTbRuleEngineSubmitStrategy.java | 2 +- .../BatchTbRuleEngineSubmitStrategy.java | 2 +- .../BurstTbRuleEngineSubmitStrategy.java | 2 +- .../service/queue/processing/IdMsgPair.java | 2 +- ...lByEntityIdTbRuleEngineSubmitStrategy.java | 2 +- ...riginatorIdTbRuleEngineSubmitStrategy.java | 2 +- ...lByTenantIdTbRuleEngineSubmitStrategy.java | 2 +- .../SequentialTbRuleEngineSubmitStrategy.java | 2 +- .../TbRuleEngineProcessingDecision.java | 2 +- .../TbRuleEngineProcessingResult.java | 2 +- .../TbRuleEngineProcessingStrategy.java | 2 +- ...TbRuleEngineProcessingStrategyFactory.java | 2 +- .../TbRuleEngineSubmitStrategy.java | 2 +- .../TbRuleEngineSubmitStrategyFactory.java | 2 +- .../service/queue/ruleengine/QueueEvent.java | 2 +- .../TbQueueConsumerManagerTask.java | 2 +- .../queue/ruleengine/TbQueueConsumerTask.java | 2 +- .../TbRuleEngineConsumerContext.java | 2 +- .../TbRuleEngineQueueConsumerManager.java | 2 +- .../resource/DefaultTbImageService.java | 188 + .../resource/DefaultTbResourceService.java | 132 +- .../service/resource/TbImageService.java | 38 + .../service/resource/TbResourceService.java | 20 +- .../rpc/DefaultTbCoreDeviceRpcService.java | 2 +- .../rpc/DefaultTbRuleEngineRpcService.java | 2 +- .../service/rpc/LocalRequestMetaData.java | 2 +- .../server/service/rpc/RpcSubmitStrategy.java | 2 +- .../service/rpc/TbCoreDeviceRpcService.java | 2 +- .../server/service/rpc/TbRpcService.java | 2 +- .../rpc/TbRuleEngineDeviceRpcService.java | 2 +- .../rule/DefaultTbRuleChainService.java | 16 +- .../service/rule/TbRuleChainService.java | 2 +- .../script/RuleNodeJsScriptEngine.java | 2 +- .../service/script/RuleNodeScriptEngine.java | 2 +- .../script/RuleNodeTbelScriptEngine.java | 2 +- .../service/security/AccessValidator.java | 2 +- .../service/security/ValidationCallback.java | 2 +- .../service/security/ValidationResult.java | 2 +- .../security/ValidationResultCode.java | 2 +- .../auth/AbstractJwtAuthenticationToken.java | 2 +- .../auth/DefaultTokenOutdatingService.java | 5 +- .../security/auth/JwtAuthenticationToken.java | 2 +- .../security/auth/MfaAuthenticationToken.java | 2 +- .../auth/RefreshAuthenticationToken.java | 2 +- .../security/auth/TokenOutdatingService.java | 5 +- .../auth/jwt/JwtAuthenticationProvider.java | 18 +- ...wtTokenAuthenticationProcessingFilter.java | 2 +- .../RefreshTokenAuthenticationProvider.java | 6 +- .../jwt/RefreshTokenProcessingFilter.java | 2 +- .../auth/jwt/RefreshTokenRequest.java | 2 +- .../auth/jwt/SkipPathRequestMatcher.java | 2 +- .../extractor/JwtHeaderTokenExtractor.java | 2 +- .../jwt/extractor/JwtQueryTokenExtractor.java | 2 +- .../auth/jwt/extractor/TokenExtractor.java | 2 +- .../settings/DefaultJwtSettingsService.java | 2 +- .../settings/DefaultJwtSettingsValidator.java | 2 +- .../settings/InstallJwtSettingsValidator.java | 2 +- .../auth/jwt/settings/JwtSettingsService.java | 2 +- .../jwt/settings/JwtSettingsValidator.java | 2 +- .../auth/mfa/DefaultTwoFactorAuthService.java | 2 +- .../auth/mfa/TwoFactorAuthService.java | 2 +- .../mfa/config/DefaultTwoFaConfigManager.java | 2 +- .../auth/mfa/config/TwoFaConfigManager.java | 2 +- .../auth/mfa/provider/TwoFaProvider.java | 2 +- .../impl/BackupCodeTwoFaProvider.java | 2 +- .../mfa/provider/impl/EmailTwoFaProvider.java | 2 +- .../provider/impl/OtpBasedTwoFaProvider.java | 2 +- .../mfa/provider/impl/SmsTwoFaProvider.java | 2 +- .../mfa/provider/impl/TotpTwoFaProvider.java | 2 +- .../oauth2/AbstractOAuth2ClientMapper.java | 2 +- .../auth/oauth2/AppleOAuth2ClientMapper.java | 2 +- .../auth/oauth2/BasicMapperUtils.java | 2 +- .../auth/oauth2/BasicOAuth2ClientMapper.java | 2 +- .../security/auth/oauth2/CookieUtils.java | 2 +- .../auth/oauth2/CustomOAuth2ClientMapper.java | 2 +- .../auth/oauth2/GithubOAuth2ClientMapper.java | 2 +- ...eOAuth2AuthorizationRequestRepository.java | 4 +- .../auth/oauth2/OAuth2ClientMapper.java | 2 +- .../oauth2/OAuth2ClientMapperProvider.java | 2 +- .../Oauth2AuthenticationFailureHandler.java | 2 +- .../Oauth2AuthenticationSuccessHandler.java | 2 +- .../auth/oauth2/TbOAuth2ParameterNames.java | 2 +- .../security/auth/rest/LoginRequest.java | 2 +- .../security/auth/rest/LoginResponse.java | 2 +- .../auth/rest/PublicLoginRequest.java | 2 +- .../auth/rest/RestAuthenticationDetails.java | 2 +- .../rest/RestAuthenticationDetailsSource.java | 2 +- .../auth/rest/RestAuthenticationProvider.java | 17 +- ...RestAwareAuthenticationFailureHandler.java | 2 +- ...RestAwareAuthenticationSuccessHandler.java | 2 +- .../auth/rest/RestLoginProcessingFilter.java | 2 +- .../rest/RestPublicLoginProcessingFilter.java | 2 +- .../device/DefaultDeviceAuthService.java | 2 +- .../AuthMethodNotSupportedException.java | 2 +- .../exception/JwtExpiredTokenException.java | 9 +- .../UserPasswordExpiredException.java | 2 +- .../UserPasswordNotValidException.java | 26 + .../security/model/ActivateUserRequest.java | 2 +- .../security/model/ChangePasswordRequest.java | 2 +- .../model/ResetPasswordEmailRequest.java | 2 +- .../security/model/ResetPasswordRequest.java | 2 +- .../service/security/model/SecurityUser.java | 2 +- .../service/security/model/UserPrincipal.java | 2 +- .../security/model/token/AccessJwtToken.java | 2 +- .../security/model/token/JwtTokenFactory.java | 14 +- .../model/token/OAuth2AppTokenFactory.java | 2 +- .../model/token/RawAccessJwtToken.java | 2 +- .../permission/AbstractPermissions.java | 2 +- .../permission/AccessControlService.java | 2 +- .../permission/CustomerUserPermissions.java | 2 +- .../DefaultAccessControlService.java | 2 +- .../security/permission/Operation.java | 2 +- .../permission/PermissionChecker.java | 2 +- .../security/permission/Permissions.java | 2 +- .../service/security/permission/Resource.java | 2 +- .../permission/SysAdminPermissions.java | 2 +- .../permission/TenantAdminPermissions.java | 2 +- .../system/DefaultSystemSecurityService.java | 67 +- .../system/SystemSecurityService.java | 11 +- .../DefaultDeviceSessionCacheService.java | 2 +- .../session/DeviceSessionCacheService.java | 2 +- .../service/session/SessionCaffeineCache.java | 2 +- .../service/session/SessionRedisCache.java | 2 +- .../service/slack/DefaultSlackService.java | 2 +- .../server/service/sms/AbstractSmsSender.java | 2 +- .../service/sms/DefaultSmsSenderFactory.java | 2 +- .../server/service/sms/DefaultSmsService.java | 2 +- .../service/sms/SmsExecutorService.java | 2 +- .../server/service/sms/aws/AwsSmsSender.java | 2 +- .../service/sms/smpp/SmppSmsSender.java | 2 +- .../service/sms/twilio/TwilioSmsSender.java | 2 +- .../state/DefaultDeviceStateService.java | 10 +- .../server/service/state/DeviceState.java | 2 +- .../server/service/state/DeviceStateData.java | 2 +- .../service/state/DeviceStateService.java | 2 +- .../service/stats/DefaultJsInvokeStats.java | 2 +- .../DefaultRuleEngineStatisticsService.java | 2 +- .../stats/RuleEngineStatisticsService.java | 2 +- .../DefaultSubscriptionManagerService.java | 2 +- ...efaultTbEntityDataSubscriptionService.java | 9 +- .../DefaultTbLocalSubscriptionService.java | 2 +- .../subscription/ReadTsKvQueryInfo.java | 2 +- .../subscription/SubscriptionErrorCode.java | 2 +- .../SubscriptionManagerService.java | 2 +- .../SubscriptionSchedulerComponent.java | 2 +- .../SubscriptionServiceStatistics.java | 2 +- .../subscription/TbAbstractDataSubCtx.java | 2 +- .../subscription/TbAbstractSubCtx.java | 4 +- .../subscription/TbAlarmCountSubCtx.java | 2 +- .../subscription/TbAlarmDataSubCtx.java | 2 +- .../subscription/TbAlarmsSubscription.java | 2 +- .../subscription/TbAttributeSubscription.java | 2 +- .../TbAttributeSubscriptionScope.java | 2 +- .../subscription/TbEntityCountSubCtx.java | 2 +- .../subscription/TbEntityDataSubCtx.java | 2 +- .../TbEntityDataSubscriptionService.java | 2 +- .../subscription/TbEntityLocalSubsInfo.java | 2 +- .../subscription/TbEntityRemoteSubsInfo.java | 2 +- .../subscription/TbEntitySubEvent.java | 2 +- .../subscription/TbEntityUpdatesInfo.java | 2 +- .../TbLocalSubscriptionService.java | 2 +- .../service/subscription/TbSubscription.java | 5 +- .../subscription/TbSubscriptionType.java | 2 +- .../subscription/TbSubscriptionUtils.java | 2 +- .../subscription/TbSubscriptionsInfo.java | 2 +- .../TbTimeSeriesSubscription.java | 2 +- .../DefaultEntitiesExportImportService.java | 6 +- .../sync/ie/EntitiesExportImportService.java | 2 +- .../DefaultExportableEntitiesService.java | 12 +- .../ie/exporting/EntityExportService.java | 2 +- .../exporting/ExportableEntitiesService.java | 4 +- .../ie/exporting/impl/AssetExportService.java | 2 +- .../impl/AssetProfileExportService.java | 3 +- .../impl/BaseEntityExportService.java | 2 +- .../impl/DashboardExportService.java | 3 +- .../impl/DefaultEntityExportService.java | 5 +- .../exporting/impl/DeviceExportService.java | 2 +- .../impl/DeviceProfileExportService.java | 3 +- .../impl/EntityViewExportService.java | 2 +- .../impl/NotificationRuleExportService.java | 2 +- .../impl/NotificationTargetExportService.java | 5 +- .../NotificationTemplateExportService.java | 2 +- .../exporting/impl/ResourceExportService.java | 44 + .../impl/RuleChainExportService.java | 2 +- .../impl/WidgetTypeExportService.java | 7 +- .../impl/WidgetsBundleExportService.java | 3 +- .../ie/importing/EntityImportService.java | 2 +- .../csv/AbstractBulkImportService.java | 4 +- .../ie/importing/csv/ImportedEntityInfo.java | 2 +- .../ie/importing/impl/AssetImportService.java | 2 +- .../impl/AssetProfileImportService.java | 2 +- .../impl/BaseEntityImportService.java | 2 +- .../importing/impl/CustomerImportService.java | 2 +- .../impl/DashboardImportService.java | 2 +- .../importing/impl/DeviceImportService.java | 2 +- .../impl/DeviceProfileImportService.java | 2 +- .../impl/EntityViewImportService.java | 2 +- .../impl/ImportServiceException.java | 2 +- .../impl/MissingEntityException.java | 2 +- .../impl/NotificationRuleImportService.java | 2 +- .../impl/NotificationTargetImportService.java | 9 +- .../NotificationTemplateImportService.java | 2 +- .../importing/impl/ResourceImportService.java | 93 + .../impl/RuleChainImportService.java | 3 +- .../impl/WidgetTypeImportService.java | 6 +- .../impl/WidgetsBundleImportService.java | 2 +- .../DefaultEntitiesVersionControlService.java | 54 +- .../DefaultGitVersionControlQueueService.java | 174 +- .../vc/EntitiesVersionControlService.java | 2 +- .../vc/GitVersionControlQueueService.java | 2 +- .../service/sync/vc/LoadEntityException.java | 2 +- ...AbstractVersionControlSettingsService.java | 2 +- .../sync/vc/VersionControlTaskCacheEntry.java | 2 +- .../vc/VersionControlTaskCaffeineCache.java | 2 +- .../sync/vc/VersionControlTaskRedisCache.java | 2 +- .../AutoCommitSettingsCaffeineCache.java | 2 +- .../AutoCommitSettingsRedisCache.java | 2 +- .../DefaultTbAutoCommitSettingsService.java | 2 +- .../TbAutoCommitSettingsService.java | 2 +- .../vc/data/ClearRepositoryGitRequest.java | 2 +- .../sync/vc/data/CommitGitRequest.java | 2 +- .../vc/data/ComplexEntitiesExportCtx.java | 2 +- .../sync/vc/data/ContentsDiffGitRequest.java | 2 +- .../vc/data/EntitiesContentGitRequest.java | 2 +- .../sync/vc/data/EntitiesExportCtx.java | 2 +- .../sync/vc/data/EntitiesImportCtx.java | 2 +- .../sync/vc/data/EntityContentGitRequest.java | 2 +- .../sync/vc/data/EntityTypeExportCtx.java | 2 +- .../sync/vc/data/ListBranchesGitRequest.java | 2 +- .../sync/vc/data/ListEntitiesGitRequest.java | 2 +- .../sync/vc/data/ListVersionsGitRequest.java | 2 +- .../sync/vc/data/PendingGitRequest.java | 2 +- .../service/sync/vc/data/ReimportTask.java | 2 +- .../sync/vc/data/SimpleEntitiesExportCtx.java | 2 +- .../sync/vc/data/VersionsDiffGitRequest.java | 2 +- .../service/sync/vc/data/VoidGitRequest.java | 2 +- .../DefaultTbRepositorySettingsService.java | 2 +- .../RepositorySettingsCaffeineCache.java | 2 +- .../RepositorySettingsRedisCache.java | 2 +- .../TbRepositorySettingsService.java | 2 +- .../system/DefaultSystemInfoService.java | 2 +- .../service/system/SystemInfoService.java | 2 +- .../AbstractSubscriptionService.java | 2 +- .../telemetry/AlarmSubscriptionService.java | 2 +- .../service/telemetry/AttributeData.java | 2 +- .../DefaultAlarmSubscriptionService.java | 2 +- .../DefaultTelemetrySubscriptionService.java | 2 +- .../telemetry/InternalTelemetryService.java | 2 +- .../TelemetrySubscriptionService.java | 2 +- .../server/service/telemetry/TsData.java | 2 +- .../BasicCredentialsValidationResult.java | 2 +- .../DefaultTbCoreToTransportService.java | 2 +- .../transport/DefaultTransportApiService.java | 18 +- .../transport/TbCoreToTransportService.java | 2 +- .../transport/TbCoreTransportApiService.java | 2 +- .../transport/TransportApiService.java | 2 +- .../msg/TransportToDeviceActorMsgWrapper.java | 2 +- .../service/ttl/AbstractCleanUpService.java | 2 +- .../service/ttl/AlarmsCleanUpService.java | 2 +- .../service/ttl/AuditLogsCleanUpService.java | 2 +- .../service/ttl/EdgeEventsCleanUpService.java | 2 +- .../service/ttl/EventsCleanUpService.java | 2 +- .../ttl/NotificationsCleanUpService.java | 2 +- .../service/ttl/TimeseriesCleanUpService.java | 2 +- .../service/ttl/rpc/RpcCleanUpService.java | 2 +- .../service/update/DefaultUpdateService.java | 22 +- .../server/service/update/UpdateService.java | 2 +- .../server/service/ws/AuthCmd.java | 19 +- .../service/ws/DefaultWebSocketService.java | 412 +- .../server/service/ws/SessionEvent.java | 2 +- .../service/ws/WebSocketMsgEndpoint.java | 2 +- .../server/service/ws/WebSocketService.java | 13 +- .../service/ws/WebSocketSessionRef.java | 24 +- .../service/ws/WebSocketSessionType.java | 20 +- .../thingsboard/server/service/ws/WsCmd.java | 28 + .../server/service/ws/WsCmdType.java | 39 + .../server/service/ws/WsCommandsWrapper.java | 71 + .../server/service/ws/WsSessionMetaData.java | 2 +- .../DefaultNotificationCommandsHandler.java | 4 +- .../NotificationCommandsHandler.java | 2 +- .../cmd/MarkAllNotificationsAsReadCmd.java | 9 +- .../cmd/MarkNotificationsAsReadCmd.java | 9 +- .../cmd/NotificationCmdsWrapper.java | 21 +- .../cmd/NotificationsCountSubCmd.java | 9 +- .../notification/cmd/NotificationsSubCmd.java | 9 +- .../cmd/NotificationsUnsubCmd.java | 9 +- .../cmd/UnreadNotificationsCountUpdate.java | 7 +- .../cmd/UnreadNotificationsUpdate.java | 7 +- .../sub/NotificationRequestUpdate.java | 2 +- .../notification/sub/NotificationUpdate.java | 2 +- .../sub/NotificationsCountSubscription.java | 3 +- .../sub/NotificationsSubscription.java | 5 +- .../sub/NotificationsSubscriptionUpdate.java | 2 +- .../ws/telemetry/TelemetryFeature.java | 2 +- .../telemetry/TelemetryWebSocketTextMsg.java | 2 +- ...Wrapper.java => TelemetryCmdsWrapper.java} | 28 +- .../cmd/v1/AttributesSubscriptionCmd.java | 8 +- .../ws/telemetry/cmd/v1/GetHistoryCmd.java | 7 +- .../ws/telemetry/cmd/v1/SubscriptionCmd.java | 4 +- .../telemetry/cmd/v1/TelemetryPluginCmd.java | 6 +- .../cmd/v1/TimeseriesSubscriptionCmd.java | 10 +- .../ws/telemetry/cmd/v2/AggHistoryCmd.java | 2 +- .../service/ws/telemetry/cmd/v2/AggKey.java | 2 +- .../ws/telemetry/cmd/v2/AggTimeSeriesCmd.java | 2 +- .../ws/telemetry/cmd/v2/AlarmCountCmd.java | 8 +- .../cmd/v2/AlarmCountUnsubscribeCmd.java | 7 +- .../ws/telemetry/cmd/v2/AlarmCountUpdate.java | 2 +- .../ws/telemetry/cmd/v2/AlarmDataCmd.java | 8 +- .../cmd/v2/AlarmDataUnsubscribeCmd.java | 7 +- .../ws/telemetry/cmd/v2/AlarmDataUpdate.java | 2 +- .../ws/telemetry/cmd/v2/CmdUpdate.java | 2 +- .../ws/telemetry/cmd/v2/CmdUpdateType.java | 2 +- .../service/ws/telemetry/cmd/v2/DataCmd.java | 5 +- .../ws/telemetry/cmd/v2/DataUpdate.java | 2 +- .../ws/telemetry/cmd/v2/EntityCountCmd.java | 8 +- .../cmd/v2/EntityCountUnsubscribeCmd.java | 7 +- .../telemetry/cmd/v2/EntityCountUpdate.java | 2 +- .../ws/telemetry/cmd/v2/EntityDataCmd.java | 7 +- .../cmd/v2/EntityDataUnsubscribeCmd.java | 7 +- .../ws/telemetry/cmd/v2/EntityDataUpdate.java | 2 +- .../ws/telemetry/cmd/v2/EntityHistoryCmd.java | 5 +- .../service/ws/telemetry/cmd/v2/GetTsCmd.java | 20 +- .../ws/telemetry/cmd/v2/LatestValueCmd.java | 2 +- .../ws/telemetry/cmd/v2/TimeSeriesCmd.java | 5 +- .../ws/telemetry/cmd/v2/UnsubscribeCmd.java | 6 +- .../sub/AlarmSubscriptionUpdate.java | 2 +- .../ws/telemetry/sub/SubscriptionState.java | 2 +- .../sub/TelemetrySubscriptionUpdate.java | 2 +- ...ngfoxHandlerProviderBeanPostProcessor.java | 2 +- .../thingsboard/server/utils/CsvUtils.java | 2 +- .../server/utils/LwM2mObjectModelUtils.java | 12 +- .../thingsboard/server/utils/MiscUtils.java | 2 +- .../server/utils/TbNodeUpgradeUtils.java | 57 +- application/src/main/resources/logback.xml | 2 +- .../templates/2fa.verification.code.ftl | 2 +- .../resources/templates/account.activated.ftl | 2 +- .../resources/templates/account.lockout.ftl | 2 +- .../main/resources/templates/activation.ftl | 2 +- .../templates/password.was.reset.ftl | 2 +- .../resources/templates/reset.password.ftl | 2 +- .../resources/templates/state.disabled.ftl | 2 +- .../resources/templates/state.enabled.ftl | 2 +- .../resources/templates/state.warning.ftl | 2 +- .../src/main/resources/templates/test.ftl | 2 +- .../src/main/resources/thingsboard.yml | 48 +- .../DeviceActorMessageProcessorTest.java | 2 +- .../server/actors/stats/StatsActorTest.java | 2 +- .../actors/stats/StatsPersistMsgTest.java | 2 +- ...CaffeineCacheDefaultConfigurationTest.java | 2 +- .../controller/AbstractControllerTest.java | 15 +- .../AbstractInMemoryStorageTest.java | 2 +- .../controller/AbstractNotifyEntityTest.java | 14 +- .../AbstractRuleEngineControllerTest.java | 2 +- .../server/controller/AbstractWebTest.java | 4 +- .../controller/AdminControllerTest.java | 2 +- .../AlarmCommentControllerTest.java | 2 +- .../controller/AlarmControllerTest.java | 8 +- .../controller/AssetControllerTest.java | 2 +- .../AssetProfileControllerTest.java | 61 +- .../controller/AuditLogControllerTest.java | 2 +- .../server/controller/AuthControllerTest.java | 85 +- .../controller/BaseQueueControllerTest.java | 2 +- .../ComponentDescriptorControllerTest.java | 2 +- .../controller/CustomerControllerTest.java | 2 +- .../controller/DashboardControllerTest.java | 23 +- .../DeviceConnectivityControllerTest.java | 452 +- .../controller/DeviceControllerTest.java | 33 +- .../DeviceProfileControllerTest.java | 61 +- .../server/controller/EdgeControllerTest.java | 221 +- .../controller/EdgeEventControllerTest.java | 2 +- .../controller/EntityQueryControllerTest.java | 2 +- .../EntityRelationControllerTest.java | 2 +- .../controller/EntityViewControllerTest.java | 2 +- .../server/controller/HomePageApiTest.java | 9 +- .../controller/ImageControllerTest.java | 337 + .../controller/OtaPackageControllerTest.java | 2 +- .../server/controller/RpcControllerTest.java | 2 +- .../controller/RuleChainControllerTest.java | 2 +- .../controller/TbResourceControllerTest.java | 120 +- .../controller/TbTestWebSocketClient.java | 45 +- .../controller/TelemetryControllerTest.java | 63 +- .../controller/TenantControllerTest.java | 2 +- .../TenantProfileControllerTest.java | 2 +- .../controller/TwoFactorAuthConfigTest.java | 2 +- .../server/controller/TwoFactorAuthTest.java | 2 +- .../server/controller/UserControllerTest.java | 2 +- .../server/controller/WebsocketApiTest.java | 9 +- .../controller/WidgetTypeControllerTest.java | 28 +- .../WidgetsBundleControllerTest.java | 2 +- .../plugin/TbWebSocketHandlerTest.java | 31 +- .../server/edge/AbstractEdgeTest.java | 74 +- .../server/edge/AlarmEdgeTest.java | 186 +- .../server/edge/AssetEdgeTest.java | 69 +- .../server/edge/AssetProfileEdgeTest.java | 61 +- .../server/edge/CustomerEdgeTest.java | 13 +- .../server/edge/DashboardEdgeTest.java | 65 +- .../server/edge/DeviceEdgeTest.java | 206 +- .../server/edge/DeviceProfileEdgeTest.java | 110 +- .../org/thingsboard/server/edge/EdgeTest.java | 8 +- .../server/edge/EntityViewEdgeTest.java | 80 +- .../server/edge/OtaPackageEdgeTest.java | 65 +- .../server/edge/QueueEdgeTest.java | 39 +- .../server/edge/RelationEdgeTest.java | 87 +- .../server/edge/ResourceEdgeTest.java | 66 +- .../server/edge/RuleChainEdgeTest.java | 34 +- .../server/edge/TelemetryEdgeTest.java | 2 +- .../server/edge/TenantEdgeTest.java | 33 +- .../server/edge/TenantProfileEdgeTest.java | 26 +- .../thingsboard/server/edge/UserEdgeTest.java | 65 +- .../server/edge/WidgetEdgeTest.java | 30 +- .../server/edge/imitator/EdgeImitator.java | 8 +- .../discovery/HashPartitionServiceTest.java | 2 +- ...AbstractRuleEngineFlowIntegrationTest.java | 2 +- .../sql/RuleEngineFlowSqlIntegrationTest.java | 2 +- ...actRuleEngineLifecycleIntegrationTest.java | 2 +- ...RuleEngineLifecycleSqlIntegrationTest.java | 2 +- .../DefaultTbApiUsageStateServiceTest.java | 2 +- .../provision/DeviceProvisionServiceTest.java | 2 +- .../RuleChainMsgConstructorTest.java | 17 +- .../rpc/processor/BaseEdgeProcessorTest.java | 294 +- .../asset/AbstractAssetProcessorTest.java | 2 +- .../asset/AssetEdgeProcessorTest.java | 10 +- .../asset/AssetProfileEdgeProcessorTest.java | 9 +- .../device/AbstractDeviceProcessorTest.java | 2 +- .../device/DeviceEdgeProcessorTest.java | 10 +- .../DeviceProfileEdgeProcessorTest.java | 11 +- .../telemetry/TelemetryEdgeProcessorTest.java | 2 +- .../alarm/DefaultTbAlarmServiceTest.java | 2 +- .../DefaultTbAlarmCommentServiceTest.java | 2 +- .../service/install/InstallScriptsTest.java | 5 +- .../SqlEntityDatabaseSchemaServiceTest.java | 2 +- .../update/DefaultDataUpdateServiceTest.java | 2 +- .../service/limits/RateLimitServiceTest.java | 2 +- .../server/service/mail/TbMailSenderTest.java | 2 +- .../AbstractNotificationApiTest.java | 5 +- .../notification/NotificationApiTest.java | 19 +- .../notification/NotificationApiWsClient.java | 28 +- .../notification/NotificationRuleApiTest.java | 2 +- .../NotificationTargetApiTest.java | 2 +- .../NotificationTemplateApiTest.java | 2 +- .../TestNotificationSettingsService.java | 2 +- .../queue/DefaultTbClusterServiceTest.java | 7 +- .../service/queue/TbMsgPackCallbackTest.java | 2 +- .../queue/TbMsgPackProcessingContextTest.java | 2 +- .../TbRuleEngineQueueConsumerManagerTest.java | 2 +- .../sql/BaseTbResourceServiceTest.java | 92 +- .../service/rpc/RpcSubmitStrategyTest.java | 2 +- .../service/script/MockJsInvokeService.java | 2 +- .../script/NashornJsInvokeServiceTest.java | 2 +- .../script/RemoteJsInvokeServiceTest.java | 2 +- .../service/script/TbelInvokeServiceTest.java | 2 +- .../security/auth/JwtTokenFactoryTest.java | 10 +- .../security/auth/TokenOutdatingTest.java | 6 +- .../security/auth/oauth2/CookieUtilsTest.java | 2 +- ...auth2AuthenticationSuccessHandlerTest.java | 2 +- .../service/sms/DefaultSmsServiceTest.java | 2 +- .../service/sms/smpp/SmppSmsSenderTest.java | 2 +- .../SequentialTimeseriesPersistenceTest.java | 2 +- .../state/DefaultDeviceStateServiceTest.java | 2 +- .../service/stats/DevicesStatisticsTest.java | 2 +- .../sync/ie/BaseExportImportServiceTest.java | 2 +- .../sync/ie/ExportImportServiceSqlTest.java | 2 +- .../DefaultTransportApiServiceTest.java | 6 +- .../service/ttl/AlarmsCleanUpServiceTest.java | 2 +- .../service/ttl/EventsCleanUpServiceTest.java | 2 +- .../server/system/BaseHttpDeviceApiTest.java | 2 +- .../server/system/BaseRestApiLimitsTest.java | 2 +- .../system/RestTemplateConvertersTest.java | 2 +- .../server/system/sql/DeviceApiSqlTest.java | 2 +- .../system/sql/RestApiLimitsSqlTest.java | 2 +- .../AbstractTransportIntegrationTest.java | 2 +- .../transport/TransportNoSqlTestSuite.java | 2 +- .../coap/AbstractCoapIntegrationTest.java | 2 +- .../transport/coap/CoapTestCallback.java | 8 +- .../server/transport/coap/CoapTestClient.java | 34 +- .../coap/CoapTestConfigProperties.java | 2 +- ...AbstractCoapAttributesIntegrationTest.java | 2 +- .../CoapAttributesRequestIntegrationTest.java | 2 +- ...pAttributesRequestJsonIntegrationTest.java | 2 +- ...AttributesRequestProtoIntegrationTest.java | 2 +- .../CoapAttributesUpdatesIntegrationTest.java | 2 +- ...pAttributesUpdatesJsonIntegrationTest.java | 2 +- ...AttributesUpdatesProtoIntegrationTest.java | 2 +- .../coap/claim/CoapClaimDeviceTest.java | 2 +- .../coap/claim/CoapClaimJsonDeviceTest.java | 2 +- .../coap/claim/CoapClaimProtoDeviceTest.java | 2 +- .../client/CoapClientIntegrationTest.java | 309 + .../CoapProvisionJsonDeviceTest.java | 2 +- .../CoapProvisionProtoDeviceTest.java | 2 +- ...tractCoapServerSideRpcIntegrationTest.java | 2 +- ...apServerSideRpcDefaultIntegrationTest.java | 2 +- .../CoapServerSideRpcJsonIntegrationTest.java | 2 +- ...CoapServerSideRpcProtoIntegrationTest.java | 2 +- .../CoapAttributesIntegrationTest.java | 2 +- .../CoapAttributesJsonIntegrationTest.java | 2 +- .../CoapAttributesProtoIntegrationTest.java | 2 +- ...AbstractCoapTimeseriesIntegrationTest.java | 2 +- ...ractCoapTimeseriesJsonIntegrationTest.java | 2 +- ...actCoapTimeseriesProtoIntegrationTest.java | 2 +- .../CoapTimeseriesNoSqlIntegrationTest.java | 2 +- ...oapTimeseriesNoSqlJsonIntegrationTest.java | 2 +- ...apTimeseriesNoSqlProtoIntegrationTest.java | 2 +- .../sql/CoapTimeseriesSqlIntegrationTest.java | 2 +- .../CoapTimeseriesSqlJsonIntegrationTest.java | 2 +- ...CoapTimeseriesSqlProtoIntegrationTest.java | 2 +- .../lwm2m/AbstractLwM2MIntegrationTest.java | 11 +- .../transport/lwm2m/Lwm2mTestHelper.java | 2 +- .../transport/lwm2m/client/FwLwM2MDevice.java | 2 +- .../lwm2m/client/LwM2MLocationParams.java | 2 +- .../lwm2m/client/LwM2MTestClient.java | 2 +- .../client/LwM2mBinaryAppDataContainer.java | 2 +- .../transport/lwm2m/client/LwM2mLocation.java | 2 +- .../lwm2m/client/LwM2mTemperatureSensor.java | 2 +- .../transport/lwm2m/client/Lwm2mServer.java | 2 +- .../lwm2m/client/SimpleLwM2MDevice.java | 2 +- .../transport/lwm2m/client/SwLwM2MDevice.java | 2 +- .../ota/AbstractOtaLwM2MIntegrationTest.java | 2 +- .../ota/sql/OtaLwM2MIntegrationTest.java | 2 +- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 2 +- .../sql/RpcLwm2mIntegrationCreateTest.java | 2 +- .../sql/RpcLwm2mIntegrationDeleteTest.java | 2 +- .../sql/RpcLwm2mIntegrationDiscoverTest.java | 2 +- .../sql/RpcLwm2mIntegrationExecuteTest.java | 2 +- .../sql/RpcLwm2mIntegrationObserveTest.java | 2 +- .../rpc/sql/RpcLwm2mIntegrationReadTest.java | 2 +- ...pcLwm2mIntegrationWriteAttributesTest.java | 2 +- .../rpc/sql/RpcLwm2mIntegrationWriteTest.java | 2 +- .../AbstractSecurityLwM2MIntegrationTest.java | 2 +- .../sql/NoSecLwM2MIntegrationTest.java | 2 +- .../security/sql/PskLwm2mIntegrationTest.java | 2 +- .../security/sql/RpkLwM2MIntegrationTest.java | 2 +- .../sql/X509_NoTrustLwM2MIntegrationTest.java | 2 +- .../sql/X509_TrustLwM2MIntegrationTest.java | 2 +- .../LwM2mTransportServerHelperTest.java | 2 +- .../mqtt/AbstractMqttIntegrationTest.java | 2 +- .../mqtt/MqttTestConfigProperties.java | 2 +- .../mqtt/mqttv3/MqttTestCallback.java | 2 +- .../transport/mqtt/mqttv3/MqttTestClient.java | 2 +- .../MqttTestSubscribeOnTopicCallback.java | 2 +- ...AbstractMqttAttributesIntegrationTest.java | 2 +- ...tBackwardCompatibilityIntegrationTest.java | 2 +- .../MqttAttributesRequestIntegrationTest.java | 2 +- ...tAttributesRequestJsonIntegrationTest.java | 2 +- ...AttributesRequestProtoIntegrationTest.java | 2 +- ...sBackwardCompatibilityIntegrationTest.java | 2 +- .../MqttAttributesUpdatesIntegrationTest.java | 2 +- ...tAttributesUpdatesJsonIntegrationTest.java | 2 +- ...AttributesUpdatesProtoIntegrationTest.java | 2 +- ...tClaimBackwardCompatibilityDeviceTest.java | 2 +- .../mqttv3/claim/MqttClaimDeviceTest.java | 2 +- .../mqttv3/claim/MqttClaimJsonDeviceTest.java | 2 +- .../claim/MqttClaimProtoDeviceTest.java | 2 +- .../AbstractMqttClientConnectionTest.java | 2 +- .../client/MqttClientConnectionTest.java | 2 +- .../credentials/BasicMqttCredentialsTest.java | 2 +- .../MqttProvisionJsonDeviceTest.java | 2 +- .../MqttProvisionProtoDeviceTest.java | 2 +- ...tractMqttServerSideRpcIntegrationTest.java | 2 +- ...cBackwardCompatibilityIntegrationTest.java | 2 +- ...ttServerSideRpcDefaultIntegrationTest.java | 2 +- .../MqttServerSideRpcJsonIntegrationTest.java | 2 +- ...MqttServerSideRpcProtoIntegrationTest.java | 2 +- ...erSideRpcSequenceOnAckIntegrationTest.java | 2 +- ...eRpcSequenceOnResponseIntegrationTest.java | 2 +- .../MqttAttributesIntegrationTest.java | 2 +- .../MqttAttributesJsonIntegrationTest.java | 2 +- .../MqttAttributesProtoIntegrationTest.java | 2 +- ...AbstractMqttTimeseriesIntegrationTest.java | 2 +- ...ractMqttTimeseriesJsonIntegrationTest.java | 2 +- ...actMqttTimeseriesProtoIntegrationTest.java | 2 +- .../MqttTimeseriesNoSqlIntegrationTest.java | 2 +- ...qttTimeseriesNoSqlJsonIntegrationTest.java | 2 +- ...ttTimeseriesNoSqlProtoIntegrationTest.java | 2 +- .../sql/MqttTimeseriesSqlIntegrationTest.java | 2 +- .../MqttTimeseriesSqlJsonIntegrationTest.java | 2 +- ...MqttTimeseriesSqlProtoIntegrationTest.java | 2 +- .../mqtt/mqttv5/AbstractMqttV5Test.java | 2 +- .../mqtt/mqttv5/MqttV5TestCallback.java | 2 +- .../mqtt/mqttv5/MqttV5TestClient.java | 2 +- .../AbstractAttributesMqttV5Test.java | 2 +- .../updates/AttributesUpdatesTest.java | 2 +- .../upload/AttributesPublishTest.java | 2 +- .../mqttv5/claim/AbstractMqttV5ClaimTest.java | 2 +- .../mqtt/mqttv5/claim/MqttV5ClaimTest.java | 2 +- .../AbstractMqttV5ClientConnectionTest.java | 2 +- .../MqttV5ClientConnectionTest.java | 2 +- .../AbstractMqttV5ClientPublishTest.java | 2 +- .../publish/MqttV5ClientPublishTest.java | 2 +- .../AbstractMqttV5ClientSubscriptionTest.java | 2 +- .../MqttV5ClientSubscriptionTest.java | 2 +- .../AbstractMqttV5ClientUnsubscribeTest.java | 2 +- .../MqttV5ClientUnsubscribeTest.java | 2 +- .../provision/MqttV5ProvisionDeviceTest.java | 2 +- .../mqttv5/rpc/AbstractMqttV5RpcTest.java | 2 +- .../mqtt/mqttv5/rpc/MqttV5RpcTest.java | 2 +- .../AbstractMqttV5TimeseriesTest.java | 2 +- .../timeseries/MqttV5TimeseriesTest.java | 2 +- .../AbstractMqttV5ClientSparkplugTest.java | 2 +- ...ctMqttV5ClientSparkplugAttributesTest.java | 2 +- ...ientSparkplugBAttributesInProfileTest.java | 4 +- .../MqttV5ClientSparkplugBAttributesTest.java | 4 +- ...ctMqttV5ClientSparkplugConnectionTest.java | 2 +- .../MqttV5ClientSparkplugBConnectionTest.java | 2 +- .../rpc/AbstractMqttV5RpcSparkplugTest.java | 2 +- .../sparkplug/rpc/MqttV5RpcSparkplugTest.java | 2 +- ...actMqttV5ClientSparkplugTelemetryTest.java | 2 +- .../MqttV5ClientSparkplugBTelemetryTest.java | 2 +- .../server/utils/TbNodeUpgradeUtilsTest.java | 61 +- build_proto.sh | 2 +- common/actor/pom.xml | 4 +- .../server/actors/AbstractTbActor.java | 2 +- .../server/actors/DefaultTbActorSystem.java | 2 +- .../thingsboard/server/actors/Dispatcher.java | 2 +- .../server/actors/InitFailureStrategy.java | 2 +- .../server/actors/JsInvokeStats.java | 2 +- .../server/actors/ProcessFailureStrategy.java | 2 +- .../thingsboard/server/actors/TbActor.java | 2 +- .../server/actors/TbActorCreator.java | 2 +- .../thingsboard/server/actors/TbActorCtx.java | 2 +- .../server/actors/TbActorException.java | 2 +- .../thingsboard/server/actors/TbActorId.java | 2 +- .../server/actors/TbActorMailbox.java | 2 +- .../actors/TbActorNotRegisteredException.java | 2 +- .../thingsboard/server/actors/TbActorRef.java | 2 +- .../server/actors/TbActorSystem.java | 2 +- .../server/actors/TbActorSystemSettings.java | 2 +- .../server/actors/TbEntityActorId.java | 2 +- .../actors/TbRuleNodeUpdateException.java | 2 +- .../server/actors/TbStringActorId.java | 2 +- .../server/actors/ActorSystemTest.java | 2 +- .../server/actors/ActorTestCtx.java | 2 +- .../server/actors/FailedToInitActor.java | 2 +- .../server/actors/IntTbActorMsg.java | 2 +- .../server/actors/SlowCreateActor.java | 2 +- .../server/actors/SlowInitActor.java | 2 +- .../server/actors/TestRootActor.java | 2 +- common/cache/pom.xml | 4 +- .../thingsboard/server/cache/CacheSpecs.java | 2 +- .../server/cache/CacheSpecsMap.java | 2 +- .../cache/CaffeineTbCacheTransaction.java | 2 +- .../cache/CaffeineTbTransactionalCache.java | 2 +- .../server/cache/RedisTbCacheTransaction.java | 2 +- .../cache/RedisTbTransactionalCache.java | 18 +- .../cache/SimpleTbCacheValueWrapper.java | 2 +- .../cache/TBRedisCacheConfiguration.java | 2 +- .../cache/TBRedisClusterConfiguration.java | 2 +- .../cache/TBRedisSentinelConfiguration.java | 2 +- .../cache/TBRedisStandaloneConfiguration.java | 2 +- .../server/cache/TbCacheTransaction.java | 2 +- .../server/cache/TbCacheValueWrapper.java | 2 +- .../cache/TbCaffeineCacheConfiguration.java | 2 +- .../server/cache/TbFSTRedisSerializer.java | 2 +- .../server/cache/TbRedisSerializer.java | 2 +- .../server/cache/TbTransactionalCache.java | 27 +- .../cache/device/DeviceCacheEvictEvent.java | 2 +- .../server/cache/device/DeviceCacheKey.java | 2 +- .../cache/device/DeviceCaffeineCache.java | 2 +- .../server/cache/device/DeviceRedisCache.java | 2 +- .../cache/ota/CaffeineOtaPackageCache.java | 2 +- .../server/cache/ota/OtaPackageDataCache.java | 2 +- .../cache/ota/RedisOtaPackageDataCache.java | 2 +- .../resourceInfo/ResourceInfoCacheKey.java | 2 +- .../ResourceInfoCaffeineCache.java | 2 +- .../resourceInfo/ResourceInfoEvictEvent.java | 2 +- .../resourceInfo/ResourceInfoRedisCache.java | 2 +- ...UsersSessionInvalidationCaffeineCache.java | 2 +- .../UsersSessionInvalidationRedisCache.java | 2 +- .../server/cache/CacheSpecsMapTest.java | 2 +- common/cluster-api/pom.xml | 8 +- .../server/cluster/TbClusterService.java | 6 +- .../server/queue/TbQueueAdmin.java | 2 +- .../server/queue/TbQueueCallback.java | 2 +- .../server/queue/TbQueueClusterService.java | 2 +- .../server/queue/TbQueueConsumer.java | 2 +- .../server/queue/TbQueueHandler.java | 2 +- .../thingsboard/server/queue/TbQueueMsg.java | 2 +- .../server/queue/TbQueueMsgDecoder.java | 2 +- .../server/queue/TbQueueMsgHeaders.java | 2 +- .../server/queue/TbQueueMsgMetadata.java | 2 +- .../server/queue/TbQueueProducer.java | 2 +- .../server/queue/TbQueueRequestTemplate.java | 2 +- .../server/queue/TbQueueResponseTemplate.java | 2 +- common/coap-server/pom.xml | 4 +- .../server/coapserver/CoapServerContext.java | 2 +- .../server/coapserver/CoapServerService.java | 2 +- .../coapserver/DefaultCoapServerService.java | 2 +- .../TbCoapDtlsCertificateVerifier.java | 2 +- .../TbCoapDtlsSessionInMemoryStorage.java | 2 +- .../coapserver/TbCoapDtlsSessionInfo.java | 2 +- .../server/coapserver/TbCoapDtlsSettings.java | 2 +- .../coapserver/TbCoapServerComponent.java | 2 +- .../TbCoapServerMessageDeliverer.java | 2 +- common/dao-api/pom.xml | 4 +- .../server/dao/alarm/AlarmCommentService.java | 3 +- .../server/dao/alarm/AlarmService.java | 2 +- .../server/dao/asset/AssetProfileService.java | 11 +- .../server/dao/asset/AssetService.java | 3 +- .../dao/attributes/AttributesService.java | 2 +- .../server/dao/audit/AuditLogService.java | 2 +- .../cassandra/AbstractCassandraCluster.java | 2 +- .../dao/cassandra/CassandraCluster.java | 2 +- .../dao/cassandra/CassandraDriverOptions.java | 2 +- .../cassandra/CassandraInstallCluster.java | 2 +- .../cassandra/guava/DefaultGuavaSession.java | 2 +- .../cassandra/guava/GuavaDriverContext.java | 2 +- .../guava/GuavaMultiPageResultSet.java | 2 +- .../guava/GuavaRequestAsyncProcessor.java | 2 +- .../dao/cassandra/guava/GuavaSession.java | 2 +- .../cassandra/guava/GuavaSessionBuilder.java | 2 +- .../cassandra/guava/GuavaSessionUtils.java | 2 +- .../component/ComponentDescriptorService.java | 2 +- .../server/dao/customer/CustomerService.java | 2 +- .../dao/dashboard/DashboardService.java | 4 +- .../dao/device/ClaimDevicesService.java | 2 +- .../dao/device/DeviceConnectivityService.java | 6 +- .../dao/device/DeviceCredentialsService.java | 2 +- .../dao/device/DeviceProfileService.java | 11 +- .../dao/device/DeviceProvisionService.java | 2 +- .../server/dao/device/DeviceService.java | 3 +- .../server/dao/device/claim/ClaimData.java | 2 +- .../dao/device/claim/ClaimResponse.java | 2 +- .../server/dao/device/claim/ClaimResult.java | 2 +- .../dao/device/claim/ReclaimResult.java | 2 +- .../provision/ProvisionFailedException.java | 2 +- .../device/provision/ProvisionRequest.java | 2 +- .../device/provision/ProvisionResponse.java | 2 +- .../provision/ProvisionResponseStatus.java | 2 +- .../server/dao/edge/EdgeEventService.java | 2 +- .../server/dao/edge/EdgeService.java | 2 +- .../dao/edge/EdgeSynchronizationManager.java | 2 +- .../server/dao/entity/EntityCountService.java | 2 +- .../server/dao/entity/EntityDaoService.java | 2 +- .../server/dao/entity/EntityService.java | 2 +- .../dao/entity/EntityServiceRegistry.java | 2 +- .../dao/entityview/EntityViewService.java | 4 +- .../server/dao/event/EventService.java | 2 +- .../dao/housekeeper/HouseKeeperService.java | 2 +- .../dao/nosql/CassandraStatementTask.java | 2 +- .../server/dao/nosql/TbResultSet.java | 2 +- .../server/dao/nosql/TbResultSetFuture.java | 2 +- .../NotificationRequestService.java | 2 +- .../notification/NotificationRuleService.java | 2 +- .../dao/notification/NotificationService.java | 2 +- .../NotificationSettingsService.java | 2 +- .../NotificationTargetService.java | 2 +- .../NotificationTemplateService.java | 2 +- .../oauth2/OAuth2ConfigTemplateService.java | 2 +- .../server/dao/oauth2/OAuth2Service.java | 2 +- .../server/dao/oauth2/OAuth2User.java | 2 +- .../server/dao/ota/OtaPackageService.java | 2 +- .../server/dao/queue/QueueService.java | 2 +- .../server/dao/relation/RelationService.java | 2 +- .../server/dao/resource/ImageService.java | 68 + .../server/dao/resource/ResourceService.java | 9 +- .../server/dao/rpc/RpcService.java | 2 +- .../server/dao/rule/RuleChainService.java | 3 +- .../server/dao/rule/RuleNodeStateService.java | 2 +- .../dao/settings/AdminSettingsService.java | 2 +- .../dao/tenant/TbTenantProfileCache.java | 2 +- .../dao/tenant/TenantProfileService.java | 2 +- .../server/dao/tenant/TenantService.java | 2 +- .../dao/timeseries/TimeseriesService.java | 2 +- .../server/dao/usage/UsageInfoService.java | 2 +- .../dao/usagerecord/ApiLimitService.java | 2 +- .../dao/usagerecord/ApiUsageStateService.java | 2 +- .../server/dao/user/UserService.java | 2 +- .../server/dao/user/UserSettingsService.java | 2 +- .../server/dao/util/AsyncTask.java | 2 +- .../server/dao/util/DbTypeInfoComponent.java | 2 +- .../dao/util/DefaultDbTypeInfoComponent.java | 2 +- .../server/dao/util/NoSqlAnyDao.java | 2 +- .../server/dao/util/NoSqlAnyDaoNonCloud.java | 2 +- .../server/dao/util/NoSqlTsDao.java | 2 +- .../server/dao/util/NoSqlTsLatestDao.java | 2 +- .../thingsboard/server/dao/util/SqlDao.java | 2 +- .../thingsboard/server/dao/util/SqlTsDao.java | 2 +- .../server/dao/util/SqlTsLatestAnyDao.java | 2 +- .../server/dao/util/SqlTsLatestDao.java | 2 +- .../dao/util/SqlTsOrTsLatestAnyDao.java | 2 +- .../server/dao/util/TbAutoConfiguration.java | 2 +- .../server/dao/util/TimescaleDBTsDao.java | 2 +- .../dao/util/TimescaleDBTsLatestDao.java | 2 +- .../dao/util/TimescaleDBTsOrTsLatestDao.java | 2 +- .../server/dao/widget/WidgetTypeService.java | 2 +- .../dao/widget/WidgetsBundleService.java | 2 +- common/data/pom.xml | 4 +- .../server/common/data/AdminSettings.java | 2 +- .../server/common/data/ApiFeature.java | 2 +- .../server/common/data/ApiUsageRecordKey.java | 2 +- .../common/data/ApiUsageRecordState.java | 2 +- .../server/common/data/ApiUsageState.java | 2 +- .../common/data/ApiUsageStateValue.java | 2 +- .../server/common/data/BaseData.java | 2 +- .../data/BaseDataWithAdditionalInfo.java | 2 +- .../server/common/data/CacheConstants.java | 2 +- .../server/common/data/ClaimRequest.java | 2 +- .../server/common/data/CoapDeviceType.java | 2 +- .../server/common/data/ContactBased.java | 2 +- .../server/common/data/Customer.java | 2 +- .../server/common/data/Dashboard.java | 2 +- .../server/common/data/DashboardInfo.java | 7 +- .../server/common/data/DataConstants.java | 6 +- .../server/common/data/Device.java | 2 +- .../server/common/data/DeviceIdInfo.java | 2 +- .../server/common/data/DeviceInfo.java | 2 +- .../server/common/data/DeviceInfoFilter.java | 2 +- .../server/common/data/DeviceProfile.java | 5 +- .../server/common/data/DeviceProfileInfo.java | 2 +- .../data/DeviceProfileProvisionType.java | 2 +- .../server/common/data/DeviceProfileType.java | 2 +- .../common/data/DeviceTransportType.java | 2 +- .../server/common/data/DynamicProtoUtils.java | 2 +- .../server/common/data/EdgeUpgradeInfo.java | 26 + .../common/data/EdgeUpgradeMessage.java | 33 + .../server/common/data/EdgeUtils.java | 15 +- .../server/common/data/EntityFieldsData.java | 2 +- .../server/common/data/EntityInfo.java | 2 +- .../server/common/data/EntitySubtype.java | 2 +- .../server/common/data/EntityType.java | 2 +- .../server/common/data/EntityView.java | 2 +- .../server/common/data/EntityViewInfo.java | 2 +- .../server/common/data/EventInfo.java | 2 +- .../server/common/data/ExportableEntity.java | 2 +- .../server/common/data/FSTUtils.java | 2 +- .../server/common/data/FeaturesInfo.java | 2 +- .../server/common/data/FstStatsService.java | 28 + .../server/common/data/HasAdditionalInfo.java | 2 +- .../server/common/data/HasCustomerId.java | 2 +- .../server/common/data/HasEmail.java | 2 +- .../server/common/data/HasImage.java | 24 + .../server/common/data/HasLabel.java | 2 +- .../server/common/data/HasName.java | 2 +- .../server/common/data/HasOtaPackage.java | 2 +- .../common/data/HasRuleEngineProfile.java | 2 +- .../server/common/data/HasTenantId.java | 2 +- .../server/common/data/HasTitle.java | 2 +- .../server/common/data/HomeDashboard.java | 2 +- .../server/common/data/HomeDashboardInfo.java | 2 +- .../server/common/data/ImageDescriptor.java | 30 + .../server/common/data/ImageExportData.java | 41 + .../server/common/data/OtaPackage.java | 2 +- .../server/common/data/OtaPackageInfo.java | 2 +- .../server/common/data/ResourceType.java | 19 +- .../server/common/data/ResourceUtils.java | 2 +- .../SaveDeviceWithCredentialsRequest.java | 2 +- .../data/SaveOtaPackageInfoRequest.java | 2 +- .../server/common/data/ShortCustomerInfo.java | 2 +- .../server/common/data/StringUtils.java | 6 +- .../server/common/data/SystemInfo.java | 2 +- .../server/common/data/SystemInfoData.java | 2 +- .../server/common/data/SystemParams.java | 3 +- .../common/data/TbImageDeleteResult.java | 32 + .../server/common/data/TbProperty.java | 2 +- .../server/common/data/TbResource.java | 71 +- .../server/common/data/TbResourceInfo.java | 92 +- .../common/data/TbResourceInfoFilter.java | 6 +- .../common/data/TbTransportService.java | 2 +- .../server/common/data/Tenant.java | 2 +- .../server/common/data/TenantInfo.java | 2 +- .../server/common/data/TenantProfile.java | 2 +- .../server/common/data/TenantProfileType.java | 2 +- .../common/data/TransportPayloadType.java | 2 +- .../server/common/data/UUIDConverter.java | 2 +- .../server/common/data/UpdateMessage.java | 2 +- .../server/common/data/UsageInfo.java | 2 +- .../thingsboard/server/common/data/User.java | 2 +- .../server/common/data/UserEmailInfo.java | 2 +- .../server/common/data/alarm/Alarm.java | 2 +- .../common/data/alarm/AlarmApiCallResult.java | 2 +- .../common/data/alarm/AlarmAssignee.java | 2 +- .../data/alarm/AlarmAssigneeUpdate.java | 2 +- .../common/data/alarm/AlarmComment.java | 2 +- .../common/data/alarm/AlarmCommentInfo.java | 2 +- .../common/data/alarm/AlarmCommentType.java | 2 +- .../AlarmCreateOrUpdateActiveRequest.java | 2 +- .../server/common/data/alarm/AlarmInfo.java | 2 +- .../data/alarm/AlarmModificationRequest.java | 2 +- .../data/alarm/AlarmPropagationInfo.java | 2 +- .../server/common/data/alarm/AlarmQuery.java | 2 +- .../common/data/alarm/AlarmQueryV2.java | 2 +- .../common/data/alarm/AlarmSearchStatus.java | 2 +- .../common/data/alarm/AlarmSeverity.java | 2 +- .../server/common/data/alarm/AlarmStatus.java | 2 +- .../common/data/alarm/AlarmStatusFilter.java | 2 +- .../common/data/alarm/AlarmUpdateRequest.java | 2 +- .../server/common/data/alarm/EntityAlarm.java | 2 +- .../server/common/data/asset/Asset.java | 2 +- .../server/common/data/asset/AssetInfo.java | 2 +- .../common/data/asset/AssetProfile.java | 9 +- .../common/data/asset/AssetProfileInfo.java | 2 +- .../common/data/asset/AssetSearchQuery.java | 2 +- .../common/data/audit/ActionStatus.java | 2 +- .../server/common/data/audit/ActionType.java | 2 +- .../server/common/data/audit/AuditLog.java | 2 +- .../common/data/device/DeviceSearchQuery.java | 2 +- .../credentials/BasicMqttCredentials.java | 2 +- .../ProvisionDeviceCredentialsData.java | 2 +- ...wM2MBootstrapClientCredentialWithKeys.java | 2 +- .../lwm2m/AbstractLwM2MClientCredential.java | 2 +- ...AbstractLwM2MClientSecurityCredential.java | 2 +- .../lwm2m/LwM2MBootstrapClientCredential.java | 2 +- .../LwM2MBootstrapClientCredentials.java | 2 +- .../lwm2m/LwM2MClientCredential.java | 2 +- .../lwm2m/LwM2MDeviceCredentials.java | 2 +- .../credentials/lwm2m/LwM2MSecurityMode.java | 2 +- .../lwm2m/NoSecBootstrapClientCredential.java | 2 +- .../lwm2m/NoSecClientCredential.java | 2 +- .../lwm2m/PSKBootstrapClientCredential.java | 2 +- .../lwm2m/PSKClientCredential.java | 2 +- .../lwm2m/RPKBootstrapClientCredential.java | 2 +- .../lwm2m/RPKClientCredential.java | 2 +- .../lwm2m/X509BootstrapClientCredential.java | 2 +- .../lwm2m/X509ClientCredential.java | 2 +- .../CoapDeviceTransportConfiguration.java | 2 +- .../data/DefaultDeviceConfiguration.java | 2 +- .../DefaultDeviceTransportConfiguration.java | 2 +- .../data/device/data/DeviceConfiguration.java | 2 +- .../common/data/device/data/DeviceData.java | 2 +- .../data/DeviceTransportConfiguration.java | 2 +- .../Lwm2mDeviceTransportConfiguration.java | 2 +- .../MqttDeviceTransportConfiguration.java | 2 +- .../common/data/device/data/PowerMode.java | 2 +- .../device/data/PowerSavingConfiguration.java | 2 +- .../SnmpDeviceTransportConfiguration.java | 2 +- .../data/device/profile/AlarmCondition.java | 2 +- .../device/profile/AlarmConditionFilter.java | 2 +- .../profile/AlarmConditionFilterKey.java | 2 +- .../device/profile/AlarmConditionKeyType.java | 2 +- .../device/profile/AlarmConditionSpec.java | 2 +- .../profile/AlarmConditionSpecType.java | 2 +- .../common/data/device/profile/AlarmRule.java | 2 +- .../data/device/profile/AlarmSchedule.java | 2 +- .../device/profile/AlarmScheduleType.java | 2 +- ...esDeviceProfileProvisionConfiguration.java | 2 +- .../data/device/profile/AnyTimeSchedule.java | 2 +- ...esDeviceProfileProvisionConfiguration.java | 2 +- ...apDeviceProfileTransportConfiguration.java | 2 +- .../profile/CoapDeviceTypeConfiguration.java | 2 +- .../device/profile/CustomTimeSchedule.java | 2 +- .../profile/CustomTimeScheduleItem.java | 2 +- .../DefaultCoapDeviceTypeConfiguration.java | 2 +- .../DefaultDeviceProfileConfiguration.java | 2 +- ...ltDeviceProfileTransportConfiguration.java | 2 +- .../device/profile/DeviceProfileAlarm.java | 2 +- .../profile/DeviceProfileConfiguration.java | 2 +- .../device/profile/DeviceProfileData.java | 2 +- .../DeviceProfileProvisionConfiguration.java | 2 +- .../DeviceProfileTransportConfiguration.java | 2 +- ...edDeviceProfileProvisionConfiguration.java | 2 +- .../profile/DurationAlarmConditionSpec.java | 2 +- .../EfentoCoapDeviceTypeConfiguration.java | 2 +- .../JsonTransportPayloadConfiguration.java | 2 +- ...2mDeviceProfileTransportConfiguration.java | 2 +- ...ttDeviceProfileTransportConfiguration.java | 2 +- .../data/device/profile/MqttTopics.java | 2 +- .../ProtoTransportPayloadConfiguration.java | 2 +- .../ProvisionDeviceProfileCredentials.java | 2 +- .../profile/RepeatingAlarmConditionSpec.java | 2 +- .../profile/SimpleAlarmConditionSpec.java | 2 +- ...mpDeviceProfileTransportConfiguration.java | 2 +- .../device/profile/SpecificTimeSchedule.java | 2 +- .../TransportPayloadTypeConfiguration.java | 2 +- ...ertificateChainProvisionConfiguration.java | 3 +- .../profile/lwm2m/ObjectAttributes.java | 2 +- .../profile/lwm2m/OtherConfiguration.java | 2 +- .../lwm2m/TelemetryMappingConfiguration.java | 2 +- ...bstractLwM2MBootstrapServerCredential.java | 2 +- .../LwM2MBootstrapServerCredential.java | 2 +- .../bootstrap/LwM2MServerSecurityConfig.java | 2 +- .../LwM2MServerSecurityConfigDefault.java | 2 +- .../NoSecLwM2MBootstrapServerCredential.java | 2 +- .../PSKLwM2MBootstrapServerCredential.java | 2 +- .../RPKLwM2MBootstrapServerCredential.java | 2 +- .../X509LwM2MBootstrapServerCredential.java | 2 +- .../server/common/data/edge/Edge.java | 2 +- .../server/common/data/edge/EdgeEvent.java | 4 +- .../common/data/edge/EdgeEventActionType.java | 59 +- .../common/data/edge/EdgeEventType.java | 3 +- .../server/common/data/edge/EdgeInfo.java | 2 +- ...nstructions.java => EdgeInstructions.java} | 8 +- .../common/data/edge/EdgeSearchQuery.java | 2 +- .../entityview/EntityViewSearchQuery.java | 2 +- .../common/data/event/DebugEventFilter.java | 2 +- .../server/common/data/event/ErrorEvent.java | 2 +- .../common/data/event/ErrorEventFilter.java | 2 +- .../server/common/data/event/Event.java | 2 +- .../server/common/data/event/EventFilter.java | 2 +- .../server/common/data/event/EventType.java | 2 +- .../data/event/LifeCycleEventFilter.java | 2 +- .../common/data/event/LifecycleEvent.java | 2 +- .../data/event/RuleChainDebugEvent.java | 2 +- .../data/event/RuleChainDebugEventFilter.java | 2 +- .../common/data/event/RuleNodeDebugEvent.java | 2 +- .../data/event/RuleNodeDebugEventFilter.java | 2 +- .../common/data/event/StatisticsEvent.java | 2 +- .../data/event/StatisticsEventFilter.java | 2 +- .../exception/AbstractRateLimitException.java | 2 +- .../ApiUsageLimitsExceededException.java | 2 +- .../exception/TenantNotFoundException.java | 2 +- .../TenantProfileNotFoundException.java | 2 +- .../data/exception/ThingsboardErrorCode.java | 5 +- .../data/exception/ThingsboardException.java | 2 +- .../ThingsboardKafkaClientError.java | 2 +- .../common/data/id/AdminSettingsId.java | 2 +- .../server/common/data/id/AlarmCommentId.java | 2 +- .../server/common/data/id/AlarmId.java | 2 +- .../common/data/id/ApiUsageStateId.java | 2 +- .../server/common/data/id/AssetId.java | 2 +- .../server/common/data/id/AssetProfileId.java | 2 +- .../server/common/data/id/AuditLogId.java | 2 +- .../common/data/id/ComponentDescriptorId.java | 2 +- .../server/common/data/id/CustomerId.java | 2 +- .../server/common/data/id/DashboardId.java | 2 +- .../common/data/id/DeviceCredentialsId.java | 2 +- .../server/common/data/id/DeviceId.java | 2 +- .../common/data/id/DeviceProfileId.java | 2 +- .../server/common/data/id/EdgeEventId.java | 2 +- .../server/common/data/id/EdgeId.java | 2 +- .../server/common/data/id/EntityId.java | 2 +- .../common/data/id/EntityIdDeserializer.java | 2 +- .../common/data/id/EntityIdFactory.java | 2 +- .../common/data/id/EntityIdSerializer.java | 2 +- .../server/common/data/id/EntityViewId.java | 2 +- .../server/common/data/id/EventId.java | 2 +- .../server/common/data/id/HasId.java | 2 +- .../server/common/data/id/HasUUID.java | 2 +- .../server/common/data/id/IdBased.java | 2 +- .../data/id/NameLabelAndCustomerDetails.java | 2 +- .../server/common/data/id/NodeId.java | 2 +- .../server/common/data/id/NotificationId.java | 2 +- .../common/data/id/NotificationRequestId.java | 2 +- .../common/data/id/NotificationRuleId.java | 2 +- .../common/data/id/NotificationTargetId.java | 2 +- .../data/id/NotificationTemplateId.java | 2 +- .../OAuth2ClientRegistrationTemplateId.java | 2 +- .../server/common/data/id/OAuth2DomainId.java | 2 +- .../server/common/data/id/OAuth2MobileId.java | 2 +- .../server/common/data/id/OAuth2ParamsId.java | 2 +- .../common/data/id/OAuth2RegistrationId.java | 2 +- .../server/common/data/id/OtaPackageId.java | 2 +- .../server/common/data/id/QueueId.java | 2 +- .../server/common/data/id/RpcId.java | 2 +- .../server/common/data/id/RuleChainId.java | 2 +- .../server/common/data/id/RuleNodeId.java | 2 +- .../common/data/id/RuleNodeStateId.java | 2 +- .../server/common/data/id/TbResourceId.java | 2 +- .../server/common/data/id/TenantId.java | 2 +- .../common/data/id/TenantProfileId.java | 2 +- .../server/common/data/id/UUIDBased.java | 2 +- .../common/data/id/UserAuthSettingsId.java | 2 +- .../common/data/id/UserCredentialsId.java | 8 +- .../server/common/data/id/UserId.java | 2 +- .../server/common/data/id/WidgetTypeId.java | 2 +- .../common/data/id/WidgetsBundleId.java | 2 +- .../server/common/data/kv/AggTsKvEntry.java | 2 +- .../server/common/data/kv/Aggregation.java | 2 +- .../common/data/kv/AggregationParams.java | 92 + .../server/common/data/kv/AttributeKey.java | 2 +- .../common/data/kv/AttributeKvEntry.java | 2 +- .../common/data/kv/BaseAttributeKvEntry.java | 2 +- .../common/data/kv/BaseDeleteTsKvQuery.java | 2 +- .../common/data/kv/BaseReadTsKvQuery.java | 28 +- .../server/common/data/kv/BaseTsKvQuery.java | 2 +- .../server/common/data/kv/BasicKvEntry.java | 2 +- .../server/common/data/kv/BasicTsKvEntry.java | 2 +- .../common/data/kv/BooleanDataEntry.java | 2 +- .../server/common/data/kv/DataType.java | 2 +- .../common/data/kv/DeleteTsKvQuery.java | 2 +- .../common/data/kv/DoubleDataEntry.java | 2 +- .../server/common/data/kv/IntervalType.java | 22 + .../server/common/data/kv/JsonDataEntry.java | 2 +- .../server/common/data/kv/KvEntry.java | 2 +- .../server/common/data/kv/LongDataEntry.java | 2 +- .../server/common/data/kv/ReadTsKvQuery.java | 14 +- .../common/data/kv/ReadTsKvQueryResult.java | 2 +- .../common/data/kv/StringDataEntry.java | 2 +- .../server/common/data/kv/TsKvEntry.java | 2 +- .../common/data/kv/TsKvEntryAggWrapper.java | 2 +- .../data/kv/TsKvLatestRemovingResult.java | 2 +- .../server/common/data/kv/TsKvQuery.java | 2 +- .../server/common/data/limit/LimitedApi.java | 2 +- .../common/data/lwm2m/LwM2mConstants.java | 2 +- .../common/data/lwm2m/LwM2mInstance.java | 2 +- .../server/common/data/lwm2m/LwM2mObject.java | 2 +- .../data/lwm2m/LwM2mResourceObserve.java | 2 +- .../common/data/mail/MailOauth2Provider.java | 2 +- .../server/common/data/msg/TbMsgType.java | 2 +- .../common/data/msg/TbNodeConnectionType.java | 2 +- .../notification/AlreadySentException.java | 2 +- .../data/notification/Notification.java | 2 +- .../NotificationDeliveryMethod.java | 2 +- .../notification/NotificationRequest.java | 2 +- .../NotificationRequestConfig.java | 2 +- .../notification/NotificationRequestInfo.java | 2 +- .../NotificationRequestPreview.java | 2 +- .../NotificationRequestStats.java | 2 +- .../NotificationRequestStatus.java | 2 +- .../data/notification/NotificationStatus.java | 2 +- .../data/notification/NotificationType.java | 2 +- .../info/AlarmAssignmentNotificationInfo.java | 2 +- .../info/AlarmCommentNotificationInfo.java | 2 +- .../info/AlarmNotificationInfo.java | 2 +- .../info/ApiUsageLimitNotificationInfo.java | 2 +- .../info/DeviceActivityNotificationInfo.java | 2 +- .../info/EntitiesLimitNotificationInfo.java | 2 +- .../info/EntityActionNotificationInfo.java | 2 +- .../NewPlatformVersionNotificationInfo.java | 2 +- .../notification/info/NotificationInfo.java | 2 +- .../info/RateLimitsNotificationInfo.java | 2 +- ...mponentLifecycleEventNotificationInfo.java | 2 +- .../RuleEngineOriginatedNotificationInfo.java | 12 +- .../info/RuleOriginatedNotificationInfo.java | 2 +- ...faultNotificationRuleRecipientsConfig.java | 2 +- ...latedNotificationRuleRecipientsConfig.java | 2 +- .../notification/rule/NotificationRule.java | 2 +- .../rule/NotificationRuleConfig.java | 2 +- .../rule/NotificationRuleInfo.java | 2 +- .../NotificationRuleRecipientsConfig.java | 2 +- .../rule/trigger/AlarmAssignmentTrigger.java | 2 +- .../rule/trigger/AlarmCommentTrigger.java | 2 +- .../rule/trigger/AlarmTrigger.java | 2 +- .../rule/trigger/ApiUsageLimitTrigger.java | 2 +- .../rule/trigger/DeviceActivityTrigger.java | 2 +- .../rule/trigger/EntitiesLimitTrigger.java | 2 +- .../rule/trigger/EntityActionTrigger.java | 2 +- .../trigger/NewPlatformVersionTrigger.java | 2 +- .../rule/trigger/NotificationRuleTrigger.java | 2 +- .../rule/trigger/RateLimitsTrigger.java | 2 +- ...eEngineComponentLifecycleEventTrigger.java | 2 +- ...signmentNotificationRuleTriggerConfig.java | 2 +- ...mCommentNotificationRuleTriggerConfig.java | 2 +- .../AlarmNotificationRuleTriggerConfig.java | 2 +- ...ageLimitNotificationRuleTriggerConfig.java | 2 +- ...ActivityNotificationRuleTriggerConfig.java | 2 +- ...iesLimitNotificationRuleTriggerConfig.java | 2 +- ...tyActionNotificationRuleTriggerConfig.java | 2 +- ...mVersionNotificationRuleTriggerConfig.java | 2 +- .../config/NotificationRuleTriggerConfig.java | 2 +- .../config/NotificationRuleTriggerType.java | 2 +- ...teLimitsNotificationRuleTriggerConfig.java | 2 +- ...cleEventNotificationRuleTriggerConfig.java | 2 +- .../settings/AccountNotificationSettings.java | 2 +- .../NotificationDeliveryMethodConfig.java | 2 +- .../settings/NotificationSettings.java | 2 +- ...SlackNotificationDeliveryMethodConfig.java | 2 +- .../settings/UserNotificationSettings.java | 2 +- ...icrosoftTeamsNotificationTargetConfig.java | 2 +- .../targets/NotificationRecipient.java | 2 +- .../targets/NotificationTarget.java | 2 +- .../targets/NotificationTargetConfig.java | 2 +- .../targets/NotificationTargetType.java | 2 +- .../AffectedTenantAdministratorsFilter.java | 2 +- .../targets/platform/AffectedUserFilter.java | 2 +- .../targets/platform/AllUsersFilter.java | 2 +- .../targets/platform/CustomerUsersFilter.java | 2 +- .../OriginatorEntityOwnerUsersFilter.java | 2 +- ...PlatformUsersNotificationTargetConfig.java | 2 +- .../platform/SystemAdministratorsFilter.java | 2 +- .../platform/TenantAdministratorsFilter.java | 2 +- .../targets/platform/UserListFilter.java | 2 +- .../targets/platform/UsersFilter.java | 2 +- .../targets/platform/UsersFilterType.java | 2 +- .../targets/slack/SlackConversation.java | 2 +- .../targets/slack/SlackConversationType.java | 2 +- .../slack/SlackNotificationTargetConfig.java | 2 +- .../DeliveryMethodNotificationTemplate.java | 2 +- ...ailDeliveryMethodNotificationTemplate.java | 2 +- .../notification/template/HasSubject.java | 2 +- ...amsDeliveryMethodNotificationTemplate.java | 2 +- .../template/NotificationTemplate.java | 2 +- .../template/NotificationTemplateConfig.java | 2 +- ...ackDeliveryMethodNotificationTemplate.java | 2 +- ...SmsDeliveryMethodNotificationTemplate.java | 2 +- .../template/TemplatableValue.java | 2 +- ...WebDeliveryMethodNotificationTemplate.java | 2 +- .../server/common/data/oauth2/MapperType.java | 2 +- .../data/oauth2/OAuth2BasicMapperConfig.java | 2 +- .../common/data/oauth2/OAuth2ClientInfo.java | 2 +- .../OAuth2ClientRegistrationTemplate.java | 2 +- .../data/oauth2/OAuth2CustomMapperConfig.java | 2 +- .../common/data/oauth2/OAuth2Domain.java | 2 +- .../common/data/oauth2/OAuth2DomainInfo.java | 2 +- .../server/common/data/oauth2/OAuth2Info.java | 2 +- .../data/oauth2/OAuth2MapperConfig.java | 2 +- .../common/data/oauth2/OAuth2Mobile.java | 2 +- .../common/data/oauth2/OAuth2MobileInfo.java | 2 +- .../common/data/oauth2/OAuth2Params.java | 2 +- .../common/data/oauth2/OAuth2ParamsInfo.java | 2 +- .../data/oauth2/OAuth2Registration.java | 2 +- .../data/oauth2/OAuth2RegistrationInfo.java | 2 +- .../common/data/oauth2/PlatformType.java | 2 +- .../server/common/data/oauth2/SchemeType.java | 2 +- .../data/oauth2/TenantNameStrategyType.java | 2 +- .../data/objects/AttributesEntityView.java | 2 +- .../data/objects/TelemetryEntityView.java | 2 +- .../common/data/ota/ChecksumAlgorithm.java | 2 +- .../server/common/data/ota/OtaPackageKey.java | 2 +- .../common/data/ota/OtaPackageType.java | 2 +- .../data/ota/OtaPackageUpdateStatus.java | 2 +- .../common/data/ota/OtaPackageUtil.java | 2 +- .../data/page/BasePageDataIterable.java | 7 +- .../server/common/data/page/PageData.java | 2 +- .../common/data/page/PageDataIterable.java | 2 +- .../data/page/PageDataIterableByTenant.java | 2 +- .../PageDataIterableByTenantIdEntityId.java | 2 +- .../server/common/data/page/PageLink.java | 2 +- .../server/common/data/page/SortOrder.java | 2 +- .../server/common/data/page/TimePageLink.java | 2 +- .../data/plugin/ComponentClusteringMode.java | 2 +- .../data/plugin/ComponentDescriptor.java | 9 +- .../data/plugin/ComponentLifecycleEvent.java | 2 +- .../data/plugin/ComponentLifecycleState.java | 2 +- .../common/data/plugin/ComponentScope.java | 2 +- .../common/data/plugin/ComponentType.java | 2 +- .../common/data/query/AbstractDataQuery.java | 2 +- .../common/data/query/AlarmCountQuery.java | 2 +- .../server/common/data/query/AlarmData.java | 2 +- .../common/data/query/AlarmDataPageLink.java | 2 +- .../common/data/query/AlarmDataQuery.java | 2 +- .../data/query/ApiUsageStateFilter.java | 2 +- .../data/query/AssetSearchQueryFilter.java | 2 +- .../common/data/query/AssetTypeFilter.java | 2 +- .../data/query/BooleanFilterPredicate.java | 2 +- .../common/data/query/ComparisonTsValue.java | 2 +- .../data/query/ComplexFilterPredicate.java | 2 +- .../data/query/DeviceSearchQueryFilter.java | 2 +- .../common/data/query/DeviceTypeFilter.java | 2 +- .../common/data/query/DynamicValue.java | 2 +- .../data/query/DynamicValueSourceType.java | 2 +- .../data/query/EdgeSearchQueryFilter.java | 2 +- .../common/data/query/EdgeTypeFilter.java | 2 +- .../common/data/query/EntityCountQuery.java | 2 +- .../server/common/data/query/EntityData.java | 2 +- .../common/data/query/EntityDataPageLink.java | 2 +- .../common/data/query/EntityDataQuery.java | 2 +- .../data/query/EntityDataSortOrder.java | 2 +- .../common/data/query/EntityFilter.java | 2 +- .../common/data/query/EntityFilterType.java | 2 +- .../server/common/data/query/EntityKey.java | 2 +- .../common/data/query/EntityKeyType.java | 2 +- .../common/data/query/EntityKeyValueType.java | 2 +- .../common/data/query/EntityListFilter.java | 2 +- .../common/data/query/EntityNameFilter.java | 2 +- .../data/query/EntitySearchQueryFilter.java | 2 +- .../common/data/query/EntityTypeFilter.java | 2 +- .../query/EntityViewSearchQueryFilter.java | 2 +- .../data/query/EntityViewTypeFilter.java | 2 +- .../data/query/FilterPredicateType.java | 2 +- .../data/query/FilterPredicateValue.java | 2 +- .../server/common/data/query/KeyFilter.java | 2 +- .../common/data/query/KeyFilterPredicate.java | 2 +- .../data/query/NumericFilterPredicate.java | 2 +- .../data/query/RelationsQueryFilter.java | 2 +- .../data/query/SimpleKeyFilterPredicate.java | 2 +- .../common/data/query/SingleEntityFilter.java | 2 +- .../data/query/StringFilterPredicate.java | 2 +- .../server/common/data/query/TsValue.java | 2 +- .../common/data/queue/ProcessingStrategy.java | 2 +- .../data/queue/ProcessingStrategyType.java | 2 +- .../server/common/data/queue/Queue.java | 2 +- .../common/data/queue/SubmitStrategy.java | 2 +- .../common/data/queue/SubmitStrategyType.java | 2 +- .../common/data/relation/EntityRelation.java | 2 +- .../data/relation/EntityRelationInfo.java | 2 +- .../data/relation/EntityRelationsQuery.java | 2 +- .../data/relation/EntitySearchDirection.java | 2 +- .../relation/RelationEntityTypeFilter.java | 2 +- .../data/relation/RelationTypeGroup.java | 2 +- .../relation/RelationsSearchParameters.java | 2 +- .../server/common/data/rpc/Rpc.java | 2 +- .../server/common/data/rpc/RpcError.java | 2 +- .../server/common/data/rpc/RpcStatus.java | 2 +- .../data/rpc/ToDeviceRpcRequestBody.java | 2 +- .../rule/DefaultRuleChainCreateRequest.java | 2 +- .../common/data/rule/NodeConnectionInfo.java | 2 +- .../server/common/data/rule/RuleChain.java | 2 +- .../data/rule/RuleChainConnectionInfo.java | 2 +- .../common/data/rule/RuleChainData.java | 2 +- .../data/rule/RuleChainImportResult.java | 2 +- .../common/data/rule/RuleChainMetaData.java | 2 +- .../data/rule/RuleChainOutputLabelsUsage.java | 2 +- .../common/data/rule/RuleChainType.java | 2 +- .../data/rule/RuleChainUpdateResult.java | 2 +- .../server/common/data/rule/RuleNode.java | 8 +- .../common/data/rule/RuleNodeState.java | 2 +- .../data/rule/RuleNodeUpdateResult.java | 2 +- .../server/common/data/rule/RuleType.java | 2 +- .../server/common/data/rule/Scope.java | 2 +- .../common/data/script/ScriptLanguage.java | 2 +- .../common/data/security/Authority.java | 2 +- .../data/security/DeviceCredentials.java | 2 +- .../security/DeviceCredentialsFilter.java | 2 +- .../data/security/DeviceCredentialsType.java | 2 +- .../data/security/DeviceTokenCredentials.java | 2 +- .../data/security/DeviceX509Credentials.java | 2 +- .../data/security/UserAuthSettings.java | 2 +- .../common/data/security/UserCredentials.java | 2 +- .../event/UserAuthDataChangedEvent.java | 2 +- .../UserCredentialsInvalidationEvent.java | 2 +- .../event/UserSessionInvalidationEvent.java | 2 +- .../common/data/security/model/JwtPair.java | 2 +- .../data/security/model/JwtSettings.java | 2 +- .../common/data/security/model/JwtToken.java | 2 +- .../data/security/model/SecuritySettings.java | 2 +- .../security/model/UserPasswordPolicy.java | 6 +- .../model/mfa/PlatformTwoFaSettings.java | 2 +- .../mfa/account/AccountTwoFaSettings.java | 2 +- .../account/BackupCodeTwoFaAccountConfig.java | 2 +- .../mfa/account/EmailTwoFaAccountConfig.java | 2 +- .../account/OtpBasedTwoFaAccountConfig.java | 2 +- .../mfa/account/SmsTwoFaAccountConfig.java | 2 +- .../mfa/account/TotpTwoFaAccountConfig.java | 2 +- .../model/mfa/account/TwoFaAccountConfig.java | 2 +- .../BackupCodeTwoFaProviderConfig.java | 2 +- .../provider/EmailTwoFaProviderConfig.java | 2 +- .../provider/OtpBasedTwoFaProviderConfig.java | 2 +- .../mfa/provider/SmsTwoFaProviderConfig.java | 2 +- .../mfa/provider/TotpTwoFaProviderConfig.java | 2 +- .../mfa/provider/TwoFaProviderConfig.java | 2 +- .../model/mfa/provider/TwoFaProviderType.java | 2 +- .../settings/AbstractUserDashboardInfo.java | 2 +- .../settings/LastVisitedDashboardInfo.java | 2 +- .../data/settings/StarredDashboardInfo.java | 2 +- .../data/settings/UserDashboardAction.java | 2 +- .../data/settings/UserDashboardsInfo.java | 2 +- .../common/data/settings/UserSettings.java | 2 +- .../settings/UserSettingsCompositeKey.java | 2 +- .../data/settings/UserSettingsType.java | 2 +- .../AwsSnsSmsProviderConfiguration.java | 2 +- .../config/SmppSmsProviderConfiguration.java | 2 +- .../sms/config/SmsProviderConfiguration.java | 2 +- .../data/sms/config/SmsProviderType.java | 2 +- .../data/sms/config/TestSmsRequest.java | 2 +- .../TwilioSmsProviderConfiguration.java | 2 +- .../server/common/data/sync/JsonTbEntity.java | 6 +- .../data/sync/ie/AttributeExportData.java | 2 +- .../common/data/sync/ie/DeviceExportData.java | 2 +- .../common/data/sync/ie/EntityExportData.java | 2 +- .../data/sync/ie/EntityExportSettings.java | 2 +- .../data/sync/ie/EntityImportResult.java | 2 +- .../data/sync/ie/EntityImportSettings.java | 2 +- .../data/sync/ie/RuleChainExportData.java | 2 +- .../data/sync/ie/WidgetTypeExportData.java | 2 +- .../data/sync/ie/WidgetsBundleExportData.java | 2 +- .../importing/csv/BulkImportColumnType.java | 2 +- .../ie/importing/csv/BulkImportRequest.java | 2 +- .../ie/importing/csv/BulkImportResult.java | 2 +- .../data/sync/vc/AutoCommitSettings.java | 2 +- .../common/data/sync/vc/BranchInfo.java | 2 +- .../common/data/sync/vc/EntityDataDiff.java | 2 +- .../common/data/sync/vc/EntityDataInfo.java | 2 +- .../common/data/sync/vc/EntityLoadError.java | 2 +- .../data/sync/vc/EntityTypeLoadResult.java | 2 +- .../common/data/sync/vc/EntityVersion.java | 2 +- .../data/sync/vc/EntityVersionsDiff.java | 2 +- .../data/sync/vc/RepositoryAuthMethod.java | 2 +- .../data/sync/vc/RepositorySettings.java | 2 +- .../data/sync/vc/RepositorySettingsInfo.java | 2 +- .../server/common/data/sync/vc/VcUtils.java | 2 +- .../data/sync/vc/VersionCreationResult.java | 2 +- .../data/sync/vc/VersionLoadResult.java | 2 +- .../data/sync/vc/VersionedEntityInfo.java | 2 +- .../create/AutoVersionCreateConfig.java | 2 +- .../create/ComplexVersionCreateRequest.java | 2 +- .../create/EntityTypeVersionCreateConfig.java | 2 +- .../SingleEntityVersionCreateRequest.java | 2 +- .../sync/vc/request/create/SyncStrategy.java | 2 +- .../request/create/VersionCreateConfig.java | 2 +- .../request/create/VersionCreateRequest.java | 2 +- .../create/VersionCreateRequestType.java | 2 +- .../load/EntityTypeVersionLoadConfig.java | 2 +- .../load/EntityTypeVersionLoadRequest.java | 2 +- .../load/SingleEntityVersionLoadRequest.java | 2 +- .../vc/request/load/VersionLoadConfig.java | 2 +- .../vc/request/load/VersionLoadRequest.java | 2 +- .../request/load/VersionLoadRequestType.java | 2 +- .../DefaultTenantProfileConfiguration.java | 3 +- .../profile/TenantProfileConfiguration.java | 2 +- .../tenant/profile/TenantProfileData.java | 2 +- .../TenantProfileQueueConfiguration.java | 2 +- .../data/transport/resource/ResourceType.java | 2 +- .../snmp/AuthenticationProtocol.java | 2 +- .../data/transport/snmp/PrivacyProtocol.java | 2 +- .../transport/snmp/SnmpCommunicationSpec.java | 2 +- .../data/transport/snmp/SnmpMapping.java | 2 +- .../data/transport/snmp/SnmpMethod.java | 2 +- .../transport/snmp/SnmpProtocolVersion.java | 2 +- ...ltipleMappingsSnmpCommunicationConfig.java | 2 +- ...eatingQueryingSnmpCommunicationConfig.java | 2 +- .../snmp/config/SnmpCommunicationConfig.java | 2 +- ...rverRpcRequestSnmpCommunicationConfig.java | 2 +- ...ibutesQueryingSnmpCommunicationConfig.java | 2 +- ...ributesSettingSnmpCommunicationConfig.java | 2 +- ...emetryQueryingSnmpCommunicationConfig.java | 2 +- ...viceRpcRequestSnmpCommunicationConfig.java | 2 +- .../common/data/util/CollectionsUtil.java | 2 +- .../common/data/util/ReflectionUtils.java | 2 +- .../common/data/util/TbDDFFileParser.java | 2 +- .../server/common/data/util/TbPair.java | 2 +- .../common/data/util/TemplateUtils.java | 2 +- .../common/data/util/ThrowingBiFunction.java | 2 +- .../common/data/util/ThrowingRunnable.java | 2 +- .../common/data/util/ThrowingSupplier.java | 23 + .../server/common/data/util/TypeCastUtil.java | 2 +- .../server/common/data/validation/Length.java | 2 +- .../server/common/data/validation/NoXss.java | 2 +- .../common/data/widget/BaseWidgetType.java | 2 +- .../common/data/widget/DeprecatedFilter.java | 2 +- .../server/common/data/widget/WidgetType.java | 2 +- .../common/data/widget/WidgetTypeDetails.java | 8 +- .../common/data/widget/WidgetTypeInfo.java | 2 +- .../common/data/widget/WidgetsBundle.java | 9 +- .../data/widget/WidgetsBundleWidget.java | 2 +- .../common/data/DynamicProtoUtilsTest.java | 2 +- .../server/common/data/EntityTypeTest.java | 2 +- .../server/common/data/StringUtilsTest.java | 2 +- .../server/common/data/UUIDConverterTest.java | 2 +- .../common/data/audit/ActionTypeTest.java | 2 +- .../server/common/data/id/EntityIdTest.java | 2 +- .../server/common/data/msg/TbMsgTypeTest.java | 2 +- .../server/common/data/rpc/RpcStatusTest.java | 2 +- common/edge-api/pom.xml | 4 +- .../exception/EdgeConnectionException.java | 2 +- .../thingsboard/edge/rpc/EdgeGrpcClient.java | 4 +- .../thingsboard/edge/rpc/EdgeRpcClient.java | 2 +- common/edge-api/src/main/proto/edge.proto | 427 +- common/message/pom.xml | 4 +- .../server/common/msg/EncryptionUtil.java | 2 +- .../server/common/msg/MsgType.java | 4 +- .../server/common/msg/TbActorError.java | 2 +- .../server/common/msg/TbActorMsg.java | 2 +- .../server/common/msg/TbActorStopReason.java | 2 +- .../thingsboard/server/common/msg/TbMsg.java | 2 +- .../server/common/msg/TbMsgDataType.java | 2 +- .../server/common/msg/TbMsgMetaData.java | 2 +- .../server/common/msg/TbMsgProcessingCtx.java | 2 +- .../common/msg/TbMsgProcessingStackItem.java | 2 +- .../common/msg/TbRuleEngineActorMsg.java | 2 +- .../msg/ToDeviceActorNotificationMsg.java | 2 +- .../common/msg/aware/CustomerAwareMsg.java | 2 +- .../common/msg/aware/DeviceAwareMsg.java | 2 +- .../server/common/msg/aware/NodeAwareMsg.java | 2 +- .../common/msg/aware/RuleChainAwareMsg.java | 2 +- .../common/msg/aware/TenantAwareMsg.java | 2 +- .../common/msg/cluster/ToAllNodesMsg.java | 2 +- .../common/msg/edge/EdgeEventUpdateMsg.java | 2 +- .../common/msg/edge/EdgeSessionMsg.java | 2 +- .../common/msg/edge/FromEdgeSyncResponse.java | 2 +- .../common/msg/edge/ToEdgeSyncRequest.java | 2 +- .../NotificationRuleProcessor.java | 2 +- .../plugin/ComponentLifecycleListener.java | 2 +- .../msg/plugin/ComponentLifecycleMsg.java | 2 +- .../common/msg/plugin/RuleNodeUpdatedMsg.java | 2 +- .../common/msg/queue/PartitionChangeMsg.java | 2 +- .../msg/queue/QueueToRuleEngineMsg.java | 2 +- .../common/msg/queue/RuleEngineException.java | 2 +- .../common/msg/queue/RuleNodeException.java | 2 +- .../server/common/msg/queue/RuleNodeInfo.java | 2 +- .../server/common/msg/queue/ServiceType.java | 2 +- .../server/common/msg/queue/TbCallback.java | 2 +- .../common/msg/queue/TbMsgCallback.java | 2 +- .../common/msg/queue/TopicPartitionInfo.java | 2 +- .../common/msg/rpc/FromDeviceRpcResponse.java | 2 +- .../rpc/FromDeviceRpcResponseActorMsg.java | 2 +- .../common/msg/rpc/RemoveRpcActorMsg.java | 2 +- .../common/msg/rpc/ToDeviceRpcRequest.java | 2 +- .../msg/rpc/ToDeviceRpcRequestActorMsg.java | 2 +- .../msg/rule/engine/DeviceAttributes.java | 2 +- .../DeviceAttributesEventNotificationMsg.java | 2 +- ...eviceCredentialsUpdateNotificationMsg.java | 2 +- .../msg/rule/engine/DeviceDeleteMsg.java | 36 + .../msg/rule/engine/DeviceEdgeUpdateMsg.java | 2 +- .../msg/rule/engine/DeviceMetaData.java | 2 +- .../engine/DeviceNameOrTypeUpdateMsg.java | 2 +- .../common/msg/session/FeatureType.java | 2 +- .../common/msg/session/SessionMsgType.java | 2 +- .../ex/ProcessingTimeoutException.java | 2 +- .../msg/session/ex/SessionAuthException.java | 2 +- .../msg/session/ex/SessionException.java | 2 +- .../DeviceActorServerSideRpcTimeoutMsg.java | 2 +- .../server/common/msg/timeout/TimeoutMsg.java | 2 +- .../common/msg/tools/SchedulerUtils.java | 2 +- .../server/common/msg/tools/TbRateLimits.java | 2 +- .../msg/tools/TbRateLimitsException.java | 2 +- common/message/src/main/proto/tbmsg.proto | 2 +- .../server/common/msg/TbMsgMetaDataTest.java | 2 +- .../msg/TbMsgProcessingStackItemTest.java | 2 +- .../msg/queue/TopicPartitionInfoTest.java | 2 +- .../common/msg/tools/RateLimitsTest.java | 2 +- common/pom.xml | 5 +- common/proto/pom.xml | 94 + .../common}/adaptor/AdaptorException.java | 4 +- .../server/common}/adaptor/JsonConverter.java | 4 +- .../common}/adaptor/JsonConverterConfig.java | 4 +- .../common}/adaptor/ProtoConverter.java | 4 +- .../server/common/util}/ProtoUtils.java | 26 +- .../src/main/proto/jsinvoke.proto | 2 +- .../src/main/proto/queue.proto | 28 +- .../src/main/proto/transport.proto | 2 +- .../common/adaptor}/JsonConverterTest.java | 5 +- .../server/common/util}/ProtoUtilsTest.java | 4 +- common/queue/pom.xml | 4 +- .../queue/RuleEngineTbQueueAdminFactory.java | 2 +- .../azure/servicebus/TbServiceBusAdmin.java | 2 +- .../TbServiceBusConsumerTemplate.java | 2 +- .../TbServiceBusProducerTemplate.java | 2 +- .../servicebus/TbServiceBusQueueConfigs.java | 2 +- .../servicebus/TbServiceBusSettings.java | 2 +- ...stractParallelTbQueueConsumerTemplate.java | 2 +- .../AbstractTbQueueConsumerTemplate.java | 2 +- .../queue/common/AbstractTbQueueTemplate.java | 2 +- .../queue/common/AsyncCallbackTemplate.java | 2 +- .../queue/common/DefaultTbQueueMsg.java | 2 +- .../common/DefaultTbQueueMsgHeaders.java | 2 +- .../common/DefaultTbQueueRequestTemplate.java | 2 +- .../DefaultTbQueueResponseTemplate.java | 2 +- .../MultipleTbQueueCallbackWrapper.java | 2 +- .../MultipleTbQueueTbMsgCallbackWrapper.java | 2 +- .../queue/common/TbProtoJsQueueMsg.java | 2 +- .../server/queue/common/TbProtoQueueMsg.java | 2 +- .../common/TbQueueTbMsgCallbackWrapper.java | 2 +- .../queue/discovery/ConsistentHashCircle.java | 2 +- .../DefaultTbServiceInfoProvider.java | 2 +- .../queue/discovery/DiscoveryService.java | 2 +- .../discovery/DummyDiscoveryService.java | 2 +- .../queue/discovery/HashPartitionService.java | 2 +- .../queue/discovery/PartitionService.java | 2 +- .../server/queue/discovery/QueueKey.java | 2 +- .../queue/discovery/QueueRoutingInfo.java | 2 +- .../discovery/QueueRoutingInfoService.java | 2 +- .../discovery/TbApplicationEventListener.java | 2 +- .../discovery/TbServiceInfoProvider.java | 2 +- .../queue/discovery/TenantRoutingInfo.java | 2 +- .../discovery/TenantRoutingInfoService.java | 2 +- .../server/queue/discovery/TopicService.java | 2 +- .../queue/discovery/ZkDiscoveryService.java | 2 +- .../event/ClusterTopologyChangeEvent.java | 2 +- .../event/OtherServiceShutdownEvent.java | 2 +- .../discovery/event/PartitionChangeEvent.java | 2 +- .../event/ServiceListChangedEvent.java | 2 +- .../discovery/event/TbApplicationEvent.java | 2 +- .../environment/EnvironmentLogService.java | 2 +- .../server/queue/kafka/KafkaTbQueueMsg.java | 2 +- .../queue/kafka/KafkaTbQueueMsgMetadata.java | 2 +- .../server/queue/kafka/TbKafkaAdmin.java | 2 +- .../kafka/TbKafkaConsumerStatisticConfig.java | 2 +- .../kafka/TbKafkaConsumerStatsService.java | 2 +- .../queue/kafka/TbKafkaConsumerTemplate.java | 2 +- .../server/queue/kafka/TbKafkaDecoder.java | 2 +- .../server/queue/kafka/TbKafkaEncoder.java | 2 +- .../queue/kafka/TbKafkaProducerTemplate.java | 2 +- .../server/queue/kafka/TbKafkaSettings.java | 2 +- .../queue/kafka/TbKafkaTopicConfigs.java | 2 +- .../queue/memory/DefaultInMemoryStorage.java | 2 +- .../server/queue/memory/InMemoryStorage.java | 2 +- .../queue/memory/InMemoryTbQueueConsumer.java | 2 +- .../queue/memory/InMemoryTbQueueProducer.java | 2 +- ...faultNotificationDeduplicationService.java | 2 +- .../NotificationDeduplicationService.java | 2 +- .../RemoteNotificationRuleProcessor.java | 2 +- .../provider/AwsSqsMonolithQueueFactory.java | 2 +- .../provider/AwsSqsTbCoreQueueFactory.java | 2 +- .../AwsSqsTbRuleEngineQueueFactory.java | 2 +- .../AwsSqsTbVersionControlQueueFactory.java | 2 +- .../provider/AwsSqsTransportQueueFactory.java | 2 +- .../InMemoryMonolithQueueFactory.java | 2 +- .../InMemoryTbTransportQueueFactory.java | 2 +- .../provider/KafkaMonolithQueueFactory.java | 2 +- .../provider/KafkaTbCoreQueueFactory.java | 2 +- .../KafkaTbRuleEngineQueueFactory.java | 2 +- .../KafkaTbTransportQueueFactory.java | 2 +- .../KafkaTbVersionControlQueueFactory.java | 2 +- .../provider/PubSubMonolithQueueFactory.java | 2 +- .../provider/PubSubTbCoreQueueFactory.java | 2 +- .../PubSubTbRuleEngineQueueFactory.java | 2 +- .../PubSubTbVersionControlQueueFactory.java | 2 +- .../provider/PubSubTransportQueueFactory.java | 2 +- .../RabbitMqMonolithQueueFactory.java | 2 +- .../provider/RabbitMqTbCoreQueueFactory.java | 2 +- .../RabbitMqTbRuleEngineQueueFactory.java | 2 +- .../RabbitMqTbVersionControlQueueFactory.java | 2 +- .../RabbitMqTransportQueueFactory.java | 2 +- .../ServiceBusMonolithQueueFactory.java | 2 +- .../ServiceBusTbCoreQueueFactory.java | 2 +- .../ServiceBusTbRuleEngineQueueFactory.java | 2 +- ...erviceBusTbVersionControlQueueFactory.java | 2 +- .../ServiceBusTransportQueueFactory.java | 2 +- .../queue/provider/TbCoreQueueFactory.java | 2 +- .../provider/TbCoreQueueProducerProvider.java | 2 +- .../provider/TbQueueProducerProvider.java | 2 +- .../TbRuleEngineProducerProvider.java | 2 +- .../provider/TbRuleEngineQueueFactory.java | 2 +- .../provider/TbTransportQueueFactory.java | 2 +- .../TbTransportQueueProducerProvider.java | 2 +- .../TbUsageStatsClientQueueFactory.java | 2 +- .../TbVersionControlProducerProvider.java | 2 +- .../TbVersionControlQueueFactory.java | 2 +- .../server/queue/pubsub/TbPubSubAdmin.java | 2 +- .../pubsub/TbPubSubConsumerTemplate.java | 4 +- .../pubsub/TbPubSubProducerTemplate.java | 8 +- .../server/queue/pubsub/TbPubSubSettings.java | 27 +- .../pubsub/TbPubSubSubscriptionSettings.java | 2 +- .../queue/rabbitmq/TbRabbitMqAdmin.java | 2 +- .../rabbitmq/TbRabbitMqConsumerTemplate.java | 2 +- .../rabbitmq/TbRabbitMqProducerTemplate.java | 2 +- .../rabbitmq/TbRabbitMqQueueArguments.java | 2 +- .../queue/rabbitmq/TbRabbitMqSettings.java | 2 +- .../scheduler/DefaultSchedulerComponent.java | 2 +- .../queue/scheduler/SchedulerComponent.java | 2 +- .../queue/settings/TbQueueCoreSettings.java | 2 +- .../TbQueueRemoteJsInvokeSettings.java | 2 +- .../settings/TbQueueRuleEngineSettings.java | 2 +- .../settings/TbQueueTransportApiSettings.java | 2 +- .../TbQueueTransportNotificationSettings.java | 2 +- .../TbQueueVersionControlSettings.java | 2 +- ...leEngineQueueAckStrategyConfiguration.java | 2 +- .../TbRuleEngineQueueConfiguration.java | 2 +- ...ngineQueueSubmitStrategyConfiguration.java | 2 +- .../queue/sqs/AwsSqsTbQueueMsgMetadata.java | 2 +- .../server/queue/sqs/TbAwsSqsAdmin.java | 14 +- .../queue/sqs/TbAwsSqsConsumerTemplate.java | 2 +- .../queue/sqs/TbAwsSqsProducerTemplate.java | 44 +- .../queue/sqs/TbAwsSqsQueueAttributes.java | 2 +- .../server/queue/sqs/TbAwsSqsSettings.java | 5 +- .../DefaultTbApiUsageReportClient.java | 2 +- .../server/queue/util/AfterContextReady.java | 2 +- .../server/queue/util/AfterStartUp.java | 2 +- .../util/DataDecodingEncodingService.java | 2 +- .../server/queue/util/PropertyUtils.java | 2 +- .../queue/util/ProtoWithFSTService.java | 21 +- .../server/queue/util/TbCoreComponent.java | 2 +- .../TbLwM2mBootstrapTransportComponent.java | 2 +- .../queue/util/TbLwM2mTransportComponent.java | 2 +- .../queue/util/TbRuleEngineComponent.java | 2 +- .../queue/util/TbSnmpTransportComponent.java | 2 +- .../queue/util/TbTransportComponent.java | 2 +- .../queue/util/TbVersionControlComponent.java | 2 +- .../DefaultTbQueueRequestTemplateTest.java | 2 +- .../server/queue/discovery/QueueKeyTest.java | 2 +- .../discovery/ZkDiscoveryServiceTest.java | 2 +- .../kafka/TbKafkaProducerTemplateTest.java | 2 +- .../queue/kafka/TbKafkaSettingsTest.java | 2 +- .../memory/DefaultInMemoryStorageTest.java | 2 +- .../server/queue/util/PropertyUtilsTest.java | 2 +- common/script/pom.xml | 4 +- common/script/remote-js-client/pom.xml | 4 +- .../service/script/JsExecutorService.java | 2 +- .../service/script/RemoteJsInvokeService.java | 2 +- .../script/RemoteJsRequestEncoder.java | 2 +- .../script/RemoteJsResponseDecoder.java | 2 +- common/script/script-api/pom.xml | 4 +- .../api/AbstractScriptInvokeService.java | 2 +- .../script/api/BlockedScriptInfo.java | 2 +- .../script/api/RuleNodeScriptFactory.java | 2 +- .../script/api/ScriptInvokeService.java | 2 +- .../script/api/ScriptStatCallback.java | 2 +- .../thingsboard/script/api/ScriptType.java | 2 +- .../script/api/TbScriptException.java | 2 +- .../script/api/TbScriptExecutionTask.java | 2 +- .../api/js/AbstractJsInvokeService.java | 2 +- .../script/api/js/JsInvokeService.java | 2 +- .../script/api/js/JsScriptExecutionTask.java | 2 +- .../script/api/js/JsScriptInfo.java | 2 +- .../script/api/js/NashornJsInvokeService.java | 2 +- .../api/tbel/DateTimeFormatOptions.java | 2 +- .../api/tbel/DefaultTbelInvokeService.java | 2 +- .../thingsboard/script/api/tbel/TbDate.java | 100 +- .../thingsboard/script/api/tbel/TbJson.java | 2 +- .../thingsboard/script/api/tbel/TbUtils.java | 30 +- .../script/api/tbel/TbelInvokeService.java | 2 +- .../script/api/tbel/TbelScript.java | 2 +- .../api/tbel/TbelScriptExecutionTask.java | 2 +- .../api/tbel/TbDateConstructorTest.java | 102 + .../script/api/tbel/TbDateTest.java | 158 +- .../script/api/tbel/TbDateTestEntity.java | 2 +- .../script/api/tbel/TbUtilsTest.java | 92 +- common/stats/pom.xml | 4 +- .../server/common/stats/DefaultCounter.java | 2 +- .../common/stats/DefaultMessagesStats.java | 2 +- .../common/stats/DefaultStatsFactory.java | 2 +- .../common/stats/FstStatsServiceImpl.java | 58 + .../server/common/stats/MessagesStats.java | 2 +- .../server/common/stats/StatsCounter.java | 2 +- .../server/common/stats/StatsFactory.java | 2 +- .../server/common/stats/StatsType.java | 2 +- .../common/stats/TbApiUsageReportClient.java | 2 +- .../common/stats/TbApiUsageStateClient.java | 2 +- common/transport/coap/pom.xml | 4 +- .../coap/AbstractCoapTransportResource.java | 2 +- .../transport/coap/CoapSessionMsgType.java | 2 +- .../transport/coap/CoapTransportContext.java | 2 +- .../transport/coap/CoapTransportResource.java | 6 +- .../transport/coap/CoapTransportService.java | 2 +- .../coap/OtaPackageTransportResource.java | 2 +- .../transport/coap/TbCoapMessageObserver.java | 2 +- .../coap/TransportConfigurationContainer.java | 2 +- .../coap/adaptors/CoapAdaptorUtils.java | 4 +- .../coap/adaptors/CoapTransportAdaptor.java | 4 +- .../coap/adaptors/JsonCoapAdaptor.java | 6 +- .../coap/adaptors/ProtoCoapAdaptor.java | 8 +- .../callback/AbstractSyncSessionCallback.java | 3 +- .../coap/callback/CoapDeviceAuthCallback.java | 2 +- .../coap/callback/CoapEfentoCallback.java | 2 +- .../coap/callback/CoapNoOpCallback.java | 2 +- .../coap/callback/CoapOkCallback.java | 6 +- .../GetAttributesSyncSessionCallback.java | 4 +- .../ToServerRpcSyncSessionCallback.java | 4 +- .../coap/client/CoapClientContext.java | 4 +- .../coap/client/DefaultCoapClientContext.java | 6 +- .../transport/coap/client/NoSecClient.java | 2 +- .../coap/client/NoSecObserveClient.java | 2 +- .../coap/client/SecureClientNoAuth.java | 2 +- .../coap/client/SecureClientX509.java | 2 +- .../coap/client/TbCoapClientState.java | 2 +- .../coap/client/TbCoapContentFormatUtil.java | 2 +- .../coap/client/TbCoapObservationState.java | 2 +- .../efento/CoapEfentoTransportResource.java | 7 +- .../efento/adaptor/EfentoCoapAdaptor.java | 6 +- .../coap/efento/utils/CoapEfentoUtils.java | 2 +- .../src/main/proto/efento/proto_config.proto | 2 +- .../main/proto/efento/proto_device_info.proto | 2 +- .../efento/proto_measurement_types.proto | 2 +- .../proto/efento/proto_measurements.proto | 2 +- .../src/main/proto/efento/proto_rule.proto | 2 +- .../coap/CoapTransportResourceTest.java | 2 +- common/transport/http/pom.xml | 4 +- .../transport/http/DeviceApiController.java | 4 +- .../transport/http/HttpTransportContext.java | 2 +- common/transport/lwm2m/pom.xml | 4 +- .../LwM2MTransportBootstrapService.java | 2 +- .../secure/LwM2MBootstrapConfig.java | 2 +- .../secure/LwM2MBootstrapServers.java | 2 +- .../secure/LwM2MServerBootstrap.java | 2 +- .../LwM2mDefaultBootstrapSessionManager.java | 2 +- ...LwM2MDtlsBootstrapCertificateVerifier.java | 2 +- .../LwM2MBootstrapClientInstanceIds.java | 2 +- ...LwM2MBootstrapConfigStoreTaskProvider.java | 2 +- .../store/LwM2MBootstrapSecurityStore.java | 2 +- .../store/LwM2MBootstrapTaskProvider.java | 2 +- .../store/LwM2MConfigurationChecker.java | 2 +- .../LwM2MInMemoryBootstrapConfigStore.java | 2 +- .../lwm2m/config/LwM2MSecureServerConfig.java | 2 +- .../config/LwM2MTransportBootstrapConfig.java | 2 +- .../config/LwM2MTransportServerConfig.java | 2 +- .../lwm2m/config/TbLwM2mVersion.java | 2 +- ...LwM2mCredentialsSecurityInfoValidator.java | 2 +- .../lwm2m/secure/LwM2mRPkCredentials.java | 2 +- .../lwm2m/secure/TbLwM2MAuthorizer.java | 2 +- .../TbLwM2MDtlsCertificateVerifier.java | 2 +- .../lwm2m/secure/TbLwM2MSecurityInfo.java | 2 +- .../lwm2m/secure/TbX509DtlsSessionInfo.java | 2 +- .../credentials/LwM2MClientCredentials.java | 2 +- .../AbstractLwM2mTransportResource.java | 2 +- .../server/DefaultLwM2mTransportService.java | 2 +- .../lwm2m/server/LwM2MNetworkConfig.java | 2 +- .../lwm2m/server/LwM2MOperationType.java | 2 +- .../lwm2m/server/LwM2MTransportService.java | 2 +- .../lwm2m/server/LwM2mOtaConvert.java | 2 +- .../lwm2m/server/LwM2mQueuedRequest.java | 2 +- .../lwm2m/server/LwM2mServerListener.java | 2 +- .../lwm2m/server/LwM2mSessionMsgListener.java | 2 +- .../server/LwM2mTransportCoapResource.java | 2 +- .../lwm2m/server/LwM2mTransportContext.java | 2 +- .../server/LwM2mTransportServerHelper.java | 2 +- .../server/LwM2mVersionedModelProvider.java | 5 +- .../server/adaptors/LwM2MJsonAdaptor.java | 6 +- .../adaptors/LwM2MTransportAdaptor.java | 4 +- .../DefaultLwM2MAttributesService.java | 2 +- .../attributes/LwM2MAttributesService.java | 2 +- .../server/client/LwM2MAuthException.java | 2 +- .../lwm2m/server/client/LwM2MClientState.java | 2 +- .../client/LwM2MClientStateException.java | 2 +- .../lwm2m/server/client/LwM2mClient.java | 2 +- .../server/client/LwM2mClientContext.java | 2 +- .../server/client/LwM2mClientContextImpl.java | 2 +- .../lwm2m/server/client/ModelObject.java | 2 +- .../client/ParametersAnalyzeResult.java | 2 +- .../lwm2m/server/client/ResourceValue.java | 2 +- .../client/ResultsAddKeyValueProto.java | 2 +- .../common/LwM2MExecutorAwareService.java | 2 +- .../AbstractTbLwM2MRequestCallback.java | 2 +- ...bstractTbLwM2MTargetedDownlinkRequest.java | 2 +- .../DefaultLwM2mDownlinkMsgHandler.java | 2 +- .../downlink/DownlinkRequestCallback.java | 2 +- .../server/downlink/HasContentFormat.java | 2 +- .../lwm2m/server/downlink/HasVersionedId.java | 2 +- .../server/downlink/HasVersionedIds.java | 2 +- .../downlink/LwM2mDownlinkMsgHandler.java | 2 +- .../TbLwM2MCancelAllObserveCallback.java | 2 +- .../downlink/TbLwM2MCancelAllRequest.java | 2 +- .../TbLwM2MCancelObserveCallback.java | 2 +- .../downlink/TbLwM2MCancelObserveRequest.java | 2 +- .../server/downlink/TbLwM2MCreateRequest.java | 2 +- .../TbLwM2MCreateResponseCallback.java | 2 +- .../downlink/TbLwM2MDeleteCallback.java | 2 +- .../server/downlink/TbLwM2MDeleteRequest.java | 2 +- .../downlink/TbLwM2MDiscoverAllRequest.java | 2 +- .../downlink/TbLwM2MDiscoverCallback.java | 2 +- .../downlink/TbLwM2MDiscoverRequest.java | 2 +- .../downlink/TbLwM2MDownlinkRequest.java | 2 +- .../downlink/TbLwM2MExecuteCallback.java | 2 +- .../downlink/TbLwM2MExecuteRequest.java | 2 +- .../server/downlink/TbLwM2MLatchCallback.java | 2 +- .../downlink/TbLwM2MObserveAllRequest.java | 2 +- .../downlink/TbLwM2MObserveCallback.java | 2 +- .../downlink/TbLwM2MObserveRequest.java | 2 +- .../server/downlink/TbLwM2MReadCallback.java | 2 +- .../server/downlink/TbLwM2MReadRequest.java | 2 +- .../downlink/TbLwM2MTargetedCallback.java | 2 +- .../TbLwM2MUplinkTargetedCallback.java | 2 +- .../TbLwM2MWriteAttributesCallback.java | 2 +- .../TbLwM2MWriteAttributesRequest.java | 2 +- .../downlink/TbLwM2MWriteReplaceRequest.java | 2 +- .../TbLwM2MWriteResponseCallback.java | 2 +- .../downlink/TbLwM2MWriteUpdateRequest.java | 2 +- ...LwM2MTargetedDownlinkCompositeRequest.java | 2 +- .../TbLwM2MReadCompositeCallback.java | 2 +- .../TbLwM2MReadCompositeRequest.java | 2 +- .../TbLwM2MWriteCompositeRequest.java | 2 +- ...TbLwM2MWriteResponseCompositeCallback.java | 2 +- .../log/DefaultLwM2MTelemetryLogService.java | 2 +- .../server/log/LwM2MTelemetryLogService.java | 2 +- .../lwm2m/server/model/LwM2MModelConfig.java | 2 +- .../server/model/LwM2MModelConfigService.java | 2 +- .../model/LwM2MModelConfigServiceImpl.java | 2 +- .../ota/DefaultLwM2MOtaUpdateService.java | 2 +- .../lwm2m/server/ota/LwM2MClientOtaInfo.java | 2 +- .../lwm2m/server/ota/LwM2MClientOtaState.java | 2 +- .../server/ota/LwM2MOtaUpdateService.java | 2 +- .../ota/firmware/FirmwareDeliveryMethod.java | 2 +- .../ota/firmware/FirmwareUpdateResult.java | 2 +- .../ota/firmware/FirmwareUpdateState.java | 2 +- .../ota/firmware/LwM2MClientFwOtaInfo.java | 2 +- .../firmware/LwM2MFirmwareUpdateStrategy.java | 2 +- .../ota/software/LwM2MClientSwOtaInfo.java | 2 +- .../software/LwM2MSoftwareUpdateStrategy.java | 2 +- .../ota/software/SoftwareUpdateResult.java | 2 +- .../ota/software/SoftwareUpdateState.java | 2 +- .../rpc/DefaultLwM2MRpcRequestHandler.java | 2 +- .../server/rpc/LwM2MRpcRequestHandler.java | 2 +- .../server/rpc/LwM2MRpcRequestHeader.java | 2 +- .../server/rpc/LwM2MRpcResponseBody.java | 2 +- .../rpc/RpcCancelAllObserveCallback.java | 2 +- .../server/rpc/RpcCancelObserveCallback.java | 2 +- .../lwm2m/server/rpc/RpcCreateRequest.java | 2 +- .../server/rpc/RpcCreateResponseCallback.java | 2 +- .../lwm2m/server/rpc/RpcDiscoverCallback.java | 2 +- .../rpc/RpcDownlinkRequestCallbackProxy.java | 2 +- .../server/rpc/RpcEmptyResponseCallback.java | 2 +- .../lwm2m/server/rpc/RpcLinkSetCallback.java | 2 +- .../server/rpc/RpcLwM2MDownlinkCallback.java | 2 +- .../server/rpc/RpcReadResponseCallback.java | 2 +- .../server/rpc/RpcWriteAttributesRequest.java | 2 +- .../server/rpc/RpcWriteReplaceRequest.java | 2 +- .../server/rpc/RpcWriteUpdateRequest.java | 2 +- .../composite/RpcReadCompositeRequest.java | 2 +- .../RpcReadResponseCompositeCallback.java | 2 +- .../composite/RpcWriteCompositeRequest.java | 2 +- .../session/DefaultLwM2MSessionManager.java | 2 +- .../server/session/LwM2MSessionManager.java | 2 +- .../store/TbDummyLwM2MClientOtaInfoStore.java | 2 +- .../server/store/TbDummyLwM2MClientStore.java | 2 +- .../store/TbDummyLwM2MModelConfigStore.java | 2 +- .../server/store/TbEditableSecurityStore.java | 2 +- .../server/store/TbInMemorySecurityStore.java | 2 +- .../TbL2M2MDtlsSessionInMemoryStore.java | 2 +- .../store/TbLwM2MClientOtaInfoStore.java | 2 +- .../server/store/TbLwM2MClientStore.java | 2 +- .../store/TbLwM2MDtlsSessionRedisStore.java | 2 +- .../server/store/TbLwM2MDtlsSessionStore.java | 2 +- .../server/store/TbLwM2MModelConfigStore.java | 2 +- .../store/TbLwM2mRedisClientOtaInfoStore.java | 2 +- .../store/TbLwM2mRedisRegistrationStore.java | 2 +- .../store/TbLwM2mRedisSecurityStore.java | 2 +- .../server/store/TbLwM2mSecurityStore.java | 2 +- .../server/store/TbLwM2mStoreFactory.java | 2 +- .../server/store/TbMainSecurityStore.java | 2 +- .../server/store/TbRedisLwM2MClientStore.java | 2 +- .../store/TbRedisLwM2MModelConfigStore.java | 2 +- .../lwm2m/server/store/TbSecurityStore.java | 2 +- .../server/store/util/LwM2MClientSerDes.java | 2 +- .../uplink/DefaultLwM2mUplinkMsgHandler.java | 2 +- .../lwm2m/server/uplink/LwM2mTypeServer.java | 2 +- .../server/uplink/LwM2mUplinkMsgHandler.java | 2 +- .../lwm2m/utils/LwM2MTransportUtil.java | 2 +- .../lwm2m/utils/LwM2mValueConverterImpl.java | 2 +- .../LwM2MModelConfigServiceImplTest.java | 2 +- .../lwm2m/server/client/LwM2mClientTest.java | 2 +- .../store/util/LwM2MClientSerDesTest.java | 2 +- common/transport/mqtt/pom.xml | 4 +- .../mqtt/MqttSslHandlerProvider.java | 2 +- .../transport/mqtt/MqttTransportContext.java | 2 +- .../transport/mqtt/MqttTransportHandler.java | 14 +- .../mqtt/MqttTransportServerInitializer.java | 2 +- .../transport/mqtt/MqttTransportService.java | 2 +- .../server/transport/mqtt/TopicType.java | 2 +- .../BackwardCompatibilityAdaptor.java | 4 +- .../mqtt/adaptors/JsonMqttAdaptor.java | 6 +- .../mqtt/adaptors/MqttTransportAdaptor.java | 4 +- .../mqtt/adaptors/ProtoMqttAdaptor.java | 8 +- .../transport/mqtt/limits/IpFilter.java | 2 +- .../transport/mqtt/limits/ProxyIpFilter.java | 2 +- .../AbstractGatewayDeviceSessionContext.java | 2 +- .../AbstractGatewaySessionHandler.java | 30 +- .../mqtt/session/DeviceSessionCtx.java | 2 +- .../session/GatewayDeviceSessionContext.java | 2 +- .../mqtt/session/GatewaySessionHandler.java | 4 +- .../MqttDeviceAwareSessionContext.java | 2 +- .../mqtt/session/MqttTopicMatcher.java | 2 +- .../SparkplugDeviceSessionContext.java | 2 +- .../session/SparkplugNodeSessionHandler.java | 8 +- .../mqtt/util/AlwaysTrueTopicFilter.java | 2 +- .../mqtt/util/EqualsTopicFilter.java | 2 +- .../transport/mqtt/util/MqttTopicFilter.java | 2 +- .../mqtt/util/MqttTopicFilterFactory.java | 2 +- .../transport/mqtt/util/RegexTopicFilter.java | 2 +- .../transport/mqtt/util/ReturnCode.java | 2 +- .../mqtt/util/ReturnCodeResolver.java | 2 +- .../mqtt/util/sparkplug/MetricDataType.java | 4 +- .../sparkplug/SparkplugConnectionState.java | 2 +- .../util/sparkplug/SparkplugMessageType.java | 2 +- .../util/sparkplug/SparkplugMetricUtil.java | 2 +- .../sparkplug/SparkplugRpcRequestHeader.java | 2 +- .../sparkplug/SparkplugRpcResponseBody.java | 2 +- .../mqtt/util/sparkplug/SparkplugTopic.java | 2 +- .../util/sparkplug/SparkplugTopicUtil.java | 2 +- .../mqtt/src/main/proto/sparkplug.proto | 2 +- .../mqtt/MqttTransportHandlerTest.java | 2 +- .../session/GatewaySessionHandlerTest.java | 2 +- .../mqtt/util/MqttTopicFilterFactoryTest.java | 2 +- common/transport/pom.xml | 4 +- common/transport/snmp/pom.xml | 4 +- .../transport/snmp/SnmpTransportContext.java | 2 +- .../ServiceListChangedEventListener.java | 2 +- .../event/SnmpTransportListChangedEvent.java | 2 +- ...SnmpTransportListChangedEventListener.java | 2 +- .../transport/snmp/service/PduService.java | 2 +- .../service/ProtoTransportEntityService.java | 2 +- .../snmp/service/SnmpAuthService.java | 2 +- .../SnmpTransportBalancingService.java | 2 +- .../snmp/service/SnmpTransportService.java | 6 +- .../snmp/session/DeviceSessionContext.java | 2 +- .../transport/snmp/SnmpDeviceSimulatorV2.java | 2 +- .../transport/snmp/SnmpDeviceSimulatorV3.java | 2 +- .../server/transport/snmp/SnmpTestV2.java | 2 +- .../server/transport/snmp/SnmpTestV3.java | 2 +- common/transport/transport-api/pom.xml | 4 +- .../common/transport/DeviceDeletedEvent.java | 2 +- .../transport/DeviceProfileUpdatedEvent.java | 2 +- .../common/transport/DeviceUpdatedEvent.java | 2 +- .../common/transport/SessionMsgListener.java | 2 +- .../common/transport/TransportAdaptor.java | 2 +- .../common/transport/TransportContext.java | 2 +- .../TransportDeviceProfileCache.java | 2 +- .../transport/TransportResourceCache.java | 2 +- .../common/transport/TransportService.java | 6 +- .../transport/TransportServiceCallback.java | 2 +- .../TransportTenantProfileCache.java | 2 +- .../activity/AbstractActivityManager.java | 187 + .../transport/activity/ActivityManager.java | 26 + .../activity/ActivityReportCallback.java | 24 + .../transport/activity/ActivityState.java | 26 + .../activity/strategy/ActivityStrategy.java | 24 + .../strategy/ActivityStrategyType.java | 47 + .../strategy/AllEventsActivityStrategy.java | 39 + .../FirstAndLastEventActivityStrategy.java | 40 + .../strategy/FirstEventActivityStrategy.java | 40 + .../strategy/LastEventActivityStrategy.java | 39 + .../transport/auth/DeviceAuthResult.java | 2 +- .../transport/auth/DeviceAuthService.java | 2 +- .../transport/auth/DeviceProfileAware.java | 2 +- .../GetOrCreateDeviceFromGatewayResponse.java | 2 +- .../transport/auth/SessionInfoCreator.java | 2 +- .../transport/auth/TransportDeviceInfo.java | 2 +- .../ValidateDeviceCredentialsResponse.java | 2 +- .../config/ssl/AbstractSslCredentials.java | 2 +- .../config/ssl/KeystoreSslCredentials.java | 2 +- .../config/ssl/PemSslCredentials.java | 2 +- .../transport/config/ssl/SslCredentials.java | 2 +- .../config/ssl/SslCredentialsConfig.java | 2 +- .../config/ssl/SslCredentialsType.java | 2 +- .../SslCredentialsWebServerCustomizer.java | 2 +- .../limits/DefaultEntityLimitsCache.java | 74 + .../DefaultTransportRateLimitService.java | 2 +- .../limits/DummyTransportRateLimit.java | 2 +- .../transport/limits/EntityLimitKey.java | 27 + .../transport/limits/EntityLimitsCache.java | 24 + .../limits/EntityTransportRateLimits.java | 2 +- .../limits/InetAddressRateLimitStats.java | 2 +- .../limits/SimpleTransportRateLimit.java | 2 +- .../transport/limits/TransportRateLimit.java | 2 +- .../limits/TransportRateLimitService.java | 2 +- .../profile/TenantProfileUpdateResult.java | 2 +- .../DefaultTransportDeviceProfileCache.java | 2 +- .../DefaultTransportResourceCache.java | 2 +- .../service/DefaultTransportService.java | 172 +- .../DefaultTransportTenantProfileCache.java | 2 +- .../transport/service/RpcRequestMetadata.java | 2 +- .../service/SessionActivityData.java | 39 - .../transport/service/SessionMetaData.java | 2 +- .../service/ToRuleEngineMsgEncoder.java | 2 +- .../ToTransportMsgResponseDecoder.java | 2 +- .../service/TransportActivityManager.java | 149 + .../service/TransportApiRequestEncoder.java | 2 +- .../service/TransportApiResponseDecoder.java | 2 +- .../TransportQueueRoutingInfoService.java | 2 +- .../TransportTenantRoutingInfoService.java | 2 +- .../session/DeviceAwareSessionContext.java | 2 +- .../transport/session/SessionContext.java | 2 +- .../common/transport/util/JsonUtils.java | 2 +- .../server/common/transport/util/SslUtil.java | 2 +- .../strategy/ActivityStrategyTypeTest.java | 44 + .../AllEventsActivityStrategyTest.java | 42 + ...FirstAndLastEventActivityStrategyTest.java | 53 + .../FirstEventActivityStrategyTest.java | 52 + .../LastEventActivityStrategyTest.java | 43 + .../service/TransportActivityManagerTest.java | 508 ++ common/util/pom.xml | 4 +- .../util/AbstractListeningExecutor.java | 2 +- .../common/util/AzureIotHubUtil.java | 2 +- .../thingsboard/common/util/DonAsynchron.java | 2 +- .../common/util/ExceptionUtil.java | 2 +- .../common/util/ExecutorProvider.java | 23 + .../thingsboard/common/util/JacksonUtil.java | 14 +- .../org/thingsboard/common/util/KvUtil.java | 2 +- .../util/LinkedHashMapRemoveEldest.java | 2 +- .../common/util/ListeningExecutor.java | 2 +- .../thingsboard/common/util/RegexUtils.java | 13 +- .../org/thingsboard/common/util/SslUtil.java | 2 +- .../thingsboard/common/util/SystemUtil.java | 2 +- .../thingsboard/common/util/TbStopWatch.java | 2 +- .../common/util/ThingsBoardExecutors.java | 2 +- ...hingsBoardForkJoinWorkerThreadFactory.java | 2 +- .../common/util/ThingsBoardThreadFactory.java | 2 +- .../common/util/ExceptionUtilTest.java | 2 +- .../common/util/JacksonUtilTest.java | 29 +- .../util/LinkedHashMapRemoveEldestTest.java | 2 +- common/version-control/pom.xml | 4 +- .../sync/vc/ClusterVersionControlService.java | 2 +- .../DefaultClusterVersionControlService.java | 17 +- .../sync/vc/DefaultGitRepositoryService.java | 2 +- .../server/service/sync/vc/GitRepository.java | 11 +- .../service/sync/vc/GitRepositoryService.java | 2 +- .../server/service/sync/vc/PendingCommit.java | 2 +- .../sync/vc/VersionControlRequestCtx.java | 2 +- dao/pom.xml | 16 +- .../java/org/thingsboard/server/dao/Dao.java | 2 +- .../org/thingsboard/server/dao/DaoUtil.java | 25 +- .../server/dao/ExportableEntityDao.java | 8 +- .../dao/ExportableEntityRepository.java | 2 +- .../server/dao/ImageContainerDao.java | 29 + .../thingsboard/server/dao/JpaDaoConfig.java | 2 +- .../server/dao/SqlTimeseriesDaoConfig.java | 2 +- .../server/dao/SqlTsDaoConfig.java | 2 +- .../server/dao/SqlTsLatestDaoConfig.java | 2 +- .../server/dao/TenantEntityDao.java | 2 +- .../server/dao/TenantEntityWithDataDao.java | 2 +- .../dao/ThingsboardPostgreSQLDialect.java | 2 +- .../server/dao/TimescaleDaoConfig.java | 2 +- .../dao/TimescaleTsLatestDaoConfig.java | 2 +- .../server/dao/alarm/AlarmCommentDao.java | 2 +- .../server/dao/alarm/AlarmDao.java | 2 +- .../dao/alarm/AlarmTypesCacheEvictEvent.java | 2 +- .../dao/alarm/AlarmTypesCaffeineCache.java | 2 +- .../dao/alarm/AlarmTypesRedisCache.java | 2 +- .../dao/alarm/BaseAlarmCommentService.java | 23 +- .../server/dao/alarm/BaseAlarmService.java | 4 +- .../server/dao/aspect/DbCallStats.java | 2 +- .../dao/aspect/DbCallStatsSnapshot.java | 2 +- .../server/dao/aspect/MethodCallStats.java | 2 +- .../dao/aspect/MethodCallStatsSnapshot.java | 2 +- .../server/dao/aspect/SqlDaoCallsAspect.java | 2 +- .../dao/asset/AssetCacheEvictEvent.java | 2 +- .../server/dao/asset/AssetCacheKey.java | 2 +- .../server/dao/asset/AssetCaffeineCache.java | 2 +- .../server/dao/asset/AssetDao.java | 3 +- .../dao/asset/AssetProfileCacheKey.java | 2 +- .../dao/asset/AssetProfileCaffeineCache.java | 2 +- .../server/dao/asset/AssetProfileDao.java | 12 +- .../dao/asset/AssetProfileEvictEvent.java | 2 +- .../dao/asset/AssetProfileRedisCache.java | 2 +- .../dao/asset/AssetProfileServiceImpl.java | 47 +- .../server/dao/asset/AssetRedisCache.java | 2 +- .../server/dao/asset/AssetTypeFilter.java | 2 +- .../server/dao/asset/BaseAssetService.java | 19 +- .../dao/attributes/AttributeCacheKey.java | 2 +- .../attributes/AttributeCaffeineCache.java | 2 +- .../dao/attributes/AttributeRedisCache.java | 2 +- .../server/dao/attributes/AttributeUtils.java | 2 +- .../server/dao/attributes/AttributesDao.java | 2 +- .../dao/attributes/BaseAttributesService.java | 2 +- .../attributes/CachedAttributesService.java | 2 +- .../server/dao/audit/AuditLogDao.java | 2 +- .../server/dao/audit/AuditLogLevelFilter.java | 2 +- .../server/dao/audit/AuditLogLevelMask.java | 2 +- .../dao/audit/AuditLogLevelProperties.java | 2 +- .../server/dao/audit/AuditLogServiceImpl.java | 2 +- .../dao/audit/DummyAuditLogServiceImpl.java | 2 +- .../server/dao/audit/sink/AuditLogSink.java | 2 +- .../dao/audit/sink/DummyAuditLogSink.java | 2 +- .../audit/sink/ElasticsearchAuditLogSink.java | 2 +- .../dao/cache/CacheExecutorService.java | 2 +- .../BaseComponentDescriptorService.java | 2 +- .../dao/component/ComponentDescriptorDao.java | 2 +- .../server/dao/customer/CustomerDao.java | 2 +- .../dao/customer/CustomerServiceImpl.java | 4 +- .../server/dao/dashboard/DashboardDao.java | 4 +- .../dao/dashboard/DashboardInfoDao.java | 9 +- .../dao/dashboard/DashboardServiceImpl.java | 11 +- .../dashboard/DashboardTitleEvictEvent.java | 2 +- .../DashboardTitlesCaffeineCache.java | 2 +- .../dashboard/DashboardTitlesRedisCache.java | 2 +- .../server/dao/device/ClaimDataInfo.java | 2 +- .../DeviceConnectivityConfiguration.java | 5 +- .../dao/device/DeviceConnectivityInfo.java | 2 +- .../device/DeviceConnectivityServiceImpl.java | 75 +- .../DeviceCredentialsCaffeineCache.java | 2 +- .../dao/device/DeviceCredentialsDao.java | 2 +- .../device/DeviceCredentialsEvictEvent.java | 2 +- .../device/DeviceCredentialsRedisCache.java | 2 +- .../device/DeviceCredentialsServiceImpl.java | 2 +- .../server/dao/device/DeviceDao.java | 3 +- .../dao/device/DeviceProfileCacheKey.java | 2 +- .../device/DeviceProfileCaffeineCache.java | 2 +- .../server/dao/device/DeviceProfileDao.java | 12 +- .../dao/device/DeviceProfileEvictEvent.java | 2 +- .../dao/device/DeviceProfileRedisCache.java | 2 +- .../dao/device/DeviceProfileServiceImpl.java | 50 +- .../server/dao/device/DeviceServiceImpl.java | 14 +- .../server/dao/edge/BaseEdgeEventService.java | 2 +- .../DefaultEdgeSynchronizationManager.java | 2 +- .../server/dao/edge/EdgeCacheEvictEvent.java | 2 +- .../server/dao/edge/EdgeCacheKey.java | 2 +- .../server/dao/edge/EdgeCaffeineCache.java | 2 +- .../thingsboard/server/dao/edge/EdgeDao.java | 2 +- .../server/dao/edge/EdgeEventDao.java | 2 +- .../server/dao/edge/EdgeRedisCache.java | 2 +- .../server/dao/edge/EdgeServiceImpl.java | 2 +- .../entity/AbstractCachedEntityService.java | 2 +- .../dao/entity/AbstractCachedService.java | 2 +- .../dao/entity/AbstractEntityService.java | 2 +- .../dao/entity/BaseEntityCountService.java | 2 +- .../server/dao/entity/BaseEntityService.java | 2 +- .../entity/DefaultEntityServiceRegistry.java | 2 +- .../entity/EntityCountCacheEvictEvent.java | 2 +- .../dao/entity/EntityCountCacheKey.java | 2 +- .../server/dao/entity/EntityQueryDao.java | 2 +- .../count/EntityCountCaffeineCache.java | 2 +- .../entity/count/EntityCountRedisCache.java | 2 +- .../dao/entityview/EntityViewCacheKey.java | 2 +- .../dao/entityview/EntityViewCacheValue.java | 2 +- .../entityview/EntityViewCaffeineCache.java | 2 +- .../server/dao/entityview/EntityViewDao.java | 2 +- .../dao/entityview/EntityViewEvictEvent.java | 2 +- .../dao/entityview/EntityViewRedisCache.java | 2 +- .../dao/entityview/EntityViewServiceImpl.java | 13 +- .../server/dao/event/BaseEventService.java | 2 +- .../server/dao/event/EventDao.java | 2 +- .../dao/eventsourcing/ActionEntityEvent.java | 2 +- .../dao/eventsourcing/DeleteEntityEvent.java | 2 +- .../eventsourcing/RelationActionEvent.java | 2 +- .../dao/eventsourcing/SaveEntityEvent.java | 4 +- .../dao/exception/BufferLimitException.java | 2 +- .../exception/DataValidationException.java | 2 +- .../dao/exception/DatabaseException.java | 2 +- .../DeviceCredentialsValidationException.java | 2 +- .../dao/exception/EntitiesLimitException.java | 35 + .../IncorrectParameterException.java | 2 +- .../server/dao/model/BaseEntity.java | 2 +- .../server/dao/model/BaseSqlEntity.java | 2 +- .../server/dao/model/ModelConstants.java | 8 +- .../thingsboard/server/dao/model/ToData.java | 2 +- .../model/sql/AbstractAlarmCommentEntity.java | 2 +- .../dao/model/sql/AbstractAlarmEntity.java | 2 +- .../dao/model/sql/AbstractAssetEntity.java | 2 +- .../dao/model/sql/AbstractDeviceEntity.java | 2 +- .../dao/model/sql/AbstractEdgeEntity.java | 2 +- .../model/sql/AbstractEntityViewEntity.java | 2 +- .../dao/model/sql/AbstractTenantEntity.java | 2 +- .../dao/model/sql/AbstractTsKvEntity.java | 2 +- .../model/sql/AbstractWidgetTypeEntity.java | 2 +- .../dao/model/sql/AdminSettingsEntity.java | 2 +- .../dao/model/sql/AlarmCommentEntity.java | 2 +- .../dao/model/sql/AlarmCommentInfoEntity.java | 2 +- .../server/dao/model/sql/AlarmEntity.java | 2 +- .../server/dao/model/sql/AlarmInfoEntity.java | 2 +- .../dao/model/sql/ApiUsageStateEntity.java | 2 +- .../server/dao/model/sql/AssetEntity.java | 2 +- .../server/dao/model/sql/AssetInfoEntity.java | 2 +- .../dao/model/sql/AssetProfileEntity.java | 2 +- .../model/sql/AttributeKvCompositeKey.java | 2 +- .../dao/model/sql/AttributeKvEntity.java | 2 +- .../server/dao/model/sql/AuditLogEntity.java | 2 +- .../model/sql/ComponentDescriptorEntity.java | 7 +- .../server/dao/model/sql/CustomerEntity.java | 2 +- .../server/dao/model/sql/DashboardEntity.java | 2 +- .../dao/model/sql/DashboardInfoEntity.java | 2 +- .../model/sql/DeviceCredentialsEntity.java | 2 +- .../server/dao/model/sql/DeviceEntity.java | 2 +- .../dao/model/sql/DeviceInfoEntity.java | 2 +- .../dao/model/sql/DeviceProfileEntity.java | 2 +- .../server/dao/model/sql/EdgeEntity.java | 2 +- .../server/dao/model/sql/EdgeEventEntity.java | 2 +- .../server/dao/model/sql/EdgeInfoEntity.java | 2 +- .../model/sql/EntityAlarmCompositeKey.java | 2 +- .../dao/model/sql/EntityAlarmEntity.java | 2 +- .../dao/model/sql/EntityViewEntity.java | 2 +- .../dao/model/sql/EntityViewInfoEntity.java | 2 +- .../dao/model/sql/ErrorEventEntity.java | 2 +- .../server/dao/model/sql/EventEntity.java | 2 +- .../dao/model/sql/LifecycleEventEntity.java | 2 +- .../dao/model/sql/NotificationEntity.java | 2 +- .../model/sql/NotificationRequestEntity.java | 2 +- .../sql/NotificationRequestInfoEntity.java | 2 +- .../dao/model/sql/NotificationRuleEntity.java | 2 +- .../model/sql/NotificationRuleInfoEntity.java | 2 +- .../model/sql/NotificationTargetEntity.java | 2 +- .../model/sql/NotificationTemplateEntity.java | 2 +- ...Auth2ClientRegistrationTemplateEntity.java | 2 +- .../dao/model/sql/OAuth2DomainEntity.java | 2 +- .../dao/model/sql/OAuth2MobileEntity.java | 2 +- .../dao/model/sql/OAuth2ParamsEntity.java | 2 +- .../model/sql/OAuth2RegistrationEntity.java | 2 +- .../dao/model/sql/OtaPackageEntity.java | 2 +- .../dao/model/sql/OtaPackageInfoEntity.java | 2 +- .../server/dao/model/sql/QueueEntity.java | 2 +- .../dao/model/sql/RelationCompositeKey.java | 2 +- .../server/dao/model/sql/RelationEntity.java | 2 +- .../server/dao/model/sql/RpcEntity.java | 2 +- .../model/sql/RuleChainDebugEventEntity.java | 2 +- .../server/dao/model/sql/RuleChainEntity.java | 2 +- .../model/sql/RuleNodeDebugEventEntity.java | 2 +- .../server/dao/model/sql/RuleNodeEntity.java | 7 +- .../dao/model/sql/RuleNodeStateEntity.java | 2 +- .../dao/model/sql/StatisticsEventEntity.java | 2 +- .../dao/model/sql/TbResourceEntity.java | 45 +- .../dao/model/sql/TbResourceInfoEntity.java | 44 +- .../server/dao/model/sql/TenantEntity.java | 2 +- .../dao/model/sql/TenantInfoEntity.java | 2 +- .../dao/model/sql/TenantProfileEntity.java | 2 +- .../dao/model/sql/UserAuthSettingsEntity.java | 2 +- .../dao/model/sql/UserCredentialsEntity.java | 2 +- .../server/dao/model/sql/UserEntity.java | 2 +- .../dao/model/sql/UserSettingsEntity.java | 2 +- .../model/sql/WidgetTypeDetailsEntity.java | 2 +- .../dao/model/sql/WidgetTypeEntity.java | 2 +- .../dao/model/sql/WidgetTypeIdFqnEntity.java | 2 +- .../dao/model/sql/WidgetTypeInfoEntity.java | 2 +- .../dao/model/sql/WidgetsBundleEntity.java | 2 +- .../sql/WidgetsBundleWidgetCompositeKey.java | 2 +- .../model/sql/WidgetsBundleWidgetEntity.java | 2 +- .../sqlts/dictionary/TsKvDictionary.java | 2 +- .../TsKvDictionaryCompositeKey.java | 2 +- .../sqlts/latest/TsKvLatestCompositeKey.java | 2 +- .../model/sqlts/latest/TsKvLatestEntity.java | 2 +- .../ts/TimescaleTsKvCompositeKey.java | 2 +- .../timescale/ts/TimescaleTsKvEntity.java | 2 +- .../dao/model/sqlts/ts/TsKvCompositeKey.java | 2 +- .../server/dao/model/sqlts/ts/TsKvEntity.java | 2 +- .../dao/nosql/CassandraAbstractAsyncDao.java | 2 +- .../dao/nosql/CassandraAbstractDao.java | 2 +- .../CassandraBufferedRateReadExecutor.java | 2 +- .../CassandraBufferedRateWriteExecutor.java | 2 +- .../DefaultNotificationRequestService.java | 2 +- .../DefaultNotificationRuleService.java | 2 +- .../DefaultNotificationService.java | 2 +- .../DefaultNotificationSettingsService.java | 2 +- .../DefaultNotificationTargetService.java | 7 +- .../DefaultNotificationTemplateService.java | 2 +- .../notification/DefaultNotifications.java | 2 +- .../dao/notification/NotificationDao.java | 2 +- .../notification/NotificationRequestDao.java | 2 +- .../dao/notification/NotificationRuleDao.java | 2 +- .../notification/NotificationTargetDao.java | 2 +- .../notification/NotificationTemplateDao.java | 2 +- .../HybridClientRegistrationRepository.java | 2 +- .../OAuth2ClientRegistrationTemplateDao.java | 2 +- .../OAuth2ConfigTemplateServiceImpl.java | 2 +- .../dao/oauth2/OAuth2Configuration.java | 2 +- .../server/dao/oauth2/OAuth2DomainDao.java | 2 +- .../server/dao/oauth2/OAuth2MobileDao.java | 2 +- .../server/dao/oauth2/OAuth2ParamsDao.java | 2 +- .../dao/oauth2/OAuth2RegistrationDao.java | 2 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 2 +- .../server/dao/oauth2/OAuth2Utils.java | 2 +- .../server/dao/ota/BaseOtaPackageService.java | 6 +- .../dao/ota/OtaPackageCacheEvictEvent.java | 2 +- .../server/dao/ota/OtaPackageCacheKey.java | 2 +- .../dao/ota/OtaPackageCaffeineCache.java | 2 +- .../server/dao/ota/OtaPackageDao.java | 2 +- .../server/dao/ota/OtaPackageInfoDao.java | 2 +- .../server/dao/ota/OtaPackageRedisCache.java | 2 +- .../server/dao/queue/BaseQueueService.java | 4 +- .../server/dao/queue/QueueDao.java | 2 +- .../dao/relation/BaseRelationService.java | 2 +- .../dao/relation/EntityRelationEvent.java | 2 +- .../server/dao/relation/RelationCacheKey.java | 2 +- .../dao/relation/RelationCacheValue.java | 2 +- .../dao/relation/RelationCaffeineCache.java | 2 +- .../server/dao/relation/RelationDao.java | 2 +- .../dao/relation/RelationRedisCache.java | 2 +- .../server/dao/resource/BaseImageService.java | 672 ++ .../dao/resource/BaseResourceService.java | 86 +- .../server/dao/resource/ImageCacheKey.java | 62 + .../server/dao/resource/TbResourceDao.java | 15 +- .../dao/resource/TbResourceInfoDao.java | 21 +- .../server/dao/rpc/BaseRpcService.java | 2 +- .../thingsboard/server/dao/rpc/RpcDao.java | 2 +- .../server/dao/rule/BaseRuleChainService.java | 7 +- .../dao/rule/BaseRuleNodeStateService.java | 2 +- .../server/dao/rule/RuleChainDao.java | 2 +- .../server/dao/rule/RuleNodeDao.java | 2 +- .../server/dao/rule/RuleNodeStateDao.java | 2 +- .../dao/service/ConstraintValidator.java | 2 +- .../server/dao/service/DataValidator.java | 11 +- .../server/dao/service/NoXssValidator.java | 2 +- .../server/dao/service/PaginatedRemover.java | 2 +- .../dao/service/StringLengthValidator.java | 2 +- .../dao/service/TimePaginatedRemover.java | 2 +- .../server/dao/service/Validator.java | 4 +- .../AbstractHasOtaPackageValidator.java | 2 +- .../validator/AdminSettingsDataValidator.java | 2 +- .../validator/AlarmCommentDataValidator.java | 2 +- .../service/validator/AlarmDataValidator.java | 2 +- .../validator/ApiUsageDataValidator.java | 2 +- .../service/validator/AssetDataValidator.java | 2 +- .../validator/AssetProfileDataValidator.java | 2 +- .../validator/AuditLogDataValidator.java | 2 +- .../BaseOtaPackageDataValidator.java | 2 +- ...ientRegistrationTemplateDataValidator.java | 2 +- .../ComponentDescriptorDataValidator.java | 2 +- .../validator/CustomerDataValidator.java | 2 +- .../validator/DashboardDataValidator.java | 2 +- .../DeviceCredentialsDataValidator.java | 2 +- .../validator/DeviceDataValidator.java | 2 +- .../validator/DeviceProfileDataValidator.java | 2 +- .../service/validator/EdgeDataValidator.java | 2 +- .../validator/EdgeEventDataValidator.java | 2 +- .../validator/EntityViewDataValidator.java | 2 +- .../service/validator/EventDataValidator.java | 2 +- .../validator/OtaPackageDataValidator.java | 2 +- .../OtaPackageInfoDataValidator.java | 2 +- .../dao/service/validator/QueueValidator.java | 2 +- .../validator/ResourceDataValidator.java | 57 +- .../validator/RuleChainDataValidator.java | 11 +- .../validator/TenantDataValidator.java | 2 +- .../validator/TenantProfileDataValidator.java | 2 +- .../UserCredentialsDataValidator.java | 2 +- .../service/validator/UserDataValidator.java | 2 +- .../validator/WidgetTypeDataValidator.java | 2 +- .../validator/WidgetsBundleDataValidator.java | 2 +- .../server/dao/settings/AdminSettingsDao.java | 2 +- .../settings/AdminSettingsServiceImpl.java | 2 +- .../server/dao/sql/JpaAbstractDao.java | 2 +- ...paAbstractDaoListeningExecutorService.java | 2 +- .../server/dao/sql/JpaExecutorService.java | 2 +- .../sql/ScheduledLogExecutorComponent.java | 2 +- .../server/dao/sql/TbSqlBlockingQueue.java | 19 +- .../dao/sql/TbSqlBlockingQueueParams.java | 2 +- .../dao/sql/TbSqlBlockingQueueWrapper.java | 2 +- .../server/dao/sql/TbSqlQueue.java | 2 +- .../server/dao/sql/TbSqlQueueElement.java | 2 +- .../dao/sql/alarm/AlarmCommentRepository.java | 2 +- .../server/dao/sql/alarm/AlarmRepository.java | 2 +- .../dao/sql/alarm/EntityAlarmRepository.java | 2 +- .../dao/sql/alarm/JpaAlarmCommentDao.java | 2 +- .../server/dao/sql/alarm/JpaAlarmDao.java | 2 +- .../dao/sql/asset/AssetProfileRepository.java | 23 +- .../server/dao/sql/asset/AssetRepository.java | 5 +- .../server/dao/sql/asset/JpaAssetDao.java | 10 +- .../dao/sql/asset/JpaAssetProfileDao.java | 28 +- .../AttributeKvInsertRepository.java | 2 +- .../sql/attributes/AttributeKvRepository.java | 2 +- .../dao/sql/attributes/JpaAttributeDao.java | 2 +- .../SqlAttributesInsertRepository.java | 2 +- .../dao/sql/audit/AuditLogRepository.java | 2 +- .../server/dao/sql/audit/JpaAuditLogDao.java | 2 +- ...ctComponentDescriptorInsertRepository.java | 5 +- .../ComponentDescriptorInsertRepository.java | 2 +- .../ComponentDescriptorRepository.java | 2 +- .../JpaBaseComponentDescriptorDao.java | 2 +- ...qlComponentDescriptorInsertRepository.java | 6 +- .../dao/sql/customer/CustomerRepository.java | 2 +- .../dao/sql/customer/JpaCustomerDao.java | 2 +- .../dashboard/DashboardInfoRepository.java | 15 +- .../sql/dashboard/DashboardRepository.java | 5 +- .../dao/sql/dashboard/JpaDashboardDao.java | 7 +- .../sql/dashboard/JpaDashboardInfoDao.java | 14 +- .../device/DefaultNativeDeviceRepository.java | 2 +- .../device/DeviceCredentialsRepository.java | 2 +- .../sql/device/DeviceProfileRepository.java | 23 +- .../dao/sql/device/DeviceRepository.java | 5 +- .../sql/device/JpaDeviceCredentialsDao.java | 2 +- .../server/dao/sql/device/JpaDeviceDao.java | 10 +- .../dao/sql/device/JpaDeviceProfileDao.java | 28 +- .../sql/device/NativeDeviceRepository.java | 2 +- .../sql/edge/EdgeEventInsertRepository.java | 2 +- .../dao/sql/edge/EdgeEventRepository.java | 2 +- .../server/dao/sql/edge/EdgeRepository.java | 2 +- .../dao/sql/edge/JpaBaseEdgeEventDao.java | 2 +- .../server/dao/sql/edge/JpaEdgeDao.java | 2 +- .../sql/entityview/EntityViewRepository.java | 2 +- .../dao/sql/entityview/JpaEntityViewDao.java | 2 +- .../dao/sql/event/ErrorEventRepository.java | 2 +- .../dao/sql/event/EventCleanupRepository.java | 2 +- .../dao/sql/event/EventInsertRepository.java | 2 +- .../event/EventPartitionConfiguration.java | 2 +- .../server/dao/sql/event/EventRepository.java | 2 +- .../server/dao/sql/event/JpaBaseEventDao.java | 2 +- .../sql/event/LifecycleEventRepository.java | 2 +- .../event/RuleChainDebugEventRepository.java | 2 +- .../event/RuleNodeDebugEventRepository.java | 2 +- .../sql/event/SqlEventCleanupRepository.java | 2 +- .../sql/event/StatisticsEventRepository.java | 2 +- .../sql/notification/JpaNotificationDao.java | 2 +- .../JpaNotificationRequestDao.java | 2 +- .../notification/JpaNotificationRuleDao.java | 2 +- .../JpaNotificationTargetDao.java | 2 +- .../JpaNotificationTemplateDao.java | 2 +- .../notification/NotificationRepository.java | 2 +- .../NotificationRequestRepository.java | 2 +- .../NotificationRuleRepository.java | 2 +- .../NotificationTargetRepository.java | 2 +- .../NotificationTemplateRepository.java | 2 +- ...paOAuth2ClientRegistrationTemplateDao.java | 2 +- .../dao/sql/oauth2/JpaOAuth2DomainDao.java | 2 +- .../dao/sql/oauth2/JpaOAuth2MobileDao.java | 2 +- .../dao/sql/oauth2/JpaOAuth2ParamsDao.java | 2 +- .../sql/oauth2/JpaOAuth2RegistrationDao.java | 2 +- ...2ClientRegistrationTemplateRepository.java | 2 +- .../sql/oauth2/OAuth2DomainRepository.java | 2 +- .../sql/oauth2/OAuth2MobileRepository.java | 2 +- .../sql/oauth2/OAuth2ParamsRepository.java | 2 +- .../oauth2/OAuth2RegistrationRepository.java | 2 +- .../server/dao/sql/ota/JpaOtaPackageDao.java | 2 +- .../dao/sql/ota/JpaOtaPackageInfoDao.java | 2 +- .../dao/sql/ota/OtaPackageInfoRepository.java | 2 +- .../dao/sql/ota/OtaPackageRepository.java | 2 +- .../dao/sql/query/AlarmDataAdapter.java | 2 +- .../dao/sql/query/AlarmQueryRepository.java | 2 +- .../query/DefaultAlarmQueryRepository.java | 2 +- .../query/DefaultEntityQueryRepository.java | 2 +- .../sql/query/DefaultQueryLogComponent.java | 2 +- .../dao/sql/query/EntityDataAdapter.java | 2 +- .../dao/sql/query/EntityKeyMapping.java | 2 +- .../dao/sql/query/EntityQueryRepository.java | 2 +- .../dao/sql/query/JpaEntityQueryDao.java | 2 +- .../server/dao/sql/query/QueryContext.java | 2 +- .../dao/sql/query/QueryLogComponent.java | 2 +- .../dao/sql/query/QuerySecurityContext.java | 2 +- .../server/dao/sql/queue/JpaQueueDao.java | 2 +- .../server/dao/sql/queue/QueueRepository.java | 2 +- .../dao/sql/relation/JpaRelationDao.java | 2 +- .../JpaRelationQueryExecutorService.java | 2 +- .../relation/RelationInsertRepository.java | 2 +- .../dao/sql/relation/RelationRepository.java | 2 +- .../relation/SqlRelationInsertRepository.java | 2 +- .../dao/sql/resource/JpaTbResourceDao.java | 43 +- .../sql/resource/JpaTbResourceInfoDao.java | 58 +- .../resource/TbResourceInfoRepository.java | 38 +- .../sql/resource/TbResourceRepository.java | 21 +- .../server/dao/sql/rpc/JpaRpcDao.java | 2 +- .../server/dao/sql/rpc/RpcRepository.java | 2 +- .../server/dao/sql/rule/JpaRuleChainDao.java | 2 +- .../server/dao/sql/rule/JpaRuleNodeDao.java | 2 +- .../dao/sql/rule/JpaRuleNodeStateDao.java | 2 +- .../dao/sql/rule/RuleChainRepository.java | 2 +- .../dao/sql/rule/RuleNodeRepository.java | 4 +- .../dao/sql/rule/RuleNodeStateRepository.java | 2 +- .../sql/settings/AdminSettingsRepository.java | 2 +- .../dao/sql/settings/JpaAdminSettingsDao.java | 2 +- .../server/dao/sql/tenant/JpaTenantDao.java | 2 +- .../dao/sql/tenant/JpaTenantProfileDao.java | 2 +- .../sql/tenant/TenantProfileRepository.java | 2 +- .../dao/sql/tenant/TenantRepository.java | 2 +- .../usagerecord/ApiUsageStateRepository.java | 2 +- .../sql/usagerecord/JpaApiUsageStateDao.java | 2 +- .../dao/sql/user/JpaUserAuthSettingsDao.java | 2 +- .../dao/sql/user/JpaUserCredentialsDao.java | 2 +- .../server/dao/sql/user/JpaUserDao.java | 2 +- .../dao/sql/user/JpaUserSettingsDao.java | 2 +- .../sql/user/UserAuthSettingsRepository.java | 2 +- .../sql/user/UserCredentialsRepository.java | 2 +- .../server/dao/sql/user/UserRepository.java | 2 +- .../dao/sql/user/UserSettingsRepository.java | 2 +- .../dao/sql/widget/JpaWidgetTypeDao.java | 25 +- .../dao/sql/widget/JpaWidgetsBundleDao.java | 18 +- .../sql/widget/WidgetTypeInfoRepository.java | 15 +- .../dao/sql/widget/WidgetTypeRepository.java | 12 +- .../sql/widget/WidgetsBundleRepository.java | 8 +- .../widget/WidgetsBundleWidgetRepository.java | 2 +- ...stractChunkedAggregationTimeseriesDao.java | 25 +- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 2 +- .../dao/sqlts/AggregationTimeseriesDao.java | 2 +- .../sqlts/BaseAbstractSqlTimeseriesDao.java | 10 +- .../server/dao/sqlts/EntityContainer.java | 2 +- .../dao/sqlts/SqlTimeseriesLatestDao.java | 2 +- .../thingsboard/server/dao/sqlts/TsKey.java | 2 +- .../dictionary/TsKvDictionaryRepository.java | 2 +- .../insert/AbstractInsertRepository.java | 2 +- .../dao/sqlts/insert/InsertTsRepository.java | 2 +- .../latest/InsertLatestTsRepository.java | 2 +- .../sql/SqlLatestInsertTsRepository.java | 2 +- .../insert/sql/SqlInsertTsRepository.java | 2 +- .../insert/sql/SqlPartitioningRepository.java | 2 +- .../TimescaleInsertTsRepository.java | 2 +- .../latest/SearchTsKvLatestRepository.java | 2 +- .../sqlts/latest/TsKvLatestRepository.java | 2 +- .../dao/sqlts/sql/JpaSqlTimeseriesDao.java | 2 +- .../timescale/AggregationRepository.java | 2 +- .../timescale/TimescaleTimeseriesDao.java | 26 +- .../timescale/TsKvTimescaleRepository.java | 2 +- .../server/dao/sqlts/ts/TsKvRepository.java | 2 +- .../tenant/DefaultTbTenantProfileCache.java | 2 +- .../dao/tenant/TenantCaffeineCache.java | 2 +- .../server/dao/tenant/TenantDao.java | 2 +- .../server/dao/tenant/TenantEvictEvent.java | 2 +- .../dao/tenant/TenantExistsCaffeineCache.java | 2 +- .../dao/tenant/TenantExistsRedisCache.java | 2 +- .../dao/tenant/TenantProfileCacheKey.java | 2 +- .../tenant/TenantProfileCaffeineCache.java | 2 +- .../server/dao/tenant/TenantProfileDao.java | 2 +- .../dao/tenant/TenantProfileEvictEvent.java | 2 +- .../dao/tenant/TenantProfileRedisCache.java | 2 +- .../dao/tenant/TenantProfileServiceImpl.java | 4 +- .../server/dao/tenant/TenantRedisCache.java | 2 +- .../server/dao/tenant/TenantServiceImpl.java | 4 +- .../AbstractCassandraBaseTimeseriesDao.java | 2 +- .../AggregatePartitionsFunction.java | 2 +- .../dao/timeseries/BaseTimeseriesService.java | 2 +- .../CassandraBaseTimeseriesDao.java | 20 +- .../CassandraBaseTimeseriesLatestDao.java | 2 +- .../CassandraPartitionCacheKey.java | 2 +- .../CassandraTsPartitionsCache.java | 2 +- .../dao/timeseries/NoSqlTsPartitionDate.java | 2 +- .../server/dao/timeseries/QueryCursor.java | 2 +- .../timeseries/SimpleListenableFuture.java | 2 +- .../server/dao/timeseries/SqlPartition.java | 2 +- .../dao/timeseries/SqlTsPartitionDate.java | 2 +- .../server/dao/timeseries/TimeseriesDao.java | 2 +- .../dao/timeseries/TimeseriesLatestDao.java | 2 +- .../dao/timeseries/TsInsertExecutorType.java | 2 +- .../dao/timeseries/TsKvQueryCursor.java | 4 +- .../dao/usage/BasicUsageInfoService.java | 2 +- .../dao/usagerecord/ApiUsageStateDao.java | 2 +- .../usagerecord/ApiUsageStateServiceImpl.java | 2 +- .../usagerecord/DefaultApiLimitService.java | 2 +- .../server/dao/user/UserAuthSettingsDao.java | 2 +- .../server/dao/user/UserCredentialsDao.java | 2 +- .../thingsboard/server/dao/user/UserDao.java | 2 +- .../server/dao/user/UserServiceImpl.java | 4 +- .../dao/user/UserSettingsCaffeineCache.java | 2 +- .../server/dao/user/UserSettingsDao.java | 2 +- .../dao/user/UserSettingsEvictEvent.java | 2 +- .../dao/user/UserSettingsRedisCache.java | 2 +- .../dao/user/UserSettingsServiceImpl.java | 2 +- .../util/AbstractBufferedRateExecutor.java | 2 +- .../server/dao/util/AsyncRateLimiter.java | 2 +- .../server/dao/util/AsyncTaskContext.java | 2 +- .../server/dao/util/BufferedRateExecutor.java | 2 +- .../dao/util/BufferedRateExecutorStats.java | 2 +- .../dao/util/DeviceConnectivityUtil.java | 162 +- .../server/dao/util/ImageUtils.java | 267 + .../dao/util/JsonNodeProcessingTask.java | 30 + .../dao/util/JsonPathProcessingTask.java | 70 + .../thingsboard/server/dao/util/KvUtils.java | 2 +- .../dao/util/TenantRateLimitException.java | 2 +- .../server/dao/util/TimeUtils.java | 45 + .../util/limits/DefaultRateLimitService.java | 2 +- .../dao/util/limits/RateLimitService.java | 2 +- .../AbstractJsonSqlTypeDescriptor.java | 2 +- .../mapping/JsonBinarySqlTypeDescriptor.java | 2 +- .../dao/util/mapping/JsonBinaryType.java | 2 +- .../mapping/JsonStringSqlTypeDescriptor.java | 2 +- .../dao/util/mapping/JsonStringType.java | 2 +- .../dao/util/mapping/JsonTypeDescriptor.java | 2 +- .../server/dao/widget/WidgetTypeDao.java | 7 +- .../dao/widget/WidgetTypeServiceImpl.java | 11 +- .../server/dao/widget/WidgetsBundleDao.java | 8 +- .../dao/widget/WidgetsBundleServiceImpl.java | 9 +- .../resources/cassandra/schema-keyspace.cql | 2 +- .../resources/cassandra/schema-ts-latest.cql | 2 +- .../main/resources/cassandra/schema-ts.cql | 2 +- .../sql/schema-entities-idx-psql-addon.sql | 2 +- .../resources/sql/schema-entities-idx.sql | 16 +- .../main/resources/sql/schema-entities.sql | 13 +- .../main/resources/sql/schema-timescale.sql | 2 +- dao/src/main/resources/sql/schema-ts-psql.sql | 2 +- .../sql/schema-views-and-functions.sql | 2 +- dao/src/main/resources/xss-policy.xml | 2 +- .../server/dao/AbstractDaoServiceTest.java | 2 +- .../server/dao/AbstractJpaDaoTest.java | 2 +- .../server/dao/AbstractNoSqlContainer.java | 3 +- .../server/dao/AbstractRedisContainer.java | 2 +- .../server/dao/NoSqlDaoServiceTestSuite.java | 2 +- .../server/dao/PostgreSqlInitializer.java | 2 +- .../server/dao/RedisSqlTestSuite.java | 2 +- .../dao/TimescaleDaoServiceTestSuite.java | 2 +- .../server/dao/TimescaleSqlInitializer.java | 2 +- .../eventsourcing/DeleteEntityEventTest.java | 2 +- .../nosql/CassandraPartitionsCacheTest.java | 2 +- .../dao/service/AbstractServiceTest.java | 2 +- .../dao/service/AdminSettingsServiceTest.java | 2 +- .../dao/service/AlarmCommentServiceTest.java | 2 +- .../server/dao/service/AlarmServiceTest.java | 2 +- .../dao/service/ApiUsageStateServiceTest.java | 2 +- .../dao/service/AssetProfileServiceTest.java | 99 +- .../server/dao/service/AssetServiceTest.java | 41 +- .../dao/service/ConstraintValidatorTest.java | 2 +- .../dao/service/CustomerServiceTest.java | 2 +- .../server/dao/service/DaoNoSqlTest.java | 2 +- .../server/dao/service/DaoSqlTest.java | 2 +- .../server/dao/service/DaoTimescaleTest.java | 2 +- .../dao/service/DashboardServiceTest.java | 2 +- .../server/dao/service/DataValidatorTest.java | 2 +- .../service/DeviceCredentialsCacheTest.java | 2 +- .../service/DeviceCredentialsServiceTest.java | 2 +- .../dao/service/DeviceProfileServiceTest.java | 96 +- .../server/dao/service/DeviceServiceTest.java | 31 +- .../dao/service/EdgeEventServiceTest.java | 2 +- .../server/dao/service/EdgeServiceTest.java | 2 +- .../service/EntityServiceRegistryTest.java | 2 +- .../server/dao/service/EntityServiceTest.java | 2 +- .../dao/service/NoXssValidatorTest.java | 2 +- .../OAuth2ConfigTemplateServiceTest.java | 2 +- .../server/dao/service/OAuth2ServiceTest.java | 2 +- .../dao/service/OtaPackageServiceTest.java | 4 +- .../server/dao/service/QueueServiceTest.java | 2 +- .../server/dao/service/RelationCacheTest.java | 2 +- .../dao/service/RelationServiceTest.java | 2 +- .../dao/service/RuleChainServiceTest.java | 2 +- .../dao/service/TbCacheSerializationTest.java | 2 +- .../dao/service/TenantProfileServiceTest.java | 2 +- .../server/dao/service/TenantServiceTest.java | 5 +- .../server/dao/service/UserServiceTest.java | 2 +- .../dao/service/WidgetTypeServiceTest.java | 2 +- .../dao/service/WidgetsBundleServiceTest.java | 2 +- .../attributes/BaseAttributesServiceTest.java | 2 +- .../sql/AttributesServiceSqlTest.java | 2 +- .../service/event/BaseEventServiceTest.java | 2 +- .../event/sql/EventServiceSqlTest.java | 2 +- .../install/sql/EntitiesSchemaSqlTest.java | 2 +- .../timeseries/BaseTimeseriesServiceTest.java | 2 +- .../nosql/TimeseriesServiceNoSqlTest.java | 2 +- .../nosql/TimeseriesServiceTimescaleTest.java | 2 +- .../sql/TimeseriesServiceSqlTest.java | 2 +- .../AdminSettingsDataValidatorTest.java | 2 +- .../validator/AlarmDataValidatorTest.java | 2 +- .../validator/AssetDataValidatorTest.java | 2 +- .../AssetProfileDataValidatorTest.java | 2 +- .../BaseOtaPackageDataValidatorTest.java | 2 +- .../ComponentDescriptorDataValidatorTest.java | 2 +- .../validator/CustomerDataValidatorTest.java | 2 +- .../validator/DashboardDataValidatorTest.java | 2 +- .../validator/DeviceDataValidatorTest.java | 2 +- .../DeviceProfileDataValidatorTest.java | 2 +- .../validator/EdgeDataValidatorTest.java | 2 +- .../EntityViewDataValidatorTest.java | 2 +- .../validator/ResourceDataValidatorTest.java | 2 +- .../validator/RuleChainDataValidatorTest.java | 2 +- .../validator/TenantDataValidatorTest.java | 2 +- .../TenantProfileDataValidatorTest.java | 2 +- .../WidgetTypeDataValidatorTest.java | 2 +- .../WidgetsBundleDataValidatorTest.java | 2 +- .../dao/sql/alarm/JpaAlarmCommentDaoTest.java | 2 +- .../server/dao/sql/alarm/JpaAlarmDaoTest.java | 2 +- .../server/dao/sql/asset/JpaAssetDaoTest.java | 2 +- .../dao/sql/audit/JpaAuditLogDaoTest.java | 2 +- .../JpaBaseComponentDescriptorDaoTest.java | 2 +- .../dao/sql/customer/JpaCustomerDaoTest.java | 2 +- .../dashboard/JpaDashboardInfoDaoTest.java | 2 +- .../device/JpaDeviceCredentialsDaoTest.java | 2 +- .../dao/sql/device/JpaDeviceDaoTest.java | 2 +- .../dao/sql/event/JpaBaseEventDaoTest.java | 2 +- .../DefaultEntityQueryRepositoryTest.java | 2 +- .../query/DefaultQueryLogComponentTest.java | 2 +- .../dao/sql/query/EntityDataAdapterTest.java | 2 +- .../dao/sql/query/EntityKeyMappingTest.java | 2 +- .../dao/sql/rule/JpaRuleNodeDaoTest.java | 2 +- .../dao/sql/tenant/JpaTenantDaoTest.java | 2 +- .../sql/user/JpaUserCredentialsDaoTest.java | 2 +- .../server/dao/sql/user/JpaUserDaoTest.java | 2 +- .../dao/sql/user/JpaUserSettingsDaoTest.java | 2 +- .../dao/sql/widget/JpaWidgetTypeDaoTest.java | 25 +- .../sql/widget/JpaWidgetsBundleDaoTest.java | 2 +- ...ctChunkedAggregationTimeseriesDaoTest.java | 2 +- ...esDaoPartitioningDaysAlwaysExistsTest.java | 2 +- ...sDaoPartitioningHoursAlwaysExistsTest.java | 2 +- ...artitioningIndefiniteAlwaysExistsTest.java | 2 +- ...aoPartitioningMinutesAlwaysExistsTest.java | 2 +- ...DaoPartitioningMonthsAlwaysExistsTest.java | 2 +- ...sDaoPartitioningYearsAlwaysExistsTest.java | 2 +- .../dao/util/DeviceConnectivityUtilTest.java | 32 + .../server/dao/util/TimeUtilsTest.java | 60 + docker/compose-utils.sh | 2 +- docker/docker-check-log-folders.sh | 2 +- docker/docker-compose.aws-sqs.yml | 2 +- docker/docker-compose.cassandra.volumes.yml | 2 +- docker/docker-compose.confluent.yml | 2 +- docker/docker-compose.hybrid.yml | 4 +- docker/docker-compose.kafka.yml | 2 +- docker/docker-compose.postgres.volumes.yml | 2 +- docker/docker-compose.postgres.yml | 4 +- docker/docker-compose.prometheus-grafana.yml | 2 +- docker/docker-compose.pubsub.yml | 2 +- docker/docker-compose.rabbitmq.yml | 2 +- .../docker-compose.redis-cluster.volumes.yml | 2 +- docker/docker-compose.redis-cluster.yml | 2 +- .../docker-compose.redis-sentinel.volumes.yml | 2 +- docker/docker-compose.redis-sentinel.yml | 2 +- docker/docker-compose.redis.volumes.yml | 2 +- docker/docker-compose.redis.yml | 2 +- docker/docker-compose.service-bus.yml | 2 +- docker/docker-compose.volumes.yml | 2 +- docker/docker-compose.yml | 2 +- docker/docker-create-log-folders.sh | 2 +- docker/docker-install-tb.sh | 2 +- docker/docker-remove-services.sh | 2 +- docker/docker-start-services.sh | 2 +- docker/docker-stop-services.sh | 2 +- docker/docker-update-service.sh | 2 +- docker/docker-upgrade-tb.sh | 2 +- .../provisioning/dashboards/dashboard.yml | 2 +- .../provisioning/datasources/datasource.yml | 2 +- docker/monitoring/prometheus/prometheus.yml | 2 +- docker/tb-transports/coap/conf/logback.xml | 2 +- .../coap/conf/tb-coap-transport.conf | 2 +- docker/tb-transports/http/conf/logback.xml | 2 +- .../http/conf/tb-http-transport.conf | 2 +- docker/tb-transports/lwm2m/conf/logback.xml | 2 +- .../lwm2m/conf/tb-lwm2m-transport.conf | 2 +- docker/tb-transports/mqtt/conf/logback.xml | 2 +- .../mqtt/conf/tb-mqtt-transport.conf | 2 +- docker/tb-transports/snmp/conf/logback.xml | 2 +- .../snmp/conf/tb-snmp-transport.conf | 2 +- docker/tb-vc-executor/conf/logback.xml | 2 +- .../tb-vc-executor/conf/tb-vc-executor.conf | 2 +- license-header-template.txt | 2 +- monitoring/pom.xml | 4 +- monitoring/src/main/conf/logback.xml | 2 +- monitoring/src/main/conf/tb-monitoring.conf | 2 +- .../ThingsboardMonitoringApplication.java | 2 +- .../monitoring/client/Lwm2mClient.java | 2 +- .../monitoring/client/TbClient.java | 2 +- .../monitoring/client/WsClient.java | 2 +- .../monitoring/client/WsClientFactory.java | 2 +- .../monitoring/config/MonitoringConfig.java | 2 +- .../monitoring/config/MonitoringTarget.java | 10 +- .../CoapTransportMonitoringConfig.java | 2 +- .../config/transport/DeviceConfig.java | 2 +- .../HttpTransportMonitoringConfig.java | 2 +- .../Lwm2mTransportMonitoringConfig.java | 2 +- .../MqttTransportMonitoringConfig.java | 2 +- .../config/transport/TransportInfo.java | 9 +- .../transport/TransportMonitoringConfig.java | 5 +- .../transport/TransportMonitoringTarget.java | 9 +- .../config/transport/TransportType.java | 11 +- .../monitoring/data/Latencies.java | 6 +- .../thingsboard/monitoring/data/Latency.java | 50 +- .../monitoring/data/MonitoredServiceKey.java | 2 +- .../data/ServiceFailureException.java | 2 +- .../monitoring/data/cmd/CmdsWrapper.java | 2 +- .../monitoring/data/cmd/EntityDataCmd.java | 2 +- .../monitoring/data/cmd/EntityDataUpdate.java | 2 +- .../monitoring/data/cmd/LatestValueCmd.java | 2 +- .../notification/HighLatencyNotification.java | 12 +- .../data/notification/Notification.java | 2 +- .../ServiceFailureNotification.java | 4 +- .../ServiceRecoveryNotification.java | 4 +- .../notification/NotificationService.java | 20 +- .../channels/NotificationChannel.java | 6 +- .../impl/SlackNotificationChannel.java | 13 +- .../monitoring/service/BaseHealthChecker.java | 17 +- .../service/BaseMonitoringService.java | 91 +- .../service/MonitoringReporter.java | 28 +- .../transport/TransportHealthChecker.java | 98 +- .../TransportsMonitoringService.java | 9 +- .../impl/CoapTransportHealthChecker.java | 2 +- .../impl/HttpTransportHealthChecker.java | 2 +- .../impl/Lwm2mTransportHealthChecker.java | 2 +- .../impl/MqttTransportHealthChecker.java | 2 +- .../monitoring/util/ResourceUtils.java | 2 +- .../monitoring/util/TbStopWatch.java | 2 +- monitoring/src/main/resources/logback.xml | 2 +- .../src/main/resources/lwm2m/models/0.xml | 2 +- .../src/main/resources/lwm2m/models/1.xml | 2 +- .../src/main/resources/lwm2m/models/2.xml | 2 +- .../resources/lwm2m/models/test-model.xml | 2 +- .../src/main/resources/root_rule_chain.json | 436 ++ .../src/main/resources/tb-monitoring.yml | 31 +- msa/black-box-tests/pom.xml | 4 +- .../server/msa/AbstractContainerTest.java | 2 +- .../server/msa/ContainerTestSuite.java | 4 +- .../server/msa/DisableUIListeners.java | 2 +- .../server/msa/DockerComposeExecutor.java | 2 +- .../msa/SeleniumRemoteWebDriverTest.java | 2 +- .../server/msa/TestCoapClient.java | 2 +- .../server/msa/TestCoapClientCallback.java | 2 +- .../thingsboard/server/msa/TestListener.java | 2 +- .../server/msa/TestProperties.java | 11 +- .../server/msa/TestRestClient.java | 43 +- .../server/msa/ThingsBoardDbInstaller.java | 8 +- .../org/thingsboard/server/msa/WsClient.java | 2 +- .../msa/connectivity/CoapClientTest.java | 2 +- .../msa/connectivity/HttpClientTest.java | 2 +- .../msa/connectivity/MqttClientTest.java | 11 +- .../connectivity/MqttGatewayClientTest.java | 2 +- .../server/msa/mapper/AttributesResponse.java | 2 +- .../msa/mapper/WsTelemetryResponse.java | 2 +- .../msa/prototypes/DevicePrototypes.java | 2 +- .../server/msa/rule/node/MqttNodeTest.java | 204 + .../server/msa/ui/base/AbstractBasePage.java | 2 +- .../msa/ui/base/AbstractDriverBaseTest.java | 2 +- .../msa/ui/listeners/RetryAnalyzer.java | 2 +- .../msa/ui/listeners/RetryTestListener.java | 2 +- .../pages/AlarmDetailsEntityTabElements.java | 2 +- .../ui/pages/AlarmDetailsEntityTabHelper.java | 2 +- .../ui/pages/AlarmDetailsViewElements.java | 2 +- .../msa/ui/pages/AlarmDetailsViewHelper.java | 2 +- .../msa/ui/pages/AlarmWidgetElements.java | 2 +- .../msa/ui/pages/AssetPageElements.java | 2 +- .../server/msa/ui/pages/AssetPageHelper.java | 2 +- .../ui/pages/CreateWidgetPopupElements.java | 2 +- .../msa/ui/pages/CreateWidgetPopupHelper.java | 2 +- .../msa/ui/pages/CustomerPageElements.java | 2 +- .../msa/ui/pages/CustomerPageHelper.java | 2 +- .../msa/ui/pages/DashboardPageElements.java | 2 +- .../msa/ui/pages/DashboardPageHelper.java | 2 +- .../msa/ui/pages/DevicePageElements.java | 2 +- .../server/msa/ui/pages/DevicePageHelper.java | 2 +- .../msa/ui/pages/EntityViewPageElements.java | 2 +- .../msa/ui/pages/EntityViewPageHelper.java | 2 +- .../msa/ui/pages/LoginPageElements.java | 2 +- .../server/msa/ui/pages/LoginPageHelper.java | 2 +- .../ui/pages/OpenRuleChainPageElements.java | 2 +- .../msa/ui/pages/OpenRuleChainPageHelper.java | 2 +- .../msa/ui/pages/OtherPageElements.java | 2 +- .../msa/ui/pages/OtherPageElementsHelper.java | 2 +- .../msa/ui/pages/ProfilesPageElements.java | 2 +- .../msa/ui/pages/ProfilesPageHelper.java | 2 +- .../msa/ui/pages/RuleChainsPageElements.java | 2 +- .../msa/ui/pages/RuleChainsPageHelper.java | 2 +- .../msa/ui/pages/SideBarMenuViewElements.java | 2 +- .../msa/ui/pages/SideBarMenuViewHelper.java | 2 +- .../msa/ui/tabs/AssignDeviceTabElements.java | 2 +- .../msa/ui/tabs/AssignDeviceTabHelper.java | 2 +- .../msa/ui/tabs/CreateDeviceTabElements.java | 2 +- .../msa/ui/tabs/CreateDeviceTabHelper.java | 2 +- .../alarmassignee/AbstractAssignTest.java | 2 +- .../AssignDetailsTabAssignTest.java | 2 +- ...ssignDetailsTabFromCustomerAssignTest.java | 2 +- .../AssignFromAlarmWidgetTest.java | 2 +- .../AssetProfileEditMenuTest.java | 2 +- .../CreateAssetProfileImportTest.java | 2 +- .../CreateAssetProfileTest.java | 2 +- .../DeleteAssetProfileTest.java | 2 +- .../DeleteSeveralAssetProfilesTest.java | 2 +- .../MakeAssetProfileDefaultTest.java | 2 +- .../SearchAssetProfileTest.java | 2 +- .../assetProfileSmoke/SortByNameTest.java | 2 +- .../customerSmoke/CreateCustomerTest.java | 2 +- .../customerSmoke/CustomerEditMenuTest.java | 2 +- .../customerSmoke/DeleteCustomerTest.java | 2 +- .../DeleteSeveralCustomerTest.java | 2 +- .../ManageCustomersAssetsTest.java | 2 +- .../ManageCustomersDashboardsTest.java | 2 +- .../ManageCustomersDevicesTest.java | 2 +- .../ManageCustomersEdgesTest.java | 2 +- .../ManageCustomersUsersTest.java | 2 +- .../customerSmoke/SearchCustomerTest.java | 2 +- .../tests/customerSmoke/SortByNameTest.java | 2 +- .../CreateDeviceProfileImportTest.java | 2 +- .../CreateDeviceProfileTest.java | 2 +- .../DeleteDeviceProfileTest.java | 2 +- .../DeleteSeveralDeviceProfilesTest.java | 2 +- .../DeviceProfileEditMenuTest.java | 2 +- .../MakeDeviceProfileDefaultTest.java | 2 +- .../SearchDeviceProfileTest.java | 2 +- .../deviceProfileSmoke/SortByNameTest.java | 2 +- .../devicessmoke/AbstractDeviceTest.java | 2 +- .../devicessmoke/AssignToCustomerTest.java | 2 +- .../tests/devicessmoke/CreateDeviceTest.java | 2 +- .../tests/devicessmoke/DeleteDeviceTest.java | 2 +- .../DeleteSeveralDevicesTest.java | 2 +- .../tests/devicessmoke/DeviceFilterTest.java | 2 +- .../ui/tests/devicessmoke/EditDeviceTest.java | 2 +- .../devicessmoke/MakeDevicePrivateTest.java | 2 +- .../devicessmoke/MakeDevicePublicTest.java | 2 +- .../AbstractRuleChainTest.java | 2 +- .../CreateRuleChainImportTest.java | 2 +- .../rulechainssmoke/CreateRuleChainTest.java | 2 +- .../rulechainssmoke/DeleteRuleChainTest.java | 2 +- .../DeleteSeveralRuleChainsTest.java | 2 +- .../MakeRuleChainRootTest.java | 2 +- .../rulechainssmoke/OpenRuleChainTest.java | 2 +- .../RuleChainEditMenuTest.java | 2 +- .../rulechainssmoke/SearchRuleChainTest.java | 2 +- .../tests/rulechainssmoke/SortByNameTest.java | 2 +- .../tests/rulechainssmoke/SortByTimeTest.java | 2 +- .../server/msa/ui/utils/Const.java | 2 +- .../msa/ui/utils/DataProviderCredential.java | 2 +- .../server/msa/ui/utils/EntityPrototypes.java | 2 +- .../resources/MqttRuleNodeTestMetadata.json | 79 + .../src/test/resources/alarmAssignee.xml | 2 +- .../src/test/resources/all.xml | 2 +- .../src/test/resources/connectivity.xml | 2 +- .../docker-compose.hybrid-test-extras.yml | 2 +- .../resources/docker-compose.mosquitto.yml | 25 + .../docker-compose.postgres-test-extras.yml | 2 +- .../docker-compose.rabbitmq-server.yml | 2 +- .../src/test/resources/docker-selenium.yml | 2 +- .../src/test/resources/forImport.txt | 2 +- .../src/test/resources/logback.xml | 2 +- .../test/resources/mosquitto/mosquitto.conf | 18 + .../src/test/resources/smokeDevices.xml | 2 +- .../src/test/resources/smokesCustomer.xml | 2 +- .../src/test/resources/smokesProfiles.xml | 2 +- .../src/test/resources/smokesRuleChain.xml | 2 +- .../test/resources/tb-node/conf/logback.xml | 2 +- .../tb-transports/coap/conf/logback.xml | 2 +- .../tb-transports/http/conf/logback.xml | 2 +- .../tb-transports/mqtt/conf/logback.xml | 2 +- .../src/test/resources/uiTests.xml | 2 +- msa/js-executor/api/httpServer.ts | 2 +- msa/js-executor/api/jsExecutor.models.ts | 2 +- msa/js-executor/api/jsExecutor.ts | 2 +- .../api/jsInvokeMessageProcessor.ts | 2 +- msa/js-executor/api/utils.ts | 2 +- .../config/custom-environment-variables.yml | 2 +- msa/js-executor/config/default.yml | 2 +- msa/js-executor/config/logger.ts | 2 +- msa/js-executor/config/tb-js-executor.conf | 2 +- msa/js-executor/docker/Dockerfile | 2 +- msa/js-executor/docker/start-js-executor.sh | 2 +- msa/js-executor/install.js | 2 +- msa/js-executor/package.json | 2 +- msa/js-executor/pom.xml | 4 +- msa/js-executor/queue/awsSqsTemplate.ts | 2 +- msa/js-executor/queue/kafkaTemplate.ts | 2 +- msa/js-executor/queue/pubSubTemplate.ts | 2 +- msa/js-executor/queue/queue.models.ts | 2 +- msa/js-executor/queue/rabbitmqTemplate.ts | 2 +- msa/js-executor/queue/serviceBusTemplate.ts | 2 +- msa/js-executor/server.ts | 2 +- msa/monitoring/docker/Dockerfile | 2 +- msa/monitoring/docker/start-tb-monitoring.sh | 2 +- msa/monitoring/pom.xml | 4 +- msa/pom.xml | 4 +- msa/tb-node/docker/Dockerfile | 2 +- msa/tb-node/docker/start-tb-node.sh | 2 +- msa/tb-node/pom.xml | 4 +- msa/tb/docker-cassandra/Dockerfile | 4 +- msa/tb/docker-cassandra/start-db.sh | 2 +- msa/tb/docker-cassandra/stop-db.sh | 2 +- msa/tb/docker-postgres/Dockerfile | 2 +- msa/tb/docker-postgres/start-db.sh | 2 +- msa/tb/docker-postgres/stop-db.sh | 2 +- msa/tb/docker/install-tb.sh | 2 +- msa/tb/docker/logback.xml | 2 +- msa/tb/docker/start-tb.sh | 2 +- msa/tb/docker/thingsboard.conf | 2 +- msa/tb/docker/upgrade-tb.sh | 2 +- msa/tb/pom.xml | 6 +- msa/transport/coap/docker/Dockerfile | 2 +- .../coap/docker/start-tb-coap-transport.sh | 2 +- msa/transport/coap/pom.xml | 4 +- msa/transport/http/docker/Dockerfile | 2 +- .../http/docker/start-tb-http-transport.sh | 2 +- msa/transport/http/pom.xml | 4 +- msa/transport/lwm2m/docker/Dockerfile | 2 +- .../lwm2m/docker/start-tb-lwm2m-transport.sh | 2 +- msa/transport/lwm2m/pom.xml | 4 +- msa/transport/mqtt/docker/Dockerfile | 2 +- .../mqtt/docker/start-tb-mqtt-transport.sh | 2 +- msa/transport/mqtt/pom.xml | 4 +- msa/transport/pom.xml | 4 +- msa/transport/snmp/docker/Dockerfile | 2 +- .../snmp/docker/start-tb-snmp-transport.sh | 2 +- msa/transport/snmp/pom.xml | 4 +- msa/vc-executor-docker/docker/Dockerfile | 2 +- .../docker/start-tb-vc-executor.sh | 2 +- msa/vc-executor-docker/pom.xml | 4 +- msa/vc-executor/pom.xml | 4 +- msa/vc-executor/src/main/conf/logback.xml | 2 +- .../src/main/conf/tb-vc-executor.conf | 2 +- ...oardVersionControlExecutorApplication.java | 2 +- ...VersionControlQueueRoutingInfoService.java | 2 +- ...ersionControlTenantRoutingInfoService.java | 2 +- .../src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-vc-executor.yml | 12 +- .../config/custom-environment-variables.yml | 2 +- msa/web-ui/config/default.yml | 2 +- msa/web-ui/config/logger.ts | 2 +- msa/web-ui/config/tb-web-ui.conf | 2 +- msa/web-ui/docker/Dockerfile | 2 +- msa/web-ui/docker/start-web-ui.sh | 2 +- msa/web-ui/install.js | 2 +- msa/web-ui/package.json | 2 +- msa/web-ui/pom.xml | 4 +- msa/web-ui/server.ts | 2 +- netty-mqtt/pom.xml | 6 +- .../mqtt/ChannelClosedException.java | 2 +- .../thingsboard/mqtt/MqttChannelHandler.java | 2 +- .../java/org/thingsboard/mqtt/MqttClient.java | 2 +- .../thingsboard/mqtt/MqttClientCallback.java | 2 +- .../thingsboard/mqtt/MqttClientConfig.java | 2 +- .../org/thingsboard/mqtt/MqttClientImpl.java | 2 +- .../thingsboard/mqtt/MqttConnectResult.java | 2 +- .../org/thingsboard/mqtt/MqttHandler.java | 2 +- .../mqtt/MqttIncomingQos2Publish.java | 2 +- .../org/thingsboard/mqtt/MqttLastWill.java | 2 +- .../thingsboard/mqtt/MqttPendingPublish.java | 2 +- .../mqtt/MqttPendingSubscription.java | 2 +- .../mqtt/MqttPendingUnsubscription.java | 2 +- .../org/thingsboard/mqtt/MqttPingHandler.java | 2 +- .../thingsboard/mqtt/MqttSubscription.java | 2 +- .../thingsboard/mqtt/PendingOperation.java | 2 +- .../mqtt/RetransmissionHandler.java | 2 +- .../thingsboard/mqtt/MqttPingHandlerTest.java | 2 +- .../integration/IntegrationTestSuite.java | 2 +- .../mqtt/integration/MqttIntegrationTest.java | 2 +- .../mqtt/integration/server/MqttServer.java | 2 +- .../server/MqttTransportHandler.java | 2 +- packaging/java/assembly/windows.xml | 2 +- packaging/java/build.gradle | 2 +- packaging/java/scripts/install/install.sh | 2 +- .../java/scripts/install/install_dev_db.sh | 2 +- packaging/java/scripts/install/logback.xml | 2 +- packaging/java/scripts/install/upgrade.sh | 2 +- .../java/scripts/install/upgrade_dev_db.sh | 2 +- packaging/js/assembly/windows.xml | 2 +- packaging/js/build.gradle | 2 +- pom.xml | 30 +- rest-client/pom.xml | 4 +- .../thingsboard/rest/client/RestClient.java | 217 +- .../rest/client/utils/RestJsonConverter.java | 2 +- rest-client/src/main/resources/logback.xml | 2 +- rule-engine/pom.xml | 4 +- rule-engine/rule-engine-api/pom.xml | 4 +- .../engine/api/EmptyNodeConfiguration.java | 2 +- .../rule/engine/api/MailService.java | 2 +- .../rule/engine/api/NodeConfiguration.java | 2 +- .../rule/engine/api/NodeDefinition.java | 2 +- .../rule/engine/api/NotificationCenter.java | 6 +- .../engine/api/RuleEngineAlarmService.java | 2 +- .../api/RuleEngineApiUsageStateService.java | 2 +- .../api/RuleEngineAssetProfileCache.java | 2 +- .../api/RuleEngineDeviceProfileCache.java | 2 +- .../api/RuleEngineDeviceRpcRequest.java | 2 +- .../api/RuleEngineDeviceRpcResponse.java | 2 +- .../rule/engine/api/RuleEngineRpcService.java | 2 +- .../api/RuleEngineTelemetryService.java | 2 +- .../thingsboard/rule/engine/api/RuleNode.java | 4 +- .../rule/engine/api/ScriptEngine.java | 2 +- .../rule/engine/api/SmsService.java | 2 +- .../rule/engine/api/TbContext.java | 16 +- .../thingsboard/rule/engine/api/TbEmail.java | 2 +- .../thingsboard/rule/engine/api/TbNode.java | 2 +- .../rule/engine/api/TbNodeConfiguration.java | 2 +- .../rule/engine/api/TbNodeException.java | 2 +- .../rule/engine/api/TbNodeState.java | 2 +- .../rule/engine/api/slack/SlackService.java | 2 +- .../rule/engine/api/sms/SmsSender.java | 2 +- .../rule/engine/api/sms/SmsSenderFactory.java | 2 +- .../api/sms/exception/SmsException.java | 2 +- .../api/sms/exception/SmsParseException.java | 2 +- .../api/sms/exception/SmsSendException.java | 2 +- .../rule/engine/api/util/TbNodeUtils.java | 3 +- .../rule/engine/api/util/TbNodeUtilsTest.java | 2 +- rule-engine/rule-engine-components/pom.xml | 4 +- .../engine/action/TbAbstractAlarmNode.java | 2 +- .../TbAbstractAlarmNodeConfiguration.java | 2 +- .../action/TbAbstractCustomerActionNode.java | 2 +- ...stractCustomerActionNodeConfiguration.java | 2 +- .../action/TbAbstractRelationActionNode.java | 2 +- ...stractRelationActionNodeConfiguration.java | 2 +- .../rule/engine/action/TbAlarmResult.java | 2 +- .../engine/action/TbAssignToCustomerNode.java | 2 +- .../TbAssignToCustomerNodeConfiguration.java | 2 +- .../rule/engine/action/TbClearAlarmNode.java | 2 +- .../action/TbClearAlarmNodeConfiguration.java | 2 +- .../TbCopyAttributesToEntityViewNode.java | 4 +- .../rule/engine/action/TbCreateAlarmNode.java | 2 +- .../TbCreateAlarmNodeConfiguration.java | 2 +- .../engine/action/TbCreateRelationNode.java | 2 +- .../TbCreateRelationNodeConfiguration.java | 2 +- .../engine/action/TbDeleteRelationNode.java | 2 +- .../TbDeleteRelationNodeConfiguration.java | 2 +- .../rule/engine/action/TbLogNode.java | 2 +- .../engine/action/TbLogNodeConfiguration.java | 2 +- .../rule/engine/action/TbMsgCountNode.java | 2 +- .../action/TbMsgCountNodeConfiguration.java | 2 +- .../TbSaveToCustomCassandraTableNode.java | 2 +- ...CustomCassandraTableNodeConfiguration.java | 2 +- .../action/TbUnassignFromCustomerNode.java | 2 +- ...UnassignFromCustomerNodeConfiguration.java | 2 +- .../rule/engine/aws/sns/TbSnsNode.java | 2 +- .../aws/sns/TbSnsNodeConfiguration.java | 2 +- .../rule/engine/aws/sqs/TbSqsNode.java | 2 +- .../aws/sqs/TbSqsNodeConfiguration.java | 2 +- .../credentials/AnonymousCredentials.java | 2 +- .../engine/credentials/BasicCredentials.java | 2 +- .../credentials/CertPemCredentials.java | 2 +- .../engine/credentials/ClientCredentials.java | 2 +- .../engine/credentials/CredentialsType.java | 2 +- .../engine/data/DeviceRelationsQuery.java | 2 +- .../rule/engine/data/RelationsQuery.java | 2 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 32 +- .../TbMsgGeneratorNodeConfiguration.java | 3 +- .../deduplication/DeduplicationData.java | 2 +- .../engine/deduplication/DeduplicationId.java | 2 +- .../deduplication/DeduplicationStrategy.java | 2 +- .../deduplication/TbMsgDeduplicationNode.java | 38 +- .../TbMsgDeduplicationNodeConfiguration.java | 3 +- .../rule/engine/delay/TbMsgDelayNode.java | 2 +- .../delay/TbMsgDelayNodeConfiguration.java | 2 +- .../engine/edge/AbstractTbMsgPushNode.java | 43 +- .../edge/BaseTbMsgPushNodeConfiguration.java | 2 +- .../engine/edge/TbMsgPushToCloudNode.java | 12 +- .../TbMsgPushToCloudNodeConfiguration.java | 2 +- .../rule/engine/edge/TbMsgPushToEdgeNode.java | 38 +- .../TbMsgPushToEdgeNodeConfiguration.java | 2 +- .../external/TbAbstractExternalNode.java | 2 +- .../filter/TbAbstractTypeSwitchNode.java | 2 +- .../engine/filter/TbAssetTypeSwitchNode.java | 2 +- .../engine/filter/TbCheckAlarmStatusNode.java | 2 +- .../filter/TbCheckAlarmStatusNodeConfig.java | 2 +- .../engine/filter/TbCheckMessageNode.java | 2 +- .../TbCheckMessageNodeConfiguration.java | 2 +- .../engine/filter/TbCheckRelationNode.java | 2 +- .../TbCheckRelationNodeConfiguration.java | 2 +- .../engine/filter/TbDeviceTypeSwitchNode.java | 2 +- .../rule/engine/filter/TbJsFilterNode.java | 2 +- .../filter/TbJsFilterNodeConfiguration.java | 2 +- .../rule/engine/filter/TbJsSwitchNode.java | 2 +- .../filter/TbJsSwitchNodeConfiguration.java | 2 +- .../engine/filter/TbMsgTypeFilterNode.java | 2 +- .../TbMsgTypeFilterNodeConfiguration.java | 2 +- .../engine/filter/TbMsgTypeSwitchNode.java | 2 +- .../filter/TbOriginatorTypeFilterNode.java | 2 +- ...OriginatorTypeFilterNodeConfiguration.java | 2 +- .../filter/TbOriginatorTypeSwitchNode.java | 2 +- .../rule/engine/flow/TbAckNode.java | 2 +- .../rule/engine/flow/TbCheckpointNode.java | 33 +- .../engine/flow/TbRuleChainInputNode.java | 2 +- .../TbRuleChainInputNodeConfiguration.java | 2 +- .../engine/flow/TbRuleChainOutputNode.java | 2 +- .../rule/engine/gcp/pubsub/TbPubSubNode.java | 8 +- .../gcp/pubsub/TbPubSubNodeConfiguration.java | 2 +- .../engine/geo/AbstractGeofencingNode.java | 2 +- .../rule/engine/geo/Coordinates.java | 2 +- .../engine/geo/EntityGeofencingState.java | 2 +- .../thingsboard/rule/engine/geo/GeoUtil.java | 2 +- .../rule/engine/geo/Perimeter.java | 2 +- .../rule/engine/geo/PerimeterType.java | 2 +- .../rule/engine/geo/RangeUnit.java | 2 +- .../engine/geo/TbGpsGeofencingActionNode.java | 2 +- ...bGpsGeofencingActionNodeConfiguration.java | 2 +- .../engine/geo/TbGpsGeofencingFilterNode.java | 2 +- ...bGpsGeofencingFilterNodeConfiguration.java | 2 +- .../rule/engine/kafka/TbKafkaNode.java | 12 +- .../kafka/TbKafkaNodeConfiguration.java | 2 +- .../rule/engine/mail/TbMsgToEmailNode.java | 42 +- .../mail/TbMsgToEmailNodeConfiguration.java | 18 +- .../rule/engine/mail/TbSendEmailNode.java | 2 +- .../mail/TbSendEmailNodeConfiguration.java | 2 +- .../rule/engine/math/TbMathArgument.java | 2 +- .../rule/engine/math/TbMathArgumentType.java | 2 +- .../rule/engine/math/TbMathArgumentValue.java | 2 +- .../rule/engine/math/TbMathNode.java | 2 +- .../engine/math/TbMathNodeConfiguration.java | 2 +- .../rule/engine/math/TbMathResult.java | 2 +- .../math/TbRuleNodeMathFunctionType.java | 2 +- .../engine/metadata/CalculateDeltaNode.java | 2 +- .../CalculateDeltaNodeConfiguration.java | 2 +- .../rule/engine/metadata/DataToFetch.java | 2 +- .../TbAbstractFetchToNodeConfiguration.java | 2 +- .../metadata/TbAbstractGetAttributesNode.java | 2 +- .../metadata/TbAbstractGetEntityDataNode.java | 2 +- .../TbAbstractGetEntityDetailsNode.java | 2 +- ...ractGetEntityDetailsNodeConfiguration.java | 2 +- .../metadata/TbAbstractGetMappedDataNode.java | 2 +- .../metadata/TbAbstractNodeWithFetchTo.java | 8 +- .../TbFetchDeviceCredentialsNode.java | 2 +- ...tchDeviceCredentialsNodeConfiguration.java | 2 +- .../engine/metadata/TbGetAttributesNode.java | 2 +- .../TbGetAttributesNodeConfiguration.java | 2 +- .../metadata/TbGetCustomerAttributeNode.java | 2 +- .../metadata/TbGetCustomerDetailsNode.java | 2 +- ...TbGetCustomerDetailsNodeConfiguration.java | 2 +- .../engine/metadata/TbGetDeviceAttrNode.java | 2 +- .../TbGetDeviceAttrNodeConfiguration.java | 2 +- .../TbGetEntityDataNodeConfiguration.java | 2 +- .../TbGetMappedDataNodeConfiguration.java | 2 +- .../TbGetOriginatorFieldsConfiguration.java | 3 +- .../metadata/TbGetOriginatorFieldsNode.java | 2 +- .../metadata/TbGetRelatedAttributeNode.java | 2 +- .../TbGetRelatedDataNodeConfiguration.java | 2 +- .../engine/metadata/TbGetTelemetryNode.java | 2 +- .../TbGetTelemetryNodeConfiguration.java | 2 +- .../metadata/TbGetTenantAttributeNode.java | 2 +- .../metadata/TbGetTenantDetailsNode.java | 2 +- .../TbGetTenantDetailsNodeConfiguration.java | 2 +- .../rule/engine/mqtt/TbMqttNode.java | 2 +- .../engine/mqtt/TbMqttNodeConfiguration.java | 2 +- .../mqtt/azure/AzureIotHubSasCredentials.java | 2 +- .../engine/mqtt/azure/TbAzureIotHubNode.java | 2 +- .../azure/TbAzureIotHubNodeConfiguration.java | 2 +- .../notification/TbNotificationNode.java | 33 +- .../TbNotificationNodeConfiguration.java | 2 +- .../rule/engine/notification/TbSlackNode.java | 2 +- .../TbSlackNodeConfiguration.java | 2 +- .../rule/engine/profile/AlarmEvalResult.java | 2 +- .../rule/engine/profile/AlarmRuleState.java | 4 +- .../rule/engine/profile/AlarmState.java | 2 +- .../profile/AlarmStateUpdateResult.java | 2 +- .../rule/engine/profile/DataSnapshot.java | 2 +- .../rule/engine/profile/DeviceState.java | 4 +- .../profile/DynamicPredicateValueCtx.java | 2 +- .../profile/DynamicPredicateValueCtxImpl.java | 2 +- .../rule/engine/profile/EntityKeyValue.java | 2 +- .../engine/profile/NumericParseException.java | 2 +- .../rule/engine/profile/ProfileState.java | 2 +- .../rule/engine/profile/SnapshotUpdate.java | 2 +- .../engine/profile/TbDeviceProfileNode.java | 2 +- .../TbDeviceProfileNodeConfiguration.java | 2 +- .../state/PersistedAlarmRuleState.java | 2 +- .../profile/state/PersistedAlarmState.java | 2 +- .../profile/state/PersistedDeviceState.java | 2 +- .../rule/engine/rabbitmq/TbRabbitMqNode.java | 2 +- .../rabbitmq/TbRabbitMqNodeConfiguration.java | 2 +- .../rule/engine/rest/TbHttpClient.java | 2 +- .../rule/engine/rest/TbRestApiCallNode.java | 2 +- .../rest/TbRestApiCallNodeConfiguration.java | 2 +- .../rule/engine/rpc/TbSendRPCReplyNode.java | 2 +- .../rule/engine/rpc/TbSendRPCRequestNode.java | 2 +- .../rpc/TbSendRpcReplyNodeConfiguration.java | 2 +- .../TbSendRpcRequestNodeConfiguration.java | 2 +- .../rule/engine/sms/TbSendSmsNode.java | 2 +- .../sms/TbSendSmsNodeConfiguration.java | 2 +- .../AttributesDeleteNodeCallback.java | 2 +- .../AttributesUpdateNodeCallback.java | 2 +- .../engine/telemetry/TbMsgAttributesNode.java | 46 +- .../TbMsgAttributesNodeConfiguration.java | 6 +- .../telemetry/TbMsgDeleteAttributesNode.java | 2 +- ...bMsgDeleteAttributesNodeConfiguration.java | 2 +- .../engine/telemetry/TbMsgTimeseriesNode.java | 4 +- .../TbMsgTimeseriesNodeConfiguration.java | 2 +- .../telemetry/TelemetryNodeCallback.java | 2 +- .../TbSynchronizationBeginNode.java | 2 +- .../transaction/TbSynchronizationEndNode.java | 2 +- .../MultipleTbMsgsCallbackWrapper.java | 2 +- .../transform/TbAbstractTransformNode.java | 2 +- ...bAbstractTransformNodeWithTbMsgSource.java | 91 + .../transform/TbChangeOriginatorNode.java | 16 +- .../TbChangeOriginatorNodeConfiguration.java | 2 +- .../rule/engine/transform/TbCopyKeysNode.java | 104 +- .../TbCopyKeysNodeConfiguration.java | 7 +- .../engine/transform/TbDeleteKeysNode.java | 108 +- .../TbDeleteKeysNodeConfiguration.java | 7 +- .../rule/engine/transform/TbJsonPathNode.java | 8 +- .../TbJsonPathNodeConfiguration.java | 2 +- .../transform/TbMsgCallbackWrapper.java | 2 +- .../engine/transform/TbRenameKeysNode.java | 95 +- .../TbRenameKeysNodeConfiguration.java | 9 +- .../engine/transform/TbSplitArrayMsgNode.java | 12 +- .../engine/transform/TbTransformMsgNode.java | 11 +- .../TbTransformMsgNodeConfiguration.java | 2 +- .../util/ContactBasedEntityDetails.java | 2 +- .../EntitiesAlarmOriginatorIdAsyncLoader.java | 2 +- .../util/EntitiesByNameAndTypeLoader.java | 2 +- .../util/EntitiesCustomerIdAsyncLoader.java | 5 +- .../util/EntitiesFieldsAsyncLoader.java | 2 +- .../EntitiesRelatedDeviceIdAsyncLoader.java | 2 +- .../EntitiesRelatedEntityIdAsyncLoader.java | 2 +- .../rule/engine/util/EntityContainer.java | 2 +- .../rule/engine/util/TbMsgSource.java | 2 +- .../rule/engine/util/TenantIdLoader.java | 2 +- .../static/rulenode/rulenode-core-config.js | 26 +- .../engine/AbstractRuleNodeUpgradeTest.java | 52 + .../rule/engine/TestDbCallbackExecutor.java | 2 +- .../rule/engine/action/TbAlarmNodeTest.java | 2 +- .../action/TbCreateRelationNodeTest.java | 2 +- .../rule/engine/action/TbLogNodeTest.java | 2 +- .../credentials/CertPemCredentialsTest.java | 2 +- .../engine/debug/TbMsgGeneratorNodeTest.java | 53 + .../engine/edge/TbMsgPushToEdgeNodeTest.java | 2 +- .../filter/TbAssetTypeSwitchNodeTest.java | 2 +- .../filter/TbCheckAlarmStatusNodeTest.java | 2 +- .../engine/filter/TbCheckMessageNodeTest.java | 2 +- .../filter/TbCheckRelationNodeTest.java | 2 +- .../filter/TbDeviceTypeSwitchNodeTest.java | 2 +- .../engine/filter/TbJsFilterNodeTest.java | 2 +- .../engine/filter/TbJsSwitchNodeTest.java | 2 +- .../filter/TbMsgTypeFilterNodeTest.java | 2 +- .../filter/TbMsgTypeSwitchNodeTest.java | 2 +- .../TbOriginatorTypeFilterNodeTest.java | 2 +- .../TbOriginatorTypeSwitchNodeTest.java | 2 +- .../engine/flow/TbCheckpointNodeTest.java | 55 + .../rule/engine/geo/GeoUtilTest.java | 2 +- .../geo/TbGpsGeofencingFilterNodeTest.java | 2 +- .../engine/mail/TbMsgToEmailNodeTest.java | 190 +- .../engine/math/TbMathArgumentValueTest.java | 2 +- .../rule/engine/math/TbMathNodeTest.java | 2 +- .../metadata/CalculateDeltaNodeTest.java | 2 +- .../TbFetchDeviceCredentialsNodeTest.java | 2 +- .../metadata/TbGetAttributesNodeTest.java | 2 +- .../TbGetCustomerAttributeNodeTest.java | 5 +- .../TbGetCustomerDetailsNodeTest.java | 5 +- .../metadata/TbGetDeviceAttrNodeTest.java | 2 +- .../TbGetOriginatorFieldsNodeTest.java | 5 +- .../TbGetRelatedAttributeNodeTest.java | 4 +- .../metadata/TbGetTelemetryNodeTest.java | 2 +- .../TbGetTenantAttributeNodeTest.java | 5 +- .../metadata/TbGetTenantDetailsNodeTest.java | 5 +- .../rule/engine/profile/AlarmStateTest.java | 2 +- .../rule/engine/profile/DeviceStateTest.java | 2 +- .../profile/TbDeviceProfileNodeTest.java | 2 +- .../rule/engine/rest/TbHttpClientTest.java | 27 +- .../engine/rest/TbRestApiCallNodeTest.java | 2 +- .../engine/rpc/TbSendRPCReplyNodeTest.java | 2 +- .../TbMsgAttributesNodeConfigurationTest.java | 4 +- .../telemetry/TbMsgAttributesNodeTest.java | 163 +- .../TbMsgDeleteAttributesNodeTest.java | 2 +- .../transform/TbChangeOriginatorNodeTest.java | 2 +- .../engine/transform/TbCopyKeysNodeTest.java | 64 +- .../transform/TbDeleteKeysNodeTest.java | 53 +- .../engine/transform/TbJsonPathNodeTest.java | 2 +- .../transform/TbMsgDeduplicationNodeTest.java | 69 +- .../transform/TbRenameKeysNodeTest.java | 42 +- .../transform/TbSplitArrayMsgNodeTest.java | 2 +- .../transform/TbTransformMsgNodeTest.java | 2 +- .../EntitiesCustomerIdAsyncLoaderTest.java | 2 +- .../util/EntitiesFieldsAsyncLoaderTest.java | 2 +- ...ntitiesRelatedDeviceIdAsyncLoaderTest.java | 2 +- ...ntitiesRelatedEntityIdAsyncLoaderTest.java | 2 +- .../rule/engine/util/TenantIdLoaderTest.java | 2 +- tools/pom.xml | 4 +- .../client/tools/MqttSslClient.java | 2 +- .../tools/migrator/DictionaryParser.java | 2 +- .../client/tools/migrator/MigratorTool.java | 2 +- .../client/tools/migrator/PgCaMigrator.java | 2 +- .../tools/migrator/RelatedEntitiesParser.java | 2 +- .../client/tools/migrator/WriterBuilder.java | 2 +- tools/src/main/python/check_yml_file.py | 2 +- tools/src/main/python/mqtt-send-telemetry.py | 2 +- .../main/python/one-way-ssl-mqtt-client.py | 2 +- tools/src/main/python/simple-mqtt-client.py | 2 +- .../main/python/two-way-ssl-mqtt-client.py | 2 +- tools/src/main/shell/client.keygen.sh | 2 +- .../lwM2M_cfssl_chain_clients_for_test.sh | 2 +- .../lwm2m/lwm2m_cfssl_chain_all_for_test.sh | 2 +- .../lwm2m_cfssl_chain_server_for_test.sh | 2 +- tools/src/main/shell/server.keygen.sh | 2 +- transport/coap/pom.xml | 4 +- transport/coap/src/main/conf/logback.xml | 2 +- .../coap/src/main/conf/tb-coap-transport.conf | 2 +- .../ThingsboardCoapTransportApplication.java | 2 +- transport/coap/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-coap-transport.yml | 12 +- transport/http/pom.xml | 4 +- transport/http/src/main/conf/logback.xml | 2 +- .../http/src/main/conf/tb-http-transport.conf | 2 +- .../ThingsboardHttpTransportApplication.java | 2 +- transport/http/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-http-transport.yml | 14 +- transport/lwm2m/pom.xml | 4 +- transport/lwm2m/src/main/conf/logback.xml | 2 +- .../src/main/conf/tb-lwm2m-transport.conf | 2 +- .../ThingsboardLwm2mTransportApplication.java | 2 +- .../lwm2m/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-lwm2m-transport.yml | 12 +- transport/mqtt/pom.xml | 4 +- transport/mqtt/src/main/conf/logback.xml | 2 +- .../mqtt/src/main/conf/tb-mqtt-transport.conf | 2 +- .../ThingsboardMqttTransportApplication.java | 2 +- transport/mqtt/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-mqtt-transport.yml | 12 +- transport/pom.xml | 4 +- transport/snmp/pom.xml | 4 +- transport/snmp/src/main/conf/logback.xml | 2 +- .../snmp/src/main/conf/tb-snmp-transport.conf | 2 +- .../ThingsboardSnmpTransportApplication.java | 2 +- transport/snmp/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-snmp-transport.yml | 12 +- ui-ngx/.editorconfig | 2 +- ui-ngx/e2e/protractor.conf.js | 2 +- ui-ngx/e2e/src/app.e2e-spec.ts | 2 +- ui-ngx/e2e/src/app.po.ts | 2 +- ui-ngx/extra-webpack.config.js | 2 +- ui-ngx/generate-types.js | 2 +- ui-ngx/package.json | 3 +- ui-ngx/pom.xml | 4 +- ui-ngx/proxy.conf.js | 2 +- ui-ngx/src/app/app-routing.module.ts | 2 +- ui-ngx/src/app/app.component.html | 2 +- ui-ngx/src/app/app.component.scss | 2 +- ui-ngx/src/app/app.component.ts | 2 +- ui-ngx/src/app/app.module.ts | 2 +- .../app/core/api/alarm-data-subscription.ts | 2 +- ui-ngx/src/app/core/api/alarm-data.service.ts | 6 +- ui-ngx/src/app/core/api/alias-controller.ts | 2 +- ui-ngx/src/app/core/api/data-aggregator.ts | 169 +- .../app/core/api/entity-data-subscription.ts | 98 +- .../src/app/core/api/entity-data.service.ts | 2 +- ui-ngx/src/app/core/api/public-api.ts | 2 +- ui-ngx/src/app/core/api/widget-api.models.ts | 2 +- .../src/app/core/api/widget-subscription.ts | 11 +- ui-ngx/src/app/core/auth/auth.actions.ts | 2 +- ui-ngx/src/app/core/auth/auth.effects.ts | 2 +- ui-ngx/src/app/core/auth/auth.models.ts | 3 +- ui-ngx/src/app/core/auth/auth.reducer.ts | 3 +- ui-ngx/src/app/core/auth/auth.selectors.ts | 2 +- ui-ngx/src/app/core/auth/auth.service.spec.ts | 2 +- ui-ngx/src/app/core/auth/auth.service.ts | 2 +- ui-ngx/src/app/core/auth/public-api.ts | 2 +- ui-ngx/src/app/core/core.module.ts | 2 +- ui-ngx/src/app/core/core.state.ts | 2 +- ui-ngx/src/app/core/css/css.js | 2 +- ui-ngx/src/app/core/guards/auth.guard.ts | 2 +- .../app/core/guards/confirm-on-exit.guard.ts | 2 +- ui-ngx/src/app/core/http/admin.service.ts | 2 +- .../app/core/http/alarm-comment.service.ts | 2 +- ui-ngx/src/app/core/http/alarm.service.ts | 2 +- .../app/core/http/asset-profile.service.ts | 6 +- ui-ngx/src/app/core/http/asset.service.ts | 8 +- ui-ngx/src/app/core/http/attribute.service.ts | 2 +- ui-ngx/src/app/core/http/audit-log.service.ts | 2 +- .../core/http/component-descriptor.service.ts | 2 +- ui-ngx/src/app/core/http/customer.service.ts | 2 +- ui-ngx/src/app/core/http/dashboard.service.ts | 6 +- .../app/core/http/device-profile.service.ts | 6 +- ui-ngx/src/app/core/http/device.service.ts | 18 +- ui-ngx/src/app/core/http/edge.service.ts | 18 +- .../http/entities-version-control.service.ts | 2 +- .../app/core/http/entity-relation.service.ts | 2 +- .../src/app/core/http/entity-view.service.ts | 2 +- ui-ngx/src/app/core/http/entity.service.ts | 10 +- ui-ngx/src/app/core/http/event.service.ts | 2 +- ui-ngx/src/app/core/http/http-utils.ts | 2 +- ui-ngx/src/app/core/http/image.service.ts | 135 + .../src/app/core/http/notification.service.ts | 2 +- ui-ngx/src/app/core/http/oauth2.service.ts | 2 +- .../src/app/core/http/ota-package.service.ts | 34 +- ui-ngx/src/app/core/http/public-api.ts | 2 +- ui-ngx/src/app/core/http/queue.service.ts | 2 +- ui-ngx/src/app/core/http/resource.service.ts | 41 +- .../src/app/core/http/rule-chain.service.ts | 2 +- .../app/core/http/tenant-profile.service.ts | 2 +- ui-ngx/src/app/core/http/tenant.service.ts | 2 +- .../http/two-factor-authentication.service.ts | 2 +- .../src/app/core/http/ui-settings.service.ts | 2 +- .../src/app/core/http/usage-info.service.ts | 2 +- .../app/core/http/user-settings.service.ts | 2 +- ui-ngx/src/app/core/http/user.service.ts | 2 +- ui-ngx/src/app/core/http/widget.service.ts | 17 +- .../interceptors/global-http-interceptor.ts | 2 +- .../core/interceptors/interceptor-config.ts | 2 +- .../interceptors/interceptor-http-params.ts | 2 +- .../src/app/core/interceptors/load.actions.ts | 2 +- .../src/app/core/interceptors/load.models.ts | 2 +- .../src/app/core/interceptors/load.reducer.ts | 2 +- .../app/core/interceptors/load.selectors.ts | 2 +- .../local-storage/local-storage.service.ts | 2 +- .../app/core/meta-reducers/debug.reducer.ts | 2 +- .../init-state-from-local-storage.reducer.ts | 2 +- .../core/notification/notification.actions.ts | 2 +- .../core/notification/notification.effects.ts | 2 +- .../core/notification/notification.models.ts | 2 +- .../core/notification/notification.reducer.ts | 2 +- ui-ngx/src/app/core/operator/enterZone.ts | 2 +- ui-ngx/src/app/core/public-api.ts | 2 +- .../core/services/active-component.service.ts | 2 +- .../src/app/core/services/broadcast.models.ts | 2 +- .../app/core/services/broadcast.service.ts | 2 +- .../core/services/dashboard-utils.service.ts | 2 +- .../src/app/core/services/dialog.service.ts | 2 +- .../dynamic-component-factory.service.ts | 2 +- ui-ngx/src/app/core/services/help.service.ts | 2 +- .../app/core/services/item-buffer.service.ts | 2 +- ui-ngx/src/app/core/services/menu.models.ts | 2 +- ui-ngx/src/app/core/services/menu.service.ts | 16 +- .../src/app/core/services/mobile.service.ts | 2 +- ui-ngx/src/app/core/services/public-api.ts | 2 +- ui-ngx/src/app/core/services/raf.service.ts | 2 +- .../app/core/services/resources.service.ts | 35 +- .../script/node-script-test.service.ts | 2 +- ui-ngx/src/app/core/services/time.service.ts | 71 +- ui-ngx/src/app/core/services/title.service.ts | 2 +- .../services/toast-notification.service.ts | 2 +- ui-ngx/src/app/core/services/utils.service.ts | 2 +- .../src/app/core/services/window.service.ts | 2 +- .../src/app/core/settings/settings.actions.ts | 2 +- .../src/app/core/settings/settings.effects.ts | 2 +- .../src/app/core/settings/settings.models.ts | 2 +- .../src/app/core/settings/settings.reducer.ts | 2 +- .../app/core/settings/settings.selectors.ts | 2 +- .../src/app/core/settings/settings.utils.ts | 2 +- .../translate/missing-translate-handler.ts | 2 +- .../translate/translate-default-compiler.ts | 2 +- .../translate/translate-default-parser.ts | 2 +- ui-ngx/src/app/core/utils.ts | 11 +- .../core/ws/notification-websocket.service.ts | 110 +- ui-ngx/src/app/core/ws/public-api.ts | 2 +- .../core/ws/telemetry-websocket.service.ts | 107 +- ui-ngx/src/app/core/ws/websocket.service.ts | 47 +- .../app/modules/common/modules-map.models.ts | 2 +- ui-ngx/src/app/modules/common/modules-map.ts | 23 +- .../dashboard/dashboard-pages.module.ts | 2 +- .../dashboard-pages.routing.module.ts | 2 +- .../dashboard/dashboard-routing.module.ts | 2 +- .../alarm/alarm-assignee-panel.component.html | 2 +- .../alarm/alarm-assignee-panel.component.scss | 3 +- .../alarm/alarm-assignee-panel.component.ts | 2 +- .../alarm-assignee-select-panel.component.ts | 2 +- .../alarm-assignee-select.component.html | 2 +- .../alarm/alarm-assignee-select.component.ts | 2 +- .../alarm/alarm-assignee.component.html | 3 +- .../alarm/alarm-assignee.component.scss | 3 +- .../alarm/alarm-assignee.component.ts | 2 +- .../alarm/alarm-comment-dialog.component.html | 2 +- .../alarm/alarm-comment-dialog.component.ts | 2 +- .../alarm/alarm-comment.component.html | 5 +- .../alarm/alarm-comment.component.scss | 2 +- .../alarm/alarm-comment.component.ts | 4 +- .../alarm/alarm-details-dialog.component.html | 2 +- .../alarm/alarm-details-dialog.component.scss | 3 +- .../alarm/alarm-details-dialog.component.ts | 2 +- .../alarm/alarm-filter-config.component.html | 2 +- .../alarm/alarm-filter-config.component.scss | 2 +- .../alarm/alarm-filter-config.component.ts | 2 +- .../components/alarm/alarm-table-config.ts | 10 +- .../alarm/alarm-table-header.component.html | 2 +- .../alarm/alarm-table-header.component.scss | 2 +- .../alarm/alarm-table-header.component.ts | 2 +- .../alarm/alarm-table.component.html | 2 +- .../alarm/alarm-table.component.scss | 2 +- .../components/alarm/alarm-table.component.ts | 2 +- ...aliases-entity-autocomplete.component.html | 2 +- .../aliases-entity-autocomplete.component.ts | 2 +- ...aliases-entity-select-panel.component.html | 2 +- ...aliases-entity-select-panel.component.scss | 2 +- .../aliases-entity-select-panel.component.ts | 2 +- .../aliases-entity-select.component.html | 2 +- .../aliases-entity-select.component.scss | 2 +- .../alias/aliases-entity-select.component.ts | 2 +- .../alias/entity-alias-dialog.component.html | 2 +- .../alias/entity-alias-dialog.component.scss | 2 +- .../alias/entity-alias-dialog.component.ts | 13 +- .../alias/entity-alias-select.component.html | 2 +- .../entity-alias-select.component.models.ts | 2 +- .../alias/entity-alias-select.component.scss | 2 +- .../alias/entity-alias-select.component.ts | 2 +- .../entity-aliases-dialog.component.html | 2 +- .../entity-aliases-dialog.component.scss | 2 +- .../alias/entity-aliases-dialog.component.ts | 2 +- .../add-attribute-dialog.component.html | 2 +- .../add-attribute-dialog.component.ts | 2 +- ...-widget-to-dashboard-dialog.component.html | 2 +- ...-widget-to-dashboard-dialog.component.scss | 2 +- ...dd-widget-to-dashboard-dialog.component.ts | 2 +- .../attribute/attribute-table.component.html | 2 +- .../attribute/attribute-table.component.scss | 2 +- .../attribute/attribute-table.component.ts | 2 +- .../delete-timeseries-panel.component.html | 3 +- .../delete-timeseries-panel.component.scss | 2 +- .../delete-timeseries-panel.component.ts | 2 +- .../edit-attribute-value-panel.component.html | 2 +- .../edit-attribute-value-panel.component.scss | 2 +- .../edit-attribute-value-panel.component.ts | 2 +- .../audit-log-details-dialog.component.html | 2 +- .../audit-log-details-dialog.component.scss | 2 +- .../audit-log-details-dialog.component.ts | 2 +- .../audit-log/audit-log-table-config.ts | 2 +- .../audit-log/audit-log-table.component.html | 2 +- .../audit-log/audit-log-table.component.scss | 2 +- .../audit-log/audit-log-table.component.ts | 2 +- .../add-widget-dialog.component.html | 2 +- .../add-widget-dialog.component.scss | 2 +- .../add-widget-dialog.component.ts | 2 +- .../dashboard-image-dialog.component.html | 10 +- .../dashboard-image-dialog.component.scss | 2 +- .../dashboard-image-dialog.component.ts | 10 +- .../dashboard-page.component.html | 4 +- .../dashboard-page.component.scss | 2 +- .../dashboard-page.component.ts | 57 +- .../dashboard-page/dashboard-page.models.ts | 2 +- .../dashboard-settings-dialog.component.html | 12 +- .../dashboard-settings-dialog.component.scss | 2 +- .../dashboard-settings-dialog.component.ts | 27 +- .../dashboard-state.component.html | 2 +- .../dashboard-state.component.scss | 2 +- .../dashboard-state.component.ts | 2 +- .../dashboard-toolbar.component.html | 2 +- .../dashboard-toolbar.component.scss | 2 +- .../dashboard-toolbar.component.ts | 2 +- .../dashboard-widget-select.component.html | 10 +- .../dashboard-widget-select.component.scss | 3 +- .../dashboard-widget-select.component.ts | 23 +- .../dashboard-page/edit-widget.component.html | 2 +- .../dashboard-page/edit-widget.component.scss | 2 +- .../dashboard-page/edit-widget.component.ts | 2 +- .../layout/dashboard-layout.component.html | 6 +- .../layout/dashboard-layout.component.scss | 2 +- .../layout/dashboard-layout.component.ts | 15 +- .../dashboard-page/layout/layout.models.ts | 2 +- ...ge-dashboard-layouts-dialog.component.html | 2 +- ...ge-dashboard-layouts-dialog.component.scss | 2 +- ...nage-dashboard-layouts-dialog.component.ts | 2 +- .../dashboard-state-dialog.component.html | 2 +- .../dashboard-state-dialog.component.ts | 2 +- .../default-state-controller.component.html | 2 +- .../default-state-controller.component.scss | 2 +- .../default-state-controller.component.ts | 2 +- .../entity-state-controller.component.html | 2 +- .../entity-state-controller.component.scss | 2 +- .../entity-state-controller.component.ts | 2 +- ...age-dashboard-states-dialog.component.html | 13 +- ...ashboard-states-dialog.component.models.ts | 2 +- ...age-dashboard-states-dialog.component.scss | 2 +- ...anage-dashboard-states-dialog.component.ts | 85 +- .../states/state-controller.component.ts | 2 +- .../states/state-controller.models.ts | 2 +- .../states/states-component.directive.ts | 2 +- .../states/states-controller.module.ts | 2 +- .../states/states-controller.service.ts | 2 +- .../widget-types-panel.component.html | 2 +- .../widget-types-panel.component.scss | 2 +- .../widget-types-panel.component.ts | 2 +- .../dashboard/dashboard.component.html | 2 +- .../dashboard/dashboard.component.scss | 2 +- .../dashboard/dashboard.component.ts | 2 +- .../components/dashboard/layout-button.scss | 2 +- ...select-target-layout-dialog.component.html | 2 +- .../select-target-layout-dialog.component.ts | 2 +- .../select-target-state-dialog.component.html | 2 +- .../select-target-state-dialog.component.ts | 2 +- .../components/details-panel.component.html | 4 +- .../components/details-panel.component.scss | 2 +- .../components/details-panel.component.ts | 2 +- .../copy-device-credentials.component.html | 2 +- .../copy-device-credentials.component.ts | 2 +- ...ce-credentials-lwm2m-server.component.html | 2 +- ...vice-credentials-lwm2m-server.component.ts | 2 +- .../device-credentials-lwm2m.component.html | 2 +- .../device-credentials-lwm2m.component.scss | 2 +- .../device-credentials-lwm2m.component.ts | 2 +- ...vice-credentials-mqtt-basic.component.html | 2 +- ...device-credentials-mqtt-basic.component.ts | 2 +- .../device/device-credentials.component.html | 2 +- .../device/device-credentials.component.scss | 2 +- .../device/device-credentials.component.ts | 2 +- .../device/device-credentials.module.ts | 2 +- .../device/device-info-filter.component.html | 2 +- .../device/device-info-filter.component.scss | 2 +- .../device/device-info-filter.component.ts | 2 +- .../edge/edge-downlink-table-config.ts | 2 +- .../edge-downlink-table-header.component.html | 2 +- .../edge-downlink-table-header.component.scss | 2 +- .../edge-downlink-table-header.component.ts | 2 +- .../edge/edge-downlink-table.component.html | 2 +- .../edge/edge-downlink-table.component.scss | 2 +- .../edge/edge-downlink-table.component.ts | 2 +- .../entity/add-entity-dialog.component.html | 2 +- .../entity/add-entity-dialog.component.scss | 2 +- .../entity/add-entity-dialog.component.ts | 2 +- .../entity/contact-based.component.ts | 2 +- .../entity/entities-table.component.html | 38 +- .../entity/entities-table.component.scss | 2 +- .../entity/entities-table.component.ts | 7 +- .../entity/entity-details-page.component.html | 4 +- .../entity/entity-details-page.component.scss | 2 +- .../entity/entity-details-page.component.ts | 8 +- .../entity-details-panel.component.html | 2 +- .../entity-details-panel.component.scss | 2 +- .../entity/entity-details-panel.component.ts | 2 +- .../entity/entity-filter-view.component.html | 2 +- .../entity/entity-filter-view.component.scss | 2 +- .../entity/entity-filter-view.component.ts | 2 +- .../entity/entity-filter.component.html | 2 +- .../entity/entity-filter.component.scss | 2 +- .../entity/entity-filter.component.ts | 56 +- .../entity/entity-table-header.component.ts | 2 +- .../entity/entity-tabs.component.ts | 2 +- .../components/entity/entity.component.ts | 2 +- .../event/event-content-dialog.component.html | 2 +- .../event/event-content-dialog.component.scss | 2 +- .../event/event-content-dialog.component.ts | 2 +- .../event/event-filter-panel.component.html | 2 +- .../event/event-filter-panel.component.scss | 2 +- .../event/event-filter-panel.component.ts | 2 +- .../components/event/event-table-config.ts | 2 +- .../event/event-table-header.component.html | 2 +- .../event/event-table-header.component.scss | 2 +- .../event/event-table-header.component.ts | 2 +- .../event/event-table.component.html | 2 +- .../event/event-table.component.scss | 2 +- .../components/event/event-table.component.ts | 2 +- .../boolean-filter-predicate.component.html | 2 +- .../boolean-filter-predicate.component.ts | 2 +- ...lex-filter-predicate-dialog.component.html | 2 +- ...mplex-filter-predicate-dialog.component.ts | 2 +- .../complex-filter-predicate.component.html | 2 +- .../complex-filter-predicate.component.ts | 2 +- .../filter/filter-component.models.ts | 2 +- .../filter/filter-dialog.component.html | 2 +- .../filter/filter-dialog.component.scss | 2 +- .../filter/filter-dialog.component.ts | 2 +- .../filter-predicate-list.component.html | 2 +- .../filter-predicate-list.component.scss | 2 +- .../filter/filter-predicate-list.component.ts | 2 +- .../filter-predicate-value.component.html | 4 +- .../filter-predicate-value.component.ts | 2 +- .../filter/filter-predicate.component.html | 2 +- .../filter/filter-predicate.component.ts | 2 +- .../components/filter/filter-predicate.scss | 2 +- .../filter/filter-select.component.html | 2 +- .../filter/filter-select.component.models.ts | 2 +- .../filter/filter-select.component.ts | 2 +- .../filter/filter-text.component.html | 2 +- .../filter/filter-text.component.scss | 2 +- .../filter/filter-text.component.ts | 2 +- .../filter-user-info-dialog.component.html | 2 +- .../filter-user-info-dialog.component.ts | 2 +- .../filter/filter-user-info.component.html | 2 +- .../filter/filter-user-info.component.ts | 2 +- .../filter/filters-dialog.component.html | 2 +- .../filter/filters-dialog.component.scss | 2 +- .../filter/filters-dialog.component.ts | 2 +- .../filter/filters-edit-panel.component.html | 2 +- .../filter/filters-edit-panel.component.scss | 2 +- .../filter/filters-edit-panel.component.ts | 2 +- .../filter/filters-edit.component.html | 2 +- .../filter/filters-edit.component.scss | 2 +- .../filter/filters-edit.component.ts | 2 +- .../filter/key-filter-dialog.component.html | 2 +- .../filter/key-filter-dialog.component.scss | 2 +- .../filter/key-filter-dialog.component.ts | 2 +- .../filter/key-filter-list.component.html | 2 +- .../filter/key-filter-list.component.scss | 2 +- .../filter/key-filter-list.component.ts | 2 +- .../numeric-filter-predicate.component.html | 2 +- .../numeric-filter-predicate.component.ts | 2 +- .../string-filter-predicate.component.html | 2 +- .../string-filter-predicate.component.ts | 2 +- .../filter/user-filter-dialog.component.html | 2 +- .../filter/user-filter-dialog.component.ts | 2 +- .../home/components/home-components.module.ts | 25 +- .../notification-bell.component.html | 2 +- .../notification-bell.component.ts | 13 +- .../send-notification-button.component.html | 2 +- .../send-notification-button.component.ts | 2 +- .../show-notification-popover.component.html | 2 +- .../show-notification-popover.component.ts | 10 +- .../add-device-profile-dialog.component.html | 7 +- .../add-device-profile-dialog.component.scss | 2 +- .../add-device-profile-dialog.component.ts | 2 +- ...rm-duration-predicate-value.component.html | 4 +- ...larm-duration-predicate-value.component.ts | 2 +- .../alarm/alarm-dynamic-value.component.html | 2 +- .../alarm/alarm-dynamic-value.component.ts | 2 +- ...alarm-rule-condition-dialog.component.html | 2 +- ...alarm-rule-condition-dialog.component.scss | 2 +- .../alarm-rule-condition-dialog.component.ts | 2 +- .../alarm/alarm-rule-condition.component.html | 2 +- .../alarm/alarm-rule-condition.component.scss | 2 +- .../alarm/alarm-rule-condition.component.ts | 2 +- .../profile/alarm/alarm-rule.component.html | 2 +- .../profile/alarm/alarm-rule.component.scss | 2 +- .../profile/alarm/alarm-rule.component.ts | 2 +- .../alarm-schedule-dialog.component.html | 2 +- .../alarm/alarm-schedule-dialog.component.ts | 2 +- .../alarm/alarm-schedule-info.component.html | 2 +- .../alarm/alarm-schedule-info.component.scss | 2 +- .../alarm/alarm-schedule-info.component.ts | 2 +- .../alarm/alarm-schedule.component.html | 2 +- .../alarm/alarm-schedule.component.scss | 2 +- .../profile/alarm/alarm-schedule.component.ts | 2 +- .../alarm/create-alarm-rules.component.html | 2 +- .../alarm/create-alarm-rules.component.scss | 2 +- .../alarm/create-alarm-rules.component.ts | 2 +- .../alarm/device-profile-alarm.component.html | 2 +- .../alarm/device-profile-alarm.component.scss | 2 +- .../alarm/device-profile-alarm.component.ts | 2 +- .../device-profile-alarms.component.html | 2 +- .../device-profile-alarms.component.scss | 2 +- .../alarm/device-profile-alarms.component.ts | 2 +- .../edit-alarm-details-dialog.component.html | 2 +- .../edit-alarm-details-dialog.component.ts | 2 +- .../asset-profile-autocomplete.component.html | 7 +- .../asset-profile-autocomplete.component.scss | 2 +- .../asset-profile-autocomplete.component.ts | 20 +- .../asset-profile-dialog.component.html | 2 +- .../profile/asset-profile-dialog.component.ts | 2 +- .../profile/asset-profile.component.html | 7 +- .../profile/asset-profile.component.ts | 2 +- ...device-profile-autocomplete.component.html | 7 +- ...device-profile-autocomplete.component.scss | 2 +- .../device-profile-autocomplete.component.ts | 20 +- .../device-profile-dialog.component.html | 2 +- .../device-profile-dialog.component.ts | 2 +- ...ile-provision-configuration.component.html | 2 +- ...ofile-provision-configuration.component.ts | 2 +- .../profile/device-profile.component.html | 7 +- .../profile/device-profile.component.ts | 2 +- ...ile-transport-configuration.component.html | 2 +- ...ile-transport-configuration.component.scss | 2 +- ...ofile-transport-configuration.component.ts | 2 +- .../common/device-profile-common.module.ts | 2 +- .../common/power-mode-setting.component.html | 2 +- .../common/power-mode-setting.component.ts | 2 +- .../common/time-unit-select.component.html | 2 +- .../common/time-unit-select.component.ts | 2 +- ...evice-profile-configuration.component.html | 2 +- ...-device-profile-configuration.component.ts | 2 +- ...ile-transport-configuration.component.html | 2 +- ...ofile-transport-configuration.component.ts | 2 +- ...evice-profile-configuration.component.html | 2 +- .../device-profile-configuration.component.ts | 2 +- ...ile-transport-configuration.component.html | 2 +- ...ofile-transport-configuration.component.ts | 2 +- .../lwm2m-attributes-dialog.component.html | 2 +- .../lwm2m-attributes-dialog.component.ts | 2 +- .../lwm2m-attributes-key-list.component.html | 2 +- .../lwm2m-attributes-key-list.component.scss | 2 +- .../lwm2m-attributes-key-list.component.ts | 2 +- .../lwm2m/lwm2m-attributes.component.html | 2 +- .../lwm2m/lwm2m-attributes.component.ts | 2 +- ...ap-add-config-server-dialog.component.html | 2 +- ...trap-add-config-server-dialog.component.ts | 2 +- ...2m-bootstrap-config-servers.component.html | 2 +- ...wm2m-bootstrap-config-servers.component.ts | 2 +- .../lwm2m-device-config-server.component.html | 2 +- .../lwm2m-device-config-server.component.ts | 2 +- ...ile-transport-configuration.component.html | 2 +- ...ile-transport-configuration.component.scss | 2 +- ...ofile-transport-configuration.component.ts | 2 +- ...object-add-instances-dialog.component.html | 2 +- ...m-object-add-instances-dialog.component.ts | 2 +- ...m-object-add-instances-list.component.html | 2 +- ...m2m-object-add-instances-list.component.ts | 2 +- .../lwm2m/lwm2m-object-list.component.html | 2 +- .../lwm2m/lwm2m-object-list.component.ts | 2 +- ...ve-attr-telemetry-instances.component.html | 2 +- ...ve-attr-telemetry-instances.component.scss | 2 +- ...erve-attr-telemetry-instances.component.ts | 2 +- ...ve-attr-telemetry-resources.component.html | 2 +- ...ve-attr-telemetry-resources.component.scss | 2 +- ...erve-attr-telemetry-resources.component.ts | 2 +- ...wm2m-observe-attr-telemetry.component.html | 2 +- ...wm2m-observe-attr-telemetry.component.scss | 2 +- .../lwm2m-observe-attr-telemetry.component.ts | 2 +- .../lwm2m/lwm2m-profile-components.module.ts | 2 +- .../lwm2m/lwm2m-profile-config.models.ts | 2 +- ...ile-transport-configuration.component.html | 2 +- ...ile-transport-configuration.component.scss | 2 +- ...ofile-transport-configuration.component.ts | 2 +- ...rofile-communication-config.component.html | 2 +- ...rofile-communication-config.component.scss | 2 +- ...-profile-communication-config.component.ts | 2 +- ...snmp-device-profile-mapping.component.html | 2 +- ...snmp-device-profile-mapping.component.scss | 2 +- .../snmp-device-profile-mapping.component.ts | 2 +- ...ile-transport-configuration.component.html | 2 +- ...ofile-transport-configuration.component.ts | 2 +- .../snmp-device-profile-transport.module.ts | 2 +- .../tenant-profile-queues.component.html | 2 +- .../tenant-profile-queues.component.scss | 2 +- .../queue/tenant-profile-queues.component.ts | 2 +- ...tenant-profile-autocomplete.component.html | 2 +- ...tenant-profile-autocomplete.component.scss | 2 +- .../tenant-profile-autocomplete.component.ts | 2 +- .../tenant-profile-data.component.html | 2 +- .../profile/tenant-profile-data.component.ts | 2 +- .../tenant-profile-dialog.component.html | 2 +- .../tenant-profile-dialog.component.scss | 2 +- .../tenant-profile-dialog.component.ts | 2 +- .../profile/tenant-profile.component.html | 2 +- .../profile/tenant-profile.component.scss | 2 +- .../profile/tenant-profile.component.ts | 2 +- ...enant-profile-configuration.component.html | 20 +- ...enant-profile-configuration.component.scss | 2 +- ...-tenant-profile-configuration.component.ts | 3 +- .../rate-limits-details-dialog.component.html | 2 +- .../rate-limits-details-dialog.component.ts | 2 +- .../rate-limits-list.component.html | 2 +- .../rate-limits-list.component.scss | 2 +- .../rate-limits/rate-limits-list.component.ts | 2 +- .../rate-limits-text.component.html | 2 +- .../rate-limits-text.component.scss | 2 +- .../rate-limits/rate-limits-text.component.ts | 2 +- .../rate-limits/rate-limits.component.html | 2 +- .../rate-limits/rate-limits.component.scss | 2 +- .../rate-limits/rate-limits.component.ts | 2 +- .../tenant/rate-limits/rate-limits.models.ts | 2 +- ...enant-profile-configuration.component.html | 2 +- .../tenant-profile-configuration.component.ts | 2 +- .../app/modules/home/components/public-api.ts | 2 +- .../queue/queue-form.component.html | 2 +- .../queue/queue-form.component.scss | 2 +- .../components/queue/queue-form.component.ts | 2 +- .../relation/relation-dialog.component.html | 2 +- .../relation/relation-dialog.component.scss | 2 +- .../relation/relation-dialog.component.ts | 2 +- .../relation/relation-filters.component.html | 2 +- .../relation/relation-filters.component.scss | 2 +- .../relation/relation-filters.component.ts | 2 +- .../relation/relation-table.component.html | 2 +- .../relation/relation-table.component.scss | 2 +- .../relation/relation-table.component.ts | 2 +- .../components/router-tabs.component.html | 2 +- .../components/router-tabs.component.scss | 2 +- .../home/components/router-tabs.component.ts | 2 +- .../rule-chain-autocomplete.component.html | 8 +- .../rule-chain-autocomplete.component.ts | 10 +- .../shared-home-components.module.ts | 2 +- ...-sns-provider-configuration.component.html | 2 +- ...ws-sns-provider-configuration.component.ts | 2 +- ...-sms-provider-configuration.component.html | 2 +- ...pp-sms-provider-configuration.component.ts | 2 +- .../sms-provider-configuration.component.html | 2 +- .../sms-provider-configuration.component.ts | 2 +- ...-sms-provider-configuration.component.html | 2 +- ...io-sms-provider-configuration.component.ts | 2 +- .../src/app/modules/home/components/tokens.ts | 2 +- .../vc/auto-commit-settings.component.html | 2 +- .../vc/auto-commit-settings.component.scss | 2 +- .../vc/auto-commit-settings.component.ts | 2 +- .../vc/complex-version-create.component.html | 2 +- .../vc/complex-version-create.component.ts | 2 +- .../vc/complex-version-load.component.html | 2 +- .../vc/complex-version-load.component.ts | 2 +- ...entity-types-version-create.component.html | 2 +- .../entity-types-version-create.component.ts | 2 +- .../entity-types-version-load.component.html | 2 +- .../vc/entity-types-version-load.component.ts | 2 +- .../vc/entity-types-version.component.scss | 2 +- .../vc/entity-version-create.component.html | 2 +- .../vc/entity-version-create.component.ts | 2 +- .../vc/entity-version-diff.component.html | 2 +- .../vc/entity-version-diff.component.scss | 2 +- .../vc/entity-version-diff.component.ts | 2 +- .../vc/entity-version-restore.component.html | 2 +- .../vc/entity-version-restore.component.ts | 2 +- .../vc/entity-versions-table.component.html | 2 +- .../vc/entity-versions-table.component.scss | 2 +- .../vc/entity-versions-table.component.ts | 2 +- ...move-other-entities-confirm.component.html | 2 +- ...remove-other-entities-confirm.component.ts | 2 +- .../vc/repository-settings.component.html | 5 +- .../vc/repository-settings.component.scss | 2 +- .../vc/repository-settings.component.ts | 2 +- .../vc/version-control.component.html | 2 +- .../vc/version-control.component.scss | 2 +- .../vc/version-control.component.ts | 2 +- .../home/components/vc/version-control.scss | 4 +- ...custom-action-pretty-editor.component.html | 2 +- ...custom-action-pretty-editor.component.scss | 2 +- .../custom-action-pretty-editor.component.ts | 2 +- ...ction-pretty-resources-tabs.component.html | 2 +- ...ction-pretty-resources-tabs.component.scss | 2 +- ...-action-pretty-resources-tabs.component.ts | 2 +- .../widget/action/custom-action.models.ts | 2 +- ...anage-widget-actions-dialog.component.html | 2 +- .../manage-widget-actions-dialog.component.ts | 2 +- .../manage-widget-actions.component.html | 2 +- .../manage-widget-actions.component.models.ts | 2 +- .../manage-widget-actions.component.scss | 2 +- .../action/manage-widget-actions.component.ts | 2 +- .../mobile-action-editor.component.html | 2 +- .../action/mobile-action-editor.component.ts | 2 +- .../action/mobile-action-editor.models.ts | 2 +- .../widget-action-dialog.component.html | 5 +- .../action/widget-action-dialog.component.ts | 2 +- .../alarm-count-basic-config.component.html | 2 +- .../alarm-count-basic-config.component.ts | 2 +- .../alarms-table-basic-config.component.html | 2 +- .../alarms-table-basic-config.component.ts | 2 +- .../widget/config/basic/basic-config.scss | 3 +- .../basic/basic-widget-config.module.ts | 20 +- .../aggregated-data-key-row.component.html | 2 +- .../aggregated-data-key-row.component.scss | 3 +- .../aggregated-data-key-row.component.ts | 2 +- .../aggregated-data-keys-panel.component.html | 2 +- .../aggregated-data-keys-panel.component.scss | 3 +- .../aggregated-data-keys-panel.component.ts | 2 +- ...ted-value-card-basic-config.component.html | 2 +- ...gated-value-card-basic-config.component.ts | 2 +- .../progress-bar-basic-config.component.html | 2 +- .../progress-bar-basic-config.component.ts | 2 +- .../simple-card-basic-config.component.html | 2 +- .../simple-card-basic-config.component.ts | 2 +- ...meseries-table-basic-config.component.html | 2 +- ...timeseries-table-basic-config.component.ts | 2 +- .../value-card-basic-config.component.html | 3 +- .../value-card-basic-config.component.ts | 2 +- ...lue-chart-card-basic-config.component.html | 2 +- ...value-chart-card-basic-config.component.ts | 2 +- ...rt-with-labels-basic-config.component.html | 248 + ...hart-with-labels-basic-config.component.ts | 323 + .../doughnut-basic-config.component.html | 19 +- .../chart/doughnut-basic-config.component.ts | 10 +- .../chart/flot-basic-config.component.html | 2 +- .../chart/flot-basic-config.component.ts | 2 +- .../range-chart-basic-config.component.html | 222 + .../range-chart-basic-config.component.ts | 269 + .../basic/common/data-key-row.component.html | 2 +- .../basic/common/data-key-row.component.scss | 3 +- .../basic/common/data-key-row.component.ts | 2 +- .../common/data-keys-panel.component.html | 2 +- .../common/data-keys-panel.component.scss | 3 +- .../basic/common/data-keys-panel.component.ts | 2 +- .../widget-actions-panel.component.html | 2 +- .../common/widget-actions-panel.component.ts | 2 +- ...entities-table-basic-config.component.html | 2 +- .../entities-table-basic-config.component.ts | 2 +- .../entity-count-basic-config.component.html | 2 +- .../entity-count-basic-config.component.ts | 2 +- .../analog-gauge-basic-config.component.html | 2 +- .../analog-gauge-basic-config.component.ts | 3 +- .../compass-gauge-basic-config.component.html | 2 +- .../compass-gauge-basic-config.component.ts | 2 +- .../radial-gauge-basic-config.component.ts | 2 +- ...eter-scale-gauge-basic-config.component.ts | 2 +- .../battery-level-basic-config.component.html | 2 +- .../battery-level-basic-config.component.ts | 2 +- ...uid-level-card-basic-config.component.html | 2 +- ...iquid-level-card-basic-config.component.ts | 2 +- ...ignal-strength-basic-config.component.html | 12 +- .../signal-strength-basic-config.component.ts | 2 +- ...peed-direction-basic-config.component.html | 2 +- ...-speed-direction-basic-config.component.ts | 2 +- .../data-key-config-dialog.component.html | 2 +- .../data-key-config-dialog.component.scss | 2 +- .../data-key-config-dialog.component.ts | 2 +- .../config/data-key-config.component.html | 2 +- .../config/data-key-config.component.ts | 2 +- .../widget/config/data-keys.component.html | 2 +- .../config/data-keys.component.models.ts | 2 +- .../widget/config/data-keys.component.scss | 3 +- .../widget/config/data-keys.component.ts | 2 +- .../widget/config/datasource.component.html | 2 +- .../config/datasource.component.models.ts | 2 +- .../widget/config/datasource.component.scss | 2 +- .../widget/config/datasource.component.ts | 2 +- .../widget/config/datasources.component.html | 2 +- .../widget/config/datasources.component.scss | 2 +- .../widget/config/datasources.component.ts | 2 +- .../timewindow-config-panel.component.html | 2 +- .../timewindow-config-panel.component.ts | 2 +- .../timewindow-style-panel.component.html | 2 +- .../timewindow-style-panel.component.scss | 3 +- .../timewindow-style-panel.component.ts | 2 +- .../config/timewindow-style.component.html | 2 +- .../config/timewindow-style.component.ts | 2 +- .../config/widget-config-components.module.ts | 2 +- .../config/widget-config.component.models.ts | 2 +- .../config/widget-settings.component.html | 2 +- .../config/widget-settings.component.scss | 2 +- .../config/widget-settings.component.ts | 2 +- .../custom-dialog-container.component.ts | 2 +- .../widget/dialog/custom-dialog.component.ts | 2 +- .../widget/dialog/custom-dialog.service.ts | 2 +- .../dialog/embed-dashboard-dialog-token.ts | 2 +- .../embed-dashboard-dialog.component.html | 2 +- .../embed-dashboard-dialog.component.scss | 2 +- .../embed-dashboard-dialog.component.ts | 2 +- .../widget/dynamic-widget.component.ts | 4 +- .../alarm/alarms-table-widget.component.html | 4 +- .../alarm/alarms-table-widget.component.scss | 2 +- .../alarm/alarms-table-widget.component.ts | 2 +- .../widget/lib/analogue-compass.models.ts | 2 +- .../components/widget/lib/analogue-compass.ts | 2 +- .../widget/lib/analogue-gauge.models.ts | 4 +- .../lib/analogue-linear-gauge.models.ts | 2 +- .../widget/lib/analogue-linear-gauge.ts | 2 +- .../lib/analogue-radial-gauge.models.ts | 2 +- .../widget/lib/analogue-radial-gauge.ts | 2 +- .../widget/lib/canvas-digital-gauge.ts | 2 +- ...ggregated-value-card-widget.component.html | 4 +- ...ggregated-value-card-widget.component.scss | 2 +- .../aggregated-value-card-widget.component.ts | 12 +- .../lib/cards/aggregated-value-card.models.ts | 6 +- .../cards/progress-bar-widget.component.html | 4 +- .../cards/progress-bar-widget.component.scss | 2 +- .../cards/progress-bar-widget.component.ts | 11 +- .../lib/cards/progress-bar-widget.models.ts | 2 +- .../cards/value-card-widget.component.html | 4 +- .../cards/value-card-widget.component.scss | 2 +- .../lib/cards/value-card-widget.component.ts | 16 +- .../lib/cards/value-card-widget.models.ts | 2 +- .../value-chart-card-widget.component.html | 4 +- .../value-chart-card-widget.component.scss | 2 +- .../value-chart-card-widget.component.ts | 13 +- .../cards/value-chart-card-widget.models.ts | 2 +- ...ar-chart-with-labels-widget.component.html | 36 + ...ar-chart-with-labels-widget.component.scss | 104 + .../bar-chart-with-labels-widget.component.ts | 482 ++ .../bar-chart-with-labels-widget.models.ts | 99 + .../lib/chart/doughnut-widget.component.html | 4 +- .../lib/chart/doughnut-widget.component.scss | 3 +- .../lib/chart/doughnut-widget.component.ts | 35 +- .../lib/chart/doughnut-widget.models.ts | 25 +- .../widget/lib/chart/echarts-widget.models.ts | 277 + .../chart/range-chart-widget.component.html | 34 + .../chart/range-chart-widget.component.scss | 104 + .../lib/chart/range-chart-widget.component.ts | 439 ++ .../lib/chart/range-chart-widget.models.ts | 95 + .../lib/count/count-widget.component.html | 2 +- .../lib/count/count-widget.component.scss | 2 +- .../lib/count/count-widget.component.ts | 6 +- .../widget/lib/count/count-widget.models.ts | 2 +- .../date-range-navigator-panel.component.html | 2 +- .../date-range-navigator-panel.component.scss | 2 +- .../date-range-navigator.component.html | 2 +- .../date-range-navigator.component.scss | 2 +- .../date-range-navigator.component.ts | 2 +- .../date-range-navigator.models.ts | 2 +- .../widget/lib/digital-gauge.models.ts | 2 +- .../components/widget/lib/digital-gauge.ts | 2 +- .../lib/display-columns-panel.component.html | 2 +- .../lib/display-columns-panel.component.scss | 2 +- .../lib/display-columns-panel.component.ts | 2 +- .../lib/edges-overview-widget.component.html | 2 +- .../lib/edges-overview-widget.component.scss | 2 +- .../lib/edges-overview-widget.component.ts | 2 +- .../lib/edges-overview-widget.models.ts | 2 +- .../entities-hierarchy-widget.component.html | 2 +- .../entities-hierarchy-widget.component.scss | 2 +- .../entities-hierarchy-widget.component.ts | 2 +- .../entities-hierarchy-widget.models.ts | 2 +- .../entities-table-widget.component.html | 6 +- .../entities-table-widget.component.scss | 2 +- .../entity/entities-table-widget.component.ts | 4 +- .../widget/lib/flot-widget.component.html | 2 +- .../widget/lib/flot-widget.component.ts | 2 +- .../widget/lib/flot-widget.models.ts | 2 +- .../home/components/widget/lib/flot-widget.ts | 14 +- .../device-gateway-command.component.html | 76 +- .../device-gateway-command.component.scss | 39 +- .../device-gateway-command.component.ts | 48 +- .../gateway-configuration.component.html | 2 +- .../gateway-configuration.component.scss | 2 +- .../gateway-configuration.component.ts | 2 +- .../gateway/gateway-connectors.component.html | 2 +- .../gateway/gateway-connectors.component.scss | 2 +- .../gateway/gateway-connectors.component.ts | 19 +- .../lib/gateway/gateway-form.component.html | 2 +- .../lib/gateway/gateway-form.component.scss | 2 +- .../lib/gateway/gateway-form.component.ts | 4 +- .../widget/lib/gateway/gateway-form.models.ts | 2 +- .../lib/gateway/gateway-logs.component.html | 2 +- .../lib/gateway/gateway-logs.component.scss | 2 +- .../lib/gateway/gateway-logs.component.ts | 10 +- .../gateway-remote-configuration-dialog.html | 2 +- .../gateway-remote-configuration-dialog.ts | 2 +- ...service-rpc-connector-template-dialog.html | 2 +- ...y-service-rpc-connector-template-dialog.ts | 2 +- ...ice-rpc-connector-templates.component.html | 2 +- ...ice-rpc-connector-templates.component.scss | 2 +- ...rvice-rpc-connector-templates.component.ts | 2 +- ...teway-service-rpc-connector.component.html | 2 +- ...teway-service-rpc-connector.component.scss | 2 +- ...gateway-service-rpc-connector.component.ts | 2 +- .../gateway-service-rpc.component.html | 2 +- .../gateway-service-rpc.component.scss | 2 +- .../gateway/gateway-service-rpc.component.ts | 2 +- .../gateway/gateway-statistics.component.html | 3 +- .../gateway/gateway-statistics.component.scss | 2 +- .../gateway/gateway-statistics.component.ts | 2 +- .../lib/gateway/gateway-widget.models.ts | 2 +- .../add-doc-link-dialog.component.html | 2 +- .../add-doc-link-dialog.component.scss | 2 +- .../add-doc-link-dialog.component.ts | 2 +- .../add-quick-link-dialog.component.html | 2 +- .../add-quick-link-dialog.component.scss | 2 +- .../add-quick-link-dialog.component.ts | 2 +- .../cluster-info-table.component.html | 2 +- .../cluster-info-table.component.scss | 2 +- .../home-page/cluster-info-table.component.ts | 2 +- .../configured-features.component.html | 2 +- .../configured-features.component.scss | 3 +- .../configured-features.component.ts | 2 +- .../lib/home-page/doc-link.component.html | 2 +- .../lib/home-page/doc-link.component.ts | 2 +- .../home-page/doc-links-widget.component.html | 2 +- .../home-page/doc-links-widget.component.ts | 2 +- .../edit-links-dialog.component.html | 2 +- .../edit-links-dialog.component.scss | 2 +- .../home-page/edit-links-dialog.component.ts | 2 +- ...ng-started-completed-dialog.component.html | 2 +- ...ng-started-completed-dialog.component.scss | 2 +- ...ting-started-completed-dialog.component.ts | 2 +- .../getting-started-widget.component.html | 2 +- .../getting-started-widget.component.scss | 3 +- .../getting-started-widget.component.ts | 2 +- .../lib/home-page/home-page-widget.scss | 2 +- .../lib/home-page/home-page-widgets.module.ts | 2 +- .../widget/lib/home-page/home-page.scss | 3 +- .../widget/lib/home-page/link.component.scss | 2 +- .../lib/home-page/links-widget.component.scss | 3 +- .../lib/home-page/quick-link.component.html | 2 +- .../lib/home-page/quick-link.component.ts | 2 +- .../quick-links-widget.component.html | 2 +- .../home-page/quick-links-widget.component.ts | 2 +- .../recent-dashboards-widget.component.html | 2 +- .../recent-dashboards-widget.component.scss | 2 +- .../recent-dashboards-widget.component.ts | 2 +- .../usage-info-widget.component.html | 2 +- .../usage-info-widget.component.scss | 2 +- .../home-page/usage-info-widget.component.ts | 2 +- .../lib/home-page/version-info.component.html | 2 +- .../lib/home-page/version-info.component.scss | 3 +- .../lib/home-page/version-info.component.ts | 2 +- .../battery-level-widget.component.html | 6 +- .../battery-level-widget.component.scss | 2 +- .../battery-level-widget.component.ts | 26 +- .../indicator/battery-level-widget.models.ts | 14 +- .../liquid-level-widget.component.html | 4 +- .../liquid-level-widget.component.scss | 2 +- .../liquid-level-widget.component.ts | 17 +- .../indicator/liquid-level-widget.models.ts | 4 +- .../signal-strength-widget.component.html | 4 +- .../signal-strength-widget.component.scss | 2 +- .../signal-strength-widget.component.ts | 25 +- .../signal-strength-widget.models.ts | 2 +- .../lib/json-input-widget.component.html | 2 +- .../lib/json-input-widget.component.scss | 2 +- .../widget/lib/json-input-widget.component.ts | 2 +- .../widget/lib/legend.component.html | 2 +- .../widget/lib/legend.component.scss | 2 +- .../components/widget/lib/legend.component.ts | 2 +- .../home/components/widget/lib/maps/circle.ts | 2 +- .../widget/lib/maps/common-maps-utils.ts | 84 +- .../select-entity-dialog.component.html | 2 +- .../select-entity-dialog.component.scss | 2 +- .../dialogs/select-entity-dialog.component.ts | 2 +- .../components/widget/lib/maps/leaflet-map.ts | 26 +- .../components/widget/lib/maps/map-models.ts | 4 +- .../widget/lib/maps/map-widget.interface.ts | 2 +- .../components/widget/lib/maps/map-widget2.ts | 2 +- .../components/widget/lib/maps/maps-utils.ts | 2 +- .../components/widget/lib/maps/markers.scss | 2 +- .../components/widget/lib/maps/markers.ts | 34 +- .../components/widget/lib/maps/polygon.ts | 2 +- .../components/widget/lib/maps/polyline.ts | 2 +- .../widget/lib/maps/providers/google-map.ts | 2 +- .../widget/lib/maps/providers/here-map.ts | 2 +- .../widget/lib/maps/providers/image-map.ts | 60 +- .../widget/lib/maps/providers/index.ts | 2 +- .../lib/maps/providers/openstreet-map.ts | 2 +- .../widget/lib/maps/providers/tencent-map.ts | 2 +- .../widget/lib/markdown-widget.component.html | 2 +- .../widget/lib/markdown-widget.component.ts | 2 +- .../lib/multiple-input-widget.component.html | 20 +- .../lib/multiple-input-widget.component.scss | 4 +- .../lib/multiple-input-widget.component.ts | 12 +- .../lib/navigation-card-widget.component.html | 2 +- .../lib/navigation-card-widget.component.scss | 2 +- .../lib/navigation-card-widget.component.ts | 2 +- .../navigation-cards-widget.component.html | 2 +- .../navigation-cards-widget.component.scss | 2 +- .../lib/navigation-cards-widget.component.ts | 2 +- .../lib/photo-camera-input.component.html | 2 +- .../lib/photo-camera-input.component.scss | 2 +- .../lib/photo-camera-input.component.ts | 2 +- .../widget/lib/qrcode-widget.component.html | 2 +- .../widget/lib/qrcode-widget.component.ts | 2 +- .../widget/lib/rpc/knob.component.html | 2 +- .../widget/lib/rpc/knob.component.scss | 2 +- .../widget/lib/rpc/knob.component.ts | 2 +- .../lib/rpc/led-indicator.component.html | 2 +- .../lib/rpc/led-indicator.component.scss | 2 +- .../widget/lib/rpc/led-indicator.component.ts | 2 +- .../rpc/persistent-add-dialog.component.html | 2 +- .../rpc/persistent-add-dialog.component.scss | 2 +- .../rpc/persistent-add-dialog.component.ts | 2 +- .../persistent-details-dialog.component.html | 2 +- .../persistent-details-dialog.component.scss | 2 +- .../persistent-details-dialog.component.ts | 2 +- .../persistent-filter-panel.component.html | 2 +- .../persistent-filter-panel.component.scss | 2 +- .../rpc/persistent-filter-panel.component.ts | 2 +- .../lib/rpc/persistent-table.component.html | 2 +- .../lib/rpc/persistent-table.component.scss | 2 +- .../lib/rpc/persistent-table.component.ts | 2 +- .../lib/rpc/round-switch.component.html | 2 +- .../lib/rpc/round-switch.component.scss | 2 +- .../widget/lib/rpc/round-switch.component.ts | 2 +- .../widget/lib/rpc/rpc-widgets.module.ts | 2 +- .../widget/lib/rpc/switch.component.html | 2 +- .../widget/lib/rpc/switch.component.scss | 2 +- .../widget/lib/rpc/switch.component.ts | 2 +- .../components/widget/lib/settings.models.ts | 2 +- ...alarm-count-widget-settings.component.html | 2 +- .../alarm-count-widget-settings.component.ts | 2 +- .../alarms-table-key-settings.component.html | 2 +- .../alarms-table-key-settings.component.ts | 2 +- ...larms-table-widget-settings.component.html | 2 +- .../alarms-table-widget-settings.component.ts | 2 +- ...ted-value-card-key-settings.component.html | 2 +- ...gated-value-card-key-settings.component.ts | 2 +- ...-value-card-widget-settings.component.html | 2 +- ...ed-value-card-widget-settings.component.ts | 2 +- ...board-state-widget-settings.component.html | 2 +- ...shboard-state-widget-settings.component.ts | 2 +- ...ck-overview-widget-settings.component.html | 2 +- ...uick-overview-widget-settings.component.ts | 2 +- .../html-card-widget-settings.component.html | 2 +- .../html-card-widget-settings.component.ts | 2 +- .../cards/label-widget-label.component.html | 2 +- .../cards/label-widget-label.component.scss | 2 +- .../cards/label-widget-label.component.ts | 2 +- .../label-widget-settings.component.html | 6 +- .../cards/label-widget-settings.component.ts | 2 +- .../markdown-widget-settings.component.html | 2 +- .../markdown-widget-settings.component.ts | 2 +- ...rogress-bar-widget-settings.component.html | 2 +- .../progress-bar-widget-settings.component.ts | 2 +- .../qrcode-widget-settings.component.html | 2 +- .../cards/qrcode-widget-settings.component.ts | 2 +- ...simple-card-widget-settings.component.html | 2 +- .../simple-card-widget-settings.component.ts | 2 +- ...meseries-table-key-settings.component.html | 2 +- ...timeseries-table-key-settings.component.ts | 2 +- ...s-table-latest-key-settings.component.html | 2 +- ...ies-table-latest-key-settings.component.ts | 2 +- ...eries-table-widget-settings.component.html | 16 +- ...eseries-table-widget-settings.component.ts | 12 +- .../value-card-widget-settings.component.html | 2 +- .../value-card-widget-settings.component.ts | 2 +- ...-chart-card-widget-settings.component.html | 2 +- ...ue-chart-card-widget-settings.component.ts | 2 +- ...with-labels-widget-settings.component.html | 150 + ...t-with-labels-widget-settings.component.ts | 170 + .../chart-widget-settings.component.html | 2 +- .../chart/chart-widget-settings.component.ts | 2 +- ...ghnut-chart-widget-settings.component.html | 2 +- ...oughnut-chart-widget-settings.component.ts | 2 +- .../doughnut-widget-settings.component.html | 18 +- .../doughnut-widget-settings.component.ts | 15 +- .../flot-bar-key-settings.component.html | 2 +- .../chart/flot-bar-key-settings.component.ts | 2 +- .../flot-bar-widget-settings.component.html | 2 +- .../flot-bar-widget-settings.component.ts | 2 +- .../chart/flot-key-settings.component.html | 2 +- .../chart/flot-key-settings.component.ts | 2 +- .../flot-latest-key-settings.component.html | 2 +- .../flot-latest-key-settings.component.ts | 2 +- .../flot-line-key-settings.component.html | 2 +- .../chart/flot-line-key-settings.component.ts | 2 +- .../flot-line-widget-settings.component.html | 2 +- .../flot-line-widget-settings.component.ts | 2 +- .../flot-pie-key-settings.component.html | 2 +- .../chart/flot-pie-key-settings.component.ts | 2 +- .../flot-pie-widget-settings.component.html | 2 +- .../flot-pie-widget-settings.component.ts | 2 +- .../chart/flot-threshold.component.html | 2 +- .../chart/flot-threshold.component.scss | 3 +- .../chart/flot-threshold.component.ts | 2 +- .../chart/flot-widget-settings.component.html | 2 +- .../chart/flot-widget-settings.component.ts | 2 +- .../chart/label-data-key.component.html | 2 +- .../chart/label-data-key.component.scss | 2 +- .../chart/label-data-key.component.ts | 2 +- ...range-chart-widget-settings.component.html | 140 + .../range-chart-widget-settings.component.ts | 147 + .../background-settings-panel.component.html | 16 +- .../background-settings-panel.component.scss | 9 +- .../background-settings-panel.component.ts | 26 +- .../common/background-settings.component.html | 4 +- .../common/background-settings.component.scss | 2 +- .../common/background-settings.component.ts | 42 +- .../common/color-range-list.component.html | 2 +- .../common/color-range-list.component.ts | 2 +- .../common/color-range-panel.component.html | 2 +- .../common/color-range-panel.component.ts | 2 +- .../color-range-settings.component.html | 2 +- .../common/color-range-settings.component.ts | 6 +- .../color-settings-panel.component.html | 2 +- .../color-settings-panel.component.scss | 3 +- .../common/color-settings-panel.component.ts | 2 +- .../common/color-settings.component.html | 2 +- .../common/color-settings.component.ts | 2 +- .../count-widget-settings.component.html | 2 +- .../common/count-widget-settings.component.ts | 2 +- .../common/css-unit-select.component.html | 2 +- .../common/css-unit-select.component.ts | 2 +- .../common/date-format-select.component.html | 2 +- .../common/date-format-select.component.ts | 17 +- .../date-format-settings-panel.component.html | 2 +- .../date-format-settings-panel.component.scss | 3 +- .../date-format-settings-panel.component.ts | 2 +- .../common/font-settings-panel.component.html | 2 +- .../common/font-settings-panel.component.scss | 2 +- .../common/font-settings-panel.component.ts | 15 +- .../common/font-settings.component.html | 2 +- .../common/font-settings.component.ts | 10 +- .../common/image-cards-select.component.html | 2 +- .../common/image-cards-select.component.scss | 2 +- .../common/image-cards-select.component.ts | 2 +- .../common/legend-config.component.html | 2 +- .../common/legend-config.component.ts | 2 +- .../common/value-source.component.html | 2 +- .../settings/common/value-source.component.ts | 2 +- .../common/widget-font.component.html | 2 +- .../settings/common/widget-font.component.ts | 2 +- .../common/widget-settings-common.module.ts | 2 +- .../device-key-autocomplete.component.html | 2 +- .../device-key-autocomplete.component.ts | 2 +- ...nob-control-widget-settings.component.html | 2 +- .../knob-control-widget-settings.component.ts | 2 +- ...d-indicator-widget-settings.component.html | 2 +- ...led-indicator-widget-settings.component.ts | 2 +- ...stent-table-widget-settings.component.html | 2 +- ...sistent-table-widget-settings.component.ts | 2 +- ...ound-switch-widget-settings.component.html | 2 +- .../round-switch-widget-settings.component.ts | 2 +- .../control/rpc-button-style.component.html | 2 +- .../control/rpc-button-style.component.ts | 2 +- .../rpc-shell-widget-settings.component.html | 2 +- .../rpc-shell-widget-settings.component.ts | 2 +- ...pc-terminal-widget-settings.component.html | 2 +- .../rpc-terminal-widget-settings.component.ts | 2 +- .../send-rpc-widget-settings.component.html | 2 +- .../send-rpc-widget-settings.component.ts | 2 +- ...lide-toggle-widget-settings.component.html | 2 +- .../slide-toggle-widget-settings.component.ts | 2 +- ...tch-control-widget-settings.component.html | 2 +- ...witch-control-widget-settings.component.ts | 2 +- .../switch-rpc-settings.component.html | 2 +- .../control/switch-rpc-settings.component.ts | 2 +- ...e-attribute-widget-settings.component.html | 2 +- ...ice-attribute-widget-settings.component.ts | 2 +- ...e-navigator-widget-settings.component.html | 2 +- ...nge-navigator-widget-settings.component.ts | 2 +- ...s-hierarchy-widget-settings.component.html | 2 +- ...ies-hierarchy-widget-settings.component.ts | 2 +- ...entities-table-key-settings.component.html | 2 +- .../entities-table-key-settings.component.ts | 2 +- ...ities-table-widget-settings.component.html | 2 +- ...ntities-table-widget-settings.component.ts | 2 +- ...ntity-count-widget-settings.component.html | 2 +- .../entity-count-widget-settings.component.ts | 2 +- ...ngle-device-widget-settings.component.html | 2 +- ...single-device-widget-settings.component.ts | 2 +- ...eway-config-widget-settings.component.html | 2 +- ...ateway-config-widget-settings.component.ts | 2 +- ...eway-events-widget-settings.component.html | 2 +- ...ateway-events-widget-settings.component.ts | 2 +- .../gateway-logs-settings.component.html | 2 +- .../gateway-logs-settings.component.ts | 2 +- ...ateway-service-rpc-settings.component.html | 2 +- .../gateway-service-rpc-settings.component.ts | 2 +- ...gue-compass-widget-settings.component.html | 2 +- ...logue-compass-widget-settings.component.ts | 2 +- ...logue-gauge-widget-settings.component.html | 2 +- ...nalogue-gauge-widget-settings.component.ts | 4 +- ...-linear-gauge-widget-settings.component.ts | 2 +- ...-radial-gauge-widget-settings.component.ts | 2 +- ...gital-gauge-widget-settings.component.html | 2 +- ...digital-gauge-widget-settings.component.ts | 2 +- .../gauge/fixed-color-level.component.html | 2 +- .../gauge/fixed-color-level.component.scss | 2 +- .../gauge/fixed-color-level.component.ts | 2 +- .../settings/gauge/tick-value.component.html | 2 +- .../settings/gauge/tick-value.component.scss | 2 +- .../settings/gauge/tick-value.component.ts | 2 +- ...pio-control-widget-settings.component.html | 2 +- .../gpio-control-widget-settings.component.ts | 2 +- .../settings/gpio/gpio-item.component.html | 2 +- .../settings/gpio/gpio-item.component.scss | 2 +- .../lib/settings/gpio/gpio-item.component.ts | 2 +- .../gpio-panel-widget-settings.component.html | 2 +- .../gpio-panel-widget-settings.component.ts | 2 +- .../doc-links-widget-settings.component.html | 2 +- .../doc-links-widget-settings.component.ts | 2 +- ...quick-links-widget-settings.component.html | 2 +- .../quick-links-widget-settings.component.ts | 2 +- ...ttery-level-widget-settings.component.html | 2 +- ...battery-level-widget-settings.component.ts | 2 +- ...-level-card-widget-settings.component.html | 2 +- ...id-level-card-widget-settings.component.ts | 2 +- ...al-strength-widget-settings.component.html | 12 +- ...gnal-strength-widget-settings.component.ts | 2 +- .../datakey-select-option.component.html | 2 +- .../datakey-select-option.component.scss | 2 +- .../input/datakey-select-option.component.ts | 2 +- ...ce-claiming-widget-settings.component.html | 2 +- ...vice-claiming-widget-settings.component.ts | 2 +- ...amera-input-widget-settings.component.html | 2 +- ...-camera-input-widget-settings.component.ts | 2 +- ...-attribute-general-settings.component.html | 2 +- ...te-attribute-general-settings.component.ts | 2 +- ...n-attribute-widget-settings.component.html | 2 +- ...ean-attribute-widget-settings.component.ts | 2 +- ...e-attribute-widget-settings.component.html | 2 +- ...ate-attribute-widget-settings.component.ts | 2 +- ...e-attribute-widget-settings.component.html | 2 +- ...ble-attribute-widget-settings.component.ts | 2 +- ...e-attribute-widget-settings.component.html | 2 +- ...age-attribute-widget-settings.component.ts | 2 +- ...r-attribute-widget-settings.component.html | 2 +- ...ger-attribute-widget-settings.component.ts | 2 +- ...n-attribute-widget-settings.component.html | 2 +- ...son-attribute-widget-settings.component.ts | 2 +- ...n-attribute-widget-settings.component.html | 2 +- ...ion-attribute-widget-settings.component.ts | 2 +- ...ple-attributes-key-settings.component.html | 6 +- ...tiple-attributes-key-settings.component.ts | 2 +- ...-attributes-widget-settings.component.html | 2 +- ...le-attributes-widget-settings.component.ts | 2 +- ...g-attribute-widget-settings.component.html | 2 +- ...ing-attribute-widget-settings.component.ts | 2 +- .../map/circle-settings.component.html | 2 +- .../settings/map/circle-settings.component.ts | 2 +- .../map/common-map-settings.component.html | 2 +- .../map/common-map-settings.component.ts | 2 +- ...atasources-key-autocomplete.component.html | 2 +- .../datasources-key-autocomplete.component.ts | 2 +- ...oogle-map-provider-settings.component.html | 2 +- .../google-map-provider-settings.component.ts | 2 +- .../here-map-provider-settings.component.html | 2 +- .../here-map-provider-settings.component.ts | 2 +- ...image-map-provider-settings.component.html | 6 +- .../image-map-provider-settings.component.ts | 2 +- .../map/map-editor-settings.component.html | 2 +- .../map/map-editor-settings.component.ts | 2 +- .../map/map-provider-settings.component.html | 2 +- .../map/map-provider-settings.component.ts | 2 +- .../settings/map/map-settings.component.html | 2 +- .../settings/map/map-settings.component.ts | 2 +- .../map/map-widget-settings.component.html | 2 +- .../map/map-widget-settings.component.ts | 2 +- .../marker-clustering-settings.component.html | 2 +- .../marker-clustering-settings.component.ts | 2 +- .../map/markers-settings.component.html | 14 +- .../map/markers-settings.component.ts | 2 +- ...treet-map-provider-settings.component.html | 2 +- ...nstreet-map-provider-settings.component.ts | 2 +- .../map/polygon-settings.component.html | 2 +- .../map/polygon-settings.component.ts | 2 +- .../map/route-map-settings.component.html | 2 +- .../map/route-map-settings.component.ts | 2 +- .../route-map-widget-settings.component.html | 2 +- .../route-map-widget-settings.component.ts | 2 +- ...ncent-map-provider-settings.component.html | 2 +- ...tencent-map-provider-settings.component.ts | 2 +- ...p-animation-common-settings.component.html | 2 +- ...rip-animation-common-settings.component.ts | 2 +- ...p-animation-marker-settings.component.html | 14 +- ...rip-animation-marker-settings.component.ts | 2 +- ...rip-animation-path-settings.component.html | 2 +- .../trip-animation-path-settings.component.ts | 2 +- ...ip-animation-point-settings.component.html | 2 +- ...trip-animation-point-settings.component.ts | 2 +- ...p-animation-widget-settings.component.html | 2 +- ...rip-animation-widget-settings.component.ts | 2 +- ...gation-card-widget-settings.component.html | 2 +- ...vigation-card-widget-settings.component.ts | 2 +- ...ation-cards-widget-settings.component.html | 2 +- ...igation-cards-widget-settings.component.ts | 2 +- ...d-direction-widget-settings.component.html | 2 +- ...eed-direction-widget-settings.component.ts | 2 +- .../lib/settings/widget-settings.module.ts | 20 +- .../widget/lib/settings/widget-settings.scss | 2 +- .../widget/lib/table-widget.models.ts | 5 +- .../components/widget/lib/table-widget.scss | 2 +- .../timeseries-table-widget.component.html | 6 +- .../timeseries-table-widget.component.scss | 2 +- .../lib/timeseries-table-widget.component.ts | 11 +- .../trip-animation.component.html | 2 +- .../trip-animation.component.scss | 2 +- .../trip-animation.component.ts | 2 +- ...wind-speed-direction-widget.component.html | 4 +- ...wind-speed-direction-widget.component.scss | 2 +- .../wind-speed-direction-widget.component.ts | 11 +- .../wind-speed-direction-widget.models.ts | 2 +- .../widget/widget-component.service.ts | 2 +- .../widget/widget-components.module.ts | 94 +- .../widget/widget-config.component.html | 2 +- .../widget/widget-config.component.scss | 3 +- .../widget/widget-config.component.ts | 2 +- .../widget/widget-container.component.html | 2 +- .../widget/widget-container.component.scss | 3 +- .../widget/widget-container.component.ts | 2 +- .../widget/widget-preview.component.html | 2 +- .../widget/widget-preview.component.scss | 2 +- .../widget/widget-preview.component.ts | 2 +- .../components/widget/widget.component.html | 2 +- .../components/widget/widget.component.scss | 2 +- .../components/widget/widget.component.ts | 2 +- .../device-wizard-dialog.component.html | 2 +- .../device-wizard-dialog.component.scss | 2 +- .../wizard/device-wizard-dialog.component.ts | 2 +- ...entities-to-customer-dialog.component.html | 2 +- ...d-entities-to-customer-dialog.component.ts | 2 +- ...add-entities-to-edge-dialog.component.html | 2 +- .../add-entities-to-edge-dialog.component.ts | 2 +- .../assign-to-customer-dialog.component.html | 2 +- .../assign-to-customer-dialog.component.ts | 2 +- .../home/dialogs/home-dialogs.module.ts | 2 +- .../home/dialogs/home-dialogs.service.ts | 4 +- .../app/modules/home/home-routing.module.ts | 2 +- .../src/app/modules/home/home.component.html | 2 +- .../src/app/modules/home/home.component.scss | 2 +- ui-ngx/src/app/modules/home/home.component.ts | 2 +- ui-ngx/src/app/modules/home/home.module.ts | 2 +- .../home/menu/menu-link.component.html | 2 +- .../home/menu/menu-link.component.scss | 2 +- .../modules/home/menu/menu-link.component.ts | 2 +- .../home/menu/menu-toggle.component.html | 2 +- .../home/menu/menu-toggle.component.scss | 2 +- .../home/menu/menu-toggle.component.ts | 2 +- .../home/menu/side-menu.component.html | 2 +- .../home/menu/side-menu.component.scss | 2 +- .../modules/home/menu/side-menu.component.ts | 2 +- .../app/modules/home/models/contact.models.ts | 2 +- .../home/models/dashboard-component.models.ts | 2 +- .../models/datasource/attribute-datasource.ts | 2 +- .../models/datasource/entity-datasource.ts | 2 +- .../models/datasource/relation-datasource.ts | 2 +- .../entity/entities-table-config.models.ts | 19 +- .../models/entity/entity-component.models.ts | 2 +- .../entity-details-page-component.models.ts | 2 +- .../entity/entity-table-component.models.ts | 2 +- .../models/searchable-component.models.ts | 2 +- .../app/modules/home/models/services.map.ts | 4 +- .../home/models/widget-component.models.ts | 5 +- .../pages/account/account-routing.module.ts | 2 +- .../home/pages/account/account.module.ts | 2 +- .../home/pages/admin/admin-routing.module.ts | 22 +- .../modules/home/pages/admin/admin.module.ts | 2 +- .../auto-commit-admin-settings.component.html | 2 +- .../auto-commit-admin-settings.component.ts | 2 +- .../admin/general-settings.component.html | 2 +- .../admin/general-settings.component.scss | 2 +- .../pages/admin/general-settings.component.ts | 4 +- .../pages/admin/home-settings.component.html | 2 +- .../pages/admin/home-settings.component.scss | 2 +- .../pages/admin/home-settings.component.ts | 2 +- .../pages/admin/mail-server.component.html | 4 +- .../pages/admin/mail-server.component.scss | 2 +- .../home/pages/admin/mail-server.component.ts | 2 +- .../admin/oauth2-settings.component.html | 8 +- .../admin/oauth2-settings.component.scss | 2 +- .../pages/admin/oauth2-settings.component.ts | 2 +- .../pages/admin/queue/queue.component.html | 2 +- .../home/pages/admin/queue/queue.component.ts | 2 +- .../queue/queues-table-config.resolver.ts | 2 +- .../repository-admin-settings.component.html | 2 +- .../repository-admin-settings.component.ts | 2 +- .../resources-library-table-config.resolve.ts | 4 +- .../resource/resources-library.component.html | 4 +- .../resource/resources-library.component.ts | 5 +- .../resources-table-header.component.html | 2 +- .../resources-table-header.component.ts | 2 +- .../admin/security-settings.component.html | 64 +- .../admin/security-settings.component.scss | 2 +- .../admin/security-settings.component.ts | 35 +- .../admin/send-test-sms-dialog.component.html | 2 +- .../admin/send-test-sms-dialog.component.ts | 2 +- .../home/pages/admin/settings-card.scss | 2 +- .../pages/admin/sms-provider.component.html | 2 +- .../pages/admin/sms-provider.component.scss | 2 +- .../pages/admin/sms-provider.component.ts | 2 +- .../two-factor-auth-settings.component.html | 2 +- .../two-factor-auth-settings.component.scss | 2 +- .../two-factor-auth-settings.component.ts | 2 +- .../home/pages/alarm/alarm-routing.module.ts | 2 +- .../modules/home/pages/alarm/alarm.module.ts | 2 +- .../api-usage/api-usage-routing.module.ts | 2 +- .../pages/api-usage/api-usage.component.html | 2 +- .../pages/api-usage/api-usage.component.scss | 2 +- .../pages/api-usage/api-usage.component.ts | 2 +- .../home/pages/api-usage/api-usage.module.ts | 2 +- .../asset-profile-routing.module.ts | 2 +- .../asset-profile-tabs.component.html | 2 +- .../asset-profile-tabs.component.ts | 2 +- .../asset-profile/asset-profile.module.ts | 2 +- .../asset-profiles-table-config.resolver.ts | 4 +- .../home/pages/asset/asset-routing.module.ts | 2 +- .../asset/asset-table-header.component.html | 2 +- .../asset/asset-table-header.component.ts | 2 +- .../pages/asset/asset-tabs.component.html | 2 +- .../home/pages/asset/asset-tabs.component.ts | 2 +- .../home/pages/asset/asset.component.html | 2 +- .../home/pages/asset/asset.component.scss | 2 +- .../home/pages/asset/asset.component.ts | 2 +- .../modules/home/pages/asset/asset.module.ts | 2 +- .../asset/assets-table-config.resolver.ts | 2 +- .../audit-log/audit-log-routing.module.ts | 2 +- .../home/pages/audit-log/audit-log.module.ts | 2 +- .../pages/customer/customer-routing.module.ts | 2 +- .../customer/customer-tabs.component.html | 2 +- .../pages/customer/customer-tabs.component.ts | 2 +- .../pages/customer/customer.component.html | 2 +- .../pages/customer/customer.component.scss | 2 +- .../home/pages/customer/customer.component.ts | 2 +- .../home/pages/customer/customer.module.ts | 2 +- .../customers-table-config.resolver.ts | 2 +- .../dashboard/dashboard-form.component.html | 35 +- .../dashboard/dashboard-form.component.scss | 2 +- .../dashboard/dashboard-form.component.ts | 11 +- .../dashboard/dashboard-routing.module.ts | 2 +- .../dashboard/dashboard-tabs.component.html | 2 +- .../dashboard/dashboard-tabs.component.ts | 2 +- .../home/pages/dashboard/dashboard.module.ts | 2 +- .../dashboards-table-config.resolver.ts | 52 +- ...ake-dashboard-public-dialog.component.html | 2 +- .../make-dashboard-public-dialog.component.ts | 2 +- ...-dashboard-customers-dialog.component.html | 2 +- ...ge-dashboard-customers-dialog.component.ts | 2 +- .../device-profile-routing.module.ts | 2 +- .../device-profile-tabs.component.html | 2 +- .../device-profile-tabs.component.ts | 2 +- .../device-profile/device-profile.module.ts | 2 +- .../device-profiles-table-config.resolver.ts | 4 +- ...ice-transport-configuration.component.html | 2 +- ...evice-transport-configuration.component.ts | 2 +- ...efault-device-configuration.component.html | 2 +- .../default-device-configuration.component.ts | 2 +- ...ice-transport-configuration.component.html | 2 +- ...evice-transport-configuration.component.ts | 2 +- .../data/device-configuration.component.html | 2 +- .../data/device-configuration.component.ts | 2 +- .../device/data/device-data.component.html | 2 +- .../device/data/device-data.component.ts | 2 +- ...ice-transport-configuration.component.html | 2 +- ...evice-transport-configuration.component.ts | 2 +- ...ice-transport-configuration.component.html | 2 +- ...evice-transport-configuration.component.ts | 2 +- ...ice-transport-configuration.component.html | 2 +- ...evice-transport-configuration.component.ts | 2 +- ...ice-transport-configuration.component.html | 2 +- ...evice-transport-configuration.component.ts | 2 +- ...e-check-connectivity-dialog.component.html | 4 +- ...e-check-connectivity-dialog.component.scss | 3 +- ...ice-check-connectivity-dialog.component.ts | 2 +- .../device-credentials-dialog.component.html | 2 +- .../device-credentials-dialog.component.scss | 2 +- .../device-credentials-dialog.component.ts | 2 +- .../pages/device/device-routing.module.ts | 2 +- .../device/device-table-header.component.html | 2 +- .../device/device-table-header.component.ts | 2 +- .../pages/device/device-tabs.component.html | 2 +- .../pages/device/device-tabs.component.ts | 2 +- .../home/pages/device/device.component.html | 2 +- .../home/pages/device/device.component.scss | 2 +- .../home/pages/device/device.component.ts | 2 +- .../home/pages/device/device.module.ts | 2 +- .../device/devices-table-config.resolver.ts | 2 +- .../edge-instructions-dialog.component.html | 2 +- .../edge-instructions-dialog.component.scss | 3 +- .../edge-instructions-dialog.component.ts | 39 +- .../home/pages/edge/edge-routing.module.ts | 2 +- .../edge/edge-table-header.component.html | 2 +- .../pages/edge/edge-table-header.component.ts | 2 +- .../home/pages/edge/edge-tabs.component.html | 2 +- .../home/pages/edge/edge-tabs.component.ts | 2 +- .../home/pages/edge/edge.component.html | 29 +- .../home/pages/edge/edge.component.scss | 2 +- .../modules/home/pages/edge/edge.component.ts | 9 +- .../modules/home/pages/edge/edge.module.ts | 2 +- .../pages/edge/edges-table-config.resolver.ts | 12 +- .../pages/entities/entities-routing.module.ts | 2 +- .../home/pages/entities/entities.module.ts | 2 +- .../entity-view/entity-view-routing.module.ts | 2 +- .../entity-view-table-header.component.html | 2 +- .../entity-view-table-header.component.ts | 2 +- .../entity-view-tabs.component.html | 2 +- .../entity-view/entity-view-tabs.component.ts | 2 +- .../entity-view/entity-view.component.html | 2 +- .../entity-view/entity-view.component.scss | 2 +- .../entity-view/entity-view.component.ts | 2 +- .../pages/entity-view/entity-view.module.ts | 2 +- .../entity-views-table-config.resolver.ts | 2 +- .../pages/features/features-routing.module.ts | 2 +- .../home/pages/features/features.module.ts | 2 +- .../home-links/home-links-routing.module.ts | 2 +- .../home-links/home-links.component.html | 2 +- .../home-links/home-links.component.scss | 2 +- .../pages/home-links/home-links.component.ts | 2 +- .../pages/home-links/home-links.module.ts | 2 +- .../modules/home/pages/home-pages.models.ts | 2 +- .../modules/home/pages/home-pages.module.ts | 2 +- .../inbox-notification-dialog.component.html | 2 +- .../inbox-notification-dialog.component.scss | 2 +- .../inbox-notification-dialog.component.ts | 2 +- .../inbox/inbox-table-config.resolver.ts | 2 +- .../inbox/inbox-table-header.component.html | 2 +- .../inbox/inbox-table-header.component.scss | 2 +- .../inbox/inbox-table-header.component.ts | 2 +- .../notification-routing.module.ts | 2 +- .../pages/notification/notification.module.ts | 2 +- ...cipient-notification-dialog.component.html | 2 +- ...cipient-notification-dialog.component.scss | 2 +- ...recipient-notification-dialog.component.ts | 2 +- .../recipient-table-config.resolver.ts | 2 +- .../recipient-table-header.component.html | 2 +- .../recipient-table-header.component.scss | 2 +- .../recipient-table-header.component.ts | 2 +- .../rule/escalation-form.component.html | 2 +- .../rule/escalation-form.component.scss | 2 +- .../rule/escalation-form.component.ts | 2 +- .../rule/escalations.component.html | 2 +- .../rule/escalations.component.ts | 2 +- .../rule-notification-dialog.component.html | 2 +- .../rule-notification-dialog.component.scss | 2 +- .../rule-notification-dialog.component.ts | 2 +- .../rule/rule-table-config.resolver.ts | 6 +- .../rule/rule-table-header.component.html | 2 +- .../rule/rule-table-header.component.scss | 2 +- .../rule/rule-table-header.component.ts | 2 +- .../sent/sent-error-dialog.component.html | 2 +- .../sent/sent-error-dialog.component.scss | 2 +- .../sent/sent-error-dialog.component.ts | 2 +- .../sent-notification-dialog.component.html | 2 +- .../sent-notification-dialog.component.scss | 2 +- .../sent/sent-notification-dialog.componet.ts | 2 +- .../sent/sent-table-config.resolver.ts | 4 +- .../notification-setting-form.component.html | 2 +- .../notification-setting-form.component.scss | 2 +- .../notification-setting-form.component.ts | 2 +- .../notification-settings-routing.modules.ts | 2 +- .../notification-settings.component.html | 2 +- .../notification-settings.component.scss | 2 +- .../notification-settings.component.ts | 25 +- .../template/template-configuration.ts | 4 +- ...emplate-notification-dialog.component.html | 2 +- ...emplate-notification-dialog.component.scss | 2 +- .../template-notification-dialog.component.ts | 2 +- .../template-table-config.resolver.ts | 2 +- .../template-table-header.component.html | 2 +- .../template-table-header.component.scss | 2 +- .../template-table-header.component.ts | 2 +- .../ota-update/ota-update-routing.module.ts | 2 +- .../ota-update-table-config.resolve.ts | 2 +- .../ota-update/ota-update.component.html | 3 +- .../pages/ota-update/ota-update.component.ts | 2 +- .../pages/ota-update/ota-update.module.ts | 2 +- .../pages/profile/profile-routing.module.ts | 2 +- .../home/pages/profile/profile.component.html | 2 +- .../home/pages/profile/profile.component.scss | 2 +- .../home/pages/profile/profile.component.ts | 2 +- .../home/pages/profile/profile.module.ts | 2 +- .../pages/profiles/profiles-routing.module.ts | 2 +- .../home/pages/profiles/profiles.module.ts | 2 +- .../src/app/modules/home/pages/public-api.ts | 2 +- .../add-rule-node-dialog.component.html | 2 +- .../add-rule-node-dialog.component.scss | 2 +- .../add-rule-node-link-dialog.component.html | 2 +- .../add-rule-node-link-dialog.component.scss | 2 +- ...ate-nested-rulechain-dialog.component.html | 2 +- .../rulechain/link-labels.component.html | 2 +- .../pages/rulechain/link-labels.component.ts | 2 +- .../pages/rulechain/rule-node-colors.scss | 2 +- .../rulechain/rule-node-config.component.html | 2 +- .../rulechain/rule-node-config.component.scss | 2 +- .../rulechain/rule-node-config.component.ts | 2 +- .../rule-node-details.component.html | 24 +- .../rule-node-details.component.scss | 2 +- .../rulechain/rule-node-details.component.ts | 71 +- .../rulechain/rule-node-link.component.html | 2 +- .../rulechain/rule-node-link.component.ts | 2 +- .../rulechain/rulechain-page.component.html | 2 +- .../rulechain/rulechain-page.component.scss | 2 +- .../rulechain/rulechain-page.component.ts | 9 +- .../pages/rulechain/rulechain-page.models.ts | 2 +- .../rulechain/rulechain-routing.module.ts | 2 +- .../rulechain/rulechain-tabs.component.html | 2 +- .../rulechain/rulechain-tabs.component.ts | 2 +- .../pages/rulechain/rulechain.component.html | 2 +- .../pages/rulechain/rulechain.component.scss | 2 +- .../pages/rulechain/rulechain.component.ts | 2 +- .../home/pages/rulechain/rulechain.module.ts | 2 +- .../rulechains-table-config.resolver.ts | 10 +- .../pages/rulechain/rulenode.component.html | 2 +- .../pages/rulechain/rulenode.component.scss | 2 +- .../pages/rulechain/rulenode.component.ts | 2 +- .../authentication-dialog.component.scss | 2 +- .../authentication-dialog.map.ts | 2 +- .../backup-code-auth-dialog.component.html | 2 +- .../backup-code-auth-dialog.component.ts | 4 +- .../email-auth-dialog.component.html | 2 +- .../email-auth-dialog.component.ts | 2 +- .../sms-auth-dialog.component.html | 2 +- .../sms-auth-dialog.component.ts | 2 +- .../totp-auth-dialog.component.html | 2 +- .../totp-auth-dialog.component.ts | 2 +- .../pages/security/security-routing.module.ts | 2 +- .../pages/security/security.component.html | 32 +- .../pages/security/security.component.scss | 2 +- .../home/pages/security/security.component.ts | 6 +- .../home/pages/security/security.module.ts | 2 +- .../tenant-profile-routing.module.ts | 2 +- .../tenant-profile-tabs.component.html | 2 +- .../tenant-profile-tabs.component.ts | 2 +- .../tenant-profile/tenant-profile.module.ts | 2 +- .../tenant-profiles-table-config.resolver.ts | 8 +- .../pages/tenant/tenant-routing.module.ts | 2 +- .../pages/tenant/tenant-tabs.component.html | 2 +- .../pages/tenant/tenant-tabs.component.ts | 2 +- .../home/pages/tenant/tenant.component.html | 2 +- .../home/pages/tenant/tenant.component.scss | 2 +- .../home/pages/tenant/tenant.component.ts | 2 +- .../home/pages/tenant/tenant.module.ts | 2 +- .../tenant/tenants-table-config.resolver.ts | 2 +- .../activation-link-dialog.component.html | 2 +- .../user/activation-link-dialog.component.ts | 2 +- .../pages/user/add-user-dialog.component.html | 2 +- .../pages/user/add-user-dialog.component.scss | 2 +- .../pages/user/add-user-dialog.component.ts | 2 +- .../home/pages/user/user-routing.module.ts | 2 +- .../home/pages/user/user-tabs.component.html | 2 +- .../home/pages/user/user-tabs.component.ts | 2 +- .../home/pages/user/user.component.html | 2 +- .../home/pages/user/user.component.scss | 2 +- .../modules/home/pages/user/user.component.ts | 2 +- .../modules/home/pages/user/user.module.ts | 2 +- .../pages/user/users-table-config.resolver.ts | 4 +- .../home/pages/vc/vc-routing.module.ts | 2 +- .../app/modules/home/pages/vc/vc.module.ts | 2 +- .../save-widget-type-as-dialog.component.html | 2 +- .../save-widget-type-as-dialog.component.ts | 2 +- .../select-widget-type-dialog.component.html | 2 +- .../select-widget-type-dialog.component.scss | 2 +- .../select-widget-type-dialog.component.ts | 2 +- .../pages/widget/widget-editor.component.html | 7 +- .../pages/widget/widget-editor.component.scss | 2 +- .../pages/widget/widget-editor.component.ts | 2 +- .../home/pages/widget/widget-editor.models.ts | 2 +- .../widget/widget-library-routing.module.ts | 50 +- .../pages/widget/widget-library.module.ts | 3 +- .../widget-type-autocomplete.component.html | 2 +- .../widget-type-autocomplete.component.scss | 2 +- .../widget-type-autocomplete.component.ts | 2 +- .../widget/widget-type-tabs.component.html | 2 +- .../widget/widget-type-tabs.component.ts | 2 +- .../pages/widget/widget-type.component.html | 13 +- .../pages/widget/widget-type.component.ts | 2 +- .../widget-types-table-config.resolver.ts | 24 +- .../widget/widgets-bundle-tabs.component.html | 2 +- .../widget/widgets-bundle-tabs.component.ts | 2 +- .../widgets-bundle-widgets.component.html | 4 +- .../widgets-bundle-widgets.component.scss | 5 +- .../widgets-bundle-widgets.component.ts | 14 +- .../widget/widgets-bundle.component.html | 17 +- .../widget/widgets-bundle.component.scss | 2 +- .../pages/widget/widgets-bundle.component.ts | 2 +- .../widgets-bundles-table-config.resolver.ts | 20 +- ui-ngx/src/app/modules/home/public-api.ts | 2 +- .../app/modules/login/login-routing.module.ts | 2 +- ui-ngx/src/app/modules/login/login.module.ts | 2 +- .../login/create-password.component.html | 2 +- .../login/create-password.component.scss | 2 +- .../pages/login/create-password.component.ts | 2 +- .../login/pages/login/login.component.html | 5 +- .../login/pages/login/login.component.scss | 2 +- .../login/pages/login/login.component.ts | 6 +- .../reset-password-request.component.html | 2 +- .../reset-password-request.component.scss | 2 +- .../login/reset-password-request.component.ts | 2 +- .../pages/login/reset-password.component.html | 2 +- .../pages/login/reset-password.component.scss | 2 +- .../pages/login/reset-password.component.ts | 2 +- .../two-factor-auth-login.component.html | 2 +- .../two-factor-auth-login.component.scss | 2 +- .../login/two-factor-auth-login.component.ts | 2 +- .../shared/adapter/custom-datatime-adapter.ts | 2 +- .../animations/speed-dial-fab.animations.ts | 2 +- .../components/breadcrumb.component.html | 2 +- .../components/breadcrumb.component.scss | 2 +- .../shared/components/breadcrumb.component.ts | 2 +- .../src/app/shared/components/breadcrumb.ts | 2 +- .../button/copy-button.component.html | 8 +- .../button/copy-button.component.scss | 2 +- .../button/copy-button.component.ts | 3 +- .../button/toggle-password.component.html | 2 +- .../button/toggle-password.component.ts | 2 +- .../shared/components/cheatsheet.component.ts | 2 +- .../components/circular-progress.directive.ts | 2 +- .../components/color-input.component.html | 2 +- .../components/color-input.component.scss | 3 +- .../components/color-input.component.ts | 2 +- .../color-picker-panel.component.html | 2 +- .../color-picker-panel.component.scss | 2 +- .../color-picker-panel.component.ts | 2 +- .../color-picker/color-picker.component.html | 2 +- .../color-picker/color-picker.component.scss | 4 +- .../color-picker/color-picker.component.ts | 2 +- .../shared/components/contact.component.html | 2 +- .../shared/components/contact.component.ts | 2 +- .../app/shared/components/css.component.html | 2 +- .../app/shared/components/css.component.scss | 2 +- .../app/shared/components/css.component.ts | 2 +- .../dashboard-autocomplete.component.html | 8 +- .../dashboard-autocomplete.component.ts | 26 +- .../dashboard-select-panel.component.html | 2 +- .../dashboard-select-panel.component.scss | 2 +- .../dashboard-select-panel.component.ts | 2 +- .../dashboard-select.component.html | 2 +- .../dashboard-select.component.scss | 2 +- .../components/dashboard-select.component.ts | 2 +- ...ashboard-state-autocomplete.component.html | 2 +- .../dashboard-state-autocomplete.component.ts | 2 +- .../app/shared/components/dialog.component.ts | 2 +- .../dialog/alert-dialog.component.html | 2 +- .../dialog/alert-dialog.component.scss | 2 +- .../dialog/alert-dialog.component.ts | 2 +- .../dialog/color-picker-dialog.component.html | 2 +- .../dialog/color-picker-dialog.component.scss | 2 +- .../dialog/color-picker-dialog.component.ts | 2 +- .../dialog/confirm-dialog.component.html | 2 +- .../dialog/confirm-dialog.component.scss | 2 +- .../dialog/confirm-dialog.component.ts | 2 +- .../dialog/error-alert-dialog.component.html | 2 +- .../dialog/error-alert-dialog.component.scss | 2 +- .../dialog/error-alert-dialog.component.ts | 2 +- .../json-object-edit-dialog.component.html | 2 +- .../json-object-edit-dialog.component.ts | 2 +- .../material-icons-dialog.component.html | 2 +- .../material-icons-dialog.component.scss | 2 +- .../dialog/material-icons-dialog.component.ts | 2 +- .../node-script-test-dialog.component.html | 2 +- .../node-script-test-dialog.component.scss | 2 +- .../node-script-test-dialog.component.ts | 2 +- .../dialog/todo-dialog.component.html | 2 +- .../dialog/todo-dialog.component.scss | 2 +- .../dialog/todo-dialog.component.ts | 2 +- .../directives/component-outlet.directive.ts | 2 +- .../sring-template-outlet.directive.ts | 2 +- .../directives/tb-json-to-string.directive.ts | 2 +- .../entity/entity-autocomplete.component.html | 8 +- .../entity/entity-autocomplete.component.ts | 7 +- .../entity-gateway-select.component.html | 2 +- .../entity/entity-gateway-select.component.ts | 2 +- .../entity/entity-keys-list.component.html | 2 +- .../entity/entity-keys-list.component.ts | 2 +- .../entity/entity-list-select.component.html | 2 +- .../entity/entity-list-select.component.scss | 2 +- .../entity/entity-list-select.component.ts | 2 +- .../entity/entity-list.component.html | 2 +- .../entity/entity-list.component.ts | 2 +- .../entity/entity-select.component.html | 2 +- .../entity/entity-select.component.scss | 2 +- .../entity/entity-select.component.ts | 2 +- ...entity-subtype-autocomplete.component.html | 2 +- .../entity-subtype-autocomplete.component.ts | 2 +- .../entity/entity-subtype-list.component.html | 2 +- .../entity/entity-subtype-list.component.ts | 4 +- .../entity-subtype-select.component.html | 2 +- .../entity-subtype-select.component.scss | 2 +- .../entity/entity-subtype-select.component.ts | 2 +- .../entity/entity-type-list.component.html | 2 +- .../entity/entity-type-list.component.ts | 2 +- .../entity/entity-type-select.component.html | 2 +- .../entity/entity-type-select.component.scss | 2 +- .../entity/entity-type-select.component.ts | 2 +- .../components/fab-toolbar.component.html | 2 +- .../components/fab-toolbar.component.scss | 2 +- .../components/fab-toolbar.component.ts | 2 +- .../components/file-input.component.html | 17 +- .../components/file-input.component.scss | 39 +- .../shared/components/file-input.component.ts | 62 +- .../footer-fab-buttons.component.html | 2 +- .../footer-fab-buttons.component.scss | 2 +- .../footer-fab-buttons.component.ts | 2 +- .../shared/components/footer.component.html | 2 +- .../shared/components/footer.component.scss | 2 +- .../app/shared/components/footer.component.ts | 2 +- .../shared/components/fullscreen.directive.ts | 2 +- .../grid}/scroll-grid-datasource.ts | 28 +- .../grid/scroll-grid.component.html | 11 +- .../grid/scroll-grid.component.scss | 3 +- .../components/grid/scroll-grid.component.ts | 76 +- .../components/help-markdown.component.html | 2 +- .../components/help-markdown.component.scss | 2 +- .../components/help-markdown.component.ts | 2 +- .../components/help-popup.component.html | 2 +- .../components/help-popup.component.scss | 2 +- .../shared/components/help-popup.component.ts | 2 +- .../app/shared/components/help.component.html | 2 +- .../app/shared/components/help.component.ts | 2 +- .../hint-tooltip-icon.component.html | 2 +- .../hint-tooltip-icon.component.scss | 2 +- .../components/hint-tooltip-icon.component.ts | 2 +- .../shared/components/hotkeys.directive.ts | 2 +- .../app/shared/components/html.component.html | 2 +- .../app/shared/components/html.component.scss | 2 +- .../app/shared/components/html.component.ts | 2 +- .../app/shared/components/icon.component.ts | 2 +- .../components/image-input.component.html | 7 +- .../components/image-input.component.scss | 31 +- .../components/image-input.component.ts | 63 +- .../image/embed-image-dialog.component.html | 60 + .../image/embed-image-dialog.component.scss | 71 + .../image/embed-image-dialog.component.ts | 94 + .../image/gallery-image-input.component.html | 94 + .../image/gallery-image-input.component.scss | 190 + .../image/gallery-image-input.component.ts | 205 + .../image/image-dialog.component.html | 107 + .../image/image-dialog.component.scss | 96 + .../image/image-dialog.component.ts | 163 + .../image/image-gallery-dialog.component.html | 32 + .../image/image-gallery-dialog.component.scss | 47 + .../image/image-gallery-dialog.component.ts | 62 + .../image/image-gallery.component.html | 397 + .../image/image-gallery.component.scss | 274 + .../image/image-gallery.component.ts | 702 ++ .../image/image-references.component.html | 54 + .../image/image-references.component.scss | 92 + .../image/image-references.component.ts | 171 + .../components/image/images-datasource.ts | 130 + .../image/images-in-use-dialog.component.html | 82 + .../image/images-in-use-dialog.component.scss | 40 + .../image/images-in-use-dialog.component.ts | 114 + ...ultiple-gallery-image-input.component.html | 111 + ...ultiple-gallery-image-input.component.scss | 225 + .../multiple-gallery-image-input.component.ts | 181 + .../image/upload-image-dialog.component.html | 65 + .../image/upload-image-dialog.component.ts | 115 + .../shared/components/js-func.component.html | 2 +- .../shared/components/js-func.component.scss | 2 +- .../shared/components/js-func.component.ts | 2 +- .../components/json-content.component.html | 2 +- .../components/json-content.component.scss | 2 +- .../components/json-content.component.ts | 2 +- .../json-form/json-form-component.models.ts | 2 +- .../json-form/json-form.component.html | 2 +- .../json-form/json-form.component.scss | 2 +- .../json-form/json-form.component.ts | 2 +- .../json-form/react/json-form-ace-editor.tsx | 2 +- .../json-form/react/json-form-array.tsx | 2 +- .../react/json-form-base-component.tsx | 2 +- .../json-form/react/json-form-checkbox.tsx | 2 +- .../json-form/react/json-form-color.tsx | 2 +- .../json-form/react/json-form-css.tsx | 2 +- .../json-form/react/json-form-date.tsx | 2 +- .../json-form/react/json-form-fieldset.tsx | 2 +- .../json-form/react/json-form-help.tsx | 2 +- .../json-form/react/json-form-html.tsx | 2 +- .../json-form/react/json-form-icon.tsx | 2 +- .../json-form/react/json-form-image.tsx | 2 +- .../json-form/react/json-form-javascript.tsx | 2 +- .../json-form/react/json-form-json.tsx | 2 +- .../json-form/react/json-form-markdown.tsx | 2 +- .../json-form/react/json-form-number.tsx | 2 +- .../json-form/react/json-form-radios.tsx | 2 +- .../json-form/react/json-form-rc-select.tsx | 2 +- .../json-form/react/json-form-react.tsx | 2 +- .../json-form/react/json-form-schema-form.tsx | 2 +- .../json-form/react/json-form-select.tsx | 2 +- .../json-form/react/json-form-text.tsx | 2 +- .../json-form/react/json-form-utils.ts | 2 +- .../json-form/react/json-form.models.ts | 2 +- .../components/json-form/react/json-form.scss | 2 +- .../react/styles/thingsboardTheme.ts | 2 +- .../json-object-edit.component.html | 2 +- .../json-object-edit.component.scss | 2 +- .../components/json-object-edit.component.ts | 2 +- .../json-object-view.component.html | 2 +- .../json-object-view.component.scss | 2 +- .../components/json-object-view.component.ts | 2 +- .../shared/components/kv-map.component.html | 2 +- .../shared/components/kv-map.component.scss | 2 +- .../app/shared/components/kv-map.component.ts | 2 +- .../components/led-light.component.html | 2 +- .../shared/components/led-light.component.ts | 2 +- .../app/shared/components/logo.component.html | 2 +- .../app/shared/components/logo.component.scss | 2 +- .../app/shared/components/logo.component.ts | 2 +- .../components/markdown-editor.component.html | 2 +- .../components/markdown-editor.component.scss | 2 +- .../components/markdown-editor.component.ts | 2 +- .../shared/components/markdown.component.html | 2 +- .../shared/components/markdown.component.scss | 3 +- .../shared/components/markdown.component.ts | 2 +- .../components/marked-options.service.ts | 2 +- .../material-icon-select.component.html | 2 +- .../material-icon-select.component.scss | 2 +- .../material-icon-select.component.ts | 2 +- .../components/material-icons.component.html | 2 +- .../components/material-icons.component.scss | 2 +- .../components/material-icons.component.ts | 2 +- .../message-type-autocomplete.component.html | 2 +- .../message-type-autocomplete.component.ts | 2 +- .../multiple-image-input.component.html | 2 +- .../multiple-image-input.component.scss | 2 +- .../multiple-image-input.component.ts | 2 +- .../shared/components/nav-tree.component.html | 2 +- .../shared/components/nav-tree.component.scss | 2 +- .../shared/components/nav-tree.component.ts | 2 +- .../notification/notification.component.html | 2 +- .../notification/notification.component.scss | 2 +- .../notification/notification.component.ts | 2 +- .../template-autocomplete.component.html | 2 +- .../template-autocomplete.component.scss | 2 +- .../template-autocomplete.component.ts | 2 +- .../ota-package-autocomplete.component.html | 7 +- .../ota-package-autocomplete.component.scss | 2 +- .../ota-package-autocomplete.component.ts | 26 +- .../app/shared/components/page.component.ts | 2 +- .../components/phone-input.component.html | 2 +- .../components/phone-input.component.scss | 2 +- .../components/phone-input.component.ts | 2 +- .../shared/components/popover.component.scss | 2 +- .../shared/components/popover.component.ts | 9 +- .../app/shared/components/popover.models.ts | 2 +- .../app/shared/components/popover.service.ts | 13 +- .../protobuf-content.component.html | 2 +- .../protobuf-content.component.scss | 2 +- .../components/protobuf-content.component.ts | 2 +- .../src/app/shared/components/public-api.ts | 4 +- .../queue/queue-autocomplete.component.html | 2 +- .../queue/queue-autocomplete.component.scss | 2 +- .../queue/queue-autocomplete.component.ts | 2 +- .../relation-type-autocomplete.component.html | 2 +- .../relation-type-autocomplete.component.ts | 2 +- .../resource-autocomplete.component.html | 2 +- .../resource-autocomplete.component.ts | 2 +- .../rule-chain-select.component.html | 2 +- .../rule-chain-select.component.scss | 2 +- .../rule-chain/rule-chain-select.component.ts | 4 +- .../components/script-lang.component.html | 2 +- .../components/script-lang.component.scss | 2 +- .../components/script-lang.component.ts | 2 +- ...k-conversation-autocomplete.component.html | 2 +- ...k-conversation-autocomplete.component.scss | 2 +- ...ack-conversation-autocomplete.component.ts | 2 +- .../components/snack-bar-component.html | 2 +- .../components/snack-bar-component.scss | 2 +- .../socialshare-panel.component.html | 2 +- .../socialshare-panel.component.scss | 2 +- .../components/socialshare-panel.component.ts | 2 +- .../string-autocomplete.component.html | 2 +- .../string-autocomplete.component.scss | 2 +- .../string-autocomplete.component.ts | 2 +- .../string-items-list.component.html | 2 +- .../components/string-items-list.component.ts | 2 +- .../shared/components/tb-anchor.component.ts | 2 +- .../components/tb-checkbox.component.html | 2 +- .../components/tb-checkbox.component.ts | 2 +- .../shared/components/tb-error.component.ts | 2 +- .../time/datetime-period.component.html | 2 +- .../time/datetime-period.component.scss | 2 +- .../time/datetime-period.component.ts | 2 +- .../components/time/datetime.component.html | 2 +- .../components/time/datetime.component.ts | 2 +- .../history-selector.component.html | 2 +- .../history-selector.component.scss | 2 +- .../history-selector.component.ts | 2 +- .../time/quick-time-interval.component.html | 2 +- .../time/quick-time-interval.component.scss | 2 +- .../time/quick-time-interval.component.ts | 2 +- .../time/timeinterval.component.html | 4 +- .../time/timeinterval.component.scss | 2 +- .../components/time/timeinterval.component.ts | 82 +- .../time/timewindow-panel.component.html | 6 +- .../time/timewindow-panel.component.scss | 2 +- .../time/timewindow-panel.component.ts | 35 +- .../components/time/timewindow.component.html | 2 +- .../components/time/timewindow.component.scss | 2 +- .../components/time/timewindow.component.ts | 2 +- .../time/timezone-select.component.html | 2 +- .../time/timezone-select.component.ts | 2 +- .../app/shared/components/toast.directive.ts | 2 +- .../components/toggle-header.component.html | 2 +- .../components/toggle-header.component.scss | 3 +- .../components/toggle-header.component.ts | 2 +- .../components/toggle-select.component.html | 2 +- .../components/toggle-select.component.ts | 2 +- ui-ngx/src/app/shared/components/tokens.ts | 2 +- .../components/unit-input.component.html | 2 +- .../components/unit-input.component.scss | 2 +- .../shared/components/unit-input.component.ts | 2 +- .../components/user-menu.component.html | 2 +- .../components/user-menu.component.scss | 2 +- .../shared/components/user-menu.component.ts | 2 +- .../components/value-input.component.html | 2 +- .../components/value-input.component.scss | 2 +- .../components/value-input.component.ts | 2 +- .../vc/branch-autocomplete.component.html | 2 +- .../vc/branch-autocomplete.component.scss | 2 +- .../vc/branch-autocomplete.component.ts | 2 +- .../widgets-bundle-search.component.html | 2 +- .../widgets-bundle-search.component.scss | 2 +- .../widgets-bundle-search.component.ts | 2 +- .../widgets-bundle-select.component.html | 2 +- .../widgets-bundle-select.component.scss | 2 +- .../widgets-bundle-select.component.ts | 2 +- ui-ngx/src/app/shared/decorators/coercion.ts | 2 +- .../src/app/shared/decorators/enumerable.ts | 2 +- .../src/app/shared/decorators/public-api.ts | 2 +- ui-ngx/src/app/shared/decorators/tb-inject.ts | 2 +- ...xport-widgets-bundle-dialog.component.html | 2 +- .../export-widgets-bundle-dialog.component.ts | 6 +- .../import-dialog-csv.component.html | 2 +- .../import-dialog-csv.component.scss | 2 +- .../import-dialog-csv.component.ts | 10 +- .../import-dialog.component.html | 2 +- .../import-export/import-dialog.component.ts | 2 +- .../import-export/import-export.models.ts | 2 +- .../import-export/import-export.service.ts | 293 +- .../table-columns-assignment.component.html | 2 +- .../table-columns-assignment.component.scss | 2 +- .../table-columns-assignment.component.ts | 4 +- .../app/shared/layout/layout.directives.ts | 2 +- .../src/app/shared/models/ace/ace.models.ts | 2 +- .../shared/models/ace/completion.models.ts | 2 +- .../models/ace/service-completion.models.ts | 2 +- .../app/shared/models/ace/tbel/mode-tbel.js | 2 +- .../app/shared/models/ace/tbel/worker-tbel.js | 2 +- .../models/ace/widget-completion.models.ts | 2 +- ui-ngx/src/app/shared/models/alarm.models.ts | 5 +- ui-ngx/src/app/shared/models/alias.models.ts | 2 +- .../src/app/shared/models/api-usage.models.ts | 2 +- ui-ngx/src/app/shared/models/asset.models.ts | 8 +- .../src/app/shared/models/audit-log.models.ts | 2 +- .../src/app/shared/models/authority.enum.ts | 2 +- ui-ngx/src/app/shared/models/base-data.ts | 4 +- .../src/app/shared/models/beautify.models.ts | 2 +- .../models/component-descriptor.models.ts | 3 +- ui-ngx/src/app/shared/models/constants.ts | 6 +- .../app/shared/models/contact-based.model.ts | 2 +- .../src/app/shared/models/country.models.ts | 2 +- .../src/app/shared/models/customer.model.ts | 5 +- .../src/app/shared/models/dashboard.models.ts | 9 +- ui-ngx/src/app/shared/models/device.models.ts | 17 +- ui-ngx/src/app/shared/models/edge.models.ts | 11 +- .../app/shared/models/entity-type.models.ts | 8 +- .../app/shared/models/entity-view.models.ts | 5 +- ui-ngx/src/app/shared/models/entity.models.ts | 7 +- ui-ngx/src/app/shared/models/error.models.ts | 2 +- ui-ngx/src/app/shared/models/event.models.ts | 2 +- ui-ngx/src/app/shared/models/icon.models.ts | 2 +- .../app/shared/models/id/alarm-comment-id.ts | 2 +- ui-ngx/src/app/shared/models/id/alarm-id.ts | 2 +- ui-ngx/src/app/shared/models/id/asset-id.ts | 2 +- .../app/shared/models/id/asset-profile-id.ts | 2 +- .../src/app/shared/models/id/audit-log-id.ts | 2 +- .../src/app/shared/models/id/customer-id.ts | 2 +- .../src/app/shared/models/id/dashboard-id.ts | 2 +- .../shared/models/id/device-credentials-id.ts | 2 +- ui-ngx/src/app/shared/models/id/device-id.ts | 2 +- .../app/shared/models/id/device-profile-id.ts | 2 +- ui-ngx/src/app/shared/models/id/edge-id.ts | 2 +- ui-ngx/src/app/shared/models/id/entity-id.ts | 2 +- .../app/shared/models/id/entity-view-id.ts | 2 +- ui-ngx/src/app/shared/models/id/event-id.ts | 2 +- ui-ngx/src/app/shared/models/id/has-uuid.ts | 2 +- .../app/shared/models/id/notification-id.ts | 2 +- .../models/id/notification-request-id.ts | 2 +- .../shared/models/id/notification-rule-id.ts | 2 +- .../models/id/notification-target-id.ts | 2 +- .../models/id/notification-template-id.ts | 2 +- .../app/shared/models/id/ota-package-id.ts | 2 +- ui-ngx/src/app/shared/models/id/public-api.ts | 2 +- ui-ngx/src/app/shared/models/id/queue-id.ts | 2 +- ui-ngx/src/app/shared/models/id/rpc-id.ts | 2 +- .../src/app/shared/models/id/rule-chain-id.ts | 2 +- .../src/app/shared/models/id/rule-node-id.ts | 2 +- .../app/shared/models/id/tb-resource-id.ts | 2 +- ui-ngx/src/app/shared/models/id/tenant-id.ts | 2 +- .../app/shared/models/id/tenant-profile-id.ts | 2 +- ui-ngx/src/app/shared/models/id/user-id.ts | 2 +- .../app/shared/models/id/widget-type-id.ts | 2 +- .../app/shared/models/id/widgets-bundle-id.ts | 2 +- .../app/shared/models/limited-api.models.ts | 2 +- ui-ngx/src/app/shared/models/login.models.ts | 2 +- .../models/lwm2m-security-config.models.ts | 2 +- .../src/app/shared/models/material.models.ts | 2 +- .../app/shared/models/notification.models.ts | 17 +- ui-ngx/src/app/shared/models/oauth2.models.ts | 2 +- .../app/shared/models/ota-package.models.ts | 5 +- .../src/app/shared/models/overlay.models.ts | 2 +- .../src/app/shared/models/page/page-data.ts | 2 +- .../src/app/shared/models/page/page-link.ts | 2 +- .../src/app/shared/models/page/public-api.ts | 2 +- .../src/app/shared/models/page/sort-order.ts | 2 +- ui-ngx/src/app/shared/models/public-api.ts | 4 +- .../app/shared/models/query/query.models.ts | 8 +- ui-ngx/src/app/shared/models/queue.models.ts | 5 +- .../src/app/shared/models/relation.models.ts | 2 +- .../src/app/shared/models/resource.models.ts | 107 +- ui-ngx/src/app/shared/models/rpc.models.ts | 2 +- .../app/shared/models/rule-chain.models.ts | 5 +- .../src/app/shared/models/rule-node.models.ts | 4 +- .../src/app/shared/models/settings.models.ts | 4 +- .../models/telemetry/telemetry.models.ts | 380 +- ui-ngx/src/app/shared/models/tenant.model.ts | 8 +- .../src/app/shared/models/time/time.models.ts | 190 +- .../shared/models/two-factor-auth.models.ts | 2 +- ui-ngx/src/app/shared/models/unit.models.ts | 2 +- ui-ngx/src/app/shared/models/usage.models.ts | 2 +- .../app/shared/models/user-settings.models.ts | 3 +- ui-ngx/src/app/shared/models/user.model.ts | 5 +- ui-ngx/src/app/shared/models/vc.models.ts | 6 +- .../websocket/notification-ws.models.ts | 240 - .../models/websocket/websocket.models.ts | 3 +- .../shared/models/widget-settings.models.ts | 137 +- ui-ngx/src/app/shared/models/widget.models.ts | 17 +- .../app/shared/models/widgets-bundle.model.ts | 5 +- .../app/shared/models/window-message.model.ts | 2 +- ui-ngx/src/app/shared/pipe/date-ago.pipe.ts | 2 +- .../src/app/shared/pipe/enum-to-array.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/file-size.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/highlight.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/image.pipe.ts | 70 + .../app/shared/pipe/keyboard-shortcut.pipe.ts | 2 +- .../pipe/milliseconds-to-time-string.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/nospace.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/public-api.ts | 3 +- ui-ngx/src/app/shared/pipe/safe.pipe.ts | 2 +- .../shared/pipe/selectable-columns.pipe.ts | 2 +- .../src/app/shared/pipe/short-number.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/tbJson.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/truncate.pipe.ts | 2 +- ui-ngx/src/app/shared/public-api.ts | 2 +- .../shared/services/custom-paginator-intl.ts | 2 +- ui-ngx/src/app/shared/shared.module.ts | 54 +- ui-ngx/src/assets/fonts/material-icons.css | 2 +- .../help/en_US/notification/rule_node.md | 1 + ...e_originator_node_fields_templatization.md | 81 + .../common_node_fields_templatization.md | 11 +- .../to_email_node_fields_templatization.md | 68 + .../examples/change-originator-ft-2.png | Bin 0 -> 43794 bytes .../examples/change-originator-ft-3.png | Bin 0 -> 43804 bytes .../examples/change-originator-ft.png | Bin 0 -> 45433 bytes .../images/rulenode/examples/to-email-ft.png | Bin 0 -> 85702 bytes .../assets/locale/locale.constant-ca_ES.json | 4 +- .../assets/locale/locale.constant-cs_CZ.json | 1 - .../assets/locale/locale.constant-de_DE.json | 233 +- .../assets/locale/locale.constant-en_US.json | 341 +- .../assets/locale/locale.constant-es_ES.json | 36 +- .../assets/locale/locale.constant-fr_FR.json | 1 - .../assets/locale/locale.constant-nl_BE.json | 6951 +++++++++++++++++ .../assets/locale/locale.constant-ru_RU.json | 1875 ----- .../assets/locale/locale.constant-tr_TR.json | 1 - .../assets/locale/locale.constant-uk_UA.json | 208 +- .../assets/locale/locale.constant-zh_CN.json | 1553 +++- .../assets/locale/locale.constant-zh_TW.json | 1 - ui-ngx/src/assets/metadata/units.json | 5 + ui-ngx/src/environments/environment.prod.ts | 2 +- ui-ngx/src/environments/environment.ts | 2 +- ui-ngx/src/form.scss | 4 +- ui-ngx/src/index.html | 2 +- ui-ngx/src/karma.conf.js | 2 +- ui-ngx/src/main.ts | 2 +- ui-ngx/src/polyfills.ts | 2 +- ui-ngx/src/scss/animations.scss | 2 +- ui-ngx/src/scss/constants.scss | 2 +- ui-ngx/src/scss/fonts.scss | 2 +- ui-ngx/src/scss/mixins.scss | 2 +- ui-ngx/src/styles.scss | 2 +- ui-ngx/src/test.ts | 2 +- ui-ngx/src/theme-overwrites.scss | 2 +- ui-ngx/src/theme.scss | 2 +- ui-ngx/src/theme/datepicker-theme.scss | 2 +- ui-ngx/src/typings/jquery.flot.typings.d.ts | 2 +- ui-ngx/src/typings/jquery.jstree.typings.d.ts | 2 +- ui-ngx/src/typings/jquery.typings.d.ts | 2 +- ui-ngx/src/typings/leaflet-extend-tb.d.ts | 2 +- ui-ngx/src/typings/leaflet-geoman-extend.d.ts | 2 +- ui-ngx/src/typings/rawloader.typings.d.ts | 2 +- ui-ngx/src/typings/split.js.typings.d.ts | 2 +- ui-ngx/src/typings/utils.d.ts | 2 +- ui-ngx/src/zone-flags.ts | 2 +- ui-ngx/yarn.lock | 5 + 6174 files changed, 49730 insertions(+), 16385 deletions(-) delete mode 100644 application/src/main/data/json/demo/dashboards/gateways.json delete mode 100644 application/src/main/data/json/edge/install_instructions/docker/localhost_warning.md rename application/src/main/data/json/edge/{install_instructions => instructions/install}/centos/instructions.md (91%) rename application/src/main/data/json/edge/{install_instructions => instructions/install}/docker/instructions.md (77%) rename application/src/main/data/json/edge/{install_instructions => instructions/install}/ubuntu/instructions.md (97%) create mode 100644 application/src/main/data/json/edge/instructions/upgrade/centos/instructions.md create mode 100644 application/src/main/data/json/edge/instructions/upgrade/docker/instructions.md create mode 100644 application/src/main/data/json/edge/instructions/upgrade/docker/start_service.md create mode 100644 application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md create mode 100644 application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_preparing.md create mode 100644 application/src/main/data/json/edge/instructions/upgrade/start_service.md create mode 100644 application/src/main/data/json/edge/instructions/upgrade/ubuntu/instructions.md create mode 100644 application/src/main/data/json/edge/instructions/upgrade/upgrade_db.md create mode 100644 application/src/main/data/json/edge/instructions/upgrade/upgrade_preparing.md create mode 100644 application/src/main/data/json/system/widget_bundles/industial_widgets.json create mode 100644 application/src/main/data/json/system/widget_types/bar_chart_with_labels.json create mode 100644 application/src/main/data/json/system/widget_types/carbon_monoxide__co__card.json create mode 100644 application/src/main/data/json/system/widget_types/carbon_monoxide__co__card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/carbon_monoxide__co__chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/carbon_monoxide__co__chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/efficiency_card.json create mode 100644 application/src/main/data/json/system/widget_types/efficiency_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/efficiency_chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/efficiency_chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/efficiency_progress_bar.json create mode 100644 application/src/main/data/json/system/widget_types/efficiency_progress_bar_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/efficiency_range_chart.json create mode 100644 application/src/main/data/json/system/widget_types/efficiency_range_chart_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/flow_rate_card.json create mode 100644 application/src/main/data/json/system/widget_types/flow_rate_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/flow_rate_chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/flow_rate_chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/flow_rate_gauge.json create mode 100644 application/src/main/data/json/system/widget_types/flow_rate_progress_bar.json create mode 100644 application/src/main/data/json/system/widget_types/flow_rate_progress_bar_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/flow_rate_range_chart.json create mode 100644 application/src/main/data/json/system/widget_types/flow_rate_range_chart_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/fluid_pressure_card.json create mode 100644 application/src/main/data/json/system/widget_types/fluid_pressure_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/fluid_pressure_chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/fluid_pressure_chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/fluid_pressure_gauge.json create mode 100644 application/src/main/data/json/system/widget_types/fluid_pressure_progress_bar.json create mode 100644 application/src/main/data/json/system/widget_types/fluid_pressure_progress_bar_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/fluid_pressure_range_chart.json create mode 100644 application/src/main/data/json/system/widget_types/fluid_pressure_range_chart_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_carbon_monoxide__co__card.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_carbon_monoxide__co__card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_efficiency_card.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_efficiency_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_flow_rate_card.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_flow_rate_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_fluid_pressure_card.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_fluid_pressure_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_individual_allergy_index__iai__card.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_individual_allergy_index__iai__card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_nitrogen_dioxide__no2__card.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_nitrogen_dioxide__no2__card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_ozone__o3__card.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_ozone__o3__card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_power_consumption_card.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_power_consumption_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_pump_vibration_card.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_pump_vibration_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_rotational_speed_card.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_rotational_speed_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_sulfur_dioxide__so2__card.json create mode 100644 application/src/main/data/json/system/widget_types/horizontal_sulfur_dioxide__so2__card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/individual_allergy_index__iai__card.json create mode 100644 application/src/main/data/json/system/widget_types/individual_allergy_index__iai__card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/individual_allergy_index__iai__chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/individual_allergy_index__iai__chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/indoor_temperature_gauge.json create mode 100644 application/src/main/data/json/system/widget_types/indoor_temperature_range_chart.json create mode 100644 application/src/main/data/json/system/widget_types/indoor_temperature_range_chart_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/nitrogen_dioxide__no2__card.json create mode 100644 application/src/main/data/json/system/widget_types/nitrogen_dioxide__no2__card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/nitrogen_dioxide__no2__chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/nitrogen_dioxide__no2__chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/ozone__o3__card.json create mode 100644 application/src/main/data/json/system/widget_types/ozone__o3__card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/ozone__o3__chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/ozone__o3__chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/power_consumption_card.json create mode 100644 application/src/main/data/json/system/widget_types/power_consumption_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/power_consumption_chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/power_consumption_chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/power_consumption_range_chart.json create mode 100644 application/src/main/data/json/system/widget_types/power_consumption_range_chart_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/pump_vibration_card.json create mode 100644 application/src/main/data/json/system/widget_types/pump_vibration_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/pump_vibration_chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/pump_vibration_chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/pump_vibration_range_chart.json create mode 100644 application/src/main/data/json/system/widget_types/pump_vibration_range_chart_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/range_chart.json create mode 100644 application/src/main/data/json/system/widget_types/rotational_speed_card.json create mode 100644 application/src/main/data/json/system/widget_types/rotational_speed_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/rotational_speed_chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/rotational_speed_chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/rotational_speed_gauge.json create mode 100644 application/src/main/data/json/system/widget_types/rotational_speed_progress_bar.json create mode 100644 application/src/main/data/json/system/widget_types/rotational_speed_progress_bar_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/rotational_speed_range_chart.json create mode 100644 application/src/main/data/json/system/widget_types/rotational_speed_range_chart_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/simple_carbon_monoxide__co__chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/simple_carbon_monoxide__co__chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/simple_efficiency_chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/simple_efficiency_chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/simple_flow_rate_chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/simple_flow_rate_chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/simple_fluid_pressure_chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/simple_fluid_pressure_chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/simple_individual_allergy_index__iai__chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/simple_individual_allergy_index__iai__chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/simple_nitrogen_dioxide__no2__chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/simple_nitrogen_dioxide__no2__chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/simple_ozone__o3__chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/simple_ozone__o3__chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/simple_power_consumption_chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/simple_power_consumption_chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/simple_pump_vibration_chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/simple_pump_vibration_chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/simple_rotational_speed_chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/simple_rotational_speed_chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/simple_sulfur_dioxide__so2__chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/simple_sulfur_dioxide__so2__chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/sulfur_dioxide__so2__card.json create mode 100644 application/src/main/data/json/system/widget_types/sulfur_dioxide__so2__card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/sulfur_dioxide__so2__chart_card.json create mode 100644 application/src/main/data/json/system/widget_types/sulfur_dioxide__so2__chart_card_with_background.json create mode 100644 application/src/main/data/json/system/widget_types/temperature_gauge.json create mode 100644 application/src/main/data/json/system/widget_types/temperature_range_chart.json create mode 100644 application/src/main/data/json/system/widget_types/temperature_range_chart_with_background.json rename application/src/main/data/json/{demo/dashboards/gateway.json => tenant/dashboards/gateways.json} (96%) create mode 100644 application/src/main/data/upgrade/3.6.1/save_attributes_node_update.sql create mode 100644 application/src/main/data/upgrade/3.6.1/schema_update.sql create mode 100644 application/src/main/data/upgrade/3.6.2/schema_update.sql create mode 100644 application/src/main/java/org/thingsboard/server/config/TbRuleEngineSecurityConfiguration.java create mode 100644 application/src/main/java/org/thingsboard/server/controller/ImageController.java create mode 100644 application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsViolationResponse.java rename application/src/main/java/org/thingsboard/server/service/edge/instructions/{DefaultEdgeInstallService.java => DefaultEdgeInstallInstructionsService.java} (77%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java rename application/src/main/java/org/thingsboard/server/service/edge/instructions/{EdgeInstallService.java => EdgeInstallInstructionsService.java} (67%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeInstructionsService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/BaseMsgConstructorFactory.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java rename application/src/main/java/org/thingsboard/server/service/{ws/notification/cmd/WsCmd.java => edge/rpc/constructor/MsgConstructor.java} (78%) delete mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/TenantProfileMsgConstructor.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{AlarmMsgConstructor.java => alarm/AlarmMsgConstructorV1.java} (88%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/BaseAlarmMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{AssetProfileMsgConstructor.java => asset/AssetMsgConstructorV1.java} (58%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/BaseAssetMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/BaseCustomerMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{CustomerMsgConstructor.java => customer/CustomerMsgConstructorV1.java} (80%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/BaseDashboardMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{DashboardMsgConstructor.java => dashboard/DashboardMsgConstructorV1.java} (77%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/BaseDeviceMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{DeviceMsgConstructor.java => device/DeviceMsgConstructorV1.java} (56%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV2.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{ => edge}/EdgeMsgConstructor.java (94%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/BaseEntityViewMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{EntityViewMsgConstructor.java => entityview/EntityViewMsgConstructorV1.java} (71%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/BaseOtaPackageMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{OtaPackageMsgConstructor.java => ota/OtaPackageMsgConstructorV1.java} (83%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/BaseQueueMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{QueueMsgConstructor.java => queue/QueueMsgConstructorV1.java} (92%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{RelationMsgConstructor.java => relation/RelationMsgConstructorV1.java} (90%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/BaseResourceMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{ResourceMsgConstructor.java => resource/ResourceMsgConstructorV1.java} (72%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorV2.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/{AbstractRuleChainMetadataConstructor.java => BaseRuleChainMetadataConstructor.java} (87%) rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{RuleChainMsgConstructor.java => rule/BaseRuleChainMsgConstructor.java} (52%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV362.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructorFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructorV1.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{AdminSettingsMsgConstructor.java => settings/AdminSettingsMsgConstructorV1.java} (78%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructorV2.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{ => telemetry}/EntityDataMsgConstructor.java (96%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{TenantMsgConstructor.java => tenant/TenantMsgConstructorV1.java} (58%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/BaseUserMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{UserMsgConstructor.java => user/UserMsgConstructorV1.java} (77%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/BaseWidgetMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorFactory.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/{WidgetTypeMsgConstructor.java => widget/WidgetMsgConstructorV1.java} (62%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV2.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseResourceEdgeEventFetcher.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/SystemResourcesEdgeEventFetcher.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV1.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV1.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/{ => profile}/AssetProfileEdgeProcessor.java (77%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessorFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessorV1.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileProcessor.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/{ => profile}/BaseAssetProfileProcessor.java (57%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorV1.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardProcessor.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProfileProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorV1.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java rename application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/{ => profile}/DeviceProfileEdgeProcessor.java (78%) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorV1.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV1.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV1.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV1.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV2.java create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/executors/PubSubRuleNodeExecutorProvider.java create mode 100644 application/src/main/java/org/thingsboard/server/service/executors/VersionControlExecutor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java create mode 100644 application/src/main/java/org/thingsboard/server/service/resource/DefaultTbImageService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/resource/TbImageService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/security/exception/UserPasswordNotValidException.java create mode 100644 application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/ResourceExportService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/ResourceImportService.java rename rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNodeConfiguration.java => application/src/main/java/org/thingsboard/server/service/ws/AuthCmd.java (60%) create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/WsCmdType.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/WsCommandsWrapper.java rename application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/{TelemetryPluginCmdsWrapper.java => TelemetryCmdsWrapper.java} (68%) create mode 100644 application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ImageService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/EdgeUpgradeInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/EdgeUpgradeMessage.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/FstStatsService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/HasImage.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/ImageDescriptor.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/ImageExportData.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/TbImageDeleteResult.java rename common/data/src/main/java/org/thingsboard/server/common/data/edge/{EdgeInstallInstructions.java => EdgeInstructions.java} (86%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/AggregationParams.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/IntervalType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/util/ThrowingSupplier.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/rule/engine/DeviceDeleteMsg.java create mode 100644 common/proto/pom.xml rename common/{transport/transport-api/src/main/java/org/thingsboard/server/common/transport => proto/src/main/java/org/thingsboard/server/common}/adaptor/AdaptorException.java (90%) rename common/{transport/transport-api/src/main/java/org/thingsboard/server/common/transport => proto/src/main/java/org/thingsboard/server/common}/adaptor/JsonConverter.java (99%) rename common/{transport/transport-api/src/main/java/org/thingsboard/server/common/transport => proto/src/main/java/org/thingsboard/server/common}/adaptor/JsonConverterConfig.java (92%) rename common/{transport/transport-api/src/main/java/org/thingsboard/server/common/transport => proto/src/main/java/org/thingsboard/server/common}/adaptor/ProtoConverter.java (99%) rename {application/src/main/java/org/thingsboard/server/service/queue => common/proto/src/main/java/org/thingsboard/server/common/util}/ProtoUtils.java (95%) rename common/{cluster-api => proto}/src/main/proto/jsinvoke.proto (97%) rename common/{cluster-api => proto}/src/main/proto/queue.proto (98%) rename common/{transport/transport-api => proto}/src/main/proto/transport.proto (97%) rename common/{transport/transport-api/src/test/java => proto/src/test/java/org/thingsboard/server/common/adaptor}/JsonConverterTest.java (97%) rename {application/src/test/java/org/thingsboard/server/service/queue => common/proto/src/test/java/org/thingsboard/server/common/util}/ProtoUtilsTest.java (99%) create mode 100644 common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateConstructorTest.java create mode 100644 common/stats/src/main/java/org/thingsboard/server/common/stats/FstStatsServiceImpl.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityReportCallback.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultEntityLimitsCache.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityLimitKey.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityLimitsCache.java delete mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionActivityData.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityManager.java create mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java create mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java create mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java create mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java create mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java create mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java create mode 100644 common/util/src/main/java/org/thingsboard/common/util/ExecutorProvider.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/ImageContainerDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitException.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/resource/ImageCacheKey.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/JsonNodeProcessingTask.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/JsonPathProcessingTask.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/TimeUtils.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/util/DeviceConnectivityUtilTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/util/TimeUtilsTest.java create mode 100644 monitoring/src/main/resources/root_rule_chain.json create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/rule/node/MqttNodeTest.java create mode 100644 msa/black-box-tests/src/test/resources/MqttRuleNodeTestMetadata.json create mode 100644 msa/black-box-tests/src/test/resources/docker-compose.mosquitto.yml create mode 100644 msa/black-box-tests/src/test/resources/mosquitto/mosquitto.conf create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNodeWithTbMsgSource.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/AbstractRuleNodeUpgradeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/flow/TbCheckpointNodeTest.java create mode 100644 ui-ngx/src/app/core/http/image.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.ts rename ui-ngx/src/app/{modules/home/models/datasource => shared/components/grid}/scroll-grid-datasource.ts (92%) rename ui-ngx/src/app/{modules/home => shared}/components/grid/scroll-grid.component.html (80%) rename ui-ngx/src/app/{modules/home => shared}/components/grid/scroll-grid.component.scss (93%) rename ui-ngx/src/app/{modules/home => shared}/components/grid/scroll-grid.component.ts (54%) create mode 100644 ui-ngx/src/app/shared/components/image/embed-image-dialog.component.html create mode 100644 ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss create mode 100644 ui-ngx/src/app/shared/components/image/embed-image-dialog.component.ts create mode 100644 ui-ngx/src/app/shared/components/image/gallery-image-input.component.html create mode 100644 ui-ngx/src/app/shared/components/image/gallery-image-input.component.scss create mode 100644 ui-ngx/src/app/shared/components/image/gallery-image-input.component.ts create mode 100644 ui-ngx/src/app/shared/components/image/image-dialog.component.html create mode 100644 ui-ngx/src/app/shared/components/image/image-dialog.component.scss create mode 100644 ui-ngx/src/app/shared/components/image/image-dialog.component.ts create mode 100644 ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.html create mode 100644 ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.scss create mode 100644 ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.ts create mode 100644 ui-ngx/src/app/shared/components/image/image-gallery.component.html create mode 100644 ui-ngx/src/app/shared/components/image/image-gallery.component.scss create mode 100644 ui-ngx/src/app/shared/components/image/image-gallery.component.ts create mode 100644 ui-ngx/src/app/shared/components/image/image-references.component.html create mode 100644 ui-ngx/src/app/shared/components/image/image-references.component.scss create mode 100644 ui-ngx/src/app/shared/components/image/image-references.component.ts create mode 100644 ui-ngx/src/app/shared/components/image/images-datasource.ts create mode 100644 ui-ngx/src/app/shared/components/image/images-in-use-dialog.component.html create mode 100644 ui-ngx/src/app/shared/components/image/images-in-use-dialog.component.scss create mode 100644 ui-ngx/src/app/shared/components/image/images-in-use-dialog.component.ts create mode 100644 ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.html create mode 100644 ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.scss create mode 100644 ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.ts create mode 100644 ui-ngx/src/app/shared/components/image/upload-image-dialog.component.html create mode 100644 ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts rename ui-ngx/src/app/{modules/home/components => shared}/import-export/export-widgets-bundle-dialog.component.html (96%) rename ui-ngx/src/app/{modules/home/components => shared}/import-export/export-widgets-bundle-dialog.component.ts (89%) rename ui-ngx/src/app/{modules/home/components => shared}/import-export/import-dialog-csv.component.html (99%) rename ui-ngx/src/app/{modules/home/components => shared}/import-export/import-dialog-csv.component.scss (96%) rename ui-ngx/src/app/{modules/home/components => shared}/import-export/import-dialog-csv.component.ts (96%) rename ui-ngx/src/app/{modules/home/components => shared}/import-export/import-dialog.component.html (97%) rename ui-ngx/src/app/{modules/home/components => shared}/import-export/import-dialog.component.ts (98%) rename ui-ngx/src/app/{modules/home/components => shared}/import-export/import-export.models.ts (99%) rename ui-ngx/src/app/{modules/home/components => shared}/import-export/import-export.service.ts (85%) rename ui-ngx/src/app/{modules/home/components => shared}/import-export/table-columns-assignment.component.html (98%) rename ui-ngx/src/app/{modules/home/components => shared}/import-export/table-columns-assignment.component.scss (94%) rename ui-ngx/src/app/{modules/home/components => shared}/import-export/table-columns-assignment.component.ts (98%) delete mode 100644 ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts create mode 100644 ui-ngx/src/app/shared/pipe/image.pipe.ts create mode 100644 ui-ngx/src/assets/help/en_US/rulenode/change_originator_node_fields_templatization.md create mode 100644 ui-ngx/src/assets/help/en_US/rulenode/to_email_node_fields_templatization.md create mode 100644 ui-ngx/src/assets/help/images/rulenode/examples/change-originator-ft-2.png create mode 100644 ui-ngx/src/assets/help/images/rulenode/examples/change-originator-ft-3.png create mode 100644 ui-ngx/src/assets/help/images/rulenode/examples/change-originator-ft.png create mode 100644 ui-ngx/src/assets/help/images/rulenode/examples/to-email-ft.png create mode 100644 ui-ngx/src/assets/locale/locale.constant-nl_BE.json delete mode 100644 ui-ngx/src/assets/locale/locale.constant-ru_RU.json diff --git a/.github/release.yml b/.github/release.yml index 1d760fe7bf..5e0ddc4300 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -1,5 +1,5 @@ # -# Copyright © 2016-2023 The Thingsboard Authors +# Copyright © 2016-2024 The Thingsboard Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/.github/workflows/check-configuration-files.yml b/.github/workflows/check-configuration-files.yml index 0b292ded79..94bfc85ce3 100644 --- a/.github/workflows/check-configuration-files.yml +++ b/.github/workflows/check-configuration-files.yml @@ -1,5 +1,5 @@ # -# Copyright © 2016-2023 The Thingsboard Authors +# Copyright © 2016-2024 The Thingsboard Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/application/pom.xml b/application/pom.xml index 9c30c4cb77..aaa8a1f447 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -1,6 +1,6 @@ + + 4.0.0 + + org.thingsboard + 3.6.3-SNAPSHOT + common + + org.thingsboard.common + proto + jar + + Thingsboard Server Common Protobuf and gRPC structures + https://thingsboard.io + + + UTF-8 + ${basedir}/../.. + + + + + org.thingsboard.common + data + + + org.thingsboard.common + message + + + com.google.protobuf + protobuf-java + + + com.google.protobuf + protobuf-java-util + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.awaitility + awaitility + test + + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + + + + + thingsboard-repo-deploy + ThingsBoard Repo Deployment + https://repo.thingsboard.io/artifactory/libs-release-public + + + + \ No newline at end of file diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/AdaptorException.java similarity index 90% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java rename to common/proto/src/main/java/org/thingsboard/server/common/adaptor/AdaptorException.java index 8b2908a4b5..226ea644a6 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/AdaptorException.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.adaptor; +package org.thingsboard.server.common.adaptor; public class AdaptorException extends Exception { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java similarity index 99% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java rename to common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java index 51e74d5097..42da1d740f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.adaptor; +package org.thingsboard.server.common.adaptor; import com.google.gson.Gson; import com.google.gson.JsonArray; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverterConfig.java similarity index 92% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java rename to common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverterConfig.java index 0a9df150e5..335bf2265b 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverterConfig.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.adaptor; +package org.thingsboard.server.common.adaptor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/ProtoConverter.java similarity index 99% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java rename to common/proto/src/main/java/org/thingsboard/server/common/adaptor/ProtoConverter.java index 54358709f0..b90b5f054f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/ProtoConverter.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.adaptor; +package org.thingsboard.server.common.adaptor; import com.google.gson.Gson; import com.google.gson.JsonElement; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java rename to common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java index 36038568bc..9daa9de9da 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.queue; +package org.thingsboard.server.common.util; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; @@ -46,6 +46,7 @@ import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.common.msg.rule.engine.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.common.msg.rule.engine.DeviceCredentialsUpdateNotificationMsg; +import org.thingsboard.server.common.msg.rule.engine.DeviceDeleteMsg; import org.thingsboard.server.common.msg.rule.engine.DeviceEdgeUpdateMsg; import org.thingsboard.server.common.msg.rule.engine.DeviceNameOrTypeUpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos; @@ -384,6 +385,21 @@ public class ProtoUtils { ); } + private static TransportProtos.DeviceDeleteMsgProto toProto(DeviceDeleteMsg msg) { + return TransportProtos.DeviceDeleteMsgProto.newBuilder() + .setTenantIdMSB(msg.getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(msg.getTenantId().getId().getLeastSignificantBits()) + .setDeviceIdMSB(msg.getDeviceId().getId().getMostSignificantBits()) + .setDeviceIdLSB(msg.getDeviceId().getId().getLeastSignificantBits()) + .build(); + } + + private static DeviceDeleteMsg fromProto(TransportProtos.DeviceDeleteMsgProto proto) { + return new DeviceDeleteMsg( + TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), + new DeviceId(new UUID(proto.getDeviceIdMSB(), proto.getDeviceIdLSB()))); + } + public static TransportProtos.ToDeviceActorNotificationMsgProto toProto(ToDeviceActorNotificationMsg msg) { if (msg instanceof DeviceEdgeUpdateMsg) { DeviceEdgeUpdateMsg updateMsg = (DeviceEdgeUpdateMsg) msg; @@ -413,6 +429,10 @@ public class ProtoUtils { RemoveRpcActorMsg updateMsg = (RemoveRpcActorMsg) msg; TransportProtos.RemoveRpcActorMsgProto proto = toProto(updateMsg); return TransportProtos.ToDeviceActorNotificationMsgProto.newBuilder().setRemoveRpcActorMsg(proto).build(); + } else if (msg instanceof DeviceDeleteMsg) { + DeviceDeleteMsg updateMsg = (DeviceDeleteMsg) msg; + TransportProtos.DeviceDeleteMsgProto proto = toProto(updateMsg); + return TransportProtos.ToDeviceActorNotificationMsgProto.newBuilder().setDeviceDeleteMsg(proto).build(); } return null; } @@ -432,6 +452,8 @@ public class ProtoUtils { return fromProto(proto.getFromDeviceRpcResponseMsg()); } else if (proto.hasRemoveRpcActorMsg()) { return fromProto(proto.getRemoveRpcActorMsg()); + } else if (proto.hasDeviceDeleteMsg()) { + return fromProto(proto.getDeviceDeleteMsg()); } return null; } diff --git a/common/cluster-api/src/main/proto/jsinvoke.proto b/common/proto/src/main/proto/jsinvoke.proto similarity index 97% rename from common/cluster-api/src/main/proto/jsinvoke.proto rename to common/proto/src/main/proto/jsinvoke.proto index 14712bb384..c8ddc03e2b 100644 --- a/common/cluster-api/src/main/proto/jsinvoke.proto +++ b/common/proto/src/main/proto/jsinvoke.proto @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto similarity index 98% rename from common/cluster-api/src/main/proto/queue.proto rename to common/proto/src/main/proto/queue.proto index 016a40f5dc..4da2883291 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -255,6 +255,12 @@ message GetOrCreateDeviceFromGatewayRequestMsg { message GetOrCreateDeviceFromGatewayResponseMsg { DeviceInfoProto deviceInfo = 1; bytes profileBody = 2; + TransportApiRequestErrorCode error = 3; +} + +enum TransportApiRequestErrorCode { + UNKNOWN_TRANSPORT_API_ERROR = 0; + ENTITY_LIMIT = 1; } message GetEntityProfileRequestMsg { @@ -300,6 +306,17 @@ message CoreStartupMsg { int64 ts = 3; } +message ResourceCacheInvalidateMsg { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + repeated ImageCacheKeyProto keys = 3; +} + +message ImageCacheKeyProto { + optional string resourceKey = 1; + optional string publicResourceKey = 2; +} + message LwM2MRegistrationRequestMsg { string tenantId = 1; string endpoint = 2; @@ -1004,6 +1021,13 @@ message RemoveRpcActorMsgProto { int64 deviceIdLSB = 6; } +message DeviceDeleteMsgProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 deviceIdMSB = 3; + int64 deviceIdLSB = 4; +} + message ToDeviceActorNotificationMsgProto { DeviceEdgeUpdateMsgProto deviceEdgeUpdateMsg = 1; DeviceNameOrTypeUpdateMsgProto deviceNameOrTypeMsg = 2; @@ -1012,6 +1036,7 @@ message ToDeviceActorNotificationMsgProto { ToDeviceRpcRequestActorMsgProto toDeviceRpcRequestMsg = 5; FromDeviceRpcResponseActorMsgProto fromDeviceRpcResponseMsg = 6; RemoveRpcActorMsgProto removeRpcActorMsg = 7; + DeviceDeleteMsgProto deviceDeleteMsg = 8; } /** @@ -1267,6 +1292,7 @@ message ToCoreNotificationMsg { EdgeEventUpdateMsgProto edgeEventUpdate = 14; ToEdgeSyncRequestMsgProto toEdgeSyncRequest = 15; FromEdgeSyncResponseMsgProto fromEdgeSyncResponse = 16; + ResourceCacheInvalidateMsg resourceCacheInvalidateMsg = 17; } /* Messages that are handled by ThingsBoard RuleEngine Service */ diff --git a/common/transport/transport-api/src/main/proto/transport.proto b/common/proto/src/main/proto/transport.proto similarity index 97% rename from common/transport/transport-api/src/main/proto/transport.proto rename to common/proto/src/main/proto/transport.proto index 12c7f3e8c6..b3faf24539 100644 --- a/common/transport/transport-api/src/main/proto/transport.proto +++ b/common/proto/src/main/proto/transport.proto @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/transport/transport-api/src/test/java/JsonConverterTest.java b/common/proto/src/test/java/org/thingsboard/server/common/adaptor/JsonConverterTest.java similarity index 97% rename from common/transport/transport-api/src/test/java/JsonConverterTest.java rename to common/proto/src/test/java/org/thingsboard/server/common/adaptor/JsonConverterTest.java index 39ed04a29a..773ebbea3d 100644 --- a/common/transport/transport-api/src/test/java/JsonConverterTest.java +++ b/common/proto/src/test/java/org/thingsboard/server/common/adaptor/JsonConverterTest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.thingsboard.server.common.adaptor; + import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import org.junit.Assert; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; import java.util.ArrayList; diff --git a/application/src/test/java/org/thingsboard/server/service/queue/ProtoUtilsTest.java b/common/proto/src/test/java/org/thingsboard/server/common/util/ProtoUtilsTest.java similarity index 99% rename from application/src/test/java/org/thingsboard/server/service/queue/ProtoUtilsTest.java rename to common/proto/src/test/java/org/thingsboard/server/common/util/ProtoUtilsTest.java index 1a696b730d..4bb98f6cd2 100644 --- a/application/src/test/java/org/thingsboard/server/service/queue/ProtoUtilsTest.java +++ b/common/proto/src/test/java/org/thingsboard/server/common/util/ProtoUtilsTest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.queue; +package org.thingsboard.server.common.util; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 32d740812d..1eb4dd6ce8 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -1,6 +1,6 @@ - diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss index 3aed0ccb5e..505db79a36 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - .user-avatar { display: inline-flex; justify-content: center; diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts index 1fb1d3f689..0fc489fbfe 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html index 8618592ae0..8145c31b06 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html @@ -1,6 +1,6 @@ -

- + {{ getSortDirectionIcon() }} + +
- - - - - Windows - - - - - - - - - - Linux - - - - - - - - - - MacOS - - - - - - - -
- -
-
device.connectivity.execute-following-command
+
+
gateway.launch-gateway
+
gateway.launch-docker-compose
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.scss index befa034375..6a1ea79a80 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,28 +14,35 @@ * limitations under the License. */ :host { - width: 100%; - height: 100%; - display: block; + .tb-commands-hint { + color: inherit; + font-weight: normal; + flex: 1; + } } :host ::ng-deep { .tb-markdown-view { .start-code { - code[class*="language-"] { - white-space: break-spaces; - word-break: break-all; - } - pre[class*="language-"] { - overflow: hidden; - background: #F3F6FA; - border-color: #305680; - } .code-wrapper { padding: 0; + + pre[class*=language-] { + margin: 0; + background: #F3F6FA; + border-color: #305680; + padding-right: 38px; + overflow: scroll; + padding-bottom: 4px; + + &::-webkit-scrollbar { + width: 4px; + height: 4px; + } + } } button.clipboard-btn { - right: 0; + right: -2px; p { color: #305680; } @@ -60,9 +67,5 @@ } } } - - .tb-form-panel.tb-tab-body { - margin-top: 16px; - } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.ts index d5516dd59b..8465b5879d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/device-gateway-command.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -14,11 +14,8 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { DeviceService } from '@core/http/device.service'; -import { helpBaseUrl } from '@shared/models/constants'; -import { getOS } from '@core/utils'; -import { PublishLaunchCommand } from '@shared/models/device.models'; @Component({ selector: 'tb-gateway-command', @@ -26,47 +23,20 @@ import { PublishLaunchCommand } from '@shared/models/device.models'; styleUrls: ['./device-gateway-command.component.scss'] }) -export class DeviceGatewayCommandComponent implements OnInit { - - @Input() - token: string; +export class DeviceGatewayCommandComponent { @Input() deviceId: string; - commands: PublishLaunchCommand; - - helpLink: string = helpBaseUrl + '/docs/iot-gateway/install/docker-installation/'; - - tabIndex = 0; - - constructor(private cd: ChangeDetectorRef, - private deviceService: DeviceService) { + constructor(private deviceService: DeviceService) { } - - ngOnInit(): void { - if (this.deviceId) { - this.deviceService.getDevicePublishLaunchCommands(this.deviceId).subscribe(commands => { - this.commands = commands; - this.cd.detectChanges(); - }); + download($event: Event) { + if ($event) { + $event.stopPropagation(); } - const currentOS = getOS(); - switch (currentOS) { - case 'linux': - case 'android': - this.tabIndex = 1; - break; - case 'macos': - case 'ios': - this.tabIndex = 2; - break; - case 'windows': - this.tabIndex = 0; - break; - default: - this.tabIndex = 1; + if (this.deviceId) { + this.deviceService.downloadGatewayDockerComposeFile(this.deviceId).subscribe(() => {}); } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html index 7ddf0c8cd9..153b6b0e14 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html @@ -1,6 +1,6 @@ -
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.scss index e704120560..cd7722d12d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.ts index 503f5643bf..a3cb79e125 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index 1d1c280053..e75bf97d23 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-doc-link-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-doc-link-dialog.component.html index 1eaf791577..7f86897f7e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-doc-link-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/add-doc-link-dialog.component.html @@ -1,6 +1,6 @@ -
+
@@ -25,7 +25,7 @@
+ [style.background-size]="vertical ? '100% ' + (batteryFillValue + 1) + '%' : (batteryFillValue + 1) + '% 100%'">
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss index fbf244a383..444417fb09 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts index 31f98aff6c..5251a0c602 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -46,6 +46,9 @@ import { BatteryLevelWidgetSettings } from '@home/components/widget/lib/indicator/battery-level-widget.models'; import { ResizeObserver } from '@juggle/resize-observer'; +import { Observable } from 'rxjs'; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; const verticalBatteryDimensions = { shapeAspectRatio: 64 / 113, @@ -117,6 +120,8 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView value: number; + batteryFillValue: number; + batterySections: boolean[]; dividedBorderRadius: string; dividedGap: string; @@ -125,7 +130,7 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView batteryShapeColor: ColorProcessor; - backgroundStyle: ComponentStyle = {}; + backgroundStyle$: Observable; overlayStyle: ComponentStyle = {}; batteryBoxResize$: ResizeObserver; @@ -136,6 +141,8 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView private units = ''; constructor(private date: DatePipe, + private imagePipe: ImagePipe, + private sanitizer: DomSanitizer, private widgetComponent: WidgetComponent, private renderer: Renderer2, private cd: ChangeDetectorRef) { @@ -190,7 +197,7 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView this.batteryShapeColor = ColorProcessor.fromSettings(this.settings.batteryShapeColor); - this.backgroundStyle = backgroundStyle(this.settings.background); + this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.overlayStyle = overlayStyle(this.settings.background.overlay); this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0; @@ -221,9 +228,10 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView public onDataUpdated() { const tsValue = getSingleTsValue(this.ctx.data); - this.value = 0; + this.batteryFillValue = 0; if (tsValue && isDefinedAndNotNull(tsValue[1]) && isNumeric(tsValue[1])) { this.value = tsValue[1]; + this.batteryFillValue = this.parseBatteryFillValue(this.value); this.valueText = formatValue(this.value, this.decimals, this.units, false); } else { this.valueText = 'N/A'; @@ -240,6 +248,16 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView this.cd.detectChanges(); } + parseBatteryFillValue(value: number) { + if (value < 0) { + return 0; + } else if (value > 100) { + return 100; + } else { + return value; + } + } + public trackBySection(index: number): number { return index; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.models.ts index 34c00a4b92..081aae6558 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -78,22 +78,22 @@ export const batteryLevelDefaultSettings: BatteryLevelWidgetSettings = { }, valueColor: constantColor('rgba(0, 0, 0, 0.87)'), batteryLevelColor: { - color: 'rgba(92, 223, 144, 1)', + color: 'rgba(224, 224, 224, 1)', type: ColorType.range, rangeList: [ - {from: 0, to: 25, color: 'rgba(227, 71, 71, 1)'}, + {from: null, to: 25, color: 'rgba(227, 71, 71, 1)'}, {from: 25, to: 50, color: 'rgba(246, 206, 67, 1)'}, - {from: 50, to: 100, color: 'rgba(92, 223, 144, 1)'} + {from: 50, to: null, color: 'rgba(92, 223, 144, 1)'} ], colorFunction: defaultColorFunction }, batteryShapeColor: { - color: 'rgba(92, 223, 144, 0.32)', + color: 'rgba(224, 224, 224, 0.32)', type: ColorType.range, rangeList: [ - {from: 0, to: 25, color: 'rgba(227, 71, 71, 0.32)'}, + {from: null, to: 25, color: 'rgba(227, 71, 71, 0.32)'}, {from: 25, to: 50, color: 'rgba(246, 206, 67, 0.32)'}, - {from: 50, to: 100, color: 'rgba(92, 223, 144, 0.32)'} + {from: 50, to: null, color: 'rgba(92, 223, 144, 0.32)'} ], colorFunction: defaultColorFunction }, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.html index 4c14686b9c..5c247ca794 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.html @@ -1,6 +1,6 @@ -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.scss index 046d1a7d3e..360a75f54c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts index 847937581b..3c79883bf3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -58,6 +58,9 @@ import { ResourcesService } from '@core/services/resources.service'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { TranslateService } from '@ngx-translate/core'; import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; +import { DataEntry } from '@shared/models/widget.models'; @Component({ selector: 'tb-liquid-level-widget', @@ -76,7 +79,7 @@ export class LiquidLevelWidgetComponent implements OnInit { @Input() widgetTitlePanel: TemplateRef; - backgroundStyle: ComponentStyle = {}; + backgroundStyle$: Observable; overlayStyle: ComponentStyle = {}; hasCardClickAction = false; @@ -106,7 +109,9 @@ export class LiquidLevelWidgetComponent implements OnInit { private capacityUnits = Object.values(CapacityUnits); - constructor(private cd: ChangeDetectorRef, + constructor(private imagePipe: ImagePipe, + private sanitizer: DomSanitizer, + private cd: ChangeDetectorRef, private resourcesService: ResourcesService, private translate: TranslateService) { } @@ -116,7 +121,7 @@ export class LiquidLevelWidgetComponent implements OnInit { this.settings = {...levelCardDefaultSettings, ...this.ctx.settings}; this.declareStyles(); - this.backgroundStyle = backgroundStyle(this.settings.background); + this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.overlayStyle = overlayStyle(this.settings.background.overlay); this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0; @@ -395,7 +400,7 @@ export class LiquidLevelWidgetComponent implements OnInit { return limits.min + (percentage / 100) * (limits.max - limits.min); } - private updateTooltip(value: [number, any]): void { + private updateTooltip(value: DataEntry): void { this.tooltipContent = this.getTooltipContent(value); if (this.tooltip) { @@ -490,7 +495,7 @@ export class LiquidLevelWidgetComponent implements OnInit { } } - private getTooltipContent(value?: [number, any]): string { + private getTooltipContent(value?: DataEntry): string { const contentValue = value || [0, '']; let tooltipValue: string | number = 'N/A'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.models.ts index cf526d6c97..954e228e79 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -418,7 +418,7 @@ export const createAbsoluteLayout = (values?: {inputValue: number | string; volu export const createPercentLayout = (value: number | string = 50, valueTextStyle: string = valueTextStyleDefaults): string => `
- +
`; export const optionsFilter = (searchText: string): ((key: DataKey) => boolean) => diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.html index a2bf74e942..79f95c52d3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.html @@ -1,6 +1,6 @@ -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.scss index c2c14ffb76..3af5baa58b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.ts index 8427cf362b..bee975bd1f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.ts @@ -1,3 +1,19 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + /// /// Copyright © 2016-2023 The Thingsboard Authors @@ -51,6 +67,9 @@ import { } from '@home/components/widget/lib/indicator/signal-strength-widget.models'; import tinycolor from 'tinycolor2'; import { TranslateService } from '@ngx-translate/core'; +import { Observable } from 'rxjs'; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; const shapeWidth = 149; const shapeHeight = 113; @@ -104,7 +123,7 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi }; tooltipDateStyle: ComponentStyle = {}; - backgroundStyle: ComponentStyle = {}; + backgroundStyle$: Observable; overlayStyle: ComponentStyle = {}; shapeResize$: ResizeObserver; @@ -128,6 +147,8 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi private noData = false; constructor(public widgetComponent: WidgetComponent, + private imagePipe: ImagePipe, + private sanitizer: DomSanitizer, private translate: TranslateService, private renderer: Renderer2, private cd: ChangeDetectorRef) { @@ -186,7 +207,7 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi this.tooltipDateLabelStyle = {...this.tooltipDateStyle, ...this.tooltipDateLabelStyle}; } - this.backgroundStyle = backgroundStyle(this.settings.background); + this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.overlayStyle = overlayStyle(this.settings.background.overlay); this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts index 59c782f5d4..144d9e891b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.html index 40636a9d1b..b24ad37ede 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.html @@ -1,6 +1,6 @@
- - +
widgets.label-widget.labels
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-settings.component.ts index 191586b683..ff38162bbe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-settings.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.html index cf5415afe0..e33529a179 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.html @@ -1,6 +1,6 @@ + +
+
widgets.bar-chart.bar-chart-card-style
+
+ + {{ 'widgets.bar-chart.label-on-bar' | translate }} + +
+ + + + +
+
+
+ + {{ 'widgets.bar-chart.value-on-bar' | translate }} + +
+ + + + +
+
+
+ + + + + {{ 'widget-config.legend' | translate }} + + + + +
+
{{ 'legend.position' | translate }}
+ + + + {{ legendPositionTranslationMap.get(pos) | translate }} + + + +
+
+
{{ 'legend.label' | translate }}
+
+ + + + +
+
+
+
+
+
+ + + + + {{ 'widget-config.tooltip' | translate }} + + + + +
+
{{ 'tooltip.value' | translate }}
+
+ + + + +
+
+
+ + {{ 'tooltip.date' | translate }} + +
+ + + + + +
+
+
+
{{ 'tooltip.background-color' | translate }}
+ + +
+
+
{{ 'tooltip.background-blur' | translate }}
+ + +
px
+
+
+
+
+
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts new file mode 100644 index 0000000000..0d547922eb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts @@ -0,0 +1,170 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Injector } from '@angular/core'; +import { + legendPositions, + legendPositionTranslationMap, + WidgetSettings, + WidgetSettingsComponent +} from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { formatValue } from '@core/utils'; +import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models'; +import { + barChartWithLabelsDefaultSettings +} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models'; + +@Component({ + selector: 'tb-bar-chart-with-labels-widget-settings', + templateUrl: './bar-chart-with-labels-widget-settings.component.html', + styleUrls: [] +}) +export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsComponent { + + legendPositions = legendPositions; + + legendPositionTranslationMap = legendPositionTranslationMap; + + barChartWidgetSettingsForm: UntypedFormGroup; + + tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); + + tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this); + + constructor(protected store: Store, + private $injector: Injector, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.barChartWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return {...barChartWithLabelsDefaultSettings}; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.barChartWidgetSettingsForm = this.fb.group({ + + showBarLabel: [settings.showBarLabel, []], + barLabelFont: [settings.barLabelFont, []], + barLabelColor: [settings.barLabelColor, []], + showBarValue: [settings.showBarValue, []], + barValueFont: [settings.barValueFont, []], + barValueColor: [settings.barValueColor, []], + + showLegend: [settings.showLegend, []], + legendPosition: [settings.legendPosition, []], + legendLabelFont: [settings.legendLabelFont, []], + legendLabelColor: [settings.legendLabelColor, []], + + showTooltip: [settings.showTooltip, []], + tooltipValueFont: [settings.tooltipValueFont, []], + tooltipValueColor: [settings.tooltipValueColor, []], + tooltipShowDate: [settings.tooltipShowDate, []], + tooltipDateFormat: [settings.tooltipDateFormat, []], + tooltipDateFont: [settings.tooltipDateFont, []], + tooltipDateColor: [settings.tooltipDateColor, []], + tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], + tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], + + background: [settings.background, []] + }); + } + + protected validatorTriggers(): string[] { + return ['showBarLabel', 'showBarValue', 'showLegend', 'showTooltip', 'tooltipShowDate']; + } + + protected updateValidators(emitEvent: boolean) { + const showBarLabel: boolean = this.barChartWidgetSettingsForm.get('showBarLabel').value; + const showBarValue: boolean = this.barChartWidgetSettingsForm.get('showBarValue').value; + const showLegend: boolean = this.barChartWidgetSettingsForm.get('showLegend').value; + const showTooltip: boolean = this.barChartWidgetSettingsForm.get('showTooltip').value; + const tooltipShowDate: boolean = this.barChartWidgetSettingsForm.get('tooltipShowDate').value; + + if (showBarLabel) { + this.barChartWidgetSettingsForm.get('barLabelFont').enable(); + this.barChartWidgetSettingsForm.get('barLabelColor').enable(); + } else { + this.barChartWidgetSettingsForm.get('barLabelFont').disable(); + this.barChartWidgetSettingsForm.get('barLabelColor').disable(); + } + + if (showBarValue) { + this.barChartWidgetSettingsForm.get('barValueFont').enable(); + this.barChartWidgetSettingsForm.get('barValueColor').enable(); + } else { + this.barChartWidgetSettingsForm.get('barValueFont').disable(); + this.barChartWidgetSettingsForm.get('barValueColor').disable(); + } + + if (showLegend) { + this.barChartWidgetSettingsForm.get('legendPosition').enable(); + this.barChartWidgetSettingsForm.get('legendLabelFont').enable(); + this.barChartWidgetSettingsForm.get('legendLabelColor').enable(); + } else { + this.barChartWidgetSettingsForm.get('legendPosition').disable(); + this.barChartWidgetSettingsForm.get('legendLabelFont').disable(); + this.barChartWidgetSettingsForm.get('legendLabelColor').disable(); + } + + if (showTooltip) { + this.barChartWidgetSettingsForm.get('tooltipValueFont').enable(); + this.barChartWidgetSettingsForm.get('tooltipValueColor').enable(); + this.barChartWidgetSettingsForm.get('tooltipShowDate').enable({emitEvent: false}); + this.barChartWidgetSettingsForm.get('tooltipBackgroundColor').enable(); + this.barChartWidgetSettingsForm.get('tooltipBackgroundBlur').enable(); + if (tooltipShowDate) { + this.barChartWidgetSettingsForm.get('tooltipDateFormat').enable(); + this.barChartWidgetSettingsForm.get('tooltipDateFont').enable(); + this.barChartWidgetSettingsForm.get('tooltipDateColor').enable(); + } else { + this.barChartWidgetSettingsForm.get('tooltipDateFormat').disable(); + this.barChartWidgetSettingsForm.get('tooltipDateFont').disable(); + this.barChartWidgetSettingsForm.get('tooltipDateColor').disable(); + } + } else { + this.barChartWidgetSettingsForm.get('tooltipValueFont').disable(); + this.barChartWidgetSettingsForm.get('tooltipValueColor').disable(); + this.barChartWidgetSettingsForm.get('tooltipShowDate').disable({emitEvent: false}); + this.barChartWidgetSettingsForm.get('tooltipDateFormat').disable(); + this.barChartWidgetSettingsForm.get('tooltipDateFont').disable(); + this.barChartWidgetSettingsForm.get('tooltipDateColor').disable(); + this.barChartWidgetSettingsForm.get('tooltipBackgroundColor').disable(); + this.barChartWidgetSettingsForm.get('tooltipBackgroundBlur').disable(); + } + } + + private _tooltipValuePreviewFn(): string { + const units: string = this.widgetConfig.config.units; + const decimals: number = this.widgetConfig.config.decimals; + return formatValue(22, decimals, units, false); + } + + private _tooltipDatePreviewFn(): string { + const dateFormat: DateFormatSettings = this.barChartWidgetSettingsForm.get('tooltipDateFormat').value; + const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat); + processor.update(Date.now()); + return processor.formatted; + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/chart-widget-settings.component.html index d30182f73d..3922858711 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/chart-widget-settings.component.html @@ -1,6 +1,6 @@ + +
+
widgets.range-chart.range-chart-card-style
+
+ + {{ 'widgets.range-chart.data-zoom' | translate }} + +
+
+
{{ 'widgets.range-chart.range-colors' | translate }}
+ + +
+
+
{{ 'widgets.range-chart.out-of-range-color' | translate }}
+ + +
+
+ + {{ 'widgets.range-chart.fill-area' | translate }} + +
+
+ + + + + {{ 'widget-config.legend' | translate }} + + + + +
+
{{ 'legend.position' | translate }}
+ + + + {{ legendPositionTranslationMap.get(pos) | translate }} + + + +
+
+
{{ 'legend.label' | translate }}
+
+ + + + +
+
+
+
+
+
+ + + + + {{ 'widget-config.tooltip' | translate }} + + + + +
+
{{ 'tooltip.value' | translate }}
+
+ + + + +
+
+
+ + {{ 'tooltip.date' | translate }} + +
+ + + + + +
+
+
+
{{ 'tooltip.background-color' | translate }}
+ + +
+
+
{{ 'tooltip.background-blur' | translate }}
+ + +
px
+
+
+
+
+
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.ts new file mode 100644 index 0000000000..c0e043995d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.ts @@ -0,0 +1,147 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Injector } from '@angular/core'; +import { + legendPositions, + legendPositionTranslationMap, + WidgetSettings, + WidgetSettingsComponent +} from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { formatValue } from '@core/utils'; +import { rangeChartDefaultSettings } from '@home/components/widget/lib/chart/range-chart-widget.models'; +import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models'; + +@Component({ + selector: 'tb-range-chart-widget-settings', + templateUrl: './range-chart-widget-settings.component.html', + styleUrls: [] +}) +export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent { + + legendPositions = legendPositions; + + legendPositionTranslationMap = legendPositionTranslationMap; + + rangeChartWidgetSettingsForm: UntypedFormGroup; + + tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); + + tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this); + + constructor(protected store: Store, + private $injector: Injector, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.rangeChartWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return {...rangeChartDefaultSettings}; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.rangeChartWidgetSettingsForm = this.fb.group({ + dataZoom: [settings.dataZoom, []], + rangeColors: [settings.rangeColors, []], + outOfRangeColor: [settings.outOfRangeColor, []], + fillArea: [settings.fillArea, []], + + showLegend: [settings.showLegend, []], + legendPosition: [settings.legendPosition, []], + legendLabelFont: [settings.legendLabelFont, []], + legendLabelColor: [settings.legendLabelColor, []], + + showTooltip: [settings.showTooltip, []], + tooltipValueFont: [settings.tooltipValueFont, []], + tooltipValueColor: [settings.tooltipValueColor, []], + tooltipShowDate: [settings.tooltipShowDate, []], + tooltipDateFormat: [settings.tooltipDateFormat, []], + tooltipDateFont: [settings.tooltipDateFont, []], + tooltipDateColor: [settings.tooltipDateColor, []], + tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], + tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], + + background: [settings.background, []] + }); + } + + protected validatorTriggers(): string[] { + return ['showLegend', 'showTooltip', 'tooltipShowDate']; + } + + protected updateValidators(emitEvent: boolean) { + const showLegend: boolean = this.rangeChartWidgetSettingsForm.get('showLegend').value; + const showTooltip: boolean = this.rangeChartWidgetSettingsForm.get('showTooltip').value; + const tooltipShowDate: boolean = this.rangeChartWidgetSettingsForm.get('tooltipShowDate').value; + + if (showLegend) { + this.rangeChartWidgetSettingsForm.get('legendPosition').enable(); + this.rangeChartWidgetSettingsForm.get('legendLabelFont').enable(); + this.rangeChartWidgetSettingsForm.get('legendLabelColor').enable(); + } else { + this.rangeChartWidgetSettingsForm.get('legendPosition').disable(); + this.rangeChartWidgetSettingsForm.get('legendLabelFont').disable(); + this.rangeChartWidgetSettingsForm.get('legendLabelColor').disable(); + } + + if (showTooltip) { + this.rangeChartWidgetSettingsForm.get('tooltipValueFont').enable(); + this.rangeChartWidgetSettingsForm.get('tooltipValueColor').enable(); + this.rangeChartWidgetSettingsForm.get('tooltipShowDate').enable({emitEvent: false}); + this.rangeChartWidgetSettingsForm.get('tooltipBackgroundColor').enable(); + this.rangeChartWidgetSettingsForm.get('tooltipBackgroundBlur').enable(); + if (tooltipShowDate) { + this.rangeChartWidgetSettingsForm.get('tooltipDateFormat').enable(); + this.rangeChartWidgetSettingsForm.get('tooltipDateFont').enable(); + this.rangeChartWidgetSettingsForm.get('tooltipDateColor').enable(); + } else { + this.rangeChartWidgetSettingsForm.get('tooltipDateFormat').disable(); + this.rangeChartWidgetSettingsForm.get('tooltipDateFont').disable(); + this.rangeChartWidgetSettingsForm.get('tooltipDateColor').disable(); + } + } else { + this.rangeChartWidgetSettingsForm.get('tooltipValueFont').disable(); + this.rangeChartWidgetSettingsForm.get('tooltipValueColor').disable(); + this.rangeChartWidgetSettingsForm.get('tooltipShowDate').disable({emitEvent: false}); + this.rangeChartWidgetSettingsForm.get('tooltipDateFormat').disable(); + this.rangeChartWidgetSettingsForm.get('tooltipDateFont').disable(); + this.rangeChartWidgetSettingsForm.get('tooltipDateColor').disable(); + this.rangeChartWidgetSettingsForm.get('tooltipBackgroundColor').disable(); + this.rangeChartWidgetSettingsForm.get('tooltipBackgroundBlur').disable(); + } + } + + private _tooltipValuePreviewFn(): string { + const units: string = this.widgetConfig.config.units; + const decimals: number = this.widgetConfig.config.decimals; + return formatValue(22, decimals, units, false); + } + + private _tooltipDatePreviewFn(): string { + const dateFormat: DateFormatSettings = this.rangeChartWidgetSettingsForm.get('tooltipDateFormat').value; + const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat); + processor.update(Date.now()); + return processor.formatted; + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html index 42c4472cca..6cca169f5a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html @@ -1,6 +1,6 @@
widgets.background.background-settings
-
+
widgets.background.background
@@ -27,14 +27,8 @@
- -
-
widgets.background.image-url
- - - -
-
+ +
widgets.color.color
@@ -64,7 +58,7 @@
widgets.background.preview
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss index 258117512a..01297b9d12 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - @import '../../../../../../../../scss/constants'; .tb-background-settings-panel { @@ -31,6 +30,12 @@ letter-spacing: 0.25px; color: rgba(0, 0, 0, 0.87); } + .tb-background-form-panel { + height: 192px; + .tb-background-color-field { + height: auto; + } + } .tb-background-settings-preview { flex: 1; background: rgba(0, 0, 0, 0.04); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts index ab94b95800..c1c85a05e1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { backgroundStyle, @@ -27,6 +27,9 @@ import { TbPopoverComponent } from '@shared/components/popover.component'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; +import { Observable } from 'rxjs'; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; @Component({ selector: 'tb-background-settings-panel', @@ -54,11 +57,14 @@ export class BackgroundSettingsPanelComponent extends PageComponent implements O backgroundSettingsFormGroup: UntypedFormGroup; - backgroundStyle: ComponentStyle = {}; + backgroundStyle$: Observable; overlayStyle: ComponentStyle = {}; constructor(private fb: UntypedFormBuilder, - protected store: Store) { + private imagePipe: ImagePipe, + private sanitizer: DomSanitizer, + protected store: Store, + private cd: ChangeDetectorRef) { super(store); } @@ -66,7 +72,6 @@ export class BackgroundSettingsPanelComponent extends PageComponent implements O this.backgroundSettingsFormGroup = this.fb.group( { type: [this.backgroundSettings?.type, []], - imageBase64: [this.backgroundSettings?.imageBase64, []], imageUrl: [this.backgroundSettings?.imageUrl, []], color: [this.backgroundSettings?.color, []], overlay: this.fb.group({ @@ -101,11 +106,11 @@ export class BackgroundSettingsPanelComponent extends PageComponent implements O private updateValidators() { const overlayEnabled: boolean = this.backgroundSettingsFormGroup.get('overlay').get('enabled').value; if (overlayEnabled) { - this.backgroundSettingsFormGroup.get('overlay').get('color').enable(); - this.backgroundSettingsFormGroup.get('overlay').get('blur').enable(); + this.backgroundSettingsFormGroup.get('overlay').get('color').enable({emitEvent: false}); + this.backgroundSettingsFormGroup.get('overlay').get('blur').enable({emitEvent: false}); } else { - this.backgroundSettingsFormGroup.get('overlay').get('color').disable(); - this.backgroundSettingsFormGroup.get('overlay').get('blur').disable(); + this.backgroundSettingsFormGroup.get('overlay').get('color').disable({emitEvent: false}); + this.backgroundSettingsFormGroup.get('overlay').get('blur').disable({emitEvent: false}); } this.backgroundSettingsFormGroup.get('overlay').get('color').updateValueAndValidity({emitEvent: false}); this.backgroundSettingsFormGroup.get('overlay').get('blur').updateValueAndValidity({emitEvent: false}); @@ -113,8 +118,9 @@ export class BackgroundSettingsPanelComponent extends PageComponent implements O private updateBackgroundStyle() { const background: BackgroundSettings = this.backgroundSettingsFormGroup.value; - this.backgroundStyle = backgroundStyle(background); + this.backgroundStyle$ = backgroundStyle(background, this.imagePipe, this.sanitizer, true); this.overlayStyle = overlayStyle(background.overlay); + this.cd.markForCheck(); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html index e9e1b99b0e..ea685bd85f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html @@ -1,6 +1,6 @@
- - +
widgets.maps.image-map-background-from-entity-attribute
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/image-map-provider-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/image-map-provider-settings.component.ts index e79735e70d..51cc5ae6d7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/image-map-provider-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/image-map-provider-settings.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-editor-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-editor-settings.component.html index 8c0eb5e6b2..edea61f456 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-editor-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-editor-settings.component.html @@ -1,6 +1,6 @@ -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.component.scss index eda98fe6ba..5f3b18a041 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.component.ts index bf43218f82..4ce5cf21d7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -47,6 +47,9 @@ import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils'; import { ResizeObserver } from '@juggle/resize-observer'; import { Path, Svg, SVG, Text } from '@svgdotjs/svg.js'; import { DataKey } from '@shared/models/widget.models'; +import { Observable } from 'rxjs'; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; const shapeSize = 180; const cx = shapeSize / 2; @@ -87,7 +90,7 @@ export class WindSpeedDirectionWidgetComponent implements OnInit, OnDestroy, Aft centerValueColor: ColorProcessor; - backgroundStyle: ComponentStyle = {}; + backgroundStyle$: Observable; overlayStyle: ComponentStyle = {}; shapeResize$: ResizeObserver; @@ -109,6 +112,8 @@ export class WindSpeedDirectionWidgetComponent implements OnInit, OnDestroy, Aft private centerValueText = 'N/A'; constructor(private widgetComponent: WidgetComponent, + private imagePipe: ImagePipe, + private sanitizer: DomSanitizer, private renderer: Renderer2, private cd: ChangeDetectorRef) { } @@ -135,7 +140,7 @@ export class WindSpeedDirectionWidgetComponent implements OnInit, OnDestroy, Aft this.centerValueColor = ColorProcessor.fromSettings(this.settings.centerValueColor); - this.backgroundStyle = backgroundStyle(this.settings.background); + this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.overlayStyle = overlayStyle(this.settings.background.overlay); this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.models.ts index 6359696f25..7801f80a17 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/weather/wind-speed-direction-widget.models.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index c29df7f243..56a1868227 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index d4828a0190..851a469642 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -74,6 +74,10 @@ import { ValueChartCardWidgetComponent } from '@home/components/widget/lib/cards import { ProgressBarWidgetComponent } from '@home/components/widget/lib/cards/progress-bar-widget.component'; import { LiquidLevelWidgetComponent } from '@home/components/widget/lib/indicator/liquid-level-widget.component'; import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/doughnut-widget.component'; +import { RangeChartWidgetComponent } from '@home/components/widget/lib/chart/range-chart-widget.component'; +import { + BarChartWithLabelsWidgetComponent +} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.component'; import { GatewayServiceRPCConnectorTemplateDialogComponent } from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog'; @@ -120,7 +124,9 @@ import { ValueChartCardWidgetComponent, ProgressBarWidgetComponent, LiquidLevelWidgetComponent, - DoughnutWidgetComponent + DoughnutWidgetComponent, + RangeChartWidgetComponent, + BarChartWithLabelsWidgetComponent ], imports: [ CommonModule, @@ -129,47 +135,49 @@ import { HomePageWidgetsModule, SharedHomeComponentsModule ], - exports: [ - EntitiesTableWidgetComponent, - AlarmsTableWidgetComponent, - TimeseriesTableWidgetComponent, - EntitiesHierarchyWidgetComponent, - EdgesOverviewWidgetComponent, - RpcWidgetsModule, - HomePageWidgetsModule, - DateRangeNavigatorWidgetComponent, - JsonInputWidgetComponent, - MultipleInputWidgetComponent, - TripAnimationComponent, - PhotoCameraInputWidgetComponent, - GatewayFormComponent, - NavigationCardsWidgetComponent, - NavigationCardWidgetComponent, - QrCodeWidgetComponent, - MarkdownWidgetComponent, - LegendComponent, - FlotWidgetComponent, - GatewayConnectorComponent, - GatewayLogsComponent, - GatewayServiceRPCConnectorComponent, - GatewayServiceRPCConnectorTemplatesComponent, - GatewayStatisticsComponent, - GatewayServiceRPCComponent, - DeviceGatewayCommandComponent, - GatewayConfigurationComponent, - GatewayRemoteConfigurationDialogComponent, - GatewayServiceRPCConnectorTemplateDialogComponent, - ValueCardWidgetComponent, - AggregatedValueCardWidgetComponent, - CountWidgetComponent, - BatteryLevelWidgetComponent, - WindSpeedDirectionWidgetComponent, - SignalStrengthWidgetComponent, - ValueChartCardWidgetComponent, - ProgressBarWidgetComponent, - LiquidLevelWidgetComponent, - DoughnutWidgetComponent - ], + exports: [ + EntitiesTableWidgetComponent, + AlarmsTableWidgetComponent, + TimeseriesTableWidgetComponent, + EntitiesHierarchyWidgetComponent, + EdgesOverviewWidgetComponent, + RpcWidgetsModule, + HomePageWidgetsModule, + DateRangeNavigatorWidgetComponent, + JsonInputWidgetComponent, + MultipleInputWidgetComponent, + TripAnimationComponent, + PhotoCameraInputWidgetComponent, + GatewayFormComponent, + NavigationCardsWidgetComponent, + NavigationCardWidgetComponent, + QrCodeWidgetComponent, + MarkdownWidgetComponent, + LegendComponent, + FlotWidgetComponent, + GatewayConnectorComponent, + GatewayLogsComponent, + GatewayServiceRPCConnectorComponent, + GatewayServiceRPCConnectorTemplatesComponent, + GatewayStatisticsComponent, + GatewayServiceRPCComponent, + DeviceGatewayCommandComponent, + GatewayConfigurationComponent, + GatewayRemoteConfigurationDialogComponent, + GatewayServiceRPCConnectorTemplateDialogComponent, + ValueCardWidgetComponent, + AggregatedValueCardWidgetComponent, + CountWidgetComponent, + BatteryLevelWidgetComponent, + WindSpeedDirectionWidgetComponent, + SignalStrengthWidgetComponent, + ValueChartCardWidgetComponent, + ProgressBarWidgetComponent, + LiquidLevelWidgetComponent, + DoughnutWidgetComponent, + RangeChartWidgetComponent, + BarChartWithLabelsWidgetComponent + ], providers: [ {provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule} ] diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html index cdd1d8e2da..e49b769fee 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html @@ -1,6 +1,6 @@
-
-
- -
{{ noFileText }}
-
{{ fileName }}
+
+ +
{{ noFileText }}
+
{{ fileName }}
+
dashboard.maximum-upload-file-size
+
diff --git a/ui-ngx/src/app/shared/components/file-input.component.scss b/ui-ngx/src/app/shared/components/file-input.component.scss index e172abd6e9..2aa4190823 100644 --- a/ui-ngx/src/app/shared/components/file-input.component.scss +++ b/ui-ngx/src/app/shared/components/file-input.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,9 +21,13 @@ $previewSize: 100px !default; .tb-container { margin-top: 0; + padding: 0 0 16px; + display: flex; + flex-direction: column; + gap: 8px; label.tb-title { display: flex; - padding-bottom: 8px; + padding-bottom: 0; } } @@ -78,19 +82,46 @@ $previewSize: 100px !default; text-align: center; .mat-icon { margin-right: 17px; + color: rgba(0,0,0,0.12); } } } + + .tb-file-info-container { + display: flex; + flex-direction: column; + gap: 8px; + font-size: 13px; + font-style: normal; + line-height: 16px; + letter-spacing: normal; + } + + .tb-file-name { + color: rgba(0, 0, 0, 0.54); + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + } + + .tb-file-hint { + color: rgba(0, 0, 0, 0.38); + font-weight: 400; + overflow: hidden; + text-overflow: ellipsis; + } } :host ::ng-deep { - button.browse-file { + button.mat-mdc-button.mat-mdc-button-base.browse-file { padding: 0; + min-width: 0; + height: 24px; font-size: 16px; label { display: block; cursor: pointer; - padding: 0 16px; + padding: 0 8px; } } diff --git a/ui-ngx/src/app/shared/components/file-input.component.ts b/ui-ngx/src/app/shared/components/file-input.component.ts index 8e70ca88ba..b385cf109a 100644 --- a/ui-ngx/src/app/shared/components/file-input.component.ts +++ b/ui-ngx/src/app/shared/components/file-input.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -32,10 +32,12 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Subscription } from 'rxjs'; -import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { FlowDirective } from '@flowjs/ngx-flow'; import { TranslateService } from '@ngx-translate/core'; import { UtilsService } from '@core/services/utils.service'; +import { DialogService } from '@core/services/dialog.service'; +import { FileSizePipe } from '@shared/pipe/file-size.pipe'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-file-input', @@ -73,43 +75,28 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, dropLabel: string; @Input() - contentConvertFunction: (content: string) => any; - - private requiredValue: boolean; - - get required(): boolean { - return this.requiredValue; - } + maxSizeByte: number; @Input() - set required(value: boolean) { - const newVal = coerceBooleanProperty(value); - if (this.requiredValue !== newVal) { - this.requiredValue = newVal; - } - } - - private requiredAsErrorValue: boolean; + contentConvertFunction: (content: string) => any; - get requiredAsError(): boolean { - return this.requiredAsErrorValue; - } + @Input() + @coerceBoolean() + required: boolean; @Input() - set requiredAsError(value: boolean) { - const newVal = coerceBooleanProperty(value); - if (this.requiredAsErrorValue !== newVal) { - this.requiredAsErrorValue = newVal; - } - } + @coerceBoolean() + requiredAsError: boolean; @Input() + @coerceBoolean() disabled: boolean; @Input() existingFileName: string; @Input() + @coerceBoolean() readAsBinary = false; @Input() @@ -148,7 +135,9 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, constructor(protected store: Store, private utils: UtilsService, - public translate: TranslateService) { + private translate: TranslateService, + private dialog: DialogService, + private fileSize: FileSizePipe) { super(store); } @@ -156,11 +145,24 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, this.autoUploadSubscription = this.flow.events$.subscribe(event => { if (event.type === 'filesAdded') { const readers = []; + let showMaxSizeAlert = false; (event.event[0] as flowjs.FlowFile[]).forEach(file => { if (this.filterFile(file)) { - readers.push(this.readerAsFile(file)); + if (this.checkMaxSize(file)) { + readers.push(this.readerAsFile(file)); + } else { + showMaxSizeAlert = true; + } } }); + + if (showMaxSizeAlert) { + this.dialog.alert( + this.translate.instant('dashboard.cannot-upload-file'), + this.translate.instant('dashboard.maximum-upload-file-size', {size: this.fileSize.transform(this.maxSizeByte)}) + ).subscribe(() => { }); + } + if (readers.length) { Promise.all(readers).then((files) => { files = files.filter(file => file.fileContent != null || file.files != null); @@ -218,6 +220,10 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, }); } + private checkMaxSize(file: flowjs.FlowFile): boolean { + return !this.maxSizeByte || file.size <= this.maxSizeByte; + } + private filterFile(file: flowjs.FlowFile): boolean { if (this.allowedExtensions) { return this.allowedExtensions.split(',').indexOf(file.getExtension()) > -1; diff --git a/ui-ngx/src/app/shared/components/footer-fab-buttons.component.html b/ui-ngx/src/app/shared/components/footer-fab-buttons.component.html index 20820ac320..7d9c0c33d5 100644 --- a/ui-ngx/src/app/shared/components/footer-fab-buttons.component.html +++ b/ui-ngx/src/app/shared/components/footer-fab-buttons.component.html @@ -1,6 +1,6 @@ - - + +
-
+
- +
diff --git a/ui-ngx/src/app/modules/home/components/grid/scroll-grid.component.scss b/ui-ngx/src/app/shared/components/grid/scroll-grid.component.scss similarity index 93% rename from ui-ngx/src/app/modules/home/components/grid/scroll-grid.component.scss rename to ui-ngx/src/app/shared/components/grid/scroll-grid.component.scss index 8787e24eb5..47d7629f7a 100644 --- a/ui-ngx/src/app/modules/home/components/grid/scroll-grid.component.scss +++ b/ui-ngx/src/app/shared/components/grid/scroll-grid.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ .cdk-virtual-scroll-content-wrapper { display: flex; flex-direction: column; + width: 100%; } .cdk-virtual-scroll-spacer { height: auto !important; diff --git a/ui-ngx/src/app/modules/home/components/grid/scroll-grid.component.ts b/ui-ngx/src/app/shared/components/grid/scroll-grid.component.ts similarity index 54% rename from ui-ngx/src/app/modules/home/components/grid/scroll-grid.component.ts rename to ui-ngx/src/app/shared/components/grid/scroll-grid.component.ts index e0eea202ff..e876b06694 100644 --- a/ui-ngx/src/app/modules/home/components/grid/scroll-grid.component.ts +++ b/ui-ngx/src/app/shared/components/grid/scroll-grid.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -15,10 +15,10 @@ /// import { - AfterViewInit, + AfterViewInit, ChangeDetectorRef, Component, Input, - OnChanges, + OnChanges, OnDestroy, OnInit, Renderer2, SimpleChanges, @@ -30,10 +30,18 @@ import { GridEntitiesFetchFunction, ScrollGridColumns, ScrollGridDatasource -} from '@home/models/datasource/scroll-grid-datasource'; +} from '@shared/components/grid/scroll-grid-datasource'; import { BreakpointObserver } from '@angular/cdk/layout'; import { isObject } from '@app/core/utils'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; +import { ResizeObserver } from '@juggle/resize-observer'; + +export type ItemSizeFunction = (itemWidth: number) => number; + +export interface ItemSizeStrategy { + defaultItemSize: number; + itemSizeFunction: ItemSizeFunction; +} @Component({ selector: 'tb-scroll-grid', @@ -41,7 +49,7 @@ import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; styleUrls: ['./scroll-grid.component.scss'], encapsulation: ViewEncapsulation.None }) -export class ScrollGridComponent implements OnInit, AfterViewInit, OnChanges { +export class ScrollGridComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy { @ViewChild('viewport') viewport: CdkVirtualScrollViewport; @@ -56,7 +64,7 @@ export class ScrollGridComponent implements OnInit, AfterViewInit, OnChang filter: F; @Input() - itemSize = 200; + itemSize: number | ItemSizeStrategy = 200; @Input() gap = 12; @@ -75,17 +83,37 @@ export class ScrollGridComponent implements OnInit, AfterViewInit, OnChang dataSource: ScrollGridDatasource; + calculatedItemSize: number; + minBuffer: number; + maxBuffer: number; + + private contentResize$: ResizeObserver; + constructor(private breakpointObserver: BreakpointObserver, + private cd: ChangeDetectorRef, private renderer: Renderer2) { } ngOnInit(): void { + if (typeof this.itemSize === 'number') { + this.calculatedItemSize = this.itemSize; + } else { + this.calculatedItemSize = this.itemSize.defaultItemSize; + } + this.minBuffer = this.calculatedItemSize; + this.maxBuffer = this.calculatedItemSize * 2; this.dataSource = new ScrollGridDatasource(this.breakpointObserver, this.columns, this.fetchFunction, this.filter); } ngAfterViewInit() { this.renderer.setStyle(this.viewport._contentWrapper.nativeElement, 'gap', this.gap + 'px'); this.renderer.setStyle(this.viewport._contentWrapper.nativeElement, 'padding', this.gap + 'px'); + if (!(typeof this.itemSize === 'number')) { + this.contentResize$ = new ResizeObserver(() => { + this.onContentResize(); + }); + this.contentResize$.observe(this.viewport._contentWrapper.nativeElement); + } } ngOnChanges(changes: SimpleChanges): void { @@ -97,7 +125,43 @@ export class ScrollGridComponent implements OnInit, AfterViewInit, OnChang } } + ngOnDestroy() { + if (this.contentResize$) { + this.contentResize$.disconnect(); + } + } + isObject(value: any): boolean { return isObject(value); } + + trackByItemsRow(index: number, itemsRow: T[]): number { + return index; + } + + trackByItem(index: number, item: T): T { + return item; + } + + public update() { + this.dataSource.update(); + } + + public updateItem(index: number, item: T) { + this.dataSource.updateItem(index, item); + } + + public deleteItem(index: number) { + this.dataSource.deleteItem(index); + } + + private onContentResize() { + const contentWidth = this.viewport._contentWrapper.nativeElement.getBoundingClientRect().width; + const columns = this.dataSource.currentColumns; + const itemWidth = (contentWidth - this.gap * (columns + 1)) / columns; + this.calculatedItemSize = (this.itemSize as ItemSizeStrategy).itemSizeFunction(itemWidth); + this.minBuffer = this.calculatedItemSize; + this.maxBuffer = this.calculatedItemSize * 2; + this.cd.markForCheck(); + } } diff --git a/ui-ngx/src/app/shared/components/help-markdown.component.html b/ui-ngx/src/app/shared/components/help-markdown.component.html index cba58b60f4..55a8786d0e 100644 --- a/ui-ngx/src/app/shared/components/help-markdown.component.html +++ b/ui-ngx/src/app/shared/components/help-markdown.component.html @@ -1,6 +1,6 @@ + +

{{ 'image.embed-image' | translate }}

+ + +
+ + +
+
+
+ + + +
+ +
{{ 'image.embed-to-html' | translate }}
+
+
+
image.embed-to-html
+
+
+ +
+ +
+
+
+
+
image.embed-to-angular-template
+
+ +
+
diff --git a/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss b/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss new file mode 100644 index 0000000000..eeaff6e83c --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss @@ -0,0 +1,71 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .mat-mdc-dialog-content { + display: flex; + flex-direction: column; + gap: 16px; + .tb-embed-image-text { + font-size: 14px; + font-weight: 400; + line-height: 20px; + color: rgba(0,0,0,0.54); + letter-spacing: 0.2px; + } + .tb-form-panel-title { + color: rgba(0, 0, 0, 0.87); + } + } +} + +:host ::ng-deep { + .tb-markdown-view { + max-width: 700px; + .tb-embed-image-code { + .code-wrapper { + padding: 0; + pre[class*=language-] { + margin: 0; + padding: 9px 38px 9px 16px; + } + code[class*="language-"], pre[class*="language-"] { + font-size: 12px; + overflow: hidden; + white-space: normal; + word-break: break-word; + } + button.clipboard-btn { + right: 0; + height: 36px; + p, div { + background: transparent; + } + p { + margin: 0; + padding: 6px; + font-size: 14px; + } + div { + top: 0; + padding: 8px; + height: 38px; + width: 38px; + } + } + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.ts b/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.ts new file mode 100644 index 0000000000..f4d3e6d989 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.ts @@ -0,0 +1,94 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ImageResourceInfo } from '@shared/models/resource.models'; +import { Component, Inject, OnInit } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { ImageService } from '@core/http/image.service'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { FormControl, UntypedFormBuilder } from '@angular/forms'; + +export interface EmbedImageDialogData { + readonly: boolean; + image: ImageResourceInfo; +} + +@Component({ + selector: 'tb-embed-image-dialog', + templateUrl: './embed-image-dialog.component.html', + styleUrls: ['./embed-image-dialog.component.scss'] +}) +export class EmbedImageDialogComponent extends + DialogComponent implements OnInit { + + image = this.data.image; + + readonly = this.data.readonly; + + imageChanged = false; + + publicStatusControl = new FormControl(this.image.public); + + constructor(protected store: Store, + protected router: Router, + private imageService: ImageService, + @Inject(MAT_DIALOG_DATA) private data: EmbedImageDialogData, + public dialogRef: MatDialogRef, + public fb: UntypedFormBuilder) { + super(store, router, dialogRef); + } + + ngOnInit(): void { + if (!this.readonly) { + this.publicStatusControl.valueChanges.subscribe( + (isPublic) => { + this.updateImagePublicStatus(isPublic); + } + ); + } + } + + cancel(): void { + this.dialogRef.close(this.imageChanged ? this.image : null); + } + + embedToHtmlCode(): string { + return '```html\n' + + ''+this.image.title.replace(/' + + '{:copy-code}\n' + + '```'; + } + + embedToAngularTemplateCode(): string { + return '```html\n' + + '' + + '{:copy-code}\n' + + '```'; + } + + private updateImagePublicStatus(isPublic: boolean): void { + this.imageService.updateImagePublicStatus(this.image, isPublic).subscribe( + (image) => { + this.image = image; + this.imageChanged = true; + } + ); + } + +} diff --git a/ui-ngx/src/app/shared/components/image/gallery-image-input.component.html b/ui-ngx/src/app/shared/components/image/gallery-image-input.component.html new file mode 100644 index 0000000000..d97035c048 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/gallery-image-input.component.html @@ -0,0 +1,94 @@ + +
+ +
+
+ +
+
+
+
+
+ +
+ {{ imageResource.title }} +
+
+
{{ imageResource.descriptor.width }}x{{ imageResource.descriptor.height }}
+ +
{{ imageResource.descriptor.size | fileSize }}
+
+
+
+
+ + +
+ +
+
+ + +
+
+
+ + +
{{ (disabled ? 'image.no-image' : 'image.no-image-selected') | translate }}
+
+ + + diff --git a/ui-ngx/src/app/shared/components/image/gallery-image-input.component.scss b/ui-ngx/src/app/shared/components/image/gallery-image-input.component.scss new file mode 100644 index 0000000000..d1c9e953a7 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/gallery-image-input.component.scss @@ -0,0 +1,190 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../scss/constants"; + +$containerHeight: 96px !default; + +:host { + .tb-container { + margin-top: 0; + padding: 0 0 16px; + display: flex; + flex-direction: column; + gap: 8px; + label.tb-title { + display: block; + padding-bottom: 0; + } + } + + .tb-image-select-container { + width: 100%; + height: $containerHeight; + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.12); + display: flex; + align-items: center; + &.disabled { + width: $containerHeight; + .tb-image-container { + width: $containerHeight - 2px; + border-right: none; + } + } + } + + .tb-image-container { + width: $containerHeight - 1px; + height: $containerHeight - 2px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 4px; + border-right: 1px solid rgba(0, 0, 0, 0.12); + background: #fff; + overflow: hidden; + } + + .tb-image-preview { + width: auto; + max-width: $containerHeight - 2px; + height: auto; + max-height: $containerHeight - 2px; + } + + .tb-no-image { + text-align: center; + color: rgba(0, 0, 0, 0.38); + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; + letter-spacing: 0.4px; + } + + .tb-image-info-container { + display: flex; + flex: 1; + align-self: stretch; + padding: 0 8px; + justify-content: flex-end; + align-items: center; + gap: 4px; + .tb-base64-image-container, .tb-resource-image-container, .tb-external-image-container { + display: flex; + flex: 1; + align-self: stretch; + } + .tb-resource-image-container { + padding: 8px; + justify-content: center; + align-items: flex-start; + flex-direction: column; + gap: 4px; + .tb-resource-image-name { + color: rgba(0, 0, 0, 0.54); + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 2; + display: -webkit-box; + -webkit-box-orient: vertical; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 16px; + letter-spacing: 0.25px; + } + .tb-resource-image-details { + display: flex; + align-items: center; + gap: 8px; + color: rgba(0, 0, 0, 0.38); + font-size: 11px; + font-style: normal; + font-weight: 400; + line-height: 16px; + letter-spacing: 0.017px; + .mat-divider.mat-divider-vertical { + height: 16px; + } + } + &.loading { + align-items: center; + } + } + + .tb-external-image-container { + padding: 16px 8px 0 16px; + flex-direction: column; + align-items: flex-start; + gap: 4px; + .tb-external-link-label { + color: rgba(0, 0, 0, 0.54); + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; + letter-spacing: 0.4px; + } + .tb-external-link-input-container { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 4px; + align-self: stretch; + .tb-inline-field { + width: 100%; + } + } + } + + .tb-image-clear-btn { + color: rgba(0,0,0,0.38); + } + } + + .tb-image-select-buttons-container { + display: flex; + flex: 1; + align-self: stretch; + padding: 8px; + gap: 8px; + justify-content: center; + align-items: flex-start; + .tb-image-select-button { + width: 100%; + height: 100%; + align-self: stretch; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 4px; + padding: 8px; + line-height: normal; + font-size: 12px; + @media #{$mat-gt-xs} { + padding: 16px; + } + .mat-icon { + width: 24px; + height: 24px; + font-size: 24px; + margin: 0; + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/image/gallery-image-input.component.ts b/ui-ngx/src/app/shared/components/image/gallery-image-input.component.ts new file mode 100644 index 0000000000..9b81a0eaba --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/gallery-image-input.component.ts @@ -0,0 +1,205 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ChangeDetectorRef, Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, } from '@angular/forms'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { + extractParamsFromImageResourceUrl, + ImageResourceInfo, + isBase64DataImageUrl, + isImageResourceUrl, + prependTbImagePrefix, + removeTbImagePrefix +} from '@shared/models/resource.models'; +import { ImageService } from '@core/http/image.service'; +import { MatDialog } from '@angular/material/dialog'; +import { ImageGalleryDialogComponent } from '@shared/components/image/image-gallery-dialog.component'; + +export enum ImageLinkType { + none = 'none', + base64 = 'base64', + external = 'external', + resource = 'resource' +} + +@Component({ + selector: 'tb-gallery-image-input', + templateUrl: './gallery-image-input.component.html', + styleUrls: ['./gallery-image-input.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => GalleryImageInputComponent), + multi: true + } + ] +}) +export class GalleryImageInputComponent extends PageComponent implements OnInit, OnDestroy, ControlValueAccessor { + + @Input() + label: string; + + @Input() + @coerceBoolean() + required = false; + + @Input() + disabled: boolean; + + imageUrl: string; + + imageResource: ImageResourceInfo; + + loadingImageResource = false; + + ImageLinkType = ImageLinkType; + + linkType: ImageLinkType = ImageLinkType.none; + + externalLinkControl = new FormControl(null); + + private propagateChange = null; + + constructor(protected store: Store, + private imageService: ImageService, + private dialog: MatDialog, + private cd: ChangeDetectorRef) { + super(store); + } + + ngOnInit() { + this.externalLinkControl.valueChanges.subscribe((value) => { + if (this.linkType === ImageLinkType.external) { + this.updateModel(value); + } + }); + } + + ngOnDestroy() { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.detectLinkType(); + this.externalLinkControl.disable({emitEvent: false}); + } else { + this.externalLinkControl.enable({emitEvent: false}); + } + } + + writeValue(value: string): void { + value = removeTbImagePrefix(value); + if (this.imageUrl !== value) { + this.reset(); + this.imageUrl = value; + this.detectLinkType(); + if (this.linkType === ImageLinkType.resource) { + const params = extractParamsFromImageResourceUrl(this.imageUrl); + this.loadingImageResource = true; + this.imageService.getImageInfo(params.type, params.key, {ignoreLoading: true, ignoreErrors: true}).subscribe( + { + next: (res) => { + this.imageResource = res; + this.loadingImageResource = false; + this.cd.markForCheck(); + }, + error: () => { + this.reset(); + this.loadingImageResource = false; + this.cd.markForCheck(); + } + } + ); + } else if (this.linkType === ImageLinkType.base64) { + this.cd.markForCheck(); + } else if (this.linkType === ImageLinkType.external) { + this.externalLinkControl.setValue(this.imageUrl, {emitEvent: false}); + this.cd.markForCheck(); + } + } + } + + private detectLinkType() { + if (this.imageUrl) { + if (isImageResourceUrl(this.imageUrl)) { + this.linkType = ImageLinkType.resource; + } else if (isBase64DataImageUrl(this.imageUrl)) { + this.linkType = ImageLinkType.base64; + } else { + this.linkType = ImageLinkType.external; + } + } else { + this.linkType = ImageLinkType.none; + } + } + + private updateModel(value: string) { + this.cd.markForCheck(); + if (this.imageUrl !== value) { + this.imageUrl = value; + this.propagateChange(prependTbImagePrefix(this.imageUrl)); + } + } + + private reset() { + this.linkType = ImageLinkType.none; + this.imageResource = null; + this.externalLinkControl.setValue(null, {emitEvent: false}); + } + + clearImage() { + this.reset(); + this.updateModel(null); + } + + setLink($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.linkType = ImageLinkType.external; + } + + openGallery($event: Event): void { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(ImageGalleryDialogComponent, { + autoFocus: false, + disableClose: false, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'] + }).afterClosed().subscribe((image) => { + if (image) { + this.linkType = ImageLinkType.resource; + this.imageResource = image; + this.updateModel(image.link); + } + }); + } + +} diff --git a/ui-ngx/src/app/shared/components/image/image-dialog.component.html b/ui-ngx/src/app/shared/components/image/image-dialog.component.html new file mode 100644 index 0000000000..dc2043097a --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/image-dialog.component.html @@ -0,0 +1,107 @@ + + + +

{{ (readonly ? 'image.image-details' : 'image.edit-image') | translate }}

+ + +
+ + +
+
+
+ + image.name + + + {{ 'image.name-required' | translate }} + + + + +
+
+
+
+ + + +
+ +
+
+
+ +
+
+
{{ image.descriptor.width }}x{{ image.descriptor.height }}
+ +
{{ image.descriptor.size | fileSize }}
+
+
+
+
+
+ diff --git a/ui-ngx/src/app/shared/components/image/image-dialog.component.scss b/ui-ngx/src/app/shared/components/image/image-dialog.component.scss new file mode 100644 index 0000000000..86ec3b2486 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/image-dialog.component.scss @@ -0,0 +1,96 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../scss/constants'; + +:host { + .tb-image-dialog { + @media #{$mat-gt-xs} { + width: 50vh; + } + .mat-mdc-dialog-content { + max-height: 100%; + } + fieldset { + height: 100%; + display: flex; + flex-direction: column; + } + } + .tb-image-container { + height: 100%; + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.05); + padding: 12px; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 12px; + + .tb-image-content { + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + .tb-image-preview-container { + position: relative; + width: 100%; + height: 100%; + border: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 4px; + background: #fff; + display: flex; + align-items: center; + justify-content: center; + z-index: 1; + .tb-image-preview-spacer { + @media #{$mat-gt-xs} { + margin-top: 100%; + } + } + .tb-image-preview { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: contain; + } + } + .tb-image-preview-details { + display: flex; + align-items: center; + gap: 8px; + color: rgba(0, 0, 0, 0.38); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + .mat-divider.mat-divider-vertical { + height: 20px; + } + } + .tb-image-actions { + display: flex; + align-items: center; + align-self: stretch; + justify-content: space-between; + gap: 8px; + color: rgba(0,0,0,0.54); + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/image/image-dialog.component.ts b/ui-ngx/src/app/shared/components/image/image-dialog.component.ts new file mode 100644 index 0000000000..434d76d74d --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/image-dialog.component.ts @@ -0,0 +1,163 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { ImageService } from '@core/http/image.service'; +import { ImageResourceInfo, imageResourceType } from '@shared/models/resource.models'; +import { + UploadImageDialogComponent, + UploadImageDialogData +} from '@shared/components/image/upload-image-dialog.component'; +import { UrlHolder } from '@shared/pipe/image.pipe'; +import { ImportExportService } from '@shared/import-export/import-export.service'; +import { EmbedImageDialogComponent, EmbedImageDialogData } from '@shared/components/image/embed-image-dialog.component'; + +export interface ImageDialogData { + readonly: boolean; + image: ImageResourceInfo; +} + +@Component({ + selector: 'tb-image-dialog', + templateUrl: './image-dialog.component.html', + styleUrls: ['./image-dialog.component.scss'] +}) +export class ImageDialogComponent extends + DialogComponent implements OnInit { + + image: ImageResourceInfo; + + readonly: boolean; + + imageFormGroup: UntypedFormGroup; + + imageChanged = false; + + imagePreviewData: UrlHolder; + + constructor(protected store: Store, + protected router: Router, + private imageService: ImageService, + private dialog: MatDialog, + private importExportService: ImportExportService, + @Inject(MAT_DIALOG_DATA) private data: ImageDialogData, + public dialogRef: MatDialogRef, + public fb: UntypedFormBuilder) { + super(store, router, dialogRef); + this.image = data.image; + this.readonly = data.readonly; + this.imagePreviewData = { + url: this.image.public ? this.image.publicLink : this.image.link + }; + } + + ngOnInit(): void { + this.imageFormGroup = this.fb.group({ + title: [this.image.title, [Validators.required]] + }); + if (this.data.readonly) { + this.imageFormGroup.disable(); + } + } + + cancel(): void { + this.dialogRef.close(this.imageChanged ? this.image : null); + } + + revertInfo(): void { + this.imageFormGroup.get('title').setValue(this.image.title); + this.imageFormGroup.markAsPristine(); + } + + saveInfo(): void { + const title: string = this.imageFormGroup.get('title').value; + const image = {...this.image, ...{title}}; + this.imageService.updateImageInfo(image).subscribe( + (saved) => { + this.image = saved; + this.imageChanged = true; + this.imageFormGroup.markAsPristine(); + } + ); + } + + downloadImage($event) { + if ($event) { + $event.stopPropagation(); + } + this.imageService.downloadImage(imageResourceType(this.image), this.image.resourceKey).subscribe(); + } + + exportImage($event) { + if ($event) { + $event.stopPropagation(); + } + this.importExportService.exportImage(imageResourceType(this.image), this.image.resourceKey); + } + + embedImage($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(EmbedImageDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + image: this.image, + readonly: this.readonly + } + }).afterClosed().subscribe((result) => { + if (result) { + this.imageChanged = true; + this.image = result; + this.imagePreviewData = { + url: this.image.public ? this.image.publicLink : this.image.link + }; + } + }); + } + + updateImage($event): void { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(UploadImageDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + image: this.image + } + }).afterClosed().subscribe((result) => { + if (result) { + this.imageChanged = true; + this.image = result; + this.imagePreviewData = { + url: this.image.public ? this.image.publicLink : this.image.link + }; + } + }); + } + +} diff --git a/ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.html b/ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.html new file mode 100644 index 0000000000..8e24e2ffb2 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.html @@ -0,0 +1,32 @@ + + diff --git a/ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.scss b/ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.scss new file mode 100644 index 0000000000..6ebda09fb5 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.scss @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../scss/constants'; + +:host { + .tb-image-gallery-dialog { + position: relative; + padding: 24px 32px 16px 32px; + width: 100vw; + height: 100vh; + max-height: 100vh; + @media #{$mat-gt-xs} { + width: 80vw; + height: 80vh; + max-height: 80vh; + } + @media #{$mat-gt-sm} { + width: 700px; + } + @media #{$mat-gt-md} { + width: 900px; + } + @media #{$mat-gt-xl} { + width: 900px; + } + .tb-image-gallery-close { + position: absolute; + top: 12px; + right: 12px; + z-index: 1; + color: rgba(0, 0, 0, 0.38); + } + } +} diff --git a/ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.ts b/ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.ts new file mode 100644 index 0000000000..482fbe76a5 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.ts @@ -0,0 +1,62 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { ImageService } from '@core/http/image.service'; +import { ImageResourceInfo, imageResourceType } from '@shared/models/resource.models'; +import { + UploadImageDialogComponent, + UploadImageDialogData +} from '@shared/components/image/upload-image-dialog.component'; +import { UrlHolder } from '@shared/pipe/image.pipe'; +import { ImportExportService } from '@shared/import-export/import-export.service'; +import { EmbedImageDialogComponent, EmbedImageDialogData } from '@shared/components/image/embed-image-dialog.component'; + +@Component({ + selector: 'tb-image-gallery-dialog', + templateUrl: './image-gallery-dialog.component.html', + styleUrls: ['./image-gallery-dialog.component.scss'] +}) +export class ImageGalleryDialogComponent extends + DialogComponent implements OnInit { + + constructor(protected store: Store, + protected router: Router, + private imageService: ImageService, + private dialog: MatDialog, + public dialogRef: MatDialogRef) { + super(store, router, dialogRef); + } + + ngOnInit(): void { + } + + cancel(): void { + this.dialogRef.close(null); + } + + imageSelected(image: ImageResourceInfo): void { + this.dialogRef.close(image); + } + +} diff --git a/ui-ngx/src/app/shared/components/image/image-gallery.component.html b/ui-ngx/src/app/shared/components/image/image-gallery.component.html new file mode 100644 index 0000000000..21b10e8c66 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/image-gallery.component.html @@ -0,0 +1,397 @@ + +
+
+ +
+
+ image.gallery +
+
+ +
+
+ +
+
+ {{ 'image.include-system-images' | translate }} +
+
+ +
+ + + + +
+ +
+
+ {{ 'image.include-system-images' | translate }} +
+ +
+ + +   + + + +
+
+ +
+ + {{ translate.get('image.selected-images', {count: dataSource?.selection.selected.length}) | async }} + + + +
+
+
+
+ + + + + + + + + + + + + + +
+ {{ image.title }} +
+
+
+ + {{ 'image.name' | translate }} + + {{ image.title }} + + + + {{ 'image.created-time' | translate }} + + {{ image.createdTime | date:'yyyy-MM-dd HH:mm:ss' }} + + + + {{ 'image.resolution' | translate }} + + {{ image.descriptor.width }}x{{ image.descriptor.height }} + + + + {{ 'image.size' | translate }} + + {{ image.descriptor.size | fileSize }} + + + + {{ 'image.system' | translate }} + + {{isSystem(image) ? 'check_box' : 'check_box_outline_blank'}} + + + + + + +
+ + + + + +
+
+ + + + + + + + +
+
+
+ + + + + + + + +
+ + + + {{ 'common.loading' | translate }} +
+ + +
+
+ + +
+
+
+ +
+ + {{ 'common.loading' | translate }} + +
+
+ +
+
+
image.no-images
+
+
+ +
+
+
+ + + + +
+
+
+
+ + +
+
+ {{ item.title }} +
+
+
+
+ {{ item.title }} +
+
sys
+
+
+
{{ item.descriptor.width }}x{{ item.descriptor.height }}
+ +
{{ item.descriptor.size | fileSize }}
+
+
+
+
+ +
+
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/shared/components/image/image-gallery.component.scss b/ui-ngx/src/app/shared/components/image/image-gallery.component.scss new file mode 100644 index 0000000000..cd81199053 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/image-gallery.component.scss @@ -0,0 +1,274 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../scss/constants"; + +$tb-button-selected-color: rgb(255, 110, 64) !default; + +.tb-images { + + &.tb-dialog-mode { + position: relative; + width: 100%; + height: 100%; + .mat-toolbar.mat-mdc-table-toolbar { + padding: 0; + } + } + + .tb-images-content { + width: 100%; + height: 100%; + background: #fff; + overflow: hidden; + + &.tb-outlined-border { + box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%); + border: solid 1px #e0e0e0; + border-radius: 4px; + } + + .mat-mdc-table-toolbar { + &.multi-row { + &.mat-toolbar-single-row { + height: 112px; + } + .mat-mdc-slide-toggle { + display: flex; + min-height: 48px; + } + } + } + + .tb-images-title { + padding-right: 20px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .tb-images-view-type-toolbar { + height: 55px; + min-height: 55px; + padding-right: 16px; + background-color: transparent; + box-shadow: none; + + .tb-toolbar-button { + height: 48px; + button.mat-mdc-icon-button { + margin: 0; + } + &.tb-selected { + background-color: rgba(255, 255, 255, .15); + border-bottom: $tb-button-selected-color solid 4px; + + button.mat-mdc-icon-button { + margin-bottom: -4px; + + .mat-icon { + color: $tb-button-selected-color; + fill: $tb-button-selected-color; + } + } + } + } + } + + .table-container, tb-scroll-grid { + position: relative; + } + + .tb-no-images { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + + .tb-image-card { + position: relative; + height: 100%; + border-radius: 4px; + box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.24); + padding: 8px; + display: flex; + gap: 8px; + flex-direction: column; + cursor: pointer; + + .tb-image-card-overlay { + position: absolute; + pointer-events: none; + inset: 0; + border-radius: 4px; + z-index: 2; + display: flex; + flex-direction: column; + .tb-image-card-overlay-buttons { + width: 100%; + display: flex; + justify-content: flex-end; + align-items: center; + color: rgba(0,0,0,0.78); + opacity: 0; + transition: opacity 0.5s; + } + } + + &:hover { + .tb-image-card-overlay { + .tb-image-card-overlay-buttons { + opacity: 1; + } + } + .tb-image-preview-container { + .tb-image-preview-overlay { + background: rgba(245,245,245,0.6); + backdrop-filter: blur(4px); + .mdc-button { + opacity: 1; + } + } + } + } + + .tb-image-preview-container { + position: relative; + .tb-image-preview-overlay { + position: absolute; + inset: 0; + z-index: 1; + background: rgba(245,245,245,0); + backdrop-filter: none; + transition: all 0.5s; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + .mdc-button { + opacity: 0; + transition: opacity 0.5s; + } + } + .tb-image-preview-spacer { + margin-top: 100%; + } + .tb-image-preview { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + } + } + + .tb-image-details { + min-height: 52px; + flex: 1; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; + gap: 4px; + align-self: stretch; + .tb-image-title-container { + display: flex; + align-items: center; + gap: 8px; + align-self: stretch; + .tb-image-title { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 2; + display: -webkit-box; + -webkit-box-orient: vertical; + color: rgba(0, 0, 0, 0.76); + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 16px; + } + .tb-image-sys { + padding: 1px 4px; + border-radius: 4px; + background: rgba(236, 236, 236, 0.64); + color: rgba(0, 0, 0, 0.54); + font-size: 11px; + font-style: normal; + font-weight: 500; + line-height: 16px; + letter-spacing: 0.017px; + } + } + .tb-image-info-container { + display: flex; + align-items: center; + gap: 8px; + align-self: stretch; + color: rgba(0, 0, 0, 0.38); + font-size: 10px; + font-style: normal; + font-weight: 400; + line-height: 16px; + .mat-divider.mat-divider-vertical { + height: 16px; + } + } + } + &.loading-cell { + .tb-image-preview-container, .tb-image-details { + background: linear-gradient(110deg, #ececec 8%, #f5f5f5 18%, #ececec 33%); + border-radius: 5px; + background-size: 200% 100%; + animation: 1s shine linear infinite; + } + } + } + + .table-container { + overflow: auto; + + .mat-sort-header-sorted .mat-sort-header-arrow { + opacity: 1 !important; + } + .mat-mdc-cell.mat-column-preview { + width: 50px; + height: 50px; + padding: 2px 12px; + } + } + + .tb-image-preview-cell { + width: 50px; + height: 50px; + } + + .tb-image-preview { + width: 50px; + height: 50px; + object-fit: contain; + border-radius: 4px; + } + } +} + +@keyframes shine { + to { + background-position-x: -200%; + } +} diff --git a/ui-ngx/src/app/shared/components/image/image-gallery.component.ts b/ui-ngx/src/app/shared/components/image/image-gallery.component.ts new file mode 100644 index 0000000000..d0451cebcd --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/image-gallery.component.ts @@ -0,0 +1,702 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ImageResourceInfo, + ImageResourceInfoWithReferences, + imageResourceType, + toImageDeleteResult +} from '@shared/models/resource.models'; +import { forkJoin, merge, Observable, of, Subject, Subscription } from 'rxjs'; +import { ImageService } from '@core/http/image.service'; +import { TranslateService } from '@ngx-translate/core'; +import { PageLink, PageQueryParam } from '@shared/models/page/page-link'; +import { catchError, debounceTime, distinctUntilChanged, map, skip, takeUntil } from 'rxjs/operators'; +import { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, EventEmitter, HostBinding, + Input, + OnDestroy, + OnInit, Output, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort, SortDirection } from '@angular/material/sort'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DialogService } from '@core/services/dialog.service'; +import { FormBuilder } from '@angular/forms'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { ResizeObserver } from '@juggle/resize-observer'; +import { hidePageSizePixelValue } from '@shared/models/constants'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { ActivatedRoute, QueryParamsHandling, Router } from '@angular/router'; +import { isEqual, isNotEmptyStr, parseHttpErrorMessage } from '@core/utils'; +import { BaseData, HasId } from '@shared/models/base-data'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { Authority } from '@shared/models/authority.enum'; +import { GridEntitiesFetchFunction, ScrollGridColumns } from '@shared/components/grid/scroll-grid-datasource'; +import { ItemSizeStrategy, ScrollGridComponent } from '@shared/components/grid/scroll-grid.component'; +import { MatDialog } from '@angular/material/dialog'; +import { + UploadImageDialogComponent, + UploadImageDialogData +} from '@shared/components/image/upload-image-dialog.component'; +import { ImageDialogComponent, ImageDialogData } from '@shared/components/image/image-dialog.component'; +import { ImportExportService } from '@shared/import-export/import-export.service'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { + ImagesInUseDialogComponent, + ImagesInUseDialogData +} from '@shared/components/image/images-in-use-dialog.component'; +import { ImagesDatasource } from '@shared/components/image/images-datasource'; +import { EmbedImageDialogComponent, EmbedImageDialogData } from '@shared/components/image/embed-image-dialog.component'; + +interface GridImagesFilter { + search: string; + includeSystemImages: boolean; +} + +const pageGridColumns: ScrollGridColumns = { + columns: 2, + breakpoints: { + 'screen and (min-width: 2320px)': 10, + 'screen and (min-width: 2000px)': 8, + 'gt-lg': 7, + 'screen and (min-width: 1600px)': 6, + 'gt-md': 5, + 'screen and (min-width: 1120px)': 4, + 'gt-xs': 3 + } +}; + +const dialogGridColumns: ScrollGridColumns = { + columns: 2, + breakpoints: { + 'gt-md': 4, + 'gt-xs': 3 + } +}; + +@Component({ + selector: 'tb-image-gallery', + templateUrl: './image-gallery.component.html', + styleUrls: ['./image-gallery.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class ImageGalleryComponent extends PageComponent implements OnInit, OnDestroy, AfterViewInit { + + @HostBinding('style.display') + private display = 'block'; + + @HostBinding('style.width') + private width = '100%'; + + @HostBinding('style.height') + private height = '100%'; + + @Input() + @coerceBoolean() + pageMode = true; + + @Input() + @coerceBoolean() + dialogMode = false; + + @Input() + mode: 'list' | 'grid' = 'list'; + + @Input() + @coerceBoolean() + selectionMode = false; + + @Output() + imageSelected = new EventEmitter(); + + @ViewChild('searchInput') searchInputField: ElementRef; + + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; + + @ViewChild(ScrollGridComponent) gridComponent: ScrollGridComponent; + + defaultPageSize = 10; + defaultSortOrder: SortOrder = { property: 'createdTime', direction: Direction.DESC }; + hidePageSize = false; + + displayedColumns: string[]; + pageSizeOptions: number[]; + pageLink: PageLink; + + textSearchMode = false; + + dataSource: ImagesDatasource; + + textSearch = this.fb.control('', {nonNullable: true}); + includeSystemImages = false; + + gridColumns: ScrollGridColumns; + + gridImagesFetchFunction: GridEntitiesFetchFunction; + gridImagesFilter: GridImagesFilter = { + search: '', + includeSystemImages: false + }; + + gridImagesItemSizeStrategy: ItemSizeStrategy = { + defaultItemSize: 200, + itemSizeFunction: itemWidth => itemWidth + 72 + }; + + authUser = getCurrentAuthUser(this.store); + + private updateDataSubscription: Subscription; + + private widgetResize$: ResizeObserver; + private destroy$ = new Subject(); + private destroyListMode$: Subject; + + constructor(protected store: Store, + private route: ActivatedRoute, + private router: Router, + private dialog: MatDialog, + public translate: TranslateService, + private imageService: ImageService, + private dialogService: DialogService, + private importExportService: ImportExportService, + private elementRef: ElementRef, + private cd: ChangeDetectorRef, + private fb: FormBuilder) { + super(store); + + this.gridImagesFetchFunction = (pageSize, page, filter) => { + const pageLink = new PageLink(pageSize, page, filter.search, { + property: 'createdTime', + direction: Direction.DESC + }); + return this.imageService.getImages(pageLink, filter.includeSystemImages); + }; + } + + ngOnInit(): void { + this.gridColumns = this.dialogMode ? dialogGridColumns : pageGridColumns; + this.displayedColumns = this.computeDisplayedColumns(); + let sortOrder: SortOrder = this.defaultSortOrder; + this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; + const routerQueryParams: PageQueryParam = this.route.snapshot.queryParams; + if (this.pageMode) { + if (routerQueryParams.hasOwnProperty('direction') + || routerQueryParams.hasOwnProperty('property')) { + sortOrder = { + property: routerQueryParams?.property || this.defaultSortOrder.property, + direction: routerQueryParams?.direction || this.defaultSortOrder.direction + }; + } + } + this.pageLink = new PageLink(this.defaultPageSize, 0, null, sortOrder); + if (this.pageMode) { + if (routerQueryParams.hasOwnProperty('page')) { + this.pageLink.page = Number(routerQueryParams.page); + } + if (routerQueryParams.hasOwnProperty('pageSize')) { + this.pageLink.pageSize = Number(routerQueryParams.pageSize); + } + const textSearchParam = routerQueryParams.textSearch; + if (isNotEmptyStr(textSearchParam)) { + const decodedTextSearch = decodeURI(textSearchParam); + this.textSearchMode = true; + this.pageLink.textSearch = decodedTextSearch.trim(); + this.textSearch.setValue(decodedTextSearch, {emitEvent: false}); + } + } + if (this.mode === 'list') { + this.dataSource = new ImagesDatasource(this.imageService, null, + entity => this.deleteEnabled(entity)); + } + } + + ngOnDestroy(): void { + if (this.widgetResize$) { + this.widgetResize$.disconnect(); + } + if (this.destroyListMode$) { + this.destroyListMode$.next(); + this.destroyListMode$.complete(); + } + this.destroy$.next(); + this.destroy$.complete(); + } + + ngAfterViewInit() { + this.textSearch.valueChanges.pipe( + debounceTime(150), + distinctUntilChanged((prev, current) => + ((this.mode === 'list' ? this.pageLink.textSearch : this.gridImagesFilter.search) ?? '') === current.trim()), + takeUntil(this.destroy$) + ).subscribe(value => { + if (this.mode === 'list') { + if (this.pageMode) { + const queryParams: PageQueryParam = { + textSearch: isNotEmptyStr(value) ? encodeURI(value) : null, + page: null + }; + this.updatedRouterParamsAndData(queryParams); + } else { + this.pageLink.textSearch = isNotEmptyStr(value) ? value.trim() : null; + this.paginator.pageIndex = 0; + this.updateData(); + } + } else { + this.gridImagesFilter = { + search: isNotEmptyStr(value) ? value.trim() : null, + includeSystemImages: this.includeSystemImages + }; + this.cd.markForCheck(); + } + }); + this.updateMode(); + } + + public includeSystemImagesChanged(value: boolean) { + this.includeSystemImages = value; + this.displayedColumns = this.computeDisplayedColumns(); + this.gridImagesFilter = { + search: this.gridImagesFilter.search, + includeSystemImages: this.includeSystemImages + }; + if (this.mode === 'list') { + this.paginator.pageIndex = 0; + this.updateData(); + } else { + this.cd.markForCheck(); + } + } + + public setMode(targetMode: 'list' | 'grid') { + if (this.mode !== targetMode) { + if (this.widgetResize$) { + this.widgetResize$.disconnect(); + this.widgetResize$ = null; + } + if (this.destroyListMode$) { + this.destroyListMode$.next(); + this.destroyListMode$.complete(); + this.destroyListMode$ = null; + } + this.mode = targetMode; + if (this.mode === 'list') { + this.dataSource = new ImagesDatasource(this.imageService, null, + entity => this.deleteEnabled(entity)); + } + setTimeout(() => { + this.updateMode(); + }); + } + } + + public get isSysAdmin(): boolean { + return this.authUser.authority === Authority.SYS_ADMIN; + } + + private computeDisplayedColumns(): string[] { + let columns: string[]; + if (this.selectionMode) { + columns = ['preview', 'title']; + if (!this.isSysAdmin && this.includeSystemImages) { + columns.push('system'); + } + columns.push('imageSelect'); + } else { + columns = ['select', 'preview', 'title', 'createdTime', 'resolution', 'size']; + if (!this.isSysAdmin && this.includeSystemImages) { + columns.push('system'); + } + columns.push('actions'); + } + return columns; + } + + private updateMode() { + if (this.mode === 'list') { + this.initListMode(); + } else { + this.initGridMode(); + } + } + + private initListMode() { + this.destroyListMode$ = new Subject(); + this.widgetResize$ = new ResizeObserver(() => { + const showHidePageSize = this.elementRef.nativeElement.offsetWidth < hidePageSizePixelValue; + if (showHidePageSize !== this.hidePageSize) { + this.hidePageSize = showHidePageSize; + this.cd.markForCheck(); + } + }); + this.widgetResize$.observe(this.elementRef.nativeElement); + if (this.pageMode) { + this.route.queryParams.pipe( + skip(1), + takeUntil(this.destroyListMode$) + ).subscribe((params: PageQueryParam) => { + this.paginator.pageIndex = Number(params.page) || 0; + this.paginator.pageSize = Number(params.pageSize) || this.defaultPageSize; + this.sort.active = params.property || this.defaultSortOrder.property; + this.sort.direction = (params.direction || this.defaultSortOrder.direction).toLowerCase() as SortDirection; + const textSearchParam = params.textSearch; + if (isNotEmptyStr(textSearchParam)) { + const decodedTextSearch = decodeURI(textSearchParam); + this.textSearchMode = true; + this.pageLink.textSearch = decodedTextSearch.trim(); + this.textSearch.setValue(decodedTextSearch, {emitEvent: false}); + } else { + this.pageLink.textSearch = null; + this.textSearch.reset('', {emitEvent: false}); + } + this.updateData(); + }); + } + this.updatePaginationSubscriptions(); + this.updateData(); + } + + private initGridMode() { + + } + + private updatePaginationSubscriptions() { + if (this.updateDataSubscription) { + this.updateDataSubscription.unsubscribe(); + this.updateDataSubscription = null; + } + const sortSubscription$: Observable = this.sort.sortChange.asObservable().pipe( + map((data) => { + const direction = data.direction.toUpperCase(); + const queryParams: PageQueryParam = { + direction: (this.defaultSortOrder.direction === direction ? null : direction) as Direction, + property: this.defaultSortOrder.property === data.active ? null : data.active + }; + queryParams.page = null; + this.paginator.pageIndex = 0; + return queryParams; + }) + ); + const paginatorSubscription$ = this.paginator.page.asObservable().pipe( + map((data) => ({ + page: data.pageIndex === 0 ? null : data.pageIndex, + pageSize: data.pageSize === this.defaultPageSize ? null : data.pageSize + })) + ); + this.updateDataSubscription = (merge(sortSubscription$, paginatorSubscription$) as Observable).pipe( + takeUntil(this.destroyListMode$) + ).subscribe(queryParams => this.updatedRouterParamsAndData(queryParams)); + } + + clearSelection() { + this.dataSource.selection.clear(); + this.cd.detectChanges(); + } + + updateData() { + if (this.mode === 'list') { + this.pageLink.page = this.paginator.pageIndex; + this.pageLink.pageSize = this.paginator.pageSize; + if (this.sort.active) { + this.pageLink.sortOrder = { + property: this.sort.active, + direction: Direction[this.sort.direction.toUpperCase()] + }; + } else { + this.pageLink.sortOrder = null; + } + this.dataSource.loadEntities(this.pageLink, this.includeSystemImages); + } else { + this.gridComponent.update(); + } + } + + private imageUpdated(image: ImageResourceInfo, index = -1) { + if (this.mode === 'list') { + this.updateData(); + } else { + this.gridComponent.updateItem(index, image); + } + } + + private imageDeleted(index = -1) { + if (this.mode === 'list') { + this.updateData(); + } else { + this.gridComponent.deleteItem(index); + } + } + + enterFilterMode() { + this.textSearchMode = true; + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + + exitFilterMode() { + this.textSearchMode = false; + this.textSearch.reset(); + } + + trackByEntity(index: number, entity: BaseData) { + return entity; + } + + isSystem(image?: ImageResourceInfo): boolean { + return !this.isSysAdmin && image?.tenantId?.id === NULL_UUID; + } + + readonly(image?: ImageResourceInfo): boolean { + return this.authUser.authority !== Authority.SYS_ADMIN && this.isSystem(image); + } + + deleteEnabled(image?: ImageResourceInfo): boolean { + return this.authUser.authority === Authority.SYS_ADMIN || !this.isSystem(image); + } + + deleteImage($event: Event, image: ImageResourceInfo, itemIndex = -1) { + if ($event) { + $event.stopPropagation(); + } + const title = this.translate.instant('image.delete-image-title', {imageTitle: image.title}); + const content = this.translate.instant('image.delete-image-text'); + this.dialogService.confirm(title, content, + this.translate.instant('action.no'), + this.translate.instant('action.yes')).subscribe((result) => { + if (result) { + this.imageService.deleteImage(imageResourceType(image), image.resourceKey, false, {ignoreErrors: true}).pipe( + map(() => toImageDeleteResult(image)), + catchError((err) => of(toImageDeleteResult(image, err))) + ).subscribe( + (deleteResult) => { + if (deleteResult.success) { + this.imageDeleted(itemIndex); + } else if (deleteResult.imageIsReferencedError) { + this.dialog.open(ImagesInUseDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + multiple: false, + images: [{...image, ...{references: deleteResult.references}}] + } + }).afterClosed().subscribe((images) => { + if (images) { + this.imageService.deleteImage(imageResourceType(image), image.resourceKey, true).subscribe( + () => { + this.imageDeleted(itemIndex); + } + ); + } + }); + } else { + const errorMessageWithTimeout = parseHttpErrorMessage(deleteResult.error, this.translate); + setTimeout(() => { + this.store.dispatch(new ActionNotificationShow({message: errorMessageWithTimeout.message, type: 'error'})); + }, errorMessageWithTimeout.timeout); + } + }); + } + }); + } + + deleteImages($event: Event) { + if ($event) { + $event.stopPropagation(); + } + const selectedImages = this.dataSource.selection.selected; + if (selectedImages && selectedImages.length) { + const title = this.translate.instant('image.delete-images-title', {count: selectedImages.length}); + const content = this.translate.instant('image.delete-images-text'); + this.dialogService.confirm(title, content, + this.translate.instant('action.no'), + this.translate.instant('action.yes')).subscribe((result) => { + if (result) { + const tasks = selectedImages.map((image) => + this.imageService.deleteImage(imageResourceType(image), image.resourceKey, false, {ignoreErrors: true}).pipe( + map(() => toImageDeleteResult(image)), + catchError((err) => of(toImageDeleteResult(image, err))) + ) + ); + forkJoin(tasks).subscribe( + (deleteResults) => { + const anySuccess = deleteResults.some(res => res.success); + const referenceErrors = deleteResults.filter(res => res.imageIsReferencedError); + const otherError = deleteResults.find(res => !res.success); + if (anySuccess) { + this.updateData(); + } + if (referenceErrors?.length) { + const imagesWithReferences: ImageResourceInfoWithReferences[] = + referenceErrors.map(ref => ({...ref.image, ...{references: ref.references}})); + this.dialog.open(ImagesInUseDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + multiple: true, + images: imagesWithReferences + } + }).afterClosed().subscribe((forceDeleteImages) => { + if (forceDeleteImages && forceDeleteImages.length) { + const forceDeleteTasks = forceDeleteImages.map((image) => + this.imageService.deleteImage(imageResourceType(image), image.resourceKey, true) + ); + forkJoin(forceDeleteTasks).subscribe( + () => { + this.updateData(); + } + ); + } + }); + } else if (otherError) { + const errorMessageWithTimeout = parseHttpErrorMessage(otherError.error, this.translate); + setTimeout(() => { + this.store.dispatch(new ActionNotificationShow({message: errorMessageWithTimeout.message, type: 'error'})); + }, errorMessageWithTimeout.timeout); + } + } + ); + } + }); + } + } + + downloadImage($event, image: ImageResourceInfo) { + if ($event) { + $event.stopPropagation(); + } + this.imageService.downloadImage(imageResourceType(image), image.resourceKey).subscribe(); + } + + exportImage($event, image: ImageResourceInfo) { + if ($event) { + $event.stopPropagation(); + } + this.importExportService.exportImage(imageResourceType(image), image.resourceKey); + } + + importImage(): void { + this.importExportService.importImage().subscribe((image) => { + if (image) { + if (this.selectionMode) { + this.imageSelected.next(image); + } else { + this.updateData(); + } + } + }); + } + + selectImage($event, image: ImageResourceInfo) { + if ($event) { + $event.stopPropagation(); + } + this.imageSelected.next(image); + } + + rowClick($event, image: ImageResourceInfo) { + if (this.selectionMode) { + this.selectImage($event, image); + } else { + if (this.deleteEnabled(image)) { + this.dataSource.selection.toggle(image); + } + } + } + + uploadImage(): void { + this.dialog.open(UploadImageDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: {} + }).afterClosed().subscribe((result) => { + if (result) { + if (this.selectionMode) { + this.imageSelected.next(result); + } else { + this.updateData(); + } + } + }); + } + + editImage($event: Event, image: ImageResourceInfo, itemIndex = -1) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(ImageDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + image, + readonly: this.readonly(image) + } + }).afterClosed().subscribe((result) => { + if (result) { + this.imageUpdated(result, itemIndex); + } + }); + } + + embedImage($event: Event, image: ImageResourceInfo, itemIndex = -1) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(EmbedImageDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + image, + readonly: this.readonly(image) + } + }).afterClosed().subscribe((result) => { + if (result) { + this.imageUpdated(result, itemIndex); + } + }); + } + + protected updatedRouterParamsAndData(queryParams: object, queryParamsHandling: QueryParamsHandling = 'merge') { + if (this.pageMode) { + this.router.navigate([], { + relativeTo: this.route, + queryParams, + queryParamsHandling + }); + if (queryParamsHandling === '' && isEqual(this.route.snapshot.queryParams, queryParams)) { + this.updateData(); + } + } else { + this.updateData(); + } + } + +} diff --git a/ui-ngx/src/app/shared/components/image/image-references.component.html b/ui-ngx/src/app/shared/components/image/image-references.component.html new file mode 100644 index 0000000000..a9b6943bea --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/image-references.component.html @@ -0,0 +1,54 @@ + + + + + + +
    + +
  • +
    {{ 'image.system-entities' | translate }}
    + +
  • + +
  • +
    + {{ 'tenant.tenant' | translate }} {{ entry[1].tenantName }} {{ 'image.entities' | translate }} +
    + +
  • +
    +
    +
+
+
+ + + + + + +
{{ referencedEntity.typeName }} + {{ referencedEntity.entity.name }} + {{ referencedEntity.entity.name }} +
+
+ + + diff --git a/ui-ngx/src/app/shared/components/image/image-references.component.scss b/ui-ngx/src/app/shared/components/image/image-references.component.scss new file mode 100644 index 0000000000..bef9cc61ea --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/image-references.component.scss @@ -0,0 +1,92 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../scss/constants"; + +:host { + ul.tb-references { + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 12px; + } + li.tb-entities-container { + padding: 8px 12px 12px 12px; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + border-radius: 8px; + } + a.tb-reference { + color: $tb-primary-color; + font-weight: inherit; + } + .tb-entities-title { + display: flex; + gap: 8px; + color: rgba(0, 0, 0, 0.87); + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + } + table.tb-entities-list-table { + position: relative; + overflow: hidden; + padding: 8px 10px 8px 4px; + border-radius: 4px; + background: #fff; + align-self: stretch; + z-index: 1; + color: rgba(0, 0, 0, 0.76); + &:before { + display: block; + height: auto; + content: ""; + position: absolute; + inset: 0; + border-radius: 4px; + border: 1px solid $tb-primary-color; + background: transparent; + opacity: 0.4; + pointer-events: none; + } + td.tb-entity-type { + white-space: nowrap; + padding-right: 20px; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 20px; + letter-spacing: 0.25px; + &:before { + content: "•"; + padding-left: 8px; + padding-right: 8px; + } + } + td.tb-entity-name { + width: 100%; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + letter-spacing: 0.2px; + } + } +} diff --git a/ui-ngx/src/app/shared/components/image/image-references.component.ts b/ui-ngx/src/app/shared/components/image/image-references.component.ts new file mode 100644 index 0000000000..e7ae17d50d --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/image-references.component.ts @@ -0,0 +1,171 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ImageReferences } from '@shared/models/resource.models'; +import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { TranslateService } from '@ngx-translate/core'; +import { getEntityDetailsPageURL } from '@core/utils'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { Authority } from '@shared/models/authority.enum'; +import { Observable } from 'rxjs'; +import { EntityService } from '@core/http/entity.service'; +import { BaseData, HasId } from '@shared/models/base-data'; +import { HasTenantId } from '@shared/models/entity.models'; +import { map } from 'rxjs/operators'; +import { TbPopoverComponent } from '@shared/components/popover.component'; + +interface ReferencedEntityInfo { + entity: BaseData & HasTenantId; + typeName: string; + detailsUrl: string; +} + +interface TenantReferencedEntities { + tenantName?: string; + tenantDetailsUrl?: string; + entities: ReferencedEntityInfo[]; +} + +type ReferencedEntities = {[tenantId: string]: TenantReferencedEntities}; +type ReferencedEntitiesEntry = [string, TenantReferencedEntities]; + +@Component({ + selector: 'tb-image-references', + templateUrl: './image-references.component.html', + styleUrls: ['./image-references.component.scss'] +}) +export class ImageReferencesComponent implements OnInit { + + @Input() + references: ImageReferences; + + popoverComponent: TbPopoverComponent; + + contentReady = false; + + authUser = getCurrentAuthUser(this.store); + + simpleList = true; + + referencedEntitiesList: ReferencedEntityInfo[]; + + referencedEntitiesEntries: ReferencedEntitiesEntry[]; + + constructor(protected store: Store, + private entityService: EntityService, + private cd: ChangeDetectorRef, + private translate: TranslateService) { + } + + ngOnInit(): void { + if (this.authUser.authority === Authority.SYS_ADMIN && this.hasNonSystemEntities(this.references)) { + this.simpleList = false; + this.toReferencedEntitiesEntries(this.references).subscribe( + (entries) => { + this.referencedEntitiesEntries = entries; + this.contentReady = true; + this.cd.detectChanges(); + if (this.popoverComponent) { + Promise.resolve().then(() => { + this.popoverComponent.updatePosition(); + }); + } + } + ); + } else { + this.referencedEntitiesList = this.toReferencedEntitiesList(this.references); + this.contentReady = true; + } + } + + isSystem(tenantId: string): boolean { + return tenantId === NULL_UUID; + } + + private hasNonSystemEntities(references: ImageReferences): boolean { + for (const entityTypeStr of Object.keys(references)) { + const entities = this.references[entityTypeStr]; + if (entities.some(e => e.tenantId && e.tenantId.id && e.tenantId.id !== NULL_UUID)) { + return true; + } + } + return false; + } + + private toReferencedEntitiesList(references: ImageReferences): ReferencedEntityInfo[] { + const result: ReferencedEntityInfo[] = []; + for (const entityTypeStr of Object.keys(references)) { + const entityType = entityTypeStr as EntityType; + const entityTypeName = this.translate.instant(entityTypeTranslations.get(entityType).type); + const entities = references[entityTypeStr]; + for (const entity of entities) { + const detailsUrl = getEntityDetailsPageURL(entity.id.id, entityType); + result.push({ + entity, + typeName: entityTypeName, + detailsUrl + }); + } + } + return result; + } + + private toReferencedEntitiesEntries(references: ImageReferences): Observable { + let referencedEntities: ReferencedEntities = {}; + const referencedEntitiesList = this.toReferencedEntitiesList(references); + for (const referencedEntityInfo of referencedEntitiesList) { + const tenantId = referencedEntityInfo.entity.tenantId?.id || NULL_UUID; + let tenantEntitiesInfo = referencedEntities[tenantId]; + if (!tenantEntitiesInfo) { + tenantEntitiesInfo = { + entities: [] + }; + referencedEntities[tenantId] = tenantEntitiesInfo; + } + tenantEntitiesInfo.entities.push(referencedEntityInfo); + } + referencedEntities = Object.keys(referencedEntities).sort((tenantId1, tenantId2) => { + if (tenantId1 === NULL_UUID) { + return -1; + } else if (tenantId2 === NULL_UUID) { + return 1; + } + return 0; + }).reduce( + (obj, key) => { + obj[key] = referencedEntities[key]; + return obj; + }, + {} + ); + const tenantIds = Object.keys(referencedEntities).filter(id => id !== NULL_UUID); + return this.entityService.getEntities(EntityType.TENANT, tenantIds).pipe( + map((tenants) => { + for (const tenant of tenants) { + const tenantEntitiesInfo = referencedEntities[tenant.id.id]; + tenantEntitiesInfo.tenantName = tenant.name; + tenantEntitiesInfo.tenantDetailsUrl = getEntityDetailsPageURL(tenant.id.id, EntityType.TENANT); + } + return Object.entries(referencedEntities); + }) + ); + } + +} diff --git a/ui-ngx/src/app/shared/components/image/images-datasource.ts b/ui-ngx/src/app/shared/components/image/images-datasource.ts new file mode 100644 index 0000000000..fd92cae367 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/images-datasource.ts @@ -0,0 +1,130 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { CollectionViewer, DataSource, SelectionModel } from '@angular/cdk/collections'; +import { ImageResourceInfo } from '@shared/models/resource.models'; +import { BehaviorSubject, Observable, of, ReplaySubject, Subject } from 'rxjs'; +import { emptyPageData, PageData } from '@shared/models/page/page-data'; +import { ImageService } from '@core/http/image.service'; +import { EntityBooleanFunction } from '@home/models/entity/entities-table-config.models'; +import { PageLink } from '@shared/models/page/page-link'; +import { catchError, map, take, tap } from 'rxjs/operators'; + +export class ImagesDatasource implements DataSource { + private entitiesSubject: Subject; + private readonly pageDataSubject: Subject>; + + public pageData$: Observable>; + + public selection = new SelectionModel(true, []); + + public dataLoading = true; + + constructor(private imageService: ImageService, + private images: ImageResourceInfo[], + private selectionEnabledFunction: EntityBooleanFunction) { + if (this.images && this.images.length) { + this.entitiesSubject = new BehaviorSubject(this.images); + } else { + this.entitiesSubject = new BehaviorSubject([]); + this.pageDataSubject = new BehaviorSubject>(emptyPageData()); + this.pageData$ = this.pageDataSubject.asObservable(); + } + } + + connect(collectionViewer: CollectionViewer): + Observable> { + return this.entitiesSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.entitiesSubject.complete(); + if (this.pageDataSubject) { + this.pageDataSubject.complete(); + } + } + + reset() { + this.entitiesSubject.next([]); + if (this.pageDataSubject) { + this.pageDataSubject.next(emptyPageData()); + } + } + + loadEntities(pageLink: PageLink, includeSystemImages = false): Observable> { + this.dataLoading = true; + const result = new ReplaySubject>(); + this.fetchEntities(pageLink, includeSystemImages).pipe( + tap(() => { + this.selection.clear(); + }), + catchError(() => of(emptyPageData())), + ).subscribe( + (pageData) => { + this.entitiesSubject.next(pageData.data); + this.pageDataSubject.next(pageData); + result.next(pageData); + this.dataLoading = false; + } + ); + return result; + } + + fetchEntities(pageLink: PageLink, includeSystemImages = false): Observable> { + return this.imageService.getImages(pageLink, includeSystemImages); + } + + isAllSelected(): Observable { + const numSelected = this.selection.selected.length; + return this.entitiesSubject.pipe( + map((entities) => numSelected === entities.length) + ); + } + + isEmpty(): Observable { + return this.entitiesSubject.pipe( + map((entities) => !entities.length) + ); + } + + total(): Observable { + return this.pageDataSubject.pipe( + map((pageData) => pageData.totalElements) + ); + } + + masterToggle() { + this.entitiesSubject.pipe( + tap((entities) => { + const numSelected = this.selection.selected.length; + if (numSelected === this.selectableEntitiesCount(entities)) { + this.selection.clear(); + } else { + entities.forEach(row => { + if (this.selectionEnabledFunction(row)) { + this.selection.select(row); + } + }); + } + }), + take(1) + ).subscribe(); + } + + private selectableEntitiesCount(entities: Array): number { + return entities.filter((entity) => this.selectionEnabledFunction(entity)).length; + } +} diff --git a/ui-ngx/src/app/shared/components/image/images-in-use-dialog.component.html b/ui-ngx/src/app/shared/components/image/images-in-use-dialog.component.html new file mode 100644 index 0000000000..6bbc62ade2 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/images-in-use-dialog.component.html @@ -0,0 +1,82 @@ + +

{{title}}

+
+
+ +
+ + + + + + + + + + + + + + + {{ image.title }} + + + + + + {{ 'image.name' | translate }} + + + {{ translate.get('image.selected-images', {count: dataSource.selection.selected.length}) | async }} + + + + {{ image.title }} + + + + + + + + + + +
+
+
+
+
+ + +
+ + +
+
diff --git a/ui-ngx/src/app/shared/components/image/images-in-use-dialog.component.scss b/ui-ngx/src/app/shared/components/image/images-in-use-dialog.component.scss new file mode 100644 index 0000000000..682f3a1d0c --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/images-in-use-dialog.component.scss @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .tb-images-in-use-content { + display: flex; + flex-direction: column; + gap: 24px; + &.multiple { + gap: 16px; + } + .table-container { + overflow: auto; + .mat-mdc-cell.mat-column-preview { + width: 50px; + height: 50px; + padding: 2px 12px; + display: block; + } + } + .tb-image-preview { + width: 50px; + height: 50px; + object-fit: contain; + border-radius: 4px; + } + } +} diff --git a/ui-ngx/src/app/shared/components/image/images-in-use-dialog.component.ts b/ui-ngx/src/app/shared/components/image/images-in-use-dialog.component.ts new file mode 100644 index 0000000000..20b551c872 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/images-in-use-dialog.component.ts @@ -0,0 +1,114 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { ImageReferences, ImageResourceInfo, ImageResourceInfoWithReferences } from '@shared/models/resource.models'; +import { ImagesDatasource } from '@shared/components/image/images-datasource'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { ImageReferencesComponent } from '@shared/components/image/image-references.component'; +import { TranslateService } from '@ngx-translate/core'; + +export interface ImagesInUseDialogData { + multiple: boolean; + images: ImageResourceInfoWithReferences[]; +} + +@Component({ + selector: 'tb-images-in-use-dialog', + templateUrl: './images-in-use-dialog.component.html', + styleUrls: ['./images-in-use-dialog.component.scss'] +}) +export class ImagesInUseDialogComponent extends + DialogComponent implements OnInit { + + title: string; + message: string; + + references: ImageReferences; + + dataSource: ImagesDatasource; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: ImagesInUseDialogData, + public dialogRef: MatDialogRef, + public translate: TranslateService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef, + private popoverService: TbPopoverService) { + super(store, router, dialogRef); + } + + ngOnInit(): void { + if (this.data.multiple) { + this.title = this.translate.instant('image.images-are-in-use'); + this.message = this.translate.instant('image.images-are-in-use-text'); + this.dataSource = new ImagesDatasource(null, this.data.images, entity => true); + } else { + this.title = this.translate.instant('image.image-is-in-use'); + this.message = this.translate.instant('image.image-is-in-use-text', {title: this.data.images[0].title}); + this.references = this.data.images[0].references; + } + } + + cancel() { + this.dialogRef.close(null); + } + + delete() { + if (this.data.multiple) { + this.dialogRef.close(this.dataSource.selection.selected); + } else { + this.dialogRef.close(this.data.images); + } + } + + toggleShowReferences($event: Event, image: ImageResourceInfoWithReferences, referencesButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = referencesButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const referencesPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, ImageReferencesComponent, 'top', true, null, + { + references: image.references + }, {}, {}, {}, + false, + visible => { + const addClasses = + visible ? 'mdc-button--unelevated mat-mdc-unelevated-button' : 'mdc-button--outlined mat-mdc-outlined-button'; + const removeClasses = + visible ? 'mdc-button--outlined mat-mdc-outlined-button' : 'mdc-button--unelevated mat-mdc-unelevated-button'; + addClasses.split(' ').forEach(clazz => { + this.renderer.addClass(trigger, clazz); + }); + removeClasses.split(' ').forEach(clazz => { + this.renderer.removeClass(trigger, clazz); + }); + }); + referencesPopover.tbComponentRef.instance.popoverComponent = referencesPopover; + } + } +} diff --git a/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.html b/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.html new file mode 100644 index 0000000000..4a60b29ba9 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.html @@ -0,0 +1,111 @@ + +
+ +
+
+
+ {{ 'image-input.images' | translate }} [{{ $index }}] +
+
+ drag_indicator +
+
+ +
+
+ +
+
+
+
{{ 'image-input.no-images' | translate }}
+
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
{{ 'image.no-image-selected' | translate }}
+
diff --git a/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.scss b/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.scss new file mode 100644 index 0000000000..f6e05ca2d9 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.scss @@ -0,0 +1,225 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../scss/constants"; + +$imagesContainerHeight: 106px !default; +$selectContainerHeight: 96px !default; +$previewSize: 64px !default; + +.image-card { + margin-bottom: 8px; + &.image-dnd-placeholder { + height: 82px; + width: 146px; + border: 2px dashed rgba(0, 0, 0, 0.2); + background: rgba(0, 0, 0, 0.1); + border-radius: 4px; + } + &.image-dragging { + display: none !important; + } + .image-title { + font-size: 11px; + font-weight: 400; + line-height: 14px; + color: rgba(0, 0, 0, 0.6); + padding-bottom: 4px; + } + + .image-content-container { + background: #FFFFFF; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 4px; + height: $previewSize; + } + + .tb-image-preview { + width: auto; + max-width: $previewSize - 2px; + height: auto; + max-height: $previewSize - 2px; + } + + .tb-image-preview-container { + position: relative; + width: $previewSize; + height: $previewSize; + margin-top: -1px; + margin-bottom: -1px; + border: 1px solid rgba(0, 0, 0, 0.54); + + .tb-image-preview { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + } + + .tb-image-action-container { + position: relative; + height: $previewSize - 2px; + display: flex; + align-items: center; + justify-content: center; + min-width: 40px; + } +} + +:host { + .tb-container { + margin-top: 0; + padding: 0 0 16px; + display: flex; + flex-direction: column; + gap: 8px; + label.tb-title { + display: block; + padding-bottom: 0; + } + } + + .images-container { + padding: 12px 12px 4px; + background: rgba(0, 0, 0, 0.03); + border-radius: 4px; + flex-wrap: wrap; + margin-bottom: 8px; + &.no-images { + height: $imagesContainerHeight; + padding-bottom: 12px; + align-items: center; + justify-content: center; + } + } + + .no-images-prompt { + font-size: 18px; + color: rgba(0, 0, 0, 0.54); + } + + .tb-image-select-container { + width: 100%; + height: $selectContainerHeight; + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.12); + display: flex; + align-items: center; + + .tb-image-container { + width: $selectContainerHeight - 1px; + height: $selectContainerHeight - 2px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 4px; + border-right: 1px solid rgba(0, 0, 0, 0.12); + background: #fff; + overflow: hidden; + + .tb-image-preview { + width: auto; + max-width: $selectContainerHeight - 2px; + height: auto; + max-height: $selectContainerHeight - 2px; + } + + .tb-no-image { + text-align: center; + color: rgba(0, 0, 0, 0.38); + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; + letter-spacing: 0.4px; + } + } + + .tb-image-info-container { + display: flex; + flex: 1; + align-self: stretch; + padding: 0 8px; + justify-content: flex-end; + align-items: center; + gap: 4px; + + .tb-external-image-container { + display: flex; + flex: 1; + align-self: stretch; + padding: 16px 8px 0 16px; + flex-direction: column; + align-items: flex-start; + gap: 4px; + .tb-external-link-label { + color: rgba(0, 0, 0, 0.54); + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; + letter-spacing: 0.4px; + } + .tb-external-link-input-container { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 4px; + align-self: stretch; + .tb-inline-field { + width: 100%; + } + .tb-image-decline-btn { + color: rgba(0,0,0,0.38); + } + } + } + + } + + .tb-image-select-buttons-container { + display: flex; + flex: 1; + align-self: stretch; + padding: 8px; + gap: 8px; + justify-content: center; + align-items: flex-start; + .tb-image-select-button { + width: 100%; + height: 100%; + align-self: stretch; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 4px; + padding: 8px; + line-height: normal; + font-size: 12px; + @media #{$mat-gt-xs} { + padding: 16px; + } + .mat-icon { + width: 24px; + height: 24px; + font-size: 24px; + margin: 0; + } + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.ts b/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.ts new file mode 100644 index 0000000000..260a7667f8 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/multiple-gallery-image-input.component.ts @@ -0,0 +1,181 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ChangeDetectorRef, Component, forwardRef, Input, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, } from '@angular/forms'; +import { moveItemInArray } from '@angular/cdk/drag-drop'; +import { DndDropEvent } from 'ngx-drag-drop'; +import { isUndefined } from '@core/utils'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { ImageLinkType } from '@shared/components/image/gallery-image-input.component'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { MatButton } from '@angular/material/button'; +import { ImageGalleryComponent } from '@shared/components/image/image-gallery.component'; +import { prependTbImagePrefixToUrls, removeTbImagePrefixFromUrls } from '@shared/models/resource.models'; + +@Component({ + selector: 'tb-multiple-gallery-image-input', + templateUrl: './multiple-gallery-image-input.component.html', + styleUrls: ['./multiple-gallery-image-input.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MultipleGalleryImageInputComponent), + multi: true + } + ] +}) +export class MultipleGalleryImageInputComponent extends PageComponent implements OnDestroy, ControlValueAccessor { + + @Input() + label: string; + + @Input() + @coerceBoolean() + required = false; + + @Input() + disabled: boolean; + + imageUrls: string[]; + + ImageLinkType = ImageLinkType; + + linkType: ImageLinkType = ImageLinkType.none; + + externalLinkControl = new FormControl(null); + + dragIndex: number; + + private propagateChange = null; + + constructor(protected store: Store, + private cd: ChangeDetectorRef, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef, + private popoverService: TbPopoverService) { + super(store); + } + + ngOnDestroy() { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: string[]): void { + this.reset(); + this.imageUrls = removeTbImagePrefixFromUrls(value); + } + + private updateModel() { + this.cd.markForCheck(); + this.propagateChange(prependTbImagePrefixToUrls(this.imageUrls)); + } + + private reset() { + this.linkType = ImageLinkType.none; + this.externalLinkControl.setValue(null, {emitEvent: false}); + } + + clearImage(index: number) { + this.imageUrls.splice(index, 1); + this.updateModel(); + } + + setLink($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.linkType = ImageLinkType.external; + } + + declineLink($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.reset(); + } + + applyLink($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.imageUrls.push(this.externalLinkControl.value); + this.reset(); + this.updateModel(); + } + + toggleGallery($event: Event, browseGalleryButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = browseGalleryButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + pageMode: false, + popoverMode: true, + mode: 'grid', + selectionMode: true + }; + const imageGalleryPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, ImageGalleryComponent, 'top', true, null, + ctx, + {}, + {}, {}, true); + imageGalleryPopover.tbComponentRef.instance.imageSelected.subscribe((image) => { + imageGalleryPopover.hide(); + this.imageUrls.push(image.link); + this.updateModel(); + }); + } + } + + imageDragStart(index: number) { + setTimeout(() => { + this.dragIndex = index; + this.cd.markForCheck(); + }); + } + + imageDragEnd() { + this.dragIndex = -1; + this.cd.markForCheck(); + } + + imageDrop(event: DndDropEvent) { + let index = event.index; + if (isUndefined(index)) { + index = this.imageUrls.length; + } + moveItemInArray(this.imageUrls, this.dragIndex, index); + this.dragIndex = -1; + this.updateModel(); + } +} diff --git a/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.html b/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.html new file mode 100644 index 0000000000..5255234ba8 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.html @@ -0,0 +1,65 @@ + +
+ +

{{ ( uploadImage ? 'image.upload-image' : 'image.update-image' ) | translate }}

+ + +
+ + +
+
+
+ + + + image.name + + + {{ 'image.name-required' | translate }} + + +
+
+
+ + +
+
diff --git a/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts b/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts new file mode 100644 index 0000000000..ae022615a1 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts @@ -0,0 +1,115 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { + FormGroupDirective, + NgForm, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validators +} from '@angular/forms'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { ImageService } from '@core/http/image.service'; +import { ImageResourceInfo, imageResourceType } from '@shared/models/resource.models'; +import { getCurrentAuthState } from '@core/auth/auth.selectors'; + +export interface UploadImageDialogData { + image?: ImageResourceInfo; +} + +@Component({ + selector: 'tb-upload-image-dialog', + templateUrl: './upload-image-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: UploadImageDialogComponent}], + styleUrls: [] +}) +export class UploadImageDialogComponent extends + DialogComponent implements OnInit, ErrorStateMatcher { + + uploadImageFormGroup: UntypedFormGroup; + + uploadImage = true; + + submitted = false; + + maxResourceSize = getCurrentAuthState(this.store).maxResourceSize; + + constructor(protected store: Store, + protected router: Router, + private imageService: ImageService, + @Inject(MAT_DIALOG_DATA) public data: UploadImageDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + public fb: UntypedFormBuilder) { + super(store, router, dialogRef); + } + + ngOnInit(): void { + this.uploadImage = !this.data?.image; + this.uploadImageFormGroup = this.fb.group({ + file: [this.data?.image?.link, [Validators.required]] + }); + if (this.uploadImage) { + this.uploadImageFormGroup.addControl('title', this.fb.control(null, [Validators.required])); + } + } + + imageFileNameChanged(fileName: string) { + if (this.uploadImage) { + const titleControl = this.uploadImageFormGroup.get('title'); + if (!titleControl.value || !titleControl.touched) { + titleControl.setValue(fileName); + } + } + } + + isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + upload(): void { + this.submitted = true; + const file: File = this.uploadImageFormGroup.get('file').value; + if (this.uploadImage) { + const title: string = this.uploadImageFormGroup.get('title').value; + this.imageService.uploadImage(file, title).subscribe( + (res) => { + this.dialogRef.close(res); + } + ); + } else { + const image = this.data.image; + this.imageService.updateImage(imageResourceType(image), image.resourceKey, file).subscribe( + (res) => { + this.dialogRef.close(res); + } + ); + } + } +} diff --git a/ui-ngx/src/app/shared/components/js-func.component.html b/ui-ngx/src/app/shared/components/js-func.component.html index 41e834668e..e072759213 100644 --- a/ui-ngx/src/app/shared/components/js-func.component.html +++ b/ui-ngx/src/app/shared/components/js-func.component.html @@ -1,6 +1,6 @@
+ (selectedTabChange)="onTimewindowTypeChange()" [(selectedIndex)]="timewindow.selectedTab">
@@ -215,6 +216,7 @@ [(hideFlag)]="timewindow.hideAggInterval" (hideFlagChange)="onHideAggIntervalChanged()" [min]="minHistoryAggInterval()" [max]="maxHistoryAggInterval()" + useCalendarIntervals predefinedName="aggregation.group-interval"> diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss index 70f43f410c..25d11f510f 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index f4a2e8b916..0c0dd4294c 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import { AggregationType, DAY, HistoryWindowType, + QuickTimeInterval, quickTimeIntervalPeriod, RealtimeWindowType, Timewindow, @@ -186,6 +187,38 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { this.timewindowForm.get('aggregation.limit').updateValueAndValidity({emitEvent: false}); } + onTimewindowTypeChange() { + this.timewindowForm.markAsDirty(); + const timewindowFormValue = this.timewindowForm.getRawValue(); + if (this.timewindow.selectedTab === TimewindowType.REALTIME) { + if (timewindowFormValue.history.historyType !== HistoryWindowType.FIXED) { + this.timewindowForm.get('realtime').patchValue({ + realtimeType: Object.keys(RealtimeWindowType).includes(HistoryWindowType[timewindowFormValue.history.historyType]) ? + RealtimeWindowType[HistoryWindowType[timewindowFormValue.history.historyType]] : + timewindowFormValue.realtime.realtimeType, + timewindowMs: timewindowFormValue.history.timewindowMs, + quickInterval: timewindowFormValue.history.quickInterval.startsWith('CURRENT') ? + timewindowFormValue.history.quickInterval : timewindowFormValue.realtime.quickInterval + }); + setTimeout(() => this.timewindowForm.get('realtime.interval').patchValue(timewindowFormValue.history.interval)); + } + } else { + this.timewindowForm.get('history').patchValue({ + historyType: HistoryWindowType[RealtimeWindowType[timewindowFormValue.realtime.realtimeType]], + timewindowMs: timewindowFormValue.realtime.timewindowMs, + quickInterval: timewindowFormValue.realtime.quickInterval + }); + setTimeout(() => this.timewindowForm.get('history.interval').patchValue(timewindowFormValue.realtime.interval)); + } + this.timewindowForm.patchValue({ + aggregation: { + type: timewindowFormValue.aggregation.type, + limit: timewindowFormValue.aggregation.limit + }, + timezone: timewindowFormValue.timezone + }); + } + update() { const timewindowFormValue = this.timewindowForm.getRawValue(); this.timewindow.realtime = { diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.html b/ui-ngx/src/app/shared/components/time/timewindow.component.html index a53ea656fc..622f5654d9 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.html @@ -1,6 +1,6 @@ }-~*nx2?$h0TM zWPW9-SJy4nHOtK0m~EInRlSLk<7{48W^DLN`qHy8Z+ zh2ws*41(5uUqC0twVRono12=Zw22!T4S^{P1RK@Ju;$RoIFM;JP(S${VDsc9G1_D3oKlHT2=p-RC1Ioh?3k(ZY((4P>v}k|No=wK|l#4XGR(4LqyBWT>a~p zFb9WKO$ZzE+Z>=7aoX>AA7-Tn=n(q*`kps1)!$7&3!&`<#q0;gHOE8>6P zx85TdJ^`6SpM%dci&ub-yFTsEkt3}rPSF4Xf_aUAhS4bsOkiTzfLCTFpmO^!&~pfq zEt~q=X!nhEl$^#P+y5(2Ut1+`_3BkZ=)_|N>3AE;*CT8<&fhG3j7PZ3qm)aHBkMbE zCwLm^=z_5%FI~E%pB3hXER7QM1Ub2$fq{~``d9NLyQ{jo-N@|_+4G$`b>9mJIg#=} zblS1!a3`*JqQk~r4mJzX%S=i^_*7S=q{vA;KiO@-ZFMxC33T%z>#0ZarbWJR(sIvyf#%d@Jk#k8% zNYvESK%$051TO%(hL2_=UXO=|2T2%6M}QROf23jKU&-btH9@+qW^+Bdk1%0_juU z<>p3Wz67l{fWG*6dWJpQwp@JH(`>g*WLszrdP@)wuDG}qB_=W+fD*Z)vJ#SgUAlw2 zw?9P1ua*$`>eVao6F5WN-ds-(4OOG3{xm#?dl6nKAt7<#zyW$v$|xN;!k}j?Kg4CB z_ajg`9$Vg~zy)imJWsvLe}8tnIT#&%9VxZHfh%I%j-pZ=U?*K@&5}!TLIT5!7G`#h z*!$q^AQTuHzL8~sDiYF)bVp;1@lzggT{%YdEY1LZ^!58tLV7WwO~T&6>VUAY&~42b z1!T^iez{S?4bxIKl=ibOw~jk zr=!sg;OE=Td7C`ZkpO-MoASWZY8DMKuzm4<(C9h|bzv4z9Q_#)Pgg#io%2Azf3`3J z+ya5%Aj4tC#tB;o)7Q~veRK0wI1pgu$fB0PbQl;Hw#PN1+bSiBX)Fz!F-$7?VVD8T z8{rGx2?(CE1rvZ=>%M+{E**bMBsD$V!o>_n^M%`YlYb|1)&B< zP=@pcf};Par;)anOR>93+c)FrA`%s@{bhI#f>LE>S4=WwW%nwb5*01!+hb3}Zkejs zy_=;f#!Y(b1j=ydjyl#3GAHx`GLnx`*`w}VgD6(5)`&h9k?v)9I2;8)tXcv+Y1TFc zb*-6+NdYu4hgeh{@7?1wZ=1I5n3|Yy#RL!yW2l;5bCM#mHB<}T`POk5v&>LOGso59 zW8|Q0Lbi*-5K&)076|&qQ>S7v78iA%zl}YJWd5VEQ9p$_`wgbzc&e}XZM~8ZAMuFL zaTmKRy?gP3qNxl8JtjblodlnIQ6G%tiXhC)z({6J1|?>T8y)J0AalY*WlQvh|8r^r zb*SJD`-16|n!<~>UOWdjJtR1;f7CRBdTj~-xTlyBtZT8d!osg>22D*&P=cC% zKZWEHKVAaF>Z!my*IB0`#lzLZa$h||X z2fw;y&|}l1Wy}A+KHmQ!%CJr;k`RgFpd^JFPK!Nrwi+L?XQfgryg9%UEB4uz*h(>OJ0tl@JM;eg;m$h;Evb2 ze_b!5B`iE#rJEteJYz4^s5S*(3Dre(bX*)pQcEswe=K6FW1lK8H8(b<{D(L(8qt!H zG&OUb*_R8srm|l}#R9)GZO}90h)Y}ko%xOlHV)?+j^J;l^|DfI^es(bAB3;|zdM}& z$DR4_^sDOoB*Wn-puSO7UJmCNcEH@B(($@ly}>MMuU^02e?manI%{BH;6KV5Up|6{ z-~9-FLKUs8dB7Uc3?o^q=ilj_0)wJlWpH@&&)}enRvO?YCQ5*vE;CQOl29)~P7aVt zSBcQMxa`~1JtCzNszz~23W_E)#OQv|O#k}!ZF*12Z=&YSwZNKUVK=&YlTg*qITmr4 z5TXGWzG7d+V>^H}AuSeYousC&VFl;A3Ii~t8ceH>larqckO~_FeFWPaQ(PNs>tpbV zLDdyDh>@I~wKb~7Z3iIlfHo7tK3fnpFgHLD?aDEsP#Q<|fXoff$941N|F@b)Oky!X zNlQ9%*%YDwfy&z7hL#QbI4dnB-H5XRv3QHX+y05_r_jdXv1d0Yj|iR`Dkgs#s+SRK!*~NfDamru>Djjsz zANEHmaxexie*QlMjm1UtYjUzS*Tr8Q3}M&C=*k%%5sT91!Yh0pcCCB`Ny$X6eMHXS zn1hTG^(@eQLNNQ&dv}nh4yvJH0(>655qN|E_-?>gJ-icdy`^P9 z@Fh$Co`t#zJe5Z5V10fgeQ>=BA*wAub(boRB_`0Rs!_)IGN-H!PsJqX^M!ByS zh;Id`iQx<8M7enMFsFeZ9j-PKyK;1QDLwHZoMsI{y8*3@FbnbYtV9@=@H;+=H^KCK z`S0;l#8r}Ye}`Ov2P+Kw6+9P!r1Y^s7(jviR8vE+W3axU>=trae2c0M1`zOp0qQu1 zb`~+d;KK)y;J#K=B-9FYTKI8EPbqtSBLB3JMYz2fToeasc-Vw1$9S!XYaSXqT(DJFfB*{Q0AZy(7FQqqY%Iv2VA(1W@7RO(U@^1Qf z{+~G#((!W51wxKJW)?-N6O6;uZ$=d*|`qImn?a8k-rz>b|eyO4FWRj&epM+H4P zh=6?!a=%#nHQ$S;z~Iuq+nhFyc#Pog7GV*5mVv>jDv%z`t7XF~Bx~*bJ278Mu?kU6 zt4Njw1@yU7^ho|;KUZfkLgTONo;3P|X&)qZ2fYW7M8O&h#~MK(0s+_T!ayT@y^&QzJR}9 zdtnf855a=Dz0(20P_6tsmq0O8*Ev&Z>RqAiBCb;O0iOe2XzHx%%c{iZEpk$t8ODB) zCQ(fyqs=n>^MGZ7A>E4CxjgK?t5yGXQ{uWJ-`=sB{)JOJ&TV+zZWZ-+f0OjaP;m&z z7ZNY_t~Tgjp!(sH;Ky6UKrpRY%F4ie3R}^&e<4Wq(w-6tz)N*?bv5^R(NcoI_aYd5 z9n4FF-B$Ahso&@3PBzBN{QH5z0s?M=HPL&=D+x&wJ2x$`uI0keQS@*?p@O92=_Atv z4Bm?yFpI=8>p*dC3m$_=4Zh1;7z&&_Bc`%fHe}b zI3knw}LfmjxQ-2}lJ76jh6@o-_e(A(P!gG&?* zz+zBp;0XhvGXEC6l?c7#zX~y4{Ql*OA)FWy%Gtla0hj`h1Vz@vg@uJgA6I8oWB3j& zIf5U0LTI!)s;a!bz)zYO9bJOF&*(BcJG(etfE4;j^mp6{TPKB`9UUDx>0iHkg~h-N z`k<@p+QA*5tDiV{@VYuto0dZVN1=$Hl9KY0WtjW($AYl60}_Nu>j;uAiYK8e5sY42+yvCIs1~`xpK7QVc|e>WPyHOcY+!&G zjc(ia2cUz-=DCu^PYU;?CS@vK?&dw-qy$C&|kw*V;F}GAz|m3DA7Wopn}x^K@Qjv1zMAU;=ty-k!xOc zeh9@YE+12CtR$5CNc@Gp7mL2c!jw#4iW;VM(8=VP(beI{*-a>rr@jEEjv5%#(tgyk~6e9u(q3?Zo1BbWGDKX#CfDg#IU-Ziuu1 zCNYXru=r;%#So{YqIwDW0-8gR*T*ryg;M6Mxt<;;hQIj!R7ak_2B^*LP-g=Zbh8j(5Wx|7qkIny3arLP8}zJLyJ#JK27L~P zwBN@74yY|kHI%bhiXR8>DwpF1y$TD?!osdl8L*+oMoY7U|A`myZ%fp+ zkPCoIj*TsEne@1}R#sT}BW8Vsr77kSwPNrX>+jFD8@-O%5_VHao~0ApD;~2D2nHVS zrVzkj%Ec-CW8yR_KDdgYaFY$=JbmWOBMo(Hc{f#2@F?oRk_3#3$`0MfzHH7jXACd{ zLnD0HTO8Ywo+!!S}*Po6lT{rNXYD1gY3hu4AJ zg7tx=6+Q^UAYvS%zcmsCa^sE@Ith)H{0Fc|GSTUxGzX(1smJkA^|W=~GshDpg9CLz zX+qSt{c_wpfBxX98rs8{DQI-lC`VtbA%7sPg(UOfcg$+*yYHQ|Qm?#Lr+s96m!EtU zyN>**vG(tXJgdI^KLd3&e|3wOp9|g9)1+NKGqO6CAH>E@!t0Q$k zmhKqCwU9A?k77T$bpvyU6Z_uple~QBkD1AIZo|>r(dqogH74c55*4-2Dx0$BRQ;w@ zYA)WZNT9f$%sRVP`KMCM9krB=OT(DIE=|$msG71ziEd@x>R5C2?vFy|V<|4yd2a4O zXRFcl0%{M^i9|RC@(zVCSFg!Mvnvw!tM6BcRQ6Wb{egMc8?oHCtRlaG*$t2lB6W_c+{UH~| z`2fbmi_LQa>DzHY*nOQaIRY&hd!u$l*&59!_@1endF-BL)^>J=@2=U| z<$!7c7Bqg-zhR@aP=V>GxA!PW98aD|fI02+wr(J`4;HL^;-G+^?*x z)3L})l@%3SG?OJd#BKqu0oe=!n53?530jugQlERX*~{(VgO~M!p~q{|C_YmG4NG!* zdR^}w)Hk}tE?33D4>847LY{=)7y&L}?onjHRgl8UQ zQ38t1<7D8|GD6~+;+MCPaQbyb-Lap&rN556cI)7D8~ps&dsLiqVIcjj!g&Yv%8_kv3y%`DEq~7-g;O-wxfeoqEWrykN4iU z9TSJ#!Zcg#MO z^!gm!(hZb)+3h|Wrywnaq7Z@G=kyCWn!y+S;CD=3Fp&Y1%of9P1Rp17=Y^5)kG#-j zAhy-P3LeEkh?^iRPmGM-K#7N92(qe!2tbOX$&68~&%xL~bf~;G4NQGZxCm4Z7g0TS z8LH*Sj~}Bl^A)FkaPd>o<1qwnZUe1GBOhOo1=ay zVR*%;hVDWKtD84tN=n@EBO(Q)2QUx=Pyt{d)C)QTKy<`}acCnpkkX^w+D zd-uT0kgGtQxrT!>et4Jv6A=-oIV&(IrJ8KE`%n=qpN1d2jTXNR4=v1VH@KH>_q|eM z?^)y{f5`ibINjRcn`yD7477*HBE0g=bCqw*p5kk6p^Y6Eiiup<5D`c)p7Xw4|D!3* zeNtAcp~`I3Qg9;i$4Y&-rt63)%l;0JxDTocGz70wKVgginquQq4EW#y9isrkVtG-5 z6kQ-{enOxDx}T`1F6&`#ig{vLm5{h>4VTSD#gAGT4?`5nK@7LW5eyj1cie-p4-zE6 zFfj^WI~T^MrU2Y>gWAE8YQu3Eh8mEfk$(r3gO!a9J3!2J`6DKLDaBEf(M0* z{xIqGhm9Tt?G3DNY~Yrb7P#vhnVYj7z^@1CpbAX{sg*gHxl50^{XW`8)Y+I8kKu}m zehAeTps7(m=FDs8#-1Qx0wI!?u125%L2e9l_ZepKdEntP@r`|dg(M{thW{-0mRvlZUk}dFYt*{fn%uSLwhJgvL)#co0^$X1VHN!Gg|$a zmoI;Y%00rYlqJqiutq4X1_m-{$GYPr_m}U|#WWp7J|^q6{WEA66lZ=wLk4??aFgq( z&*Sf<>7#Hg-{3O!MB%{jJzX%Y{=gO)8dxMipaUOg}J}W zH>gP1pWJKLJotTe!WbuR<>%Y?RpyMZ&(##O#6N60cNX-b`S(17^Ce;x%L3x(jnzr` zIyOFTc}aDKG(09kPR(QHgIZB zvJ=(y_4N%6qTsh;N5dWsn;N44Sg?b%kKz&HA5E9Z-$y8X%WJ<`g1&*#taJ$tWZfY6 zpJrtAtn9~mAl!U+(3qmb*4doeVj-OE+6ijW*IFq#d3j?I5w0j_j9yVNY6ALOcFn5& z2NS7=VeUAkTJfHsAW8^RF>f8>)x(Vc?ORURXuWu$v|Ntxz{y7wA0H1SAuKr?6d+XY zAamOktU@0rdleM{%&~Eq6h!A(eSDBTdn+nigPj>5u)_ud2Q6gBG25fzK|y&K1&E0W zoIQ;Wo%9dI))Be}qfHqZmaHELdjP5nM`&=(x*645P*T<4V@@AxmjlSu?<(aVP;~6#=l3WLgEJgyt40 z(#vaC`TZ{#J0U+J!$ay?U#o5f4@oFluV258UIM^BHIJ#Xv*adw7cyBU>%H!?>gi93 zGIUOlJ@XtwFknuYzvHkrSi~ww1NFydvwawv=L~u&iYjnZqQGN5d|1^;loJ}&?vE@^ zWnNJA;a$KiS5Q`-M@fL*9eas+>;mLm@7})MMMf4PMTd$7H!sysbH@gRIk^EDL%Nk# zk$YW_FVH#vEhxmfTq+h6y8b&~nx;HB?V!YEn<3(O%x;n_qWpJfwxrdFzULBqKP211Ej+c5pg19eV2~WRxB}~;YdpvfWd$K z$OYPhVIO2T_V)I*HD+MW;g@pY90`oU=$f7N!sZ_Y>ZaCK2UL@d_dk4CM&d_;mlH3q z8Oue@30WncFfyNtq@*#(52z<@*(;Z$`nrQ7B+#Q^;agc* zVU9=(BOHirbug6z%10yQcq&-hSNHN&T&J`&vs;uc-Wu7W8*5$OGD9Dj0!AYIcY5x_ zvKxp)mruRO}4@l6#PIi!Hix*AwYc*FA25|GGs7%hO# zi(1&H6hj7J=LbkVy1KfE8z2S@3EL0HvOt>p-l8MK3lfwMIAOphJjbP#6*RNpPkllW zfl4>LO~>U=Umh9$e$V!Y#9+;7d~{Sp=aHz(A|SIJOlgwANU%8{tzCY4x^2O*%{BpcDt`O+BRicj7=IAL7?yfuS<2wW&KC?UjkK05Q`+{h_ zewM1-??U~CrS80_7Uh029Py^~y0ySV#@uG;o$wJ~x#< zurubf_(@4lt^iwYX>1naxs9uN01Ss=gBy@Jo|crxYIDX<{*Mcetu*`Srkhf$O0GGT zmMX>g@1dt>v`MyBc3!z>V&c}-O0t@|vujw}Z2jSL1t+-X5mU0@H|fc|X=c7JEw8*+ zN+=-Q8@JH+Y+6Rc4CVJf6q|#S|9%n>c3mPIRmG7F+g|v~tSxAI>(;FW)BbVC z%}(+DR0T2jh52t&I+!F;fmc8l!clMxRT0hxZXCv1#9`Y8vEcM2{Ex!APuN!!UF@ZyExg|%_HZS zzP;I90k{PE7BkbaZtS;8Vlvs3et+XUh6M;n&OnP$=38MXfcfQ1%X;HoyLa>3jgZe> z5EQ%zEXJQkbp0i1xGrDX0LKxx8UZ4SE_AbP=7PUZvjbaXh^pvTuewaCdX&V(oXCvZfMFC zxUplJ?W_`JJA;UdI40)~ig|<^mkqaql7E(kF>fPA6NnEVi;9X9P9`hEKjBJp&H#s* zveWA*K`J|3+)E!WR7${Vzk`jGy|M8rBk((p)7YFBxC(C^fbTFyz7q)0(63^zQ;OyR zDltH3k4~2(H5V5Mo*w>G2dzG|%>}+pfX-tZBJv%-Zy$nJ;yZw4%*jlAd}{LYQ`xEk zQn#X1BFptbK7b(TGn^n$5CxSe6TErd)1csx6#9RUHo0|RjW zMxlf%8FYj@!}TYGg@uPIec+7&s19KrpvNCf!B<;e-cYDJ90GSL@5maPA*nKMd z&la~SvWMc4cK`UH3W5s$KZ38}^XE&Nnkx`bgH4`qKk-t53F0EgSlDVqfDAO^OC>QG z{?X9jPmn(_7Fo zj!{#O4GpOpZPdb2m+l~L?G%7ca8j{jh*=-<8$(e}7(97N#M|`4-R$qIg49*Z~_Dj8O0y@vMnn2c%}yOPIiLsfAVJ_2C6p7N+H^$WBJ$?&Vzn6F{3P$YSBbkfA!zbc*V3I3@O;{v&o)n z-!jFee@#y1!MyfL!cZ0K1EptuO^q#%!Z@^Y&WuaRAuXZeu!m* z*E*Zd0qROT0z7gYS$M4;Mj0UdfJadZuF6p3Ivx!S z)WD_KNp?IRDT{FLz^_!nv>5;Z_9E(lPdPEQ>6{qDYA7gh-u`x~JP`N_M2cqRK9DG! z#Jde_Qk=2a8DsJEam%^Br#!|f6r9KGfRm%mu4`yOzjP1P9S#=*ISRHqDt+7T3va=!LKYt>7|^@g!Dt)M_^e zqBgDy(IJ#glOb3dVfV&rJ1SH_CQXn~o;r0=MJ3n^Ey&KjM>$d0kuFQp9jrrUL$1Vb zgVhAQRxnh8f`nZMO0B=oPhW=P$Dz48aNZ%Pg&{Z9`P+Bki!Y!1%nQe#A$XQxIzdex zfn|#giD;<>Y~uX+oT3)Y!TzUdfnWGNYp}eq&xo^Yu;dWJZc$}pae$T1s+lWGcMy^N zkrx6u#o@!e;J70g0n$=U*jAI~=>?ESW}KCx9~GkrVglDjA`HrdUcAT#)JY(dh~@y; zYi*L1t0DCXeXaIwDLw*QCGnkK)hw0jhXEAU{|+i@Fh5h`H|FanBwT07tY8}fS^U6& zxHx))FBd9@BXiLn1L+{9cU*>Fu}9Dlnzf{~Ahq=L^nhA}s+>sTc6lz7CYm31K0!Re zj1qgso0z~9m5xM!tv+|7AR)4C=q~xhw}y%97>o5w+I+|->m1wQ;z7wA;o}v z1k^R`M<6YQg@W1D=%)bKu{5z^0bx=$Gd~X^DWWb;4`e>q`mUA%?jkU>FhlzTkhhkk z`XI7C=A1xMyl!I@g>Z?m0MJo(5T6q3z#OC+&oUDX_>q{fKda4R^b?T~;PCH_SZ#_= zflUVB9kfaGrtl&_3Me%JMfb?+P1m<(-j^S_u-u$0Gh|M@? z@TOk~DZKiY78jFJQV@z^9;TXLfe@;rql4B4mxHzh9hcC_1~dxos^O`aEkHbFXlRJr zXNFp`_Ubf%{F&hZl`!Xu$Xbb~H-MIU&aF$&| zEZmq+W%(7{M0|aHk*v^(<>urVK8ePzBgWOAw+G2b=7Q6JJNO!(7+mmz0{$1ws8kja zM^Jg75Yxs`+wQ%J<4+(noHVJ#eeLWViFlQ}MfYO(&SgWcaAjstGw{a`RxrT*c4Q0s zHiQ6s#;l(Jl;K_!wiJOcc9Wva*iS~L1A`~nq9YGOm4RoA;QW7sp`n{bZVum{4;PEt zVMd%qbktvk$(BVNXSs0!K14`EI<5q0neg5~SYomDKB*HMYW3{MDcuDm7aFXNpB+^7I&9;j@xzL_h0YFuQLl5hk7 zwi%Wm&Nc)Y!CML(OIRvl#r~^Q3oh8o2Fn>J_%X8+hoVA8SDdARamADW1{4-yl~J#Z zqmPm)yUb>|bP#{B@7y0wNu%)8LeTA&R*_z?)|OXR@(8au;^X60(AZ-&;kK)xfmTuB zIeoepg$%fir^rFCCWpB>(+PPNOT*0t;UZE}`&rB*HkS6Jf9r?4W;hM1WPN!Ip$Ewt z9}(dU9_Y-9`L*?#vhE=;5Ho{;7=4hFy;6LG20&lf(jlzDSMHj%&3m{?p~ymf;h0)Y zsWmCQ_t!FEBmVl?xo~MPn{XHle2$ZAI~SxQbHSglZ9#&86t*kovM0ib(Yu8_A&afd z^5L0AV7n_ zOXN%@N|dvxx+=T7H@*g(K@7tx#L*YECRfF=gYmgB*o*2UJ%D2^si>*3c3qHLv3XJO z;_z7r+A#!WI$7lPwW|xf(i)s2vMAX`3QMld;UMdaD&&>029GH7MpV9lq;bk87tWy| z{f&AIdFlil8o}qq9m+FrFKrHwisA^C4(E%3c_41tY{ohVvTFXjv#jETkh)r;tK)GI{aB!FtXeQi4ZS0H+rGt$TZ@#$O!xp z*`c+AwH$C$6du*GM|__>yTyzX&6d(6LNw%^(hu$0erUWNur^|EjSG2RZZ2GnXk$^R zz}!Lr=bhTUOg8kSRQnHA@n#5ct>>@NGVpo!49yZn_5ccF6aY95hQ;cLh?Dy7kr5nf z_Ey}1@cQ^&i!F+yU20N`icWCM<*;Qw0e1yu2cE1T>d(QFL6bcSPSth~st~RD`2M0) zKy`qcAp7mx#Wa|RAzz@V0reKN92l+`T9ymhk00gMy@aYBEz966S}M@BFuk946oSQ< zkyltjOwQZ4)1Xm;A5eP?-jfKADi1D_Q&XpgQ78?mQlhnRUKlSxT0{e6q^h@Rn|W1E zP8hE|XZ;OwJ0YQ>zEl}dR_lyliSU&G+g1=+Py<|zZv^QQW;YQL5$-mX7$icEiAJEm zzdxM5e2lgDgE}ia6O*36rI}|U1lAT3f*R>Xl_7RVTb>ooTwexGX$lY5Shm~+vj)^e zTxHF~xNB40gC@!E*)z6tnRl4FymwoWtqIjTwh-9u{2Kb2d1a*l#@84 zpT%g7WAb@lrEz$rVfa3lY`Ozr|Dt&&t+({3ewB@whhhPRw$JW1p;{v2?+@;Ts;b~VNgzhTjyVig1!wP49rIX5+DIj zOc3WG*#m(=>fV2pGny#Io{0f$UlCoH^v5Da9 zaA!wgb^sIsGbZZ`#{n!tr?G;JjXZD83ty4z7)y6{3bC_KVj;mZ200%gy0f#WI_{gp zH@+hE)W!ZQw=BO2m?Dhg+tZKuT%0aML#Ln4O#7dQ`K zFwC3ag>x-`y)2H9zz>}TrKZ?`IO>9rFVu1`Dy!--+lik%0FNO$3XsasDiD)D#dP*; z0T5b`ldN!|z_^U)Y5;8i^WdWMEzBNuO5Eg1zVI}sM@NG+MbfB^CS^oW5J)0G4ea`q z&R>W_P$dFpMUpZ>E3pa{&h!BO!w%ReC}e9~5JEbdd;gqYygG zKjLIycv9K_eP7&=ukJ0CTOCEHZ3cO-HZWx>Wc|Lu0a*v)8Tu*Wa$Ou+M}<>kW8p6Z zyIm$%(|V)`M8R8AdMRTa(A{xCeuL>gEp2IS?bAqjDQx_#ijJ(z6{Mw^Q%*BUoz9 zS2h5rN7_S`udZElA9WW;kX)FXdr@)W{?yZV z2mdxvS1mq$u5eG5N-q}EgZAT>cPT0;pj>~XRs0Cj^WVtRP||^=diLy90i{_P2y;<@ zX$xGC+XeQsDx&hBV8hmw7m<<0$N`}8K(U0vp^AR{Up#lQcUC{r+HoI8JEDsj^|KaH z3c$psvC0Bte~>EY4b-;um>K9~%02?M^(eL_4NYc3!Ve5;N5!9WWT8C=LV!LmhWfBb z1kQH?CIDn*WJY{cOSu3HFgetLAKkMbyJ&xO=E2dLn2Izqa%mOX*R`~$0B8#OLQVtEYGCzt*nCbmf&>a1Fwv13a0AeU z`Nt9`UEiJf+0xM=h*><=A`44RX{kPX>-oQhV8)d-G;jzA++aP7Q?D5zHKV0{2v3gV z7ZjFvH-wjX7N|M^8!D@+GCam<&G<1z+5q^g5C)IA zmQW1>cD6^tfIVSU0}beWXpyiCEcErc^uO#{9=pgguG1U+_&MY+@SX;d5g`;w=&{HM zR%>ZlnUhdtc=)j^is*qeZE8?XJtTxp&___ocxj@FT!wbo~73N5$h)X9Qy;G>%d-K+5>IxT?Q?#hJ3~ zr|A$IVOohzj9ibRo87k@E{pO~Qb6v*&zoGwnn1C83(9OnDYh5{c~;gm5XMlSuHb@D z^TBzRIP4Cv7CI?VcvZX5^J*_dgP4gQ0`@Th#s)^aXuV;vibzPHUb11|(9h#&@RR<- zz&!^;u-yDWDnsQ52Hft`cw19A)uQ@F@JI6oZ-X(rWUOP;PrXddCMylQcE7KDyETFI zC7%_f?Qnbq(CQ8F|eLC01Gg3=-g3pIO*iDl6OB*uaY-rN9sw zAHXo|9dKNp)oRb@ZP-OXA&~-rEW(+xsFWR*K9qR)aSYVZ5}?9|VLSTk_nq2`iq_z` z0mB5%3spXL{^RGk3*g$rtr9)@)shF9)od(B&)+6LF$76wd?@x}&q`TIDk-}0>u#Jnz^dYhY@Q5SLuuq!MR2Zo3Dcr4amg9tg& zl~GXwNgrf``38hK4i1nOTBnHJg=wvz-N`WWe-;VhqkI1h)X1epAyMi3b6d<74^w z`lfb#s;is8Z=qZfi{TU!TE*_V%Un|Z>(+i|@?8kwOw4D_^rL~a(v8pRwoA5tE<^@|;J=;hwm>T60W);A=l+fM&{Mnd|Lj#FRF-4j)Hb(2k1z!gY z%$IXbVNeA-GsxUg&PY|(q;v+7_d(|lQ-S~~%o4xh$s>ZpI+BKlhMqni&jbGkVh%YL zDzTR2A>t+-d-3ln2v4Ff{d&6gdM|NK!6*OSiiBSK=-&S=|B;PUe4<^+kx9H{f!S@E zZkuOZ<4^9g;me*ER6i^8gyQyk#(n!1NSrX&e7{cL`U2@}M)}6`IW6EqkIrL$`Dqu# zQ8j~K3lATvDiC8mtf&lMbA9i}feJw`EW~_M1xBjo_K(K=V|cvGTR1szGTfH)F8RlZ z)5!ieeT@I^W;{L9T_!`PT%bS~qCk12Dvx3M1U3t)c?fMNtSCeb9CEHN?))Yl)4Nit zy8P#BwweTOjCA$7>s(b9)%348CTTjXo!#_-0(s~0lf0Wh8K}CfQpH^zwC9Rr|7X6- zigOR zd49ZJ1@U@jx8=wjh^M>d(WZ#||KUGXOC}|E4;Hz1gV(t0^g-{(bw(6oc6a|UZ14j2 zOs}2*2Gg3MA#5z4aZ|J6^w{CIC3!}XC0i{T> zD~Z@w3^ohxCQNTye(tq#6w(Q%W-PiVsfY$2AnRGSvpih9644K=F47&;E_CKOH{;sv zwpMVw9>I5gZ2~9mX+d^YecW-0GobVr6xlV_;`W;`=4??CCQ;i_YgS z1NB?-tu-{-@pcEhOIlm`re_x#o0|h2Zf)ICO!72}1#T|+IDHn#**y}^kwOPQnmsWz zznY4i_Uw_75z)oz^m%d-xh;3QwlrsAtNFy9KhHC&yX3Oo=`@#4#mDeit9ZV3&RG6e z=Pz5EcMVa3YBz2$H~9Rs#f9T24amEZshelGHKv7EepQUalp`YWmFzAQK>Y3P2JUM- z0M&TtuXMFMrn%yz!5^BM6dkQ_)5+R;27Sv1`-#c%@y0mg=fw{mNHxDL{<3|`{TRF~ zdL&u7x)F^}IOzsePl1!uV{+)b@7qrxEk1Jo)A-|ifGu0E=jYx#;1PexP}*{TYI}d&Q;&l3mA|{E zx{3j@qtiL;pNP7gc1rrQzi)#Y_xbZaK2K}EE`ou1?4(Bz3rAUOg~#F#eJA-<cUngJJhzzG#5uS7gbjh4;ZqtExA#9l9k1x@EU?B6^)rr~ z=FH=)5WlR)0IQGtH1++G{yHCh;Byn}&c@2><+}6^`w1lA+4Q5Ij4z@pUL=lJB(eW3 zzAM0sKG`I>(*Fdv(<~h)x#G?+5(MNP$uGT3BV*LxM5(EcX)lj)EGv%Yz>uY_Efk5! zoL3V`p*|yd-fSEbZgho6NWnotvU0Lmg%y_rFBTKZR5yirgXAl;ijAK8>FMbk3%FqwF6q+?$fx}?iepAC`4yuz`aAK*ztRUoMvHy+@KTHbQw{= zFCI8_n3P@jgZQok-jAv~OP> zrhS;)E=)F6e);m|P5!`C35TbLHePLs=nNJQ!0+qfLl-feUJ=8DgKmRE*H-8`!I?lw zZ_-jNdG)F{d0+R3yG~98>Z}Zph4S4qQKsVn`uPxxj-4=EzC6XAIJZS_-1ZhIdMPXV zy(o*Z99{aCImJM6>>O)FirK$CJ!e7RzyN`3P}`Tr7c<_(`qvw4t;sU@8TGci&MFcW zlET8;0+r-qC`knGPR3?rTvAkq*SU9cat?WF#j_5F5hl-{+rH|V2917CAA3admxaT9 zaXN}@;-_@?^HVH5^tZlnl&P}n-q7`TeDX3T_(xY2MU1wp#!No?S?j}1moPS5Pw0~NV8Uo6R%YjbZ-62Lq5u-p4tD=W5@C!CX z@@(VR)Pak@`2aAKG3N;h-4N_j*3tsC^ci+2_IO-eM953#U-;DYw6w=FslFa5i+!rj z8p&_q>`AtDX$JtWh7;dP?VAVoExQS!U< zc=@Ee=K>OE1)~m~R$3ob2FJ-PR&YU?duBMiF?N;NI{Ei(S zSA0WVZ2B7{Xs-DEdW#|u{RDYzTxvwXjG|7<;sKXus>^KO<;t6DWp4~Sv*te8hq$}@ zG!3^1)iLVUe(M+kAiMJ?WFMnzqlH=v=W~85;RSv2&!1;b>#R685obJ8Qs&Igz~qkh zF@ZsFpXtB0X;mvudzPJj!Rpt}ZL+6y_l^Zag%g!vFNYMH5*yn$+Wt;$N+i$fid5_M zzzy#`cA1M=&*L7E>@)A}{gLJRshvA9o;q(~E*!^bzKwbGOY5~xiL;8T>d=VZ5u5p4 zAMKZ?lqCJBvJ6&o@AIyotkc350pa1+OO5f@PIU17y+bW@yC=0x-9*uQs(XXiDb}H8 zh)wf6HNU>2ivWw4f^3X9 zg@bvwL^q?@uJH5Lt38*t#YaqZ6juJK(^$CJ%V%Xj^|QUMC0Vyy2e@JgduB{>P)JC| zje??QC7H&ye*?}uk%>4btbcdhTKiIx6@8!SWf19GnJ<4urzB`d@HF=|`z*gvGfpoW)C)vr7<83jxw9Lp~ z@iaiyA66_`2NqX!c9+XkMMTnX zEPdm69ui`&mw1gmBLhbYCKwrE%4_8#n02Y^=ns~-goH?(Oo-eo>hKQKO?YHCzW4Ch z@t7QyhdpBBLcQ)=1vRy)k$^I^swRe(lauRH*%X*)a63*v-zvHpB*|!~xb0tQ#7WF& zx`=F)d4Bn+YnzlR@V=36W(J4q;*|0gE2V_;VwVD^If($N3)YQR@A}f-yn%HN%=v zs@_U2n?Z%&m}pO;?~!^Ab)PGd>L>vKQ`S| zavH?VM09MR%pLzUhmvAwFXB^$fllA`Ne5)?Ek!(|Q1c zW5;L^r@*dDNJ(KXtZD8}Tv=W|AafbCUJU`QC_ zGmn+@dl4AuN3)+FhG`U_RCfm1ijoUj=P-^`wLZ95=HMU1RRn2|Qhgsm{@sLWj@{?cJv_l9(H+zbY%+ z{No4q`kh(Bk=3O=BaY3@874qiGIW+S1*i>vPH`{KPcX7owzh&h!XFTj2VSGuliyI#xxinQNISvw%kTt-(rD;x)1pyswnIl~Eg(mRVW; zEaEv8ed6?+D|;Fiwpy%1-SYL@&BlMI!z0uj^3N){ZrFU^xRb3VR z>CsFc#2N;8d=Xw)pt z(mYSAX*Heu_3g8-efBwfpS`c^fBr7l=c8KdUGMumzvnmH1IB$A%ITiABhwCdw^f)9 z`bJQb(0XiQj=3`MonHEJSN=IFkEc|?)d9Jfb;@qHdNZg(Zu`&p2ZfkGT+vSd*ovL1 zHCb0eCKSU@Y^aKgOdD&Ye30RfK0Ul*`FF0(#16Vzi_w?L=_Te$KEsU{nO}$DWa*922?DG-m zD;;^Jm1prtfDDDmtP$bPyxkqA<8Sg3vPIW)UXa{Aq zr{JI+z32rP3H>E>K+&7o}RIfw?DJg(0*mu}K$CAYOQFsa$;eBioY?xoS|52tB_g@(RONr{Y) zhhR=7te?%zUjt6`e9tM6XIg4%Tqk`a0r}_RqtPE822-E~c{!Q*WE^q+(oq zspeRGb#y7Fc6?fyzmVr|G0Pr}C}R*SnVz0`dMVW((4gL~ScO&dVfQazP&F+J+5i45 zc>#`Tswyfc_bW_BY>si3ar0$Tm?>VId7jMVvtKYb<>r>GslCaW*o%z3qR64&MzIF- zYKDb`q&wD#UH-PzVP%z`{BD)`Yhp?Q$iiEnJ(FTp4y+ML3TSWN&uq?|>(5nD@mj=g ztSK*Nbkn9MPgKXtuBRbjG{;#MwYP^A9o54uL6GET@jT(kk?kz287?J_fG-8)zsX3#>by7Dk+J>PiD}P!EY!1mO+*;*Y8LzR0DrBtIh4e zBTGq`SzgRg9N$@+a>mn_W~4dWxJ`j#Z#401^4F?!i+gwnRo?gR+oNM+)FjlVHY4>z zodvffWDeb(IdykHnO?ff<#*>hQ2L?ncl2lqSsb&TA-1jxh{D;-9y6RUO)%xw&9*ML z4B*%)@ggt~1vG41dFP7ud83IU78@s5Szc8YfBRi;G#ka;hou37aj_Pa_Vx~#UK;e8 zJMg7xw5M98KH9wct}h{mjiW(aiQa~rRf0nEaJ*XdcTW!{JVX5hy}f1@7Dr#t>3zPx z>O42rAxQ-=_yK`?PhY?8@Z7yd`=q~bfau$Z#|2ZNFugP!NK~S5%3!q0G(USkB}LE0 zrPE_q1vPsp!nUcaHmkK3H{7kT3{Xqe>h8*wH#+|nPQGWfv!m7R?HyUf52Ra_ zruXxTMu^$(-)WUuEHG9caM)|lsf%cecXggg6m8NgSn_2GeTHc#qP%#2^@@3g%Ap%Q zwLRa&*bZl#HWvoPWy44K_wVZ&=bx|U-s{#a&J$k^ZQyq)jwU_YtPpatP=9Sb?3^Ye zAHip~tnP*?Bkn@(-2q#uzl0u^4?p8f!7%*I8`r$My{n__>#lMhmfkB-RxB;Y;kB>r z=$q$-;VuBjrTf>n&hCpb#Y~@?^ibl#gU@Rd^{K9FyEr5;Fo=vx$}uQUSL}SKp$<%N zXpV(`hDjfbYgI;ecIOA<$|p9ackf!6bXJzUwO28S&avYj+KFm>`vH+$!g8wNRN`~sg3l169(Z+i)bxIQxYL8fP&ST~s+s395+wh5X}+(w|E2JW zh%-0!Y;ZDNhSkrquINLv`Y%(V;)49LM{!&L`~CiUX(BSsCvN+j>Kje=)ml_E8k2+8 zuOIvBy)sJ%m~LY; zWbebNDi?G#DaE%dt_BS%n$9A=(;RJOwSzfbvGsPHJ0u3zt2wj}=ha=+K61a&uJ)ql z>}X~_7t=lNzYli34|F*%6MyE(Yy}+e5Hv5HehbimA`_kqU6b1{YSZGzM==x-Y=&+Z&-7$%w4)94^Yce zhZ9<#T1S@43ghsS>klvZl^4#7Svu|3ouAB#43sFDAJZOEXTEy9zWV3nqom1BIGG3M zn{HIvp>;&SsY{<*`o(&q_~p(eF^Y7z<3qJAG+lNr7mt71D9&izAz9*%e_+}45(&V% z;mv7kY_~w+hYw zCxeXtYy$BA_t)NaMs4#fjp9r!yomY7{fQ0I(7hTN3Dmb#ep1;N1Py6*V|?;SXHIVJ z9;=V~sml(YC%_fJ(ClA+;?dcIN~)?W4t>qqFCfymd&^_woU^J2@l(fi(Z^8YR8d!7 zb8wHZp+3s$$H~th03-NCCsYHeB7fw3X&9xIk+}nr!bv|oGz2VMs@*$EL=v=>K!RnX z-*SBG9z29^VDJEU0v!$YF`vbG4XXWkhfg~9QZQ^^_g{J1|AhtdpMFbRryLLbR=K!# zZh1^E-Q?Jf_pO~w1G+63NaVznl+`95Ri6ivY&+4?0+bAhnCNI+^l%a(ly{ZxSrqbK zh!#VPx1piu_HfaNRHE41OVFFkDc2`H3l&-8IjEb>r=<$21CRW%LjR-YdoU&?1%qEu zkqbLX!@Pt*3oxz}E@C)UxE*s`By@kC*m+KR;DY|=6H~f;8CUwx6Qh^D7S4xnSNU|k zY2ZtH#iR})=X>?oAKB>tr+=Kf@CsZ|2#(l0I?A!_Z0XzS@ujV;4O41N&!-dHgs-Tn zLBpVLWJJ5K6;WH*^v5VB`&}Q@vIa&*Od$#nIK#vKaLpLj#a!py3212n?tozHz1 z#A!fL?8bEffQ#7*e&B^vfBgccX9Q?0Fc5MDWqGW)M>KxR84po=!#D{z+8E~T04Kru zGKa8rm=TvHoXg;B9PsFoBydqde~gs}`*3c84K@iiQd`>t$F616e1gxX9qDt7?u z&|;w4_?N?CYSHR2P#Az1hVVUY(#;2xl4rXqEAcGmCm z+vy=ifEOBKpW}sL?g=K(pr2UoNE9Fzp!DIJfaHO4FHj(n^Qa?UpK;6vKLSh?zz(>! z(N4*raB^ba^$VyIvOn;4^na{ZByCmL4;LHFIK{y_w?u7;qaPuTLc*xg#!~tQ437{)Ptw6-a ztz$1RV02Np)QR54Cn_QIqqd{ zUY=suNk6)%gakNItH52ZHD3U6_l@#wFKX9_G<7^Ms1~6_eEz%|yK3k$;!0Lpn(W1k z>6w|MwUHYNZUayD59$~ba^T-LZWJ(Y6~I~(7A8~`@Sw>?NIJJ|*#dkBtF$tR(^zNd zUeZg0Sjff6c?+mVdg++xXlQcxBSz6nW2^-Bx~7&^Cn!%*oRk5?hK882pJ^q~Vgu@e zUK&2PU-05^s)A#3R6LY6@ZErr=N9kf)%gH0=n3GuQU(B-!Uzk4P#2fN(IF*zLs)5y zj^gjoc>;M8VFjEbX=StwAk&taoeihBI8GC+D|{e4vZQWS14F}>pFh#kkAgE@R6Nk% zfNjl>H2a7VZu0=(!|j?&e+^1(kg!B4S@$gq_ocJow6rqFA9xvHZ?LEE`C?*liMTsx zH6VsY%kF}M9QMq!D3BfCKMKGIigRxILQD?;XG6%TD^ds*s)h|9Rwohyke#sot-Epl zBN9M73SpqWwSKn}s~;ecU(paNJOMmE-V#?5z4cu;L!|zNp(K8&W8(EN(UDV32HQ?m zNCt==CNVy?M*rmjB)1Drg+4wt=z8D;q#^;*OrD=d2dkcj4YjU8G}aC*C9JGYkf1Qs zhiylY_H1p1PMp93Ye8^CdmMM<4&ya>Po!MD5My+5H(8qX9b z(%aPYU=D!nh>~uQHc(N|Ei7!mpG;${{64V=MJi*+h=A4sS5c1b!y_X=hOIPq4nrt7F&x@+{R#H}Jsp2xB^+%D;BsMj*(Ba0c*aDBTIE z&ysGCfiu8-0n8fYhPRy0mgkM*Qd9Lm`jZoJtI=ap&3!-pvjXs2OpxcsJ5JyDx=lg` z#|9A8CUC`x+bR*~wuPlYP-mlq2yMY`@avL4KK;w}; zswBefYa%W4CDhA9`cR6&FDeoMeo0Dyy*38aKT-53$xY1q?<^=MpN?Zu-5 zCljJuh_%Y}F|o0Xknwk%c@G_R&(h24>Z!-xJ`5K!gFIL%`eta-05o3RIDnv{B-tzz@JRlYADz z!_60`UTsmK%ZgTRBKYfnz^M1{ugj$cwWX;ktN0_BuMiYZdjB4*6hZ+Lk(&IICZa}N zO^t|+-|hgq4)L&2Z;%hkG-9QtrNS2N;L4?!Xbb9Zq*$b4ZKE|KAp>GZMh5>G*&w>& zJ}YXHMC5x3RW#4;+(`pgK)w8YE)#Hb5NA0rB~|MPT>RxDOqYR9Bxzjs{*tR~KL_4L}*9 zIfUcZ6DcX89W{yks17{w0st4VaR8eI&f9yu{!KtNat~sQ0?~>v2V*Wkk&XYx*$l=J zI!#T;xzJ??wiPTM*eN=~&?bEr8Zu=3q~r=VUUfo!J<|3xLv+}9H&H})ZmX;QuAqkrC*bXSYKz1iByYmP9{L^Ni%C!|_DVY9n4-*$IbvHyxKPS#Y9?@t0XtWC`BIx_~kb_w~&pLB>bGvcn126hQA2WPv*a(}zuU+>)kXsv<&pc*r8~aHr0~U=|k* z)dzsZA?7^R-MwSy&f=aa0LQ_GXw9*7h44O5%fK}t3kXV%#aUMq)1V{i=ZrF9f zK=D21fOw+ctE!L!Ea5?$xbC1LA+}*jCMt1?LS7817{Yb52gvOnc!FTD14zWMMF&GN zgk?xmP2$xMSuiv}EmTgcqH2uKO1v{ZD+D%xTJD642A42|unA#tWdB$#MEF1y!PPeC zN&#!O+T$aRvj8G1Ft_lXDVS&B%^1vMc*m>Oa^nebo2CZgG& zR6!jWK|+ELLXh4w|5U^#LCemt@YZxl+k)T?1Bq=giP4+g3tpb-0p5oK100aauTORXL!&1p+&hHFzRR1@PT54e;Szp)(#E;T z_x>LTo&Tq`_J5(8Cn{em604sEYa|;zJ5g7!JInBshcUGJ_ig%KT|cg}Qx!_|JGMwu z5k5jRuiT>8u3u!0{GdbY$v)A#%y(-0u|F;V#~!;yxk>otn=1Q>zV(t}J5RU>T&1Iy z_K>=6-~8LPjK4yL?N9e&TSKi_VIPxWEjt0g6`&dG*(ZF1z}o(&_|g9iTZ*p<5YpWK zD>k1W{(U9(656tU>duAPz_ZIk0qf{dg-(X`>-rZJqN>1r0ZN{lgiVZ`fHKGM)XNK6 z#Xqo4O*$_fq`MS$GHrSqKM1_@)QHM+`{>kuA6d4YVDVFJd;i9-lo~6H0G@ld^ zsX!CG1|A|v9^yC636N8}Q5>SEUmXPmU&N}n42DC5y0A}H;z3?sbBH_Wr#i*Ag!XRB zA<3~6I`Bn)|vKJ)KzZ(${t&pcYad!7$3oo zfhQt%z6MXqSg;rNQd?sqxpF&nzhIf5v-klAAczIU#Sg2E&^W`8{Y7P;%VIavUzF5d z;MVc0!W0zGL&2Q*M9h?%=zc&Q;)k(a%Ux{SDF`qRP@MNZiwXxT7*Y)QDBGC?29ab; zr_>E_^70Zks8B>elrDT9?_haDwb1j(v&1XzzB*cByr)mMefi>6Gmz1;wpL_UKsG8b z%qZMVZH znN6-=M*$y;Fi$6eQvwA=XQ8txcszI+2phF%8Q#1JVQ~C^2N+bWeziFGwn9ngrLXUs zNx%Ju1QBw&1k-=G-IH$P}0=?AjFoT@k`3PF?W`hzLwfNPMrL zQHVq(_v6P7Ts2BG)I0>`0o5<~(~#yO3<;2g)bZ^qXa=8=I6H!a?Le*S?KJ@36qN^R zK9>%h#Ay6C?nVQFb;kr_s?`yc^?%mxQE4m7>cxx*v)SP&F~j1yQ!EGG<} zK6$c- z()8aPZa8DW5{3CP(B&u<>yVw*)TDwRi=r4kFtD!x=ow^ZXng`Va$t=(F1@^JVW9`G z1Nt+l%dh~{3mq6*H3C!p-fdAuWrsc}bOS>3@|K9>8Vv#{%mCoH>y_OU9zD_(EDjL} zV;l|Ej{Hm~0aUQh{!`Q4C7Y4-!?Tm{p^Im5zV$4sAIdwhC&J3b|0AXSH#i8q>E{oc*Go9I_AqDTfU7lm->%a@HhIZuX4+vtP89^TL@ z%;Bc7XT)OL$DlC3n>S+D233D2O1j1w^b1?I-s+AYXL8c}sFb-nm{m0X<<@lIx4G$w z`U=hLunNKjX~bIZG&b;TD5uo{Cye2x8~Kp3rBp}duw{P z*XZ!i1=rQd)t6U3+?!bZHdP*UUS!5~==#lTQdFmIPkrw$nh5kLHP;EP+S@5Bo7O5G zuGn#J&mvj=?q<1`$2GqL#=o$6vAa}{@(fRzRPLB@Fgzf7?-xjUAoxQ`wdN;)tVT%c zu?bE??7fAQ`%l9aZ#BYLlxRS-SatgOI} zKI|8y^^m)=zI!)G^m#vm2Ixkh00B3FI1#fOLKAhLK3&DxgasoGiIT^p|jg zaT@(KI7`9w541$Q@6X1@%;I+%@Ct=Sf*B~XaD73sg1p+z7cmRr2}ZoRh^;s$+1ym@ zj-r4BA>0JAgxG}WhNBa~y}~bid=XIY#~TwGK>Q=m&JxoEDxAI@ z80KOd;?v4OenZd>dUQY$f4(G3Jp2l9Y; z2|hY1C_)iZn<7tr*PM5QQV8IW41&GYBa$1g6kRivYw4)p@b2t^T4K+kcJ<+dsOxGN zo&U?1@enTh$1;{syFv|!TL#I6tXJ#R?#>_{P;J>_(waGh8;f%EPm3I*lm@g!O+Sc{ zS+o#uv6j}kmuLd6SyWUcKxyDA%D@B-0%?f-Xv#$=sFhp;M2a z%g<(CV!3rAHbPrTz+N)`qu+SNopT<>r%ZixaDt269Z5Z1GeA4m*wVJ>)3IADdY~fc zihXiGmhlC}CFZ!|)OP2oY3J6)2f*&gJz8@Jd|BxH^{nN$uUqHzIO$&0=6%bTd-ap= zQ^V)$HwK@pQya^DBP$IYu@rHU)Dnw`%QU5&H5Y7ZX{(W0OOL7k>BP#Pb9;6{?@6mk zLrxgIC;M^BD?(2?_q0`7&Mv%p@1E+dQ>b3Tz`4t#BQ(T^diw3!4vF^AkfK%J?y*~% zC3;>Z?>T3%LU7nZYyi+UoDUCIkEQI=*Vjkof}u&EJ%op5C#cKM%AW< z=+q!j-6a7vHc+~w-EFO`fK10355+k&PqD~c0fNIo;iFCp=(*UBDC-7!kq#4n%xxYk zdpY393I+!TFNC5MRP}D&M5oih(vq)Wx~VDP2TCOz<7lv>(~{mY3Yb`hdyuMz#y!YT z;8=LIdgBhZCr&mV#>$o!O(yLmGBPcEVkofz{fvqmz15*PbWoJk)NHM->8Unikev0A zDgA?v1-KP5Y)F=|9}1|5xf(QbATtG~G>*5}8FwDU44C{tb>IdflxqJh?(WNw5R9g= zs-YnW2`%UbpyN$R6uUS12^~+2G~lF%zCYRBv@fWn`!?sZi@;^8sfDBG*|Gjg-`}nn zn?W*z=+W}>4k_dfdrMe4p^${GtobR`Y#K8k7X@8Undl@c2(V6$$v8~^BrOlpB;_Ix%*hFWvu4k<5-nBIrt%!9#v1(rrIa1he z_H#rV$pv#bS2@oewJ}TmA(kT4L&=o?Uga9_f<*5@MUqJSQdiOYWjd`xJMYKpLs5so zlQ%|&nszySY1P>BlcGHYKAGg=sm`AAao z^1R27r|BhzL_{dgpMqjCBjd$GTUYM(jkh3Mfr;BShX-Sg~9{{SsjvJ^jilZBN<2ela?d4rB1&LV%4qaV&5eDkf;K-Hf959%9l_UO?g z!t*XhZue#Rwp$3?5DdX|7e99j0#B_v^?KG z7LeX?ED+YVgd1;PU+I@GkVW#PL-!1^h}hWh0|z-FbYHEB#R0*E-q}d)ttriyS9 zE?-rb%~`HV33ZyKzV#z7c`4w-_I*Abd(xJFEjIO>u-bCu*~Zmu4?0#qC5g1?51Bmt zse=p8-Q$W`c1To6|Mn7nkh$?$RHBaQyQI4M&nJGDhrU@O+#iC*iWa6zzNMcak))Q3 z*6NOMz6VczzYxi-(#W%vVQpVy-LG8|R$Dya@Fc{1BIM)S)4F;-*4 zrj7fTN4~edZPvF8c7aYhdu0kBi$D1QS#TjE=U?=f!>;!*wtrfSeE zIJszQz9OI40@Q$Aq!LC(BG8NQld*<&3PL`lZq7q(_Ul(8v`|zTi3SC7OEi>2>FVJm zh4ut=W<;uGpoKxTZ5t-Ck4B3tc;h~NfU^BfM0x}u#7s{wFEb3z0(a{m#=d!TKb%W> zMwyBPK?2AQpnTNMFanh!8RYyx^G9^oZ{D=)t6+E*W#DdVI$7QvpUVa0LsN0_Z@fGF zvf?9`x%PPhYKI#_c4lH>u>>eWUWlRg#-$xQcA#p6Ei5ZBazS_a5>(M-WsPx<8M0HS ztF`aW#DCtM-QBt{np+N!mA$zqPnLBhtM9WD)}HBdwGcO$is0*t8#YZ!Yk9X}#+`dr zxKv70R$1{%Q*)`&)vIz29*6ZcwP+E(t;X7VtJ-QhO{VPK`7SgBDD+Q=G?ytYO;bFM z?EMHm{N!{sL4&f!Dp852pu0)0cE~8oy{0?icRA2idLeWSxP-vEp`UcIyr| z%5&I86bOzDJgY7|L8!b$#;t3RROniNnC2KdL*O^o1(<-_PxL|E+&1P8%xuLN8t9h= zggli0Z`Q0Zp+u_o`s_g&Sy{i;%~*CYdBF_D{4#As4Jac;x4R$8uu&jd3wX+C#5pK0 z;idwO;O2|=yxe9r2}#Kzdo~0@2-OwqD+D&fnjK+~Ku`<}TsVLJVh+ZW5Su#X+(itq z$rEvy4sLy1j0pq;B`}J5Su-GPT;GcX4rnms-56Gc8(w|@EpE(z`j*u3VhAdw*Y8ww zXg?YrmVKLNdy&9|N8!Iil_}BDD!_$ZGxG~yJPt{(p@JI{k-mkAh?VKvZzW9Y{O>{y zY9+jugowgzYlkH)OorXN5q)%Xtvw38Sp9BcX`!ctDH!-ZtJ+XU7CLom5s5KGB#`Rx z@LbB~#Bdt<6RIJUBJNL~z!SLH#)$pQDG@L_IqBhr(qI>^ZgC3JZh&Jj=D>k{8N*vX znZVru_AJ86?XL$1Nu+Zk9I+K|Wq`MU`gpWanhOU!Nis)14RiQ;^lOGl0e9l!zeR&frvi3=)v-MxG0F6JSlo_^5C&!lUwD~l$AAeETC872X(kJHqA&Ckq)7n zMa-yQ+ws%P7E+A>v52DuAy=tb^NIe6;q-PO zWFeuqa~oI|wE6+`Q60=k$PfC~LzwO%RFU`7554zJPoE}s6^q!rq$CK8C>c{j6bAk1 z{j@iM;vwH^hWM`cfioh|YQ!7?3B3BL^~CTFr72!xAHR{;s1@8R@EV>{QLqdQQOp)0 zl44A8&(1 zaE|7JU*FgmGoTndcS4TnDOcMQ;J8AoR}EhHncomBIv<#Xo{$5c9I_6ud@)TsBOqXn z7Q3~&MW0<7;zuA2fuF)y{G-cCZg_Ynp&hs8YGBH)y&2suR0a-F+ep#KO#oGMU7m6W z$^~_cwXJya|6#veko&gDoVp;bhnyT$e}el)$85k7)2*Z(64@)O4Z|hsu@)kAWoJz( zZ@k@nkDqaVEKwKKBU~XBr4UZ57Q1%0#JDfZE~4nU)2hW1IYKl-zouF8_^fAbS=|i!w{WQ^RNN~u%vk&Bd`|s!&T{j??5eHbuBZLDG@LhGQ`A2u z5|>!T{39zc@!92IH3e$pmde$9oufPLMKrvIyCtdY7mv=?C4Kp(;%*th=Ir z&!03C1Cico=1b(Bo9JVryABMi^S74WTzq^*c&Nxg1-?~<2y(;XE`^zTol=ILdFW!U z2KW0GhE@DIZ_#sfqP1xs&cpS~@3}-T3}{>3xdY+PSEugAhJ>`Dk!56+XRPBrG7@p~W`Goq<)J{ow4`dkNZO-zP@VWSO^*{K7rUL~f z^o06!w-O`2Ey}UyWaQ*R`EM%p^@09Rv|=FT?BI|)I*m#Ur*C=M^{%hjI%tsg*^n)H z1O&)HDMb|Iojpis#o>41Faxao)PU%0=3bi-sM1>hN%#?G!|j;3IPf1^koJ|85hqvu zggQiUZyknFqR4ZD?O%rtKu=iVguveCHKPmWM|B|8N5BN_lTc?SXg#PEXMjR)l>)+X|$KvQkqQsAEe z1(xiV>S_hCKASCr)@n3-ZGX!VQhrl_UZfJFKf37$WJG&uN z>H-zf5Pbcbz)0c5+_Ps7U_AG$jnpwPAOnVlpU2X}JP>!#9H1VUHxUZ#U(YY$C`QN{ zwB&l~;qejeT&QW9TG(KkODJFg@C%~>+~A7_W^uHR5ECu{*`+Xpe7&$Jv=>uPhmM_#ny2Z!v=fC#jb$!<`E^$XK+Eav>qE7Rj%hpq zAOAqV(d&MU?j?}JR3v1_gw!aS#+ZnFxjuz#go=c_b4ft~{d58&gV3Q^!*&we8*N^k zpE0SauF!>)C<9g!8SNPeaw1bjX9J1pE(xRzSf|IY{Uf}OQqCrRn)5P}5>z(W`1;vq zSYIuGQ4SIK^pKDc!Y~3>Q1~=dB(%A4Dgr+U;XI2#jy5#afojG#qhsIx^XE%%J~z5F zjjBE?@rj2J(8mh(rAvpU78J<1h&2k}Z(o0ZHpbHc--L_W`_n;rpAK7ug(U(Dbce+M z-jQEko2uU?1ReFyCW7Wd)hllgRZP=e+24?%sX#>Q#&`<*DGn z$v`=Ur3uL*OoaitDMF~hh!~A=wZUgt4CgOjuE#S$x&%}5kpPewpz5CRY!9ac=J?w54)pkvb2wFa0-Iy?AT|BN71 zDC|lIVq0NVoOw&x*mwvjJrxP(6lU@f$g41K!wW#+Mm=&`FsHsEPyOVrz+?fGN-mZP zFjMB{_7qbJ)JE=B`_EJaxYVYCkaeGKZSy?w9|`-B*9`w%7u1CqFGwjXr$J{6B9yhY zw~@b+Bcfr44)QPC&Oajhsd{d7sDR++g;WsET7=B=@`4}TG|Xo(;)akn4r81r|13Ae zZ#0l`vi00PiQ3)zly&vW@-onfkb1zxkeY;I3u=SVLB@-bUDFvR@8tvOjrnKz$vf9^ zq>*3vp<@8}kDNG1YoB|jxOSt%jUn^7c_$UIDa2wR znt*4xeR4mPtdY|rau1sf_V=H^koofEp4{N1imz%JiiIy21S~ssot^^H~(iEj`e&|??SYPIP6l>10g`ALg_R^n5EHHn=Cilw$7^r zd~Z#Na@4Qijo!1L3>68z93&%Pr5ps6JSHkif(nA6AQ{1J>MWdLP)*?Ik*C^=5P+Bh zuEEu7*Xl6&5sRq#UmJ8|gM%`Jo*0@2479p^%F%eZ5Q)G^fmY+jzs>=(5EB^f>TAmn zdPObs_NF6zeCn4k!+S$PIId^bTG$ z@W+9M7#vRmBYH@G4Ic${5l(X=uK-YniU1YROIJsT>*u02zJ@hNz#Cq_CZ==%k=0e= zwjw7(R2IOY&$V3(0$<7Cu>KoOO5cbGBnmDR3WUlWe##*>HZ(xl;xgrk#-j?3qjXdt zRh|lS?Gm&8g~3;nm7TC~4n~Bq$phlU??7HcdRo(H0H2|an7#9$ldSLop-|nWAweES zPm6L~9r7MBk1hby&m#j>TP})!bPP;ZIY+j!r$z+%q4K?E%H~3wa z@3lNefX2=MqMS@~4)+&17!C=vv*SNQYKMj1ofbwf}tp{@d~lz4hbaMEdX-II}g$#A2K{cT`|TgMGUS+x&3X#4>* z0!aJwXV0SI6jkFYb|K!PC&cJkiZ(a)1r47&)XI^uZQXkJ_U$)0sp)C5ShKYeQw-mu z`h(L&BJ#!b72w@|?AUuyZuE`kxwMV@WL28Xk0c3I?^(CU{o000f*Dy^z?mRhcqPT^ z@%%Xrlr`Pjaa=%v6#zyjv53k(C<^$=Wa6M&Shz>)4w;6V*tcd`82mYo+&t7m^7VfCKR&thb z`~ubx<64UeHyb;<^W5JULzxMk=H`}T^;6MolA6jwPfzqKaIm2-WMSa~TSPw@G8j5=RLeaJUsfb%Ek%{kh#l(Y zYKbyX{QUj4lM#DZO9X{ler`vyoHR0%CbuHvNV3imIWmR)-1&Ou@paUu&P2#!0l6*~G_bI;W^ zRiSADEC9|R#h1q-D_YewkK-O7QWF@kY9j~|;5b8|t;+5+4ZysE2stn+uA`zVY{(hU* zm`tzrH=3X{y7~fywad77z`zvU`5TERo+{T8PY?=LW%?$ZBbGV=mLMAgZ~;K0bp1j) z5G0s{8oL5sEU>ZSV!Md>+FnxRTw&7sScl+$CyGvJwl9eQN%^*zzYh($IrPdYLh%!_ z80Fz{$Lzu`{zn(R{yV72_bdLq3aBlqNlAI2PBD7+_4G8HrED5>H$aG0L;Bunlc0#y zl$Vp!S8VTN0y#mreYbQUz>jY^_Gu%!4>ph1AI2Z|DGxXa?CiED|J^?h*Kn4k^azvD z(YW6(nCWBr@%&!fl3Yf-*rY5#x!O?IEm6L8tDF}9V)Dr-?d1Ny{{VUIQ?>W;>W~;cIk~K)Skt5=WQT`N=i=*cT+%dogz`Smeye4Y$~Ji!jN7@6 zNvz$AAS(a&ulT?H5x@FPbhGbF)ZyGI2f5{5?Ok}=`1N4vT+N{WxL+ugeIj>xyZs!1 z$ttofMd!|YH{l!ZD@OgRZ0?iU1l}6XD@W-`U+|S7-8 zhdpeiq0SeUC0MK+4yte%#J zq{8MjKFqp!ODMV^Xaju=*eO~FW(F2|Iyzy2fwEMmT*u9BnC?RsDp7_Gq1iigDBn_% zhK!Bl6v7d_rY3K7e)$nZ=N}aQwP$MEwr$j;a#>lsaVexcLs}ALQ2)Q7Ul@H_7YY!N zXhBsXRozM@Q7btStS)#GC1)HjIoV+?(45TC$X3<`mhj4YL`yeyK~ zoRE;*NI&J(t=jX|sjwAM5JgHpygllpJup;6hD1#Y!W?YFU&uA_ zX&@(=5E*$Sj+?koYu7KQdgbPxS5lgI`gAf`TWIgT(37Y0ezvxhf4}ZmO5kf&FrWY# z7EW_G<+L&12`Dw}@(#$Ri4KpL zG(hsO(c@gMJFS=JkY!oqvKQKpY=?gwy8;@)x0VeaMQGlC&rn*fSHRf=8 zOp>Rksm(sL!*@y`4bRHV;^#jRodkI#Jqy^`Q9I6$9(F^P4FU+&#>b_l?2P@4WFaJe zsr&skc+}eNuRC*Q1#4oM6^xg4PI8x?otV0F=kLdVBPWG)AbxAUdNm*Q7Gy6t#n5zZ zKBxRbgYB~atHxqLfp~(z<~~yZ4YKHW;G09^9gUp-20oXsT!zWZA5t>(nxOo6)6lRG&$Xf=N*Ku>@ef8uY=y3C-I7r;rE6Uu zzVul^_D9S~20h8f#(Ex39yWTsJ~6Po%YmjB{~2EMiEsd$3s~K-HmNs0EH3`*V;Ir5 zZS;~YIAMe%(6ZZpNli*t_PBcqz|Q6kLig!@Pjpdw%WDt0Z$}9JH(93CWxB@<7C0 z=!cLG%NZ}K?Gv4E2Xe%&;q4b*ggh}CCg|v3t0?4AJ*ynvz;p`ec)ow#Tjd}t)T_G#{~9kW zuJj_!q+O#^6@R<$Ft-7HZ-k@7ZZ<~_k(4~8_Yx<$lIb^SD3b69IxZQ_1ozLSjO*OKlJ#1|{`%*Ki*|2WAZJz{^B2W|qSu+6nVHpVWxejc zs2D*v zeZ1#}jdJW~Jm>m*T0Vocus4|PlYSNQidVbvcV*@Jb+>*2K&66oZu$fDE{rnH+dZgV zU0t$L^064VzyQD&c{yXjdpjkcSNySZIn<{dzEn5K9;Bm_*tFW{iggj4blD&AIpkO0 zQ1?We$#_@`aB20bSbrHt-zP0v+Vc!&#QSTKQY2UFO59)PS>Lg;!uWje!py;?*tvQW zItk8aiIeASpq%ji0dspt*yim_8rOb5{a4M`Ga(Mjv ztmI1OEn^BxOz{6KR+KuP56 zTuavL)uqM<;a8+t@V4yT`Y=gc-+Dk8Ut+=Nsryzph#=c`k_#_?MM1!m{UD+adr^AYd6%8 zG=Q_*^yL0^XCD=hLJbNGoP%zn4h!2?Mq-LqLspkm_$(*nLvvaM(q5muwtwYwZNlB) zww&S^mR`h+TTK=2H3RCL@(1a*F`wXMKV|~=wBUaa0&LV1J&iBL`x6&kJkNOUDZs2TG>v9ZQ1J~ zJXXP&1y$HzD~_&vk6X=#5OUs!E^GsYs;R!-qON!bd=jz{YJFwD9Jqm~rj)}*>6_O9 zc&ZfHc04)v$bGac=O#4F1oh*YG&HmTF>rgV8|NyoDCgGUC-B9sKVDLso0B3$e=#!E3p}`?;CB|nk$lv z&XKs|+(YCe)zUj8AfXG6v&me${jS58Fgx#lPkID7ViAjFH~X2@vZ}I|$DKpW79*6k zDgH(`Z}MuU=c{r@fLjtNxv6hpAXOvFXyt3QnUG_9>J8MfRHV(@nfHpju#$Qw{d^M| zRtG*bLE6TvpkSp!)znM=l8hjqbKvQ(-@ZkN*j%rG{_CZcm8rMx(CK?;l>sp}B&8hh zuMG5YtD71dPXv41$}HWwoA^jW^$fUg{4NoPLmk$yV>;1Kb4>M3LIN6y80Sb+MRJ;~ zg)yAkH!TY&~f5CNu3;QVWN9Q zTvs2iZg63B6UZ`!)J$HI`s#S+TLL@}DHI5g!})f+!}I77-m%)W8+{EeY0jZGH%4an ztv=_&r+2iS2wQGbk&v<3o!Fm*X|E4p{ks_%6B80T^6e#MWfPLCq9YQD6yari$c;xY zoxGS4w|RA&t)1n0D!5&HLWZE9w++X}%8GVeZ0zdZ{D?xT=T+7FUI~gg^u7n&>B*DW z(KHmwaHcu6-y7X#(iBnCs6D@HG3DQ)u7b%m?rJjK~J3OQ|Z(nZ7GM)c8)4xjs5T=OW;HAOtHS2h=WHn)PJjJqe*A&e=h*x<{1d`U|`3_>Nx+Ju6!{O}NCE)&GVG*e`INZO= z+JB1l1+vKSv4(iRMA?{j4&XVV+3N#6DFaaMqxB|qZ7~PX`*6D)uot?W%+Adf^%d~s zINFXDVO0Ff-NysB4nboX@&{do40Q=t+@Zn^*HJgU)nA`iT$~-DvE#G%v_kF_bl6)r zZIrpGJ2*Tft1f+^i`^~LxGhL5D%;yT!Nd5{rEMg(H@c1QNDD|c%4VeH z72htjR_}72Pf3jsmsTcsgC!Sm#UGUb6$v|ZjPe|^d-Jrzi{)>db?8Y_SLEbGsLMeE zgXxIK#D%U;_v!pYXVw%ci(aieB?PhrV)z(+zCkppE9>rsi8luj^`Fo#z9RG#l|pwV zczKJh{fMLl7oLzjBuN+wkY{Pq@1}EG280I%g$B13N^Tz7$;|wEOB<$V_2QVd?vzO6 z*}U$}`s+@qmuzj-fP`cg8(ecD#>IILKD)X;wi|Bj`xNncrho0ujM2-~RG`mI`s$H8 zWM*Z>6s2qM3>7G2m~PowXxu-}n&<4Irx%ztytgq)d;UOox3Jwr>v8L{H)E%5?HzNh ztgX>j0ixdXdzjd4!>5QL-i-7NSY##f;B*c~1MYdzs01a=@z0nzxj03C>$Xw)9WEIa zuPOm4{17dry|gBthCF|;Az@LX>Fk9IIjDx;zaJSpZ4H3(>a##dJzkcS3=rEQt^Cpc z<2N7o{o)5$^Uk^GiFLe6`$zb;ZC{}>bH3kr z$Qb8Cs=T0B!js_DncAD`rwSX_*_CZrcjcH^1q>!4=zb6PD^Mw`_U)4BuhGBSoS9&~ z;i$UbD(Tq!2G{`~fK$Yh+836sjOv<95jAn*(p*rQ zLkIMcp-Y-&(P4D9NG#88j67#i+A{@zn|%jdW`37$t>=6jm&8FwuVyDj>%MDw;9N`F z>&&cGc`b1A^>Pi66|ET<7^$4f{nCB=9v7FY%W9Jo)?~8wJQOn8^Y2JLsD>M0szpXs z)ur4-9!5E7&5u_GQVJaAgE~Has`aJw5AmO#>=q{kz1@XO)b1+lM+Idi|C;K#klLdM z=5R}Ky}t^-y=L}wR;pq(-^$I}<^~S8Ue$p7_3QRLZ`p7O)8V#kv+SH4yW6)9x*_Jq zPc0rfQvOG|mOBZd`Um;zlk?~CcL;Lsd?uCLmYI-#7V46GdS7~%o3p~n-I7q}j}W!L zL}mP=>d@4RBj$$2-9#JO`9LNH_nC9^@=j~vtE!MT_>p3)W)y3^0kvs+*jf>H#pk-? zTwIqarhaE9*8+`yz-gX(<6kX@F0zc*LjhlgN3X!-acF3siy$*sip6-2g|v)o!R=L@ ztM0Z~QT1L);lr7(8i{mmqY2)KHH9a223vOLzCYEbm9``^r!wPTshMqgv zotRg#y7m4eeVa5M%k^TbzWcdIROr}0st4YXWzFMD6>PL zyL`A_T2!(hgTEe*OCq;_)nu8fA!HRGOI#}~$gyl=?GVPQLs^2ER?G|=*I;u|QEmt& z^~MEQoowHlw~9=$wY612;k%o!%gSc3;GQoVF7(L-<4n{0cwKYg6K#5qNkiD{*N|(0 zr?)cn|EcVk`sJk-7q$RykC(Qi?GnWHL;o?y+{s4!Jr|9Jp%X2iXVGgcI67% zR>j+xs@rYbwrtbBai|skH0TR>H@VKi&FH|%dm8HMmUC4@GXvkVO-+hSYxEpK;($T> zTynuIL_jlt@PD-T7En>QZ@Z|zzAC7Mq9P#Rh=72AAR+xa5=uAHBGM%}l4F1f2&i;R zcX!MnBA|pw3?n%-l0$dwd;Ip^>-%>6_de^av(7o|92W~+I86NFdG7nVuLvcdKttJs zE0EYLZ8_(Hp!91#S;U6pL7MGNj$$Eg{bjHj~OmG=8T=^B#Vczy*D(`b+>dq!( zE%YkUw+~?`UU4LDxx7wSYVDkCJw4WCaNn?TuvW`&>R@+E%JI4jG_KHYfUhge)ia|Y zm3om%QWaF0Qp!tL|B5MvzqG$U3or? zp&Zuw7N^Y2(EEJ6oSaS*8Qe;?u@7p8#3l40Y!GN0o&9%?d_uqG;A}S5H(;n#YBwMG z$p}*}VBY?FKZbPxr`#yiCG5pqEZY`v z&qQ(T|ly{GT;FX%X$?Q z;MqAj_Jm>uZ24cuc48qr1ZXI&t@Ha=IV211S@!;5I4eCq3~VP(G@3JY9MwGk;Nui< zOQkdN-aSmFQcRsm&*jrGFWF_^zN=%r4mw8y;44kTp2NUJZOu+8nsnmQi)Io4&b)EG+ah45n~O&pBN2ZcbKhOK+Vk^Ts~5MVBvz-8kmZCMXH_5p#pu4L zS~~-ER`<2d`#!R^rFGiuTwu${fLiz9BX7O$K%Br%B_Npjy5%LGiaxitTF+N7CMI%E z8FCzZA4gxjijlLfD~S8-Ibm_ZZDu#sSO@K3n9zH+5TlvL7!(avOQU zEHia=jqx3y$)D(|IS7#f0O{DqiGK+|{d;gqF_F+v=80DpVIsx$Np|*fgiRUx9kF-b z?D@n)g2ry2|KscXqJfk*b?pD6f8AY`sFOxUoRxV(E~6IiWkcen3Igf>zeEF08b%!< zI*sT8d^tJxgui_&CPmdQKLn$~uOC11Crfu1NtRF1nYH>|_XQuEW2cgD!kFYEiu=$X z+*_(%Hi^Oyx6|HiuE6}`H4n)fw%H027OP&90?Gbm@Zr0MYHYl`sGX8@3+1GbhTCkC zK0g+8X@4Z;+86eQhePZI1SmiYYe%R6qgEZa#b5>#z+!sK*?=Gcw$^)-<`-oEDzCXr z8a$P^qyfNSQmPB-*eXz{9srgd5E@E*rp@M;jD|w4?j0&hIZETM<@MRHOKnl)eqS8W zmZZzjH@^OLb^&xyK|u;kQa(VvQzC<0Bgl~M1CE#dGMnTf+&#WX6G$17N zLnO6}V1og+cbQpPzw%;EBPPM<3iia2k^J9Jl3-V1S@1A2T7rI7)OBeU-*mPkuCl1C z1nhaM&tIM%Zmli)v~0GZfPB#05(*$Qx4s8Vg3lvC?s@%7BlsKwURPl`wzI(lQ@_us z7mgVI1#ndx3JAOy)a3hqDE=z)El~C`LfnF`AZ~1CWBb9wV}dUPN@qB=x}N&3BqI6E z%S4!-MqH$g(|=@uahlp()c2)^lxpw63SgXC&_;nFxbT-RkZb_<5io#;>4jaafcqL7 zwyFvO5noa}Z7E+wfIw=}$fXP#)Uro5HVj+lOkUX7&_c*2gucLsaVs_@3%WI!B1766 zfbMa^_E3L#xVuYLp^4waTK?qLz>Y(E@uEOQ1@_tDzSNQUwl)3&I68ZNE`lpodVG4C ziDqrz8;LZvflaU$rZAA^myWl#whF$&rw&Pg;gP>*X93nM7BT~^zALdSjQY#ivn6R6 z56{XPXZFAr4e~jhJ`i~RA&kO>;pYbv48M!4vvj>doikh<9ENqzUQ$0XGjrW&jeyke zp?CvURvA$FS{1_?10p1-f8^wJpz?%$5{B3yG=gEg&Ev=4CbBOeAvGFW6_SQ|eg=JH zRF-Mc79BkUI|3=1z`PVRBdOONYHNi}OX(u-0{V`ndm}?v{3tCg5BBRTw9J{Ag+PLo z`r5Xq zoNgSB8;W*G9v)uaEtvOla*AtdVH2^5Bz2oI&^4766fPZZU7A_{qz*d3nm4k3AA!ma z(=mJd5csaDT4X(RcYj(5%yGae$>TND80}yW6~92s*O{M3PlIe{hyh>7==x^ugyvYD=ioMB)4=Y_ieki+D2L;U!0xHNP0j87oPoBt=C0m@vD}ohg zb8t~!o+6V1X zuZhu7P<^MJ&{7Atjk!5%h)*1L5S$aW)kfix0V zRgB_Fsz0pxhYZq!SJu{!N)%4QZjfM#!|S-R|A%5p4|_9kpxoI%v@i**YC7UDVA?ZO zeW%AK=^if-E8N$Ylc1w`%n6g{+h>z9H8U5bK()cd;%P=70zRvNB!dL27($ud+kg@! z;j#Ub7IO>E>Bbqg=^#sMD_Fy9=@Z&V9B-GA#9W!90!n4v^3-4g9xVxOJkx3aRyvOp4teEZ-Agt;#U zPARx^a+2_gx~cccjer+J<`)eT2KM5f(=O22>WkHK1{q?VBK+l?k#9ei06kzr>IR#h zp#81dm&wT=iVPlrJ1>7pQCXTziO+Cs2;&J-Y#JG)iO$HhuffsbF|3HE=C|35w>K1p zL_z4EWDE^WyC*q(sUav0zJOquo^EAls!>Qwx?JVH*4tYOZ<{n$VWqHeIM3|H%iJw&>Vs9^>jj(nbn6a?UxvGBbs! zp}O-zt^-6c3j)5$!QMvpf!fW$-ASQ-k$&Izz#nYo5%?+m{%|N4l?0hb);3m^5O=fb z51-=2fT>Y9EH{98Z+`E9MG2_965uk#B=Pz;D=cXn`ZQWvk_h0Lope)*lc#m64(v`k zIyVB6^it=N&Ui^&E{gFLiG|R60h(`aQ0W4Xq4uZf48aGoAz24{cMsJ3GLn2uZqa)e#GZSYKQ4yu$2|M;Xj0 z>b4JYgUD|H)&&Q@JAEh)&Nd5>V3n&~U{Gx>Xcz)xo%P{b*xDr|^jCNGd8KqoMd{@U zhvtHY-HkZg#f82CT~bzLYRkLDUwrSe8?&Xb?jLOq_KK-}nD5(XkUvGi1cHk3!AkpJ z#yi%-131PLC(w+-P=>91*#U&CJz6lXaSS;45U>wg-||8`0LUl=~ZFaDqLi~N7uuRBh9K8Ie7 zHt_3(z?7RieWXU$8e*+4{<$^T^1l3S@GZz0dwq7&*t508rdFZ_hBEsz32a{7o!NQkgA zv4WtDN4(`wpS&mxHc^v>BK+Gou)lyT_Ju=?Cosxf!a-OcQs)iDEFOxd=?;BR+R-3^ zLIh#1;LiYhwZIgRPl2oeCTwwnYu9`s?G1F?IKZ^D-TGkj0sA7rb=y0*p}hTrB1FG3 zaW=ad$jQqEGveTYfSu*B*FiyC2kA#~{Dy&&L4nR>j;r$w9YEbB1ynInTJPjhZ{^ZBF ztb93%d<%+A9G4E*@H;s>MTB1jl^!_)xZH=B-!QwD_9Z}WF{G)6tv&p+xj^}0<_uD3`Mkj=Uxr_|eCCBQ(b>WZmv$JtA zF%n+e@7tzelX1Lzl~m>f%`6CueHjuWlvKz@pdo!CQIlWCXrKWY%LZd1D4lH>;z)`e zuuYW#wW5>9i2!Luhm5>5&_Zx|sr((+v1vQIF?x6aX?0+MG>ZhDHdrJUI{Y((fJ^4_ z8R(b-Q~vgQ1-!=f^|G1HL=Zy28w8^ ztQW|W3NKUtRED;`ZK|fK#;C^SLyLx%s_J`5AAqf4bOs_G81Pjmo`GlVJ@G$?ai8~1 z$ubl}ZEx?2h>H5`r2^B<>-4sG`hO-5L)`9~AMDHwfEd7G85qzTafN#6g~zg~44o2p z2V@-vD=FT_kgDK4($D@>!Hn*B61Fo((YW+SeEi<;KM;L4O**HBGmGS|GEKSAJH2SgB@Ab5EzYs#}ZIAUVx2qlv3;IcBY>oI=bO9iGMpwaR3 zlc;fpSzdShFe?|A22Z*5D1=A$;4||SDMMHP+CD{XhK-~O2$0OL19)Ic3xNM~bNwV1 z6alOK4h)-Vc@1vE$nVP+q&xm3eIyWLjzryDrlx>eDCM*s4Fm7WLiNTJmyy6Q(b$+jy;_L!~$d^-1Mwt1)vV~ADc;H+(;(dzpHxNbh z-kT;T8IHoZqgSQlu+#(B`Yx0X(V`4eq$Ck#~vZ+0RN%Z8JwlP-lag zmF3DuUS9vkF|g4-T8QcCwnlrxtwcxRB&voHj((#ESu;0pe#1bz0u;~Z6u5FtTU3@c z-Jvl7v1ve5R3+pv8>iYEKRi(#eT=|7z0vwjU0a(;*kl|8tl-YbVYvO@w;!0-9_{gb zhBZn+t+X#tUf*vZ5u+frZ#E}<5^N~gp1XT&?^VQ#S-3$P?*b7OOL;NChgzxRRj>x3h$UOb1ySEVq&Y6@{lDAho*InkdWK$hiQv42{ z?7l&fGk_mEY>8(OXU?oPioPj0T+(kdr%8&r?ez6FLK>tvmJbFajJ>UN%RN!6C$>U= z(~oc<3J8#Re(u!$4R(1a{ZTs*-utWtp9IUOg7qr^LCs)=D~@Mh-+#jNKPH1a~9KpJ!75<|COorXbubW_k}ZPA#Au#0beotS8> z^=;Y+f`ml*w$)soXz-H?zRH&epZ^5SxX<1qzF;gNPb$`$9#-W2=kV#uZP3aJDrYII zZ2Bv6g6wd3E-}kW0vC~u^C2LQd-powTpsaOx5cPx6fjPR!-Y_M78H-81+>Z9{ z{+bDI^!%lM?}mMRerRPZC-VM)&*m&U#$z*DV8u#pqeWsRhWx}S(DWuIE)n_r!bV4865R30jG zoJg}^=QS$1mEyT&!7gEn@pM`(fUTNCBU55$()VCbgURQ2LsO9C^Zfw!OeIedUg52s z@11a&b#r=pdZu<+x_jn3)EmMEyWE=BzjcnT#2VL1)InQ_%(XO!+Tl1Y$5L1|GL#3C zN*t!S?xbn4hcKh(zPErVb#G+vtbgbg=!3^)&Jqf9_D`Oj_Ip!-_8xf!NE+ys=AWAN zl)+8VA|bl#bgu#IZJR?F>X#3;3eHq%33jLGJ|2w~l-io@sob&az~}8$qYdh+Mw&8u zW+Qow1l)#9K@|&rjo#*8G-wh%o(MeW94^y?99&vIYV*>a zd|}$JE$re)Uw}2#nW!cgy1%kBlhon9QSR$|oLINk2Py^@7SGGHw8Z{W&mZyl0`A2@ z2kj}^N%TQSWn!Wc@@>ZP<1BBeloS-sQc@l|c2KdaBAjZEIo*dC);MWxlCw%%ab!=B0!A02vWF)-lF(c-EOyo-TJMXxp&|HmgHQQ6B3en_yBoXNJsE@j{Tsi}Tiw0_Q;-5~U4D=C)KG~- zUuw8Jn1)%tfD&$SHI`Y^_+Yp)s?gwYHMcNb?0qz1%Om~^?KKooUWr!MfCKSO6 z6q&D=0ERAWosx_a(`pg&uEU%oM~^?asN%eauvj0D1S7Js`5Bb{+O$@}LmN17%(o0~ z87|`XUt_F!QK-;A7tnz@Xx0ElaE6pZua*S-0-}AY* zar&mOB|Y`>{u(cj!S-QWPo|-#CGsukO7-5%)zx7DZ>K-AzP7csy6!QyGh@#jP2G=~ z7bDh@f-Hjrb3kc@mU`-G;X53fuF_o*|^4J$SqR1^khXa?8UV7C~T` z|5{HUn=URcVe{+ToZ;3JXJ=HD(e~cnS;WA}(+Uni2lAud*@?QPZ(V-0Pv%t!juJey zlT*`uX=ivH9NOmRq?Ys?U7gDf76t1lSz%EV_WJsbHHcPrYjFeCHfN_2n@23Lv#mcR za5=*&1+5#0mqBa&4c};R@YpydIU?B|1lI1}yWd-SGU~w-q!|(@z^2wfU(duOd5>^p zOoP06F>sn&??EB(9aZARyV9NI<`)YMum)DSOEr6m4XB}Nv$kn7g+H60Oi+}p?B+QN zJ1OUBFch^oTt1jP&q9o>5uiJDBC`+)u*$*v~_ADSR z=!l-jJ-!_t8KHZ0p-MY9f--ZL#w=Mo*VxBw`aadj(f1Z+L)RLkggc&V8Np$R-aG3p)L!HFqXssT`{?1AcdDs*hdXYtO^nZR z(G%S%WJz-Urjpuywo}r=%#f*_4Mm^0WA_bOTD+d%h~B+>hvh5jY{7X(`8*Y|&W?`f zkp^`x-|?M=+FJW>^SzNix;0)-94kfSzGi20pnk!Jr65OoZD6D{iWto(}z{JfRTZ zZCwtoB^sF+Tg>fi*G6+luxn6Ikhr+G7AHJ=J=QzbpMWBkq$|L(H!wIzRgL-`$sGs2 zpB4(?E350AJO%qE+JlQyF&-{vR zc1j&|N*)B0>@3y#Lx7|uloXMEq6zjLu|#7Jt5`Ei)#0VYtixlK_WkP5svkr3RPMdh z57sWfCns-F%)hO#ce+*M8uYu5I|b6fIILQ7OD!NN_m)03g)%q_?)IY{ivtz8tg$9- zeQ-2Dl2M_y^t99lL-mtYdv6!x_C;^;@CeaU0Lu`h)-19k%#1Y7d!LRIy!SdLRK#6} z_3p;}lgds!o&idEKVm&CL>64QaNQSN#+{s8+P_r+w>x^>7=mN;4dDYu)k_wj1cJ>AYWJga3i` z_DGO$mi5S0&mJhVL-7%lX0koq-Dw{M^ZWX~(IdQ7d$OUr`NOd0%DFe7)Ssv4MkO_W z-Mn)pcx|R*u{YaU3mx~yb*|2CP`hm~3BAjQC`f8}oOLK|IMqLsSc)G&o4M*h2xLIR z9G(uj>f2Pu?THW&Cxu(*2<9y|c8{nUpy-Ouhb-i(l+cgUkRvK3U*T<^S8KDM=CUtr&T#keCk z>u>@)=O&Ku&CJws+mxazsB>9)qGgx6<631owud6P`%mQqH8|7X!pLZf)tSF=FO=T$ z=_IWgKnmdg6BAx$I;CSgf+9*l3jvq0^;#DD+w;SA? zh?J1e0yvI?N9b~Uk4klIEli^BkvYPCN3zH`Bz|xW z2&hG|*Gq2UUXnZZRaz^6qtz#zHE{UzdS-p8*;V3y)tHr<_4!pzA8DI1F#_{M2zncI z2FW#THldPG!kJ?VHwJA|(tguS(qezD2)nh%GGmR`8n?Qg&(D7*5Qq!arO zjd^J1zl2%MYHjMlt+jBuOG21;3u2@nI^gpa+P}4v9eS#_Y2A&%AIMP%@UJpFZvEE& z+@<{pmIDV~q7i2A9iy;vYy!LcXoEl8X_7nJsl3?jp`J$PWk%FH^a6R?K-Kp4&YE#( z;chLoQyV><*l1ygrEpp{IEKHYeL9sAZygZ8j%2o!7wcu(u&;Z^Sm;VH0+Fl`RR}H- z^*Mi2QFUf(WOl}jnD#UiK!ob-xV-}5KxKry(K ztVUJ8((KhXaMGb}qhXKev)>iC|7!oU`AZF>Sh(KWN`w$5qyXUEmL@INyJKU9wA@C! zOJWWZJ6}zimYBArmPg^t+c>2ZIJG384z?#^hik*Q6-GSfhhf9Q7C%k|`Z+uTOtmy{ z8g4l{9mkETo%&Oi(W3Api`MY$B%jq4$bo{ShEVXY^RDvJm6grN&DB3TbcEYt&$7v% zOUGwwdhRmKpu>2YMWOK%9Mt?D)7zk&hJ4v`QpWYCndvv?a!coz_Zp!@0>6j7cXU0_ zm37^^UG#=lUkTjPZc0cjre10jq>oceWvWoj&C$99r5`vlbc)}xn*AsP{S(wtq1596 z?Cb_$i12!g0K0>nVG~ zC@Oci@g7*`?Xsqa$KT0Lqy?yu3@pF+TF}>U(LWO>TyuyfjeWK?kCrf4|zrhc7RXt8nx%Tv>a9a`Yl;l|D(t3XY_Z67Y}(-Qathr zMcLh2pmYN_yHZ9plh(a-bs5>scoCb;`f8=!>E+GMn4@NWg{WIb#ZRhpgKRVlM`+lU zat1Fl_kO)Y!$n8!MLh7kB$_~-V&5nmJmSwK3AYFGZSG;-U-T3tQsh62f6O6NrAYI* zf13Y~ecwkZJ(u6C|D+5AiBT!*FB&Sh3~S1g^;aCu1JQDu_oI`d-;bX^DRwGJ&$~Y( z5POy7&%f}GX0A-P0!S@V&DnCBG)er{TrQGv{v97jySX#wq6_J2`TSXLQQbgKjaTc$ zu@`aQyGAOs2l0p-y;m{++i$g(;?Wk=F7!oi$BwBAhp|Zx|5VzRyDuA~KPOFk^(Q3C z>9;E+rs@AHzdypr&;h?oC~iJi01~b5Mt7L|Asr`L3oCB|UIGL7xqtgPNMS4mVi|ON z;PJ3U0Y_|?6C5=JC}HLSj#$D4`yN63U}xrm!9o@Q_6!VYoOB*N%EHe>NV|iTO1r+^Be7d zojNvJ{}3ix0`&08389k7*ahG+6S-bxeEQ0vnTW zAAkeLaY=~2Ts^z$WqGg8!tI!Lvr8UGpb8absNO8Ah-HW}pR;@MFjM%wDNMMv+RaOq z9jY9s@cQ9=v81fRPZxv*ZuIJhCo90=(VTnp=I=j47=Bq2hv@&3?-}-=Yc$k1!8-8} zM_E45)wNRbc@WGb3TsopKiyrdG`|1pe(cFpBcnpv$U?lsuVqjFO~zOVo-Ok0HGuvD zjI^#_uYr!N*Np%LJ2)1AHKuTi3EZ#>6#ZPKQmgbU^xJB~Ky_)U zi?0}<4*W1SE$zAC_y7d0fDx{|0+iNxO9A1k7#Ogrosgp|*8>M=7R=__mWqPXaHT_G z!()J`I5_rPL=8ikK6DRgu&XdhIf_e4CU-lPl$L}f-8ItFn;RYd3E|o>A+o6i&yt}o zQ#lrJNYl^=3Jko;$Ox9ffPki#1DrM)Kts#V1Gm#%5fM=J>(as?#%1ywGk9+43;+H9|r{f}MTPQlO@)3YPokrY0>tGyWK`&Mz(DPuE%;I0>AdhjJd1zdyLy z4JL@YLb6Obzz82djsukjXu6R97akrCaR!1n?%rUzlM3_X=4Q4mt@i>yTt`3qWa}(^ zK0E*yyTFi;^T?+&PXSI6q}a}F5KZ9W<`#rG_fvkTe&LS$H8`k{D`Yot<)^?u4!570 zo8P>6#+al87~qI#Z~5!k&_5Jc%?h4?6B~a_8Xl$+^74V)YSWY1Vf6HBpB4T#t z3+Kc=(O#n&Bp6%1ud{*h z@;GGj0|~cv0Mjd}u#L^lvSh^$piQq014}tU`i>Q;o;y4SS8hLh537?)8tvNZE%rOS z#BsR#626E+XA5X%zJftqRNVaZG(fZXMo8$H49!>#>jpZ=v5-SwSK)B8HAO2^=E1e1BD$ysocLs;%|^{--Ajwv;)rrUVe0Jssmy7=HXhCQXh*z&qn z3#C;d_%yJyF8=<>cMGmy6j5?%iODq!^TW?}xI{Nyy`_0`o|zVpz25@3`m0x7h_4Y` zQTU@x@Z0oMRJ1*+s`_F01w3rLr`N3%VvI&cAZ-_Xk)6{sk*QrS#4Z*dc+w;+=sX6% zAf7r!Bl8E~mFJNzb3-C*6@+q6=vU2qnuEdUXAWP8HZbtoqhti*Ms) zRv8~178f6!J;TE+>QdA_aQs*nFU1g-hNfm6k7GXdm&doCT3Emp@$1)NIcG9_Qpob? z>MFJyC_9g=s`b1fIPCL#6!;s75%$cu+$aIJo?_EqU@Uodbq!8FSha_J_`qlT>+N8y zFfH=A=U)6>ACjfsiPOUcU6w$d1t+$_Oci6R8%+&eCc`!5<@s=Qt^~^>e2ofG&M5ed z-3<-*=Kd0a%rzMMMe~9Dd50VUfCumN)I$lykC2gEuz0nEjFSAr4`AXkqS)C1f4?nM zkpveT<~4>InTykV6P(8g5ew<+8^IxwOTMei>qC{OP7sebei41h@b{@x@`Zq`!RQyj z-JHYUBt8M$W$$E7A!dL&<@4<6?G0LCr*kPlaxgG zk&v0&|LZyzSI^*Jo0D9nY^v3Oz-Ssy$)a!UdtaY&X71{1*xif-wiy+byBQTv?wq9M zc?z|-F7{o1eMBxo=``&2ettS>tjCXca4xD<6LzH2?UzGZg?N_!DKdC#Cf+2 zjv)r~n)=J&py$!yfoOvA+|2DO=irX9_k-0nEp6c}1%=&kh7xR0N$3nTGu1i=T%@U+UY5kw%+C5U-#0|3=20iY{#hqCdu=pE8yWkvG;r=cfYQ#nOS=Gg>pR>U$Ch2^Y@?02xfqFvkH#p z%M*KO<9cjf;Ad1m3wr;9;}}o~++mvpYd`!7@jH*3n_jv)*!}@s*mKdy#3aE*bQsja zuwyzom7tasYo4#DM=dYYYo9sx52n`7zIk(F<17^w0ZcI}-;yyi69CUhl{@@ZXZ1QP ztW-TaFQ@XKY|Tv~^VS}59x$2d(a}$;H$iE6a1hsr`{PgMMVYqh;9UWkHz^939?ycLbxik)&Hg}5WTyZLc(0+k>_L;M18yjoE z82AhVMqtpJ198@(@eRSw))xK?OdaF!c?z;*Y6b>%0KUN}0!aFwK3!`IR@Td}P>QQS zUzOC|z&)J?hrW&eQShp&VxtP|& zFkch}7O*s3^3S7H(<8E(HTdr2FHj>N_DojrO z)8%fCu9uLI#J9V>4Zl^cw>UR9fc5r=V{FXeeGv(Ufga#B4JU-fL;?zC*!C6hHpMZ{2+ga%-qL8Sn!*bW=0MwCEc z`0SY~X9RpsNT~*_=vL?k3>Q#NZfZH<6>e^%duRmXFM!yJWw~_5uL$*w?2NUYsVV4; z>p^5TJT>*T;Oh_N5alhPv(9pz zw?99e-oQ5NY&BBnml+i`l?A6+0K*~0fAUyWQ@^FXCNUsITSGEe&7c*-x%xgV>|idw z-?T-dnOFL+WA^lNo`VdUtK0&l2H{i1TmB;vsQpfyqFmkFtU1g$c5G<%PppFQ)jv|f zh-;)yl8)uSet)4Cz1&MfG3@^1bbhN(Ry8vb$coE^NQU>ssDtQWY1n>F& z^>-~a#r@BerNG=yE?lzi?m z6^di1AuoqUEJs%XPTZ%J(i>MqWa)zB?*t?J{Gn+rP}nUH1_ljnUz>wcWbbv1>dlLh zv^sVV3*meP$GC>@#VAhK3%b-=ORos0t8-uk<2rQG={x|fuq?YJB725UPR0Dz4j;$P zx9l?s$;olrEEPg?OUc~Lg#J5eW;7f>4F-kplU};3WW6C;F@uGjM|^*7Mif(=%%Ei> zM2}dU`aAQp@gVJ6^&S*PiL$WK?T? zsBvFir$aM)PU<;{6`SSvDy92FkJM23$3Cg++XY4@{>t&pA`^`Z3VsyB1#yUExwq6$ z9(Qh148Q+#bAGxUS=gVbue!eU)ugm}WMw?d*Nj*4k*4}#7w<#(4TggIxfx2B;U8u* zTMIwCzc&;l$E{Jh-WP_uP^7;|7)NMJSDESS9;D2VJr^?fICE!yZ#|09KWe{d(M#Ef zh8NH7-+b=fxl2f|o%tmZW_GNA^=F;=t;tE+&4r)VOEf4kYdb$`-KaAb`P-~J-#FMk z2OHEFljxXo)pB7Rz&qKob9ZHQWlhpvnTjg#Digl^CFK_LYmYZ`6wM;cg@!^?4OR9H zxx&P#NUn>47H?WYeiO>m45F&msOAmzgxA~`i);0SR^%T_WvY*4jnI%TwJeUX{`e25 z>X=afjW0VY6fpuN_0&&@#id~ciYM z%OPx$BihZ^e1)}_L_9p!M-%xB2Yl9@yT@_*e$+aFt+E}vcjw2aGT+EnT1|^NH}S@8 zr4J9Cz{Dbx-x8TVdQGl}+-F}q6PWgvjqWPWWWf-lY-Y6Yvv%lXIH()Rbtw?VPGK|L zK9jrQxq2qB#$TxZo?R*|#4loVe$FbfmA+0;O1JSIN*F=gKgW+w^H91SA6cU6*tbOQ z_Fq;KPcMhlc%E^j0+~r%{~2$ZdB=~M16VKdtzM3rU$S0G_)^pI(h`*jSrqCWCk@t z<8y@F`&Gs&74nWKWHV$qQ^F&8uqlk^Qc|0B0+OBYBcZ9|o?`k|0euy#KA+<5r)xIL z{CyUIjTam!dG`3JCr9fQC38%}^F<@su!m%7vpKsmq*dzjuA6up|hgbkzhY}H_xEnVymmC`=e)yn4GKa)Z%owzN(AR zp1SIbDEVxGSVf^Zv{O@mo&xf%&`U`UD+@pBzTAI1DSB7XDnw%xqx)`tp#u@8ma3gU z?D(r|#BF`_CPMnGlnUZ2R~wqrZtSSP<|)Fr%Q7b;>xbQ61C{G&3tR>l>(ogZ(A`z& zb6Qx|FRSz|#TPGR(#)*+BE7IYJ0Au;B_-23l|-=e55l~3AXec6N>}fRK#ZsVNw}R3 zZaLy}DE3A?Jbu@)6Z_qkw+~j%M`F|=_PhO?CmfZL$LmhcbIe@@-CvF*NEqIy%XP!? z*jR}Sn+HM0ZdVV@_Qpc;|I+^9(MxEPA>+G_oi92xL@|n#TQ_3WUVD%scqWYsGH&`I zSC&dy;Nq;eXAQc2cTaQ6TfxH;8>p zoz5bWS6O@?h`N*@9j^4=(IhLjpZUN_aF}PjEUlw{Qh zcLM+ZgQ)lY?}GmV=G|*Lw^U3%6y{L+!X^C~{VG@%_fsjW)|y_0blH6fOAKb{YpqXa zP)C{M@8>5i+XRy?eN#5pW9jPdXvNdPdXa=Z`tsGoXiE9?l5PQ(w)?u^TU z2WJ>M*Ndd43<|nxXuIk?Ym6}yTW zf=&Sb0Kv7(vMdiH_|QZqT^$~3RSv8M8;EN_w9fj(~Zj0Lw06z@M_xy_WmbW%bEo@MP{`{t!31vBJ%TO!b=Z`vRJA7JGR}j7czQWKlZb#ae|k_xu%<`vu3K$@F-WYigK`V( z_D3g6ehBFBT{#DlKk6I5yCy(}2^|sC=H1UoLf8EE$G77FC<13%fK-o;-{9sJrT<2q R3|r-~hce321<1#*{u4BRvo8Pu literal 0 HcmV?d00001 diff --git a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json index f52ed102f2..db7d6b06ce 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json @@ -3646,8 +3646,7 @@ "assign-rulechains": "Assignar cadenes de regles", "delete-rulechains": "Eliminar cadenes de regles", "unassign-rulechain": "Anul·lar assignació de cadena de regles", - "unassign-rulechains-from-edge-action-title": "Anul·lar assignació {count, plural, =1 {1 cadena de regles} other {# cadenes de regles} } des vores", - "assign-new-rulechain": "Assignar nova cadena de regles" + "unassign-rulechains-from-edge-action-title": "Anul·lar assignació {count, plural, =1 {1 cadena de regles} other {# cadenes de regles} } des vores" }, "rulenode": { "details": "Detalls", @@ -5692,7 +5691,6 @@ "show-empty-space-hidden-action": "Mostrar espai buit en lloc de cel·la oculta", "dont-reserve-space-hidden-action": "No reserveu espai per als botons en cel·la oculta", "display-timestamp": "Mostra columna timestamp", - "display-milliseconds": "Mostrar mil·lisegons", "display-pagination": "Mostrar pàgines", "default-page-size": "Mida de pàgina per defecte", "use-entity-label-tab-name": "Usar etiqueta important al nom de la taula", diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index c0950bb047..7402aedc5c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -2411,7 +2411,6 @@ "search": "Vyhledat řetězy pravidel", "selected-rulechains": "Vybráno { count, plural, =1 {1 řetězů pravidel} other {# řetězů pravidel} }", "open-rulechain": "Otevřít řetěz pravidel", - "assign-new-rulechain": "Přiřadit nový řetěz pravidel", "edge-template-root": "Hlavní šablona", "assign-to-edge": "Přiřadit k edge", "edge-rulechain": "Řetěz pravidel edge", diff --git a/ui-ngx/src/assets/locale/locale.constant-de_DE.json b/ui-ngx/src/assets/locale/locale.constant-de_DE.json index 87579470d2..582e3e2902 100644 --- a/ui-ngx/src/assets/locale/locale.constant-de_DE.json +++ b/ui-ngx/src/assets/locale/locale.constant-de_DE.json @@ -19,6 +19,7 @@ "suspend": "Unterbrechen", "save": "Speichern", "saveAs": "Speichern unter", + "move": "Verschieben", "cancel": "Abbrechen", "ok": "OK", "delete": "Löschen", @@ -27,18 +28,20 @@ "no": "Nein", "update": "Aktualisieren", "remove": "Löschen", - "select": "Auswählen", "search": "Suche", "clear-search": "Suchanfrage löschen", "assign": "Zuordnen", "unassign": "Zuordnung aufheben", "share": "Teilen", "make-private": "Privat machen", + "make-public": "Öffentlich machen", "apply": "Anwenden", "apply-changes": "Änderungen übernehmen", "edit-mode": "Bearbeitungsmodus", "enter-edit-mode": "Zum Bearbeitungsmodus wechseln", "decline-changes": "Änderungen nicht übernehmen", + "open": "Öffnen", + "decline": "Verwerfen", "close": "Schließen", "back": "Zurück", "run": "Ausführen", @@ -56,16 +59,27 @@ "import": "Importieren", "export": "Exportieren", "share-via": "Teilen mit {{provider}}", + "select": "Auswählen", "continue": "Fortsetzen", "discard-changes": "Änderungen verwerfen", "download": "Download", + "next": "Nächste", "next-with-label": "Nächste: {{label}}", "read-more": "Mehr dazu", "hide": "Verstecken", "done": "Erledigt", "print": "Drucken", "restore": "Wiederherstellen", - "confirm": "Bestätigen" + "confirm": "Bestätigen", + "more": "Mehr", + "less": "Weniger", + "skip": "Überspringen", + "send": "Senden", + "reset": "Zurücksetzen", + "show-more": "Zeige mehr", + "dont-show-again": "Nicht wieder anzeigen", + "see-documentation": "Siehe Dokumentation", + "clear": "Leeren" }, "aggregation": { "aggregation": "Aggregation", @@ -80,9 +94,11 @@ "none": "kein Wert" }, "admin": { + "settings": "Einstellungen", "general": "Allgemein", "general-settings": "Allgemeine Einstellungen", "home-settings": "Home Einstellungen", + "home": "Home", "outgoing-mail": "E-Mail Versand", "outgoing-mail-settings": "Konfiguration des Postausgangsservers", "system-settings": "Systemeinstellungen", @@ -103,7 +119,7 @@ "timeout-required": "Wartezeit ist erforderlich.", "timeout-invalid": "Das ist keine gültige Wartezeit.", "enable-tls": "TLS aktivieren", - "tls-version" : "TLS-Version", + "tls-version": "TLS-Version", "enable-proxy": "Proxy aktivieren", "proxy-host": "Proxy Host", "proxy-host-required": "Proxy Host ist erforderlich.", @@ -114,8 +130,21 @@ "proxy-password": "Proxy Passwort", "change-password": "Passwort ändern", "send-test-mail": "Test E-Mail senden", + "use-system-mail-settings": "System E-Mail-Server Einstellungen benutzen", + "mail-templates": "E-Mail Vorlagen", + "mail-template-settings": "Einstellungen E-Mail Vorlagen", + "use-system-mail-template-settings": "System E-Mail Vorlagen benutzen", + "mail-template": { + "mail-template": "E-Mail Vorlage", + "test": "E-Mail Nachricht testen", + "activation": "Nachricht Benutzerkontoaktivierung", + "account-activated": "Nachricht Benutzerkonto aktiviert" + }, + "mail-subject": "E-Mail Betreff", + "mail-body": "E-Mail Nachricht", "sms-provider": "SMS Anbieter", "sms-provider-settings": "SMS Anbieter Einstellungen", + "use-system-sms-settings": "System SMS Anbieter benutzen", "sms-provider-type": "SMS Anbieter Typ", "sms-provider-type-required": "SMS Anbieter Typ ist erforderlich.", "sms-provider-type-aws-sns": "Amazon SNS", @@ -138,6 +167,7 @@ "password-expiration-period-days-range": "Die Gültigkeitsdauer des Passworts in Tagen kann nicht negativ sein", "password-reuse-frequency-days": "Häufigkeit der Kennwortwiederverwendung in Tagen", "password-reuse-frequency-days-range": "Die Häufigkeit der Kennwortwiederverwendung in Tagen kann nicht negativ sein", + "allow-whitespace": "Leerzeichen erlauben", "general-policy": "Allgemeine Politik", "max-failed-login-attempts": "Maximale Anzahl fehlgeschlagener Anmeldeversuche, bevor das Konto gesperrt wird", "minimum-max-failed-login-attempts-range": "Die maximale Anzahl fehlgeschlagener Anmeldeversuche kann nicht negativ sein", @@ -146,11 +176,14 @@ "alarm": { "alarm": "Alarm", "alarms": "Alarme", + "all-alarms": "Alle Alarme", "select-alarm": "Alarm auswählen", "no-alarms-matching": "Keine passenden Alarme zu '{{entity}}' wurden gefunden.", "alarm-required": "Alarm ist erforderlich", + "alarm-filter": "Alarmfilter", + "filter": "Filter", "alarm-status": "Alarm Status", - "alarm-status-list": "Alarm Status Liste", + "alarm-status-list": "Alarm Statusliste", "any-status": "Jeder Status", "search-status": { "ANY": "Jeder", @@ -178,6 +211,7 @@ "end-time": "Endzeit", "ack-time": "Bestätigungszeit", "clear-time": "Zeit gelöscht", + "duration": "Dauer", "alarm-severity-list": "Alarm Schwere Liste", "any-severity": "Jede Schwere", "severity-critical": "Kritisch", @@ -187,6 +221,7 @@ "severity-indeterminate": "Unbestimmt", "acknowledge": "Bestätigen", "clear": "Löschen", + "delete": "Löschen", "search": "Alarme suchen", "selected-alarms": "{ count, plural, =1 {1 Alarm} other {# Alarme} } ausgewählt", "no-data": "Keine Daten zum Anzeigen", @@ -197,11 +232,18 @@ "aknowledge-alarms-text": "Sind Sie sicher, dass Sie { count, plural, =1 {1 Alarm} other {# Alarme} } bestätigen möchten?", "aknowledge-alarm-title": "Alarm bestätigen", "aknowledge-alarm-text": "Möchten Sie den Alarm wirklich bestätigen?", + "selected-alarms-are-acknowledged": "Ausgewählte Alarme wurden bereits bestätigt", "clear-alarms-title": "{ count, plural, =1 {1 Alarm} other {# Alarme} } löschen", "clear-alarms-text": "Möchten Sie wirklich { count, plural, =1 {1 Alarm} other {# Alarme} } löschen?", "clear-alarm-title": "Alarm löschen", "clear-alarm-text": "Möchten Sie den Alarm wirklich löschen?", - "alarm-status-filter": "Alarm Status Filter" + "delete-alarms-title": "Lösche { count, plural, =1 {1 Alarm} other {# Alarme} }", + "delete-alarms-text": "Sind Sie sicher { count, plural, =1 {1 Alarm} other {# Alarme} } zu löschen?", + "selected-alarms-are-cleared": "Ausgewählte Alarme wurden bereits gelöscht", + "alarm-status-filter": "Alarm Status Filter", + "alarm-filter-title": "Alarmfilter", + "assigned": "Zugewiesen", + "filter-title": "Filter" }, "alias": { "add": "Alias hinzufügen", @@ -228,14 +270,14 @@ "filter-type-edge-type-description": "Rand vom Typ '{{edgeTypes}}'", "filter-type-relations-query": "Beziehungsabfrage", "filter-type-relations-query-description": "{{entities}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", + "filter-type-edge-search-query": "Edge-Abfrage", + "filter-type-edge-search-query-description": "Edge vom Typ {{edgeTypes}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", "filter-type-asset-search-query": "Objektabfrage", "filter-type-asset-search-query-description": "Objekte vom Typ {{assetTypes}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", "filter-type-device-search-query": "Geräteabfrage", "filter-type-device-search-query-description": "Geräte vom Typ {{deviceTypes}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", "filter-type-entity-view-search-query": "Entitätsansichtsabfrage", "filter-type-entity-view-search-query-description": "Entitätsansichten vom Typ {{entityViewTypes}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", - "filter-type-edge-search-query": "Randabfrage", - "filter-type-edge-search-query-description": "Rand vom Typ {{edgeTypes}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", "entity-filter": "Entitätsfilter", "resolve-multiple": "Als mehrere Entitäten auflösen", "filter-type": "Filtertyp", @@ -254,11 +296,16 @@ "any-relation": "Jede Beziehung" }, "asset": { + "all": "Alle", + "all-assets": "Alle Objekte", + "groups": "Gruppen", + "shared": "Geteilt", "asset": "Objekt", "assets": "Objekte", "management": "Objektverwaltung", "view-assets": "Objekte anzeigen", "add": "Objekt hinzufügen", + "asset-type-max-length": "Objekttyp sollte kürzer als 256 Zeichen sein", "assign-to-customer": "Einem Kunden zuordnen", "assign-asset-to-customer": "Objekte dem Kunden zuordnen", "assign-asset-to-customer-text": "Bitte wählen Sie die Objekte aus, die dem Kunden zugeordnet werden sollen", @@ -281,6 +328,8 @@ "asset-types": "Objekttypen", "name": "Name", "name-required": "Name ist erforderlich.", + "name-max-length": "Name sollte weniger als 256 Zeichen sein", + "label-max-length": "Label sollte weniger als 256 Zeichen sein", "description": "Beschreibung", "type": "Typ", "type-required": "Typ ist erforderlich.", @@ -290,6 +339,7 @@ "asset-details": "Objektdetails", "assign-assets": "Objekte zuordnen", "assign-assets-text": "Kunden { count, plural, =1 {1 Objekt} other {# Objekte} } zuordnen", + "assign-asset-to-edge-text":"Bitte wählen Sie die Objekte aus, die dem Edge zugeordnet werden sollen", "delete-assets": "Objekte löschen", "unassign-assets": "Objektzuordnungen aufheben", "unassign-assets-action-title": "Kunden { count, plural, =1 {1 Objektzuordnung} other {# Objektzuordnungen} } aufheben", @@ -316,7 +366,6 @@ "name-starts-with": "Name des Objekts beginnt mit", "label": "Bezeichnung", "assign-asset-to-edge": "Objekte dem Rand zuordnen", - "assign-asset-to-edge-text":"Bitte wählen Sie die Objekte aus, die dem Rand zugeordnet werden sollen", "unassign-asset-from-edge": "Objekte von Rand entfernen", "unassign-asset-from-edge-title": "Sind Sie sicher, dass Sie die Zuordnung für das Objekt '{{assetName}}' aufheben möchten?", "unassign-asset-from-edge-text": "Nach Bestätigung wird die Zuordnung des Objekts aufgehoben und es ist für den Kunden nicht mehr zugänglich.", @@ -327,6 +376,7 @@ "attribute": { "attributes": "Eigenschaften", "latest-telemetry": "Neueste Telemetrie", + "no-latest-telemetry": "Keine Telemetriedaten", "attributes-scope": "Entitätseigenschaftsbereich", "scope-telemetry": "Telemetrie", "scope-latest-telemetry": "Neueste Telemetrie", @@ -334,11 +384,15 @@ "scope-server": "Server Eigenschaften", "scope-shared": "Gemeinsame Eigenschaften", "add": "Eigenschaft hinzufügen", + "add-attribute-prompt": "Bitte Attribut hinzufügen", "key": "Schlüssel", + "key-max-length": "Der Schlüssel sollte weniger als 256 Zeichen haben", "last-update-time": "Datum der letzten Aktualisierung", "key-required": "Eigenschaftsschlüssel ist erforderlich.", "value": "Wert", "value-required": "Eigenschaftswert ist erforderlich.", + "telemetry-key-required": "Telemetrieschlüssel ist erforderlich", + "telemetry-value-required": "Telemetriewert ist erforderlich", "delete-attributes-title": "Sind Sie sicher, dass Sie { count, plural, =1 {1 Eigenschaft} other {# Eigenschaften} } löschen möchten?", "delete-attributes-text": "Seien Sie vorsichtig, nach der Bestätigung werden alle ausgewählten Eigenschaften entfernt.", "delete-attributes": "Eigenschaften löschen", @@ -350,7 +404,12 @@ "add-to-dashboard": "Zum Dashboard hinzufügen", "add-widget-to-dashboard": "Widget zum Dashboard hinzufügen", "selected-attributes": "{ count, plural, =1 {1 Eigenschaft} other {# Eigenschaften} } ausgewählt", - "selected-telemetry": "{ count, plural, =1 {1 Telemetrieeinheit } other {# Telemetrieeinheiten} } ausgewählt" + "selected-telemetry": "{ count, plural, =1 {1 Telemetrieeinheit } other {# Telemetrieeinheiten} } ausgewählt", + "no-attributes-text": "Keine Attribute gefunden", + "no-telemetry-text": "Keine Telemetriedaten gefunden", + "copy-key": "Schlüssel kopieren", + "add-telemetry": "Telemetriedaten hinzufügen", + "copy-value": "Wert kopieren" }, "audit-log": { "audit": "Audit", @@ -371,17 +430,27 @@ "type-credentials-updated": "Anmeldeinformationen wurden aktualisiert", "type-assigned-to-customer": "Kunden Zuordnung", "type-unassigned-from-customer": "Kunden Zuordnung aufgehoben", - "type-assigned-to-edge": "Rand Zuordnung", - "type-unassigned-from-edge": "Rand Zuordnung aufgehoben", + "type-assigned-to-edge": "Zum Edge hinzugefügt", + "type-unassigned-from-edge": "Vom Edge entfernt", "type-activated": "Aktiviert", "type-suspended": "Ausgesetzt", "type-credentials-read": "Anmeldeinformationen gelesen", "type-attributes-read": "Eigenschaften gelesen", + "type-added-to-entity-group": "Zur Gruppe hinzugefügt", + "type-removed-from-entity-group": "Aus der Gruppe entfernt", "type-relation-add-or-update": "Beziehung aktualisiert", "type-relation-delete": "Beziehung gelöscht", "type-relations-delete": "Alle Beziehungen gelöscht", "type-alarm-ack": "Bestätigt", "type-alarm-clear": "Gelöscht", + "type-alarm-assign": "Zugeweisen", + "type-alarm-unassign": "nicht zugewiesen", + "type-added-comment": "Kommentar hinzugefügt", + "type-updated-comment": "Kommentar aktualisiert", + "type-deleted-comment": "Kommentar gelöscht", + "type-rest-api-rule-engine-call": "Regelkette REST API Aufruf", + "type-made-public": "Öffentlich machen", + "type-made-private": "Privat machen", "type-login": "Anmeldung", "type-logout": "Ausloggen", "type-lockout": "Aussperrung", @@ -392,7 +461,15 @@ "action-data": "Aktionsdaten", "failure-details": "Fehlerdetails", "search": "Audit-Protokolle durchsuchen", - "clear-search": "Suche leeren" + "clear-search": "Suche leeren", + "type-assigned-from-tenant": "Vom Tenant zugewiesen", + "type-assigned-to-tenant": "Dem Tenant zugewiesen", + "type-provision-success": "Gerätebereitstellung erfolgreich", + "type-provision-failure": "Gerätebereitstellung fehlgeschlagen", + "type-timeseries-updated": "Telemetriedaten aktualisiert", + "type-timeseries-deleted": "Telemetriedaten gelöscht", + "type-owner-changed": "Besitzer wurde gewechselt", + "type-sms-sent": "SMS gesendet" }, "confirm-on-exit": { "message": "Sie haben nicht gespeicherte Änderungen. Möchten Sie diese Seite wirklich verlassen?", @@ -409,7 +486,10 @@ "address2": "Adresse 2", "phone": "Telefon", "email": "E-Mail", - "no-address": "Keine Adresse" + "no-address": "Keine Adresse", + "state-max-length": "Staat sollte weniger als 256 Zeichen haben", + "phone-max-length": "Telefonnummer sollte weniger als 256 Zeichen haben", + "city-max-length": "Ort sollte weniger als 256 Zeichen haben" }, "common": { "username": "Benutzername", @@ -417,7 +497,12 @@ "enter-username": "Benutzername eingeben", "enter-password": "Passwort eingeben", "enter-search": "Suche eingeben", - "created-time": "Erstellungszeit" + "created-time": "Erstellungszeit", + "loading": "wird geladen...", + "proceed": "Fortfahren", + "open-details-page": "Detailseite öffnen", + "not-found": "Nicht gefunden", + "documentation": "Dokumentation" }, "content-type": { "json": "Json", @@ -425,6 +510,11 @@ "binary": "Binär (Base64)" }, "customer": { + "all": "Alle", + "all-customers": "Alle Kunden", + "groups": "Gruppen", + "shared": "Geteilt", + "hierarchy": "Hierarchie", "customer": "Kunde", "customers": "Kunden", "management": "Kundenverwaltung", @@ -437,7 +527,6 @@ "public-devices": "Öffentliche Geräte", "public-assets": "Öffentliche Objekte", "public-entity-views": "Öffentliche Entitätsansichten", - "public-edges": "Öffentliche Rand", "add": "Kunde hinzufügen", "delete": "Kunde löschen", "manage-customer-users": "Kundenbenutzer verwalten", @@ -446,7 +535,6 @@ "manage-public-devices": "Öffentliche Geräte verwalten", "manage-public-dashboards": "Öffentliche Dashboards verwalten", "manage-customer-assets": "Kundenobjekte verwalten", - "manage-customer-edges": "Randobjekte verwalten", "manage-public-assets": "Öffentliche Objekte verwalten", "manage-public-edges": "Öffentliche Rand verwalten", "add-customer-text": "Neuen Kunden hinzufügen", @@ -463,6 +551,7 @@ "manage-dashboards": "Dashboards verwalten", "title": "Titel", "title-required": "Titel ist erforderlich.", + "title-max-length": "Titel sollte weniger asl 256 Zeichen haben", "description": "Beschreibung", "details": "Details", "events": "Ereignisse", @@ -1463,7 +1552,6 @@ "management": "Regelverwaltung", "debug-mode": "Modus zur Fehlersuche", "assign-rulechains": "Regelketten zuweisen", - "assign-new-rulechain": "Neues Regelkette zuweisen", "delete-rulechains": "Regelketten löschen", "unassign-rulechain": "Nicht zugeordnete Regelkette", "unassign-rulechains": "Nicht zugeordnete Regelketten", @@ -1588,26 +1676,98 @@ "hours": "Stunden", "minutes": "Minuten", "seconds": "Sekunden", - "advanced": "Erweitert" + "advanced": "Erweitert", + "predefined": { + "yesterday": "Gestern", + "day-before-yesterday": "Vorgestern", + "this-day-last-week": "Dieser Tag letzte Woche", + "previous-week": "Letzte Woche (So - Sa)", + "previous-week-iso": "Letzte Woche (Mo - So)", + "previous-month": "Letzter Monat", + "previous-quarter": "Letztes Quartal", + "previous-half-year": "Letztes Halbjahr", + "previous-year": "Letztes Jahr", + "current-hour": "Aktuelle Stunde", + "current-day": "Aktueller Tag", + "current-day-so-far": "Aktueller Tag bisher", + "current-week": "Aktuelle Wiche (So - Sa)", + "current-week-iso": "Aktuelle Woche (Mo - So)", + "current-week-so-far": "Aktuelle Woche bisher (So - Sa)", + "current-week-iso-so-far": "Aktuelle Woche bisher (Mo - So)", + "current-month": "Aktueller Monat", + "current-month-so-far": "Aktueller Monat bisher", + "current-quarter": "Aktuelles Quartal", + "current-quarter-so-far": "Aktuelles Quartal bisher", + "current-half-year": "Aktuelles Halbjahr", + "current-half-year-so-far": "Aktuelles Halbjahr bisher", + "current-year": "Aktuelles Jahr", + "current-year-so-far": "Aktuelles Jahr bisher" + } + }, + "timeunit": { + "milliseconds": "Millisekunden", + "seconds": "Sekunden", + "minutes": "Minuten", + "hours": "Stunden", + "days": "Tage" }, "timewindow": { + "timewindow": "Zeitfenster", + "years": "{ years, plural, =1 { Jahr } other {# Jahre } }", + "years-short": "{{ years }}J", + "months": "{ months, plural, =1 { Monat } other {# Monate } }", + "months-short": "{{ months }}M", + "weeks": "{ weeks, plural, =1 { Woche } other {# Wochen } }", + "weeks-short": "{{ weeks }}W", "days": "{ days, plural, =1 { Tag } other {# Tage } }", + "days-short": "{{ days }}T", "hours": "{ hours, plural, =0 { Stunde } =1 {1 Stunde } other {# Stunden } }", + "hr": "{{ hr }} Std", + "hr-short": "{{ hr }}S", "minutes": "{ minutes, plural, =0 { Minute } =1 {1 Minute } other {# Minuten } }", + "min": "{{ min }} Min", + "min-short": "{{ min }}m", "seconds": "{ seconds, plural, =0 { Sekunde } =1 {1 Sekunde } other {# Sekunden } }", + "sec": "{{ sec }} Sek", + "sec-short": "{{ sec }}s", + "short": { + "days": "{ days, plural, =1 {1 Tag } other {# Tage } }", + "hours": "{ hours, plural, =1 {1 Stunde } other {# Stunden } }", + "minutes": "{{minutes}} Min ", + "seconds": "{{seconds}} Sek " + }, "realtime": "Echtzeit", "history": "Historie", "last-prefix": "letzte", "period": "von {{ startTime }} bis {{ endTime }}", "edit": "Zeitfenster bearbeiten", "date-range": "Datumsbereich", + "for-all-time": "Für immer", "last": "Letzte", - "time-period": "Zeitfenster" + "time-period": "Zeitfenster", + "hide": "Verstecken", + "interval": "Intervall", + "just-now": "Soeben", + "just-now-lower": "soeben", + "ago": "vor", + "style": "Zeitfenster Style", + "icon": "Symbol", + "icon-position": "Position Symbol", + "icon-position-left": "Links", + "icon-position-right": "Rechts", + "font": "Schriftart", + "color": "Farbe", + "displayTypePrefix": "Echtzeit-/Verlaufspräfix anzeigen", + "preview": "Vorschau" }, "user": { - "user": "User", - "users": "Users", - "customer-users": "Kunden Users", + "all": "Alle", + "all-users": "Alle Benutzer", + "groups": "Gruppen", + "user": "Benutzer", + "users": "Benutzer", + "management": "Benutzerverwaltung", + "customer-users": "Kunden-Benutzer", "tenant-admins": "Mandanten-Administratoren", "sys-admin": "System-Administrator", "tenant-admin": "Mandanten-Administrator", @@ -1618,6 +1778,7 @@ "add-user-text": "Neuen Benutzer hinzufügen", "no-users-text": "Keine Benutzer gefunden", "user-details": "Benutzer-Details", + "delete-users": "Benutzer löschen", "delete-user-title": "Möchten Sie den Benutzer '{{userEmail}}' wirklich löschen?", "delete-user-text": "Vorsicht, nach Bestätigung werden der Benutzer und alle zugehörigen Daten gelöscht.", "delete-users-title": "Sind Sie sicher, dass Sie { count, plural, =1 {1 Benutzer} other {# Benutzer} } löschen möchten??", @@ -1649,7 +1810,11 @@ "disable-account": "Benutzerkonto deaktivieren", "enable-account": "Benutzerkonto aktivieren", "enable-account-message": "Benutzerkonto wurde erfolgreich aktiviert!", - "disable-account-message": "Benutzerkonto wurde erfolgreich deaktiviert!" + "disable-account-message": "Benutzerkonto wurde erfolgreich deaktiviert!", + "copyId": "Benutzer-Id kopieren", + "idCopiedMessage": "Benutzer-Id in die Zwischenablage kopiert", + "user-list": "Benutzerliste", + "user-list-required": "Benutzerliste ist erforderlich" }, "value": { "type": "Wertetyp", @@ -1813,6 +1978,27 @@ }, "widgets": { "date-range-navigator": { + "date-range-picker-settings": "Einstellungen Datumsbereichsauswahl", + "hide-date-range-picker": "Datumsbereichsauswahl verstecken", + "picker-one-panel": "Datumsbereichsauswahl in einem Bereich", + "picker-auto-confirm": "Automatische Bestätigung der Datumsbereichsauswahl", + "picker-show-template": "Vorlage für die Anzeige der Datumsbereichsauswahl", + "first-day-of-week": "Erster Tag der Woche", + "interval-settings": "Intervalleinstellungen", + "hide-interval": "Intervall verstecken", + "initial-interval": "Anfangsintervall", + "interval-hour": "Stunde", + "interval-day": "Tag", + "interval-week": "Woche", + "interval-two-weeks": "2 Wochen", + "interval-month": "Monat", + "interval-three-months": "3 Monate", + "interval-six-months": "6 Monate", + "step-settings": "Schritteinstellungen", + "hide-step-size": "Schrittweite verstecken", + "initial-step-size": "Anfangsschrittweite", + "hide-labels": "Beschriftung verstecken", + "use-session-storage": "Sessionspeicher verwenden", "localizationMap": { "Sun": "So.", "Mon": "Mo.", @@ -1897,6 +2083,7 @@ }, "icon": { "icon": "Symbol", + "icons": "Symbole", "select-icon": "Symbol auswählen", "material-icons": "Material-Symbole", "show-all": "Alle Symbole anzeigen" diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 7937b197f3..34109326b7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -77,7 +77,10 @@ "show-more": "Show more", "dont-show-again": "Do not show again", "see-documentation": "See documentation", - "clear": "Clear" + "clear": "Clear", + "upload": "Upload", + "delete-anyway": "Delete anyway", + "delete-selected": "Delete selected" }, "aggregation": { "aggregation": "Aggregation", @@ -179,7 +182,10 @@ "password-policy": "Password policy", "minimum-password-length": "Minimum password length", "minimum-password-length-required": "Minimum password length is required", - "minimum-password-length-range": "Minimum password length should be in a range from 5 to 50", + "minimum-password-length-range": "Minimum password length should be in a range from 6 to 50", + "maximum-password-length": "Maximum password length", + "maximum-password-length-min": "Maximum password length should be at least 6", + "maximum-password-length-less-min": "Maximum password length should be greater than minimum length", "minimum-uppercase-letters": "Minimum number of uppercase letters", "minimum-uppercase-letters-range": "Minimum number of uppercase letters can't be negative", "minimum-lowercase-letters": "Minimum number of lowercase letters", @@ -193,6 +199,8 @@ "password-reuse-frequency-days": "Password reuse frequency in days", "password-reuse-frequency-days-range": "Password reuse frequency in days can't be negative", "allow-whitespace": "Allow whitespace", + "force-reset-password-if-no-valid": "Force to reset password if not valid", + "force-reset-password-if-no-valid-hint": "Please be careful when enabling this feature: it will require users with not valid password to reset their password via email.", "general-policy": "General policy", "max-failed-login-attempts": "Maximum number of failed login attempts, before account is locked", "minimum-max-failed-login-attempts-range": "Maximum number of failed login attempts can't be negative", @@ -664,7 +672,7 @@ "asset-type": "Asset type", "asset-type-required": "Asset type is required.", "select-asset-type": "Select asset type", - "enter-asset-type": "Enter asset type", + "enter-asset-type": "Enter asset profile", "any-asset": "Any asset", "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.", "asset-type-list-empty": "No asset types selected.", @@ -1178,6 +1186,7 @@ "hide-details": "Hide details", "select-state": "Select target state", "state-controller": "State controller", + "state-controller-default": "static (deprecated)", "search": "Search dashboards", "selected-dashboards": "{ count, plural, =1 {1 dashboard} other {# dashboards} } selected", "home-dashboard": "Home dashboard", @@ -1188,7 +1197,8 @@ "assign-dashboard-to-edge": "Assign Dashboard(s) To Edge", "assign-dashboard-to-edge-text": "Please select the dashboards to assign to the edge", "non-existent-dashboard-state-error": "Dashboard state with id \"{{ stateId }}\" is not found", - "edit-mode": "Edit mode" + "edit-mode": "Edit mode", + "duplicate-state-action": "Duplicate state" }, "datakey": { "settings": "Settings", @@ -2017,6 +2027,7 @@ "sync-process-started-successfully": "Sync process started successfully!", "missing-related-rule-chains-title": "Edge has missing related rule chain(s)", "missing-related-rule-chains-text": "Assigned to edge rule chain(s) use rule nodes that forward message(s) to rule chain(s) that are not assigned to this edge.

List of missing rule chain(s):
{{missingRuleChains}}", + "upgrade-instructions": "Upgrade Instructions", "widget-datasource-error": "This widget supports only EDGE entity datasource" }, "edge-event": { @@ -2198,6 +2209,8 @@ "list-of-edges": "{ count, plural, =1 {One edge} other {List of # edges} }", "edge-name-starts-with": "Edges whose names start with '{{prefix}}'", "type-tb-resource": "Resource", + "type-tb-resources": "Resources", + "list-of-tb-resources": "{ count, plural, =1 {One resource} other {List of # resources} }", "type-ota-package": "OTA package", "type-rpc": "RPC", "type-queue": "Queue", @@ -2686,7 +2699,12 @@ "rpc-command-result": "Response", "rpc-command-edit-params": "Edit parameters", "gateway-configuration": "General Configuration", - "docker-label": "In order to run ThingsBoard IoT gateway in docker with credentials for this device you can use the following commands.", + "docker-label": "Use the following instruction to run IoT Gateway in in Docker compose with credentials for selected device", + "install-docker-compose": "Use the instructions to download, install and setup docker compose", + "download-configuration-file": "Download configuration file", + "download-docker-compose": "Download docker-compose.yml for your gateway", + "launch-gateway": "Launch gateway", + "launch-docker-compose": "Start the gateway using the following command in the terminal from folder with docker-compose.yml file", "create-new-gateway": "Create a new gateway", "create-new-gateway-text": "Are you sure you want create a new gateway with name: '{{gatewayName}}'?", "created-time": "Created time", @@ -3027,6 +3045,59 @@ "browse-file": "Browse file", "browse-files": "Browse files" }, + "image": { + "gallery": "Image gallery", + "search": "Search image", + "selected-images": "{ count, plural, =1 {1 image} other {# images} } selected", + "created-time": "Created time", + "name": "Name", + "name-required": "Name is required.", + "resolution": "Resolution", + "size": "Size", + "system": "System", + "download-image": "Download image", + "export-image": "Export image to JSON", + "import-image": "Import image from JSON", + "upload-image": "Upload image", + "edit-image": "Edit image", + "image-details": "Image details", + "no-images": "No images found", + "delete-image": "Delete image", + "delete-image-title": "Are you sure you want to delete image '{{imageTitle}}'?", + "delete-image-text": "Be careful, after the confirmation image will become unrecoverable.", + "delete-images-title": "Are you sure you want to delete { count, plural, =1 {1 image} other {# images} }?", + "delete-images-text": "Be careful, after the confirmation all selected images will be removed and all related data will become unrecoverable.", + "list-mode": "List view", + "grid-mode": "Grid view", + "image-preview": "Image preview", + "update-image": "Update image", + "export-failed-error": "Unable to export image: {{error}}", + "image-json-file": "Image JSON file", + "invalid-image-json-file-error": "Unable to import image from JSON: Invalid image JSON data structure.", + "image-is-in-use": "Image is used by other entities", + "images-are-in-use": "Images are used by other entities", + "image-is-in-use-text": "The image '{{title}}' was not deleted because it is used by the following entities:", + "images-are-in-use-text": "Not all images have been deleted because they are used by other entities.
You can view referenced entities by clicking the References button in the corresponding image row.
If you still want to delete these images, select them in the table below and click the Delete selected button.", + "delete-image-in-use-text": "If you still want to delete the image, click the Delete anyway button.", + "system-entities": "System entities:", + "entities": "entities:", + "references": "References", + "include-system-images": "Include system images", + "clear-image": "Clear image", + "no-image": "No image", + "no-image-selected": "No image selected", + "browse-from-gallery": "Browse from gallery", + "set-link": "Set link", + "image-link": "Image link", + "link": "Link", + "copy-image-link": "Copy image link", + "embed-image": "Embed image", + "embed-to-html": "Embed to HTML", + "embed-to-html-hint": "This feature will make link available to any unauthorized user.", + "embed-to-html-text": "Using the following code snippet, you may embed an image into the components based on the plain HTML.
Such components include HTML card widgets, cell content functions, etc.", + "embed-to-angular-template": "Embed to Angular HTML template", + "embed-to-angular-template-text": "Using the following code snippet, you may embed an image into the Angular HTML template.
Such components include the Markdown widget, HTML section in the widget editor, custom actions, etc." + }, "image-input": { "drop-images-or": "Drag and drop an images or", "drag-and-drop": "Drag & Drop", @@ -3175,7 +3246,9 @@ "weeks": "(week ago)", "months": "(month ago)", "years": "(year ago)" - } + }, + "label": "Label", + "value": "Value" }, "login": { "login": "Login", @@ -3365,15 +3438,13 @@ "recipient-type": { "affected-tenant-administrators": "Affected tenant administrators", "affected-user": "Affected user", - "affected-user-hint": "Affected user hint", "all-users": "All users", "customer-users": "Customer users", "system-administrators": "System administrators", "tenant-administrators": "Tenant administrators", "user-filters": "User filter", "user-list": "User list", - "users-entity-owner": "Users of the entity owner", - "users-entity-owner-hint": "Users of the entity owner hint" + "users-entity-owner": "Users of the entity owner" }, "recipients": "Recipients", "notification-recipients": "Notifications / Recipients", @@ -3485,69 +3556,70 @@ } }, "ota-update": { - "add": "Add package", - "assign-firmware": "Assigned firmware", - "assign-firmware-required": "Assigned firmware is required", - "assign-software": "Assigned software", - "assign-software-required": "Assigned software is required", - "auto-generate-checksum": "Auto-generate checksum", - "checksum": "Checksum", - "checksum-hint": "If checksum is empty, it will be generated automatically", - "checksum-algorithm": "Checksum algorithm", - "checksum-copied-message": "Package checksum has been copied to clipboard", - "change-firmware": "Change of the firmware may cause update of { count, plural, =1 {1 device} other {# devices} }.", - "change-software": "Change of the software may cause update of { count, plural, =1 {1 device} other {# devices} }.", - "chose-compatible-device-profile": "The uploaded package will be available only for devices with the chosen profile.", - "chose-firmware-distributed-device": "Choose firmware that will be distributed to the devices", - "chose-software-distributed-device": "Choose software that will be distributed to the devices", - "content-type": "Content type", - "copy-checksum": "Copy checksum", - "copy-direct-url": "Copy direct URL", - "copyId": "Copy package Id", - "copied": "Copied!", - "delete": "Delete package", - "delete-ota-update-text": "Be careful, after the confirmation the OTA update will become unrecoverable.", - "delete-ota-update-title": "Are you sure you want to delete the OTA update '{{title}}'?", - "delete-ota-updates-text": "Be careful, after the confirmation all selected OTA updates will be removed.", - "delete-ota-updates-title": "Are you sure you want to delete { count, plural, =1 {1 OTA update} other {# OTA updates} }?", - "description": "Description", - "direct-url": "Direct URL", - "direct-url-copied-message": "Package direct URL has been copied to clipboard", - "direct-url-required": "Direct URL is required", - "download": "Download package", - "drop-file": "Drop a package file or click to select a file to upload.", - "drop-package-file-or": "Drag and drop a package file or", - "file-name": "File name", - "file-size": "File size", - "file-size-bytes": "File size in bytes", - "idCopiedMessage": "Package Id has been copied to clipboard", - "no-firmware-matching": "No compatible Firmware OTA Update packages matching '{{entity}}' were found.", - "no-firmware-text": "No compatible Firmware OTA Update packages provisioned.", - "no-packages-text": "No packages found", - "no-software-matching": "No compatible Software OTA Update packages matching '{{entity}}' were found.", - "no-software-text": "No compatible Software OTA Update packages provisioned.", - "ota-update": "OTA update", - "ota-update-details": "OTA update details", - "ota-updates": "OTA updates", - "package-type": "Package type", - "packages-repository": "Packages repository", - "search": "Search packages", - "selected-package": "{ count, plural, =1 {1 package} other {# packages} } selected", - "title": "Title", - "title-required": "Title is required.", - "title-max-length": "Title should be less than 256", - "types": { - "firmware": "Firmware", - "software": "Software" - }, - "upload-binary-file": "Upload binary file", - "use-external-url": "Use external URL", - "version": "Version", - "version-required": "Version is required.", - "version-tag": "Version tag", - "version-tag-hint": "Custom tag should match the package version reported by your device.", - "version-max-length": "Version should be less than 256", - "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type." + "add": "Add package", + "assign-firmware": "Assigned firmware", + "assign-firmware-required": "Assigned firmware is required", + "assign-software": "Assigned software", + "assign-software-required": "Assigned software is required", + "auto-generate-checksum": "Auto-generate checksum", + "checksum": "Checksum", + "checksum-hint": "If checksum is empty, it will be generated automatically", + "checksum-algorithm": "Checksum algorithm", + "checksum-copied-message": "Package checksum has been copied to clipboard", + "change-firmware": "Change of the firmware may cause update of { count, plural, =1 {1 device} other {# devices} }.", + "change-software": "Change of the software may cause update of { count, plural, =1 {1 device} other {# devices} }.", + "chose-compatible-device-profile": "The uploaded package will be available only for devices with the chosen profile.", + "chose-firmware-distributed-device": "Choose firmware that will be distributed to the devices", + "chose-software-distributed-device": "Choose software that will be distributed to the devices", + "content-type": "Content type", + "copy-checksum": "Copy checksum", + "copy-direct-url": "Copy direct URL", + "copyId": "Copy package Id", + "copied": "Copied!", + "delete": "Delete package", + "delete-ota-update-text": "Be careful, after the confirmation the OTA update will become unrecoverable.", + "delete-ota-update-title": "Are you sure you want to delete the OTA update '{{title}}'?", + "delete-ota-updates-text": "Be careful, after the confirmation all selected OTA updates will be removed.", + "delete-ota-updates-title": "Are you sure you want to delete { count, plural, =1 {1 OTA update} other {# OTA updates} }?", + "description": "Description", + "direct-url": "Direct URL", + "direct-url-copied-message": "Package direct URL has been copied to clipboard", + "direct-url-required": "Direct URL is required", + "download": "Download package", + "drop-file": "Drop a package file or click to select a file to upload.", + "drop-package-file-or": "Drag and drop a package file or", + "file-name": "File name", + "file-size": "File size", + "file-size-bytes": "File size in bytes", + "idCopiedMessage": "Package Id has been copied to clipboard", + "no-firmware-matching": "No compatible Firmware OTA Update packages matching '{{entity}}' were found.", + "no-firmware-text": "No compatible Firmware OTA Update packages provisioned.", + "no-packages-text": "No packages found", + "no-software-matching": "No compatible Software OTA Update packages matching '{{entity}}' were found.", + "no-software-text": "No compatible Software OTA Update packages provisioned.", + "ota-update": "OTA update", + "ota-update-details": "OTA update details", + "ota-updates": "OTA updates", + "package-file": "Package file", + "package-type": "Package type", + "packages-repository": "Packages repository", + "search": "Search packages", + "selected-package": "{ count, plural, =1 {1 package} other {# packages} } selected", + "title": "Title", + "title-required": "Title is required.", + "title-max-length": "Title should be less than 256", + "types": { + "firmware": "Firmware", + "software": "Software" + }, + "upload-binary-file": "Upload binary file", + "use-external-url": "Use external URL", + "version": "Version", + "version-required": "Version is required.", + "version-tag": "Version tag", + "version-tag-hint": "Custom tag should match the package version reported by your device.", + "version-max-length": "Version should be less than 256", + "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type." }, "position": { "top": "Top", @@ -3645,7 +3717,8 @@ "password-requirements": "Password requirements", "password-should-difference": "New password should be different from current", "special-character": "{ count, plural, =1 {1 special character} other {# special characters} }", - "uppercase-letter": "{ count, plural, =1 {1 uppercase letter} other {# uppercase letters} }" + "uppercase-letter": "{ count, plural, =1 {1 uppercase letter} other {# uppercase letters} }", + "at-most": "At most:" } }, "relation": { @@ -3714,6 +3787,8 @@ "no-resource-text": "No resources found", "open-widgets-bundle": "Open widgets bundle", "resource": "Resource", + "resource-file": "Resource file", + "resource-files": "Resource files", "resource-library-details": "Resource details", "resource-type": "Resource type", "resources-library": "Resources library", @@ -3771,7 +3846,6 @@ "search": "Search rule chains", "selected-rulechains": "{ count, plural, =1 {1 rule chain} other {# rule chains} } selected", "open-rulechain": "Open rule chain", - "assign-new-rulechain": "Assign new rulechain", "edge-template-root": "Template root", "assign-to-edge": "Assign to edge", "edge-rulechain": "Edge rule chain", @@ -3865,7 +3939,9 @@ "test": "Test", "help": "Help", "reset-debug-mode": "Reset debug mode in all nodes", - "test-with-this-message": "{{test}} with this message" + "test-with-this-message": "{{test}} with this message", + "queue-hint": "Select a queue for message forwarding to another queue. 'Main' queue is used by default.", + "queue-singleton-hint": "Select a queue for message forwarding in multi-instance environments. 'Main' queue is used by default." }, "timezone": { "timezone": "Timezone", @@ -4053,7 +4129,7 @@ "rule-engine": "Rule Engine", "time-to-live": "Time-to-live", "alarms-and-notifications": "Alarms and notifications", - "ota-files-in-bytes": "OTA files in bytes", + "ota-files-in-bytes": "Files", "ws-title": "WS", "unlimited": "(0 - unlimited)", "maximum-devices": "Devices maximum number", @@ -4074,12 +4150,15 @@ "maximum-rule-chains": "Rule chains maximum number", "maximum-rule-chains-required": "Rule chains maximum number is required.", "maximum-rule-chains-range": "Rule chains maximum number can't be negative", - "maximum-resources-sum-data-size": "Resource files sum size", - "maximum-resources-sum-data-size-required": "Resource files sum size is required.", - "maximum-resources-sum-data-size-range": "Resource files sum size can`t be negative", - "maximum-ota-packages-sum-data-size": "OTA package files sum size", - "maximum-ota-package-sum-data-size-required": "OTA package files sum size is required.", - "maximum-ota-package-sum-data-size-range": "OTA package files sum size can`t be negative", + "maximum-resources-sum-data-size": "Maximum total size of resources files (bytes)", + "maximum-resources-sum-data-size-required": "Maximum total size of resources files is required.", + "maximum-resources-sum-data-size-range": "Maximum total size of resources files can't be negative", + "maximum-resource-size": "Maximum resource file size (bytes)", + "maximum-resource-size-required": "Maximum resource file size is required", + "maximum-resource-size-range": "Maximum resource file size can't be negative", + "maximum-ota-packages-sum-data-size": "Maximum total size of OTA package files (bytes)", + "maximum-ota-package-sum-data-size-required": "Maximum total size of OTA package files is required.", + "maximum-ota-package-sum-data-size-range": "Maximum total size of OTA package files can't be negative", "rest-requests-for-tenant": "REST requests for tenant", "transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages", "transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points", @@ -4206,30 +4285,36 @@ "seconds": "Seconds", "advanced": "Advanced", "predefined": { - "yesterday": "Yesterday", - "day-before-yesterday": "Day before yesterday", - "this-day-last-week": "This day last week", - "previous-week": "Previous week (Sun - Sat)", - "previous-week-iso": "Previous week (Mon - Sun)", - "previous-month": "Previous month", - "previous-quarter": "Previous quarter", - "previous-half-year": "Previous half year", - "previous-year": "Previous year", - "current-hour": "Current hour", - "current-day": "Current day", - "current-day-so-far": "Current day so far", - "current-week": "Current week (Sun - Sat)", - "current-week-iso": "Current week (Mon - Sun)", - "current-week-so-far": "Current week so far (Sun - Sat)", - "current-week-iso-so-far": "Current week so far (Mon - Sun)", - "current-month": "Current month", - "current-month-so-far": "Current month so far", - "current-quarter": "Current quarter", - "current-quarter-so-far": "Current quarter so far", - "current-half-year": "Current half year", - "current-half-year-so-far": "Current half year so far", - "current-year": "Current year", - "current-year-so-far": "Current year so far" + "yesterday": "Yesterday", + "day-before-yesterday": "Day before yesterday", + "this-day-last-week": "This day last week", + "previous-week": "Previous week (Sun - Sat)", + "previous-week-iso": "Previous week (Mon - Sun)", + "previous-month": "Previous month", + "previous-quarter": "Previous quarter", + "previous-half-year": "Previous half year", + "previous-year": "Previous year", + "current-hour": "Current hour", + "current-day": "Current day", + "current-day-so-far": "Current day so far", + "current-week": "Current week (Sun - Sat)", + "current-week-iso": "Current week (Mon - Sun)", + "current-week-so-far": "Current week so far (Sun - Sat)", + "current-week-iso-so-far": "Current week so far (Mon - Sun)", + "current-month": "Current month", + "current-month-so-far": "Current month so far", + "current-quarter": "Current quarter", + "current-quarter-so-far": "Current quarter so far", + "current-half-year": "Current half year", + "current-half-year-so-far": "Current half year so far", + "current-year": "Current year", + "current-year-so-far": "Current year so far" + }, + "type": { + "week": "Week (Sun - Sat)", + "week-iso": "Week (Mon - Sun)", + "month": "Month", + "quarter": "Quarter" } }, "timeunit": { @@ -4288,6 +4373,12 @@ "displayTypePrefix": "Display Realtime/History prefix", "preview": "Preview" }, + "tooltip": { + "value": "Value", + "date": "Date", + "background-color": "Background color", + "background-blur": "Background blur" + }, "unit": { "millimeter": "Millimeter", "centimeter": "Centimeter", @@ -4566,6 +4657,7 @@ "gram-per-cubic-centimeter": "Gram per cubic centimeter", "kilogram-per-square-meter": "Kilogram per square metre", "milligram-per-milliliter": "Milligram per milliliter", + "milligram-per-cubic-meter": "Milligram per cubic meter", "pound-per-cubic-foot": "Pound per cubic foot", "ounces-per-cubic-inch": "Ounces per cubic inch", "tons-per-cubic-yard": "Tons per cubic yard", @@ -5155,15 +5247,20 @@ "background": { "background": "Background", "background-settings": "Background settings", - "background-type-image": "Upload image", - "background-type-image-url": "Image URL", - "background-type-color": "Solid color", + "background-type-image": "Image", + "background-type-color": "Color", "image-url": "Image URL", "overlay": "Overlay", "enable-overlay": "Enable overlay", "blur": "Blur", "preview": "Preview" }, + "bar-chart": { + "bar-appearance": "Bar appearance", + "label-on-bar": "Label on bar", + "value-on-bar": "Value on bar", + "bar-chart-card-style": "Bar chart card style" + }, "battery-level": { "layout": "Layout", "layout-vertical-solid": "Vertical. Solid", @@ -5189,9 +5286,6 @@ "date": "Date", "active-bars-color": "Active signal bars color", "inactive-bars-color": "Inactive signal bars color", - "tooltip": "Tooltip", - "background-color": "Background color", - "background-blur": "Background blur", "signal-strength-card-style": "Signal strength card style" }, "chart": { @@ -5434,18 +5528,8 @@ "clockwise-layout": "Clockwise layout", "sort-series": "Sort series by label", "central-total-value": "Central total value", - "legend-position-top": "Top", - "legend-position-bottom": "Bottom", - "legend-position-left": "Left", - "legend-position-right": "Right", - "legend-label": "Label", - "legend-value": "Value", - "tooltip": "Tooltip", - "tooltip-value": "Value", "tooltip-value-type-absolute": "Absolute", "tooltip-value-type-percentage": "Percentage", - "tooltip-background-color": "Background color", - "tooltip-background-blur": "Background blur", "doughnut-card-style": "Doughnut card style" }, "entities-hierarchy": { @@ -5921,6 +6005,14 @@ "no-columns-found": "No columns found", "no-columns-matching": "'{{column}}' not found." }, + "range-chart": { + "chart": "Chart", + "data-zoom": "Data zoom", + "range-colors": "Range colors", + "out-of-range-color": "Out of range color", + "fill-area": "Fill area", + "range-chart-card-style": "Range chart card style" + }, "rpc": { "value-settings": "Value settings", "initial-value": "Initial value", @@ -6348,8 +6440,7 @@ "hidden-cell-button-display-mode": "Hidden cell button actions display mode", "show-empty-space-hidden-action": "Show empty space instead of hidden cell button action", "dont-reserve-space-hidden-action": "Don't reserve space for hidden action buttons", - "display-timestamp": "Display timestamp column", - "display-milliseconds": "Display timestamp milliseconds", + "display-timestamp": "Timestamp", "display-pagination": "Display pagination", "default-page-size": "Default page size", "use-entity-label-tab-name": "Use entity label in tab name", @@ -6710,9 +6801,9 @@ "ka_GE": "ქართული", "ko_KR": "한국어", "lv_LV": "Latviešu", + "nl_BE": "Koninkrijk België", "pt_BR": "Português do Brasil", "ro_RO": "Română", - "ru_RU": "Русский", "sl_SI": "Slovenščina", "tr_TR": "Türkçe", "uk_UA": "Українська", diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index b9a386071a..8727339851 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -2655,7 +2655,6 @@ "rpc-command-result": "Resultado", "rpc-command-edit-params": "Editar parametros", "gateway-configuration": "Configuración General", - "docker-label": "Usa los siguientes comandos para ejecutar el gateway en Docker.", "create-new-gateway": "Crear un gateway nuevo", "create-new-gateway-text": "Crear un nuevo gateway con el nombre: '{{gatewayName}}'?", "created-time": "Hora de creación", @@ -3094,6 +3093,35 @@ "backup-code-auth-description": "Por favor, introduce el código de backup.", "backup-code-auth-placeholder": "Código de Backup" }, + "signup": { + "firstname": "Nombre", + "lastname": "Apellido", + "email": "Correo electrónico", + "signup": "Registrarse", + "create-password": "Crear una contraseña", + "repeat-password": "Repite tu contraseña", + "have-account": "¿Ya tienes una cuenta?", + "signin": "Iniciar sesión", + "no-captcha-message": "Debes confirmar que no eres un robot", + "password-length-message": "Tu contraseña debe tener al menos 6 caracteres", + "email-verification": "Verificación de correo electrónico", + "email-verification-message": "Se envió un correo electrónico con detalles de verificación a la dirección de correo electrónico especificada. Por favor, sigue las instrucciones proporcionadas en el correo electrónico para completar tu proceso de registro. Nota: si no has visto el correo electrónico durante un tiempo, por favor revisa tu carpeta de 'spam' o intenta reenviar el correo electrónico haciendo clic en el botón 'Reenviar'.", + "account-activation-title": "Activación de cuenta", + "account-activated": "¡Cuenta activada con éxito!", + "account-activated-text": "¡Felicidades! Tu cuenta ha sido activada.", + "resend": "Reenviar", + "inactive-user-exists-title": "Ya existe un usuario inactivo", + "inactive-user-exists-text": "Ya hay un usuario registrado con dirección de correo electrónico no verificada. Haz clic en el botón 'Reenviar' si deseas reenviar el correo electrónico de verificación.", + "activating-account": "Activando cuenta...", + "activating-account-text": "Tu cuenta se está activando actualmente. Por favor espera...", + "accept-privacy-policy": "Aceptar la Política de Privacidad", + "accept": "Aceptar", + "privacy-policy": "Política de Privacidad", + "accept-privacy-policy-message": "Debes aceptar nuestra Política de Privacidad", + "recaptcha-title": "reCAPTCHA", + "terms-of-use": "Términos de Uso", + "accept-terms-of-use-message": "Debes aceptar nuestros Términos de Uso" + }, "markdown": { "edit": "Editar", "preview": "Previsualizar", @@ -3247,15 +3275,13 @@ "recipient-type": { "affected-tenant-administrators": "Administradores afectados", "affected-user": "Usuario afectado", - "affected-user-hint": "Sugerencia en usuario afectado", "all-users": "Todos los usuarios", "customer-users": "Usuarios del cliente", "system-administrators": "Administradores del sistema", "tenant-administrators": "Administradores de propietarios", "user-filters": "Filtro de usuarios", "user-list": "Lista de usuarios", - "users-entity-owner": "Usuarios que sean propietarios de la entidad", - "users-entity-owner-hint": "Sugerencia en usuarios propietarios de la entidad" + "users-entity-owner": "Usuarios que sean propietarios de la entidad" }, "recipients": "Destinatarios", "notification-recipients": "Notificaciones / Destinatarios", @@ -3653,7 +3679,6 @@ "search": "Buscar cadenas de reglas", "selected-rulechains": "{ count, plural, =1 {1 cadena de reglas} other {# cadenas de reglas} } seleccionadas", "open-rulechain": "Abrir cadena de reglas", - "assign-new-rulechain": "Asignar nueva cadena de reglas", "edge-template-root": "Raíz de plantilla", "assign-to-edge": "Asignar a Edge", "edge-rulechain": "Cadena de reglas de Edge", @@ -6066,7 +6091,6 @@ "show-empty-space-hidden-action": "Mostrar espacio vacío en lugar de celda oculta", "dont-reserve-space-hidden-action": "No reservar espacio para los botones en celda oculta", "display-timestamp": "Mostrar columna timestamp", - "display-milliseconds": "Mostrar milisegundos", "display-pagination": "Mostrar páginas", "default-page-size": "Tamaño de página por defecto", "use-entity-label-tab-name": "Usar etiqueta de entidad en el nombre de la tabla", diff --git a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json index 9fbdfef9f4..7e678d9ed9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -1866,7 +1866,6 @@ "set-root-rulechain-title": "Voulez-vous vraiment que la chaîne de règles '{{ruleChainName}} soit racine (root) ?", "system": "Système", "assign-rulechains": "Attribuer aux chaînes de règles", - "assign-new-rulechain": "Attribuer une nouvele chaînes de règles", "delete-rulechains": "Supprimer une chaînes de règles", "unassign-rulechain": "Retirer chaîne de règles", "unassign-rulechains": "Retirer chaînes de règles", diff --git a/ui-ngx/src/assets/locale/locale.constant-nl_BE.json b/ui-ngx/src/assets/locale/locale.constant-nl_BE.json new file mode 100644 index 0000000000..df3820d497 --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-nl_BE.json @@ -0,0 +1,6951 @@ +{ + "access": { + "unauthorized": "Geen toegang", + "unauthorized-access": "Geen toegang", + "unauthorized-access-text": "Gelieve u zich opnieuw in te loggen.", + "access-forbidden": "Toegang verboden", + "access-forbidden-text": "Je hebt geen toegangsrechten tot deze locatie!
Probeer in te loggen met een andere gebruiker als u toch toegang wilt verkrijgen.", + "refresh-token-expired": "Sessie is verlopen", + "refresh-token-failed": "Kan sessie niet vernieuwen", + "permission-denied": "Toestemming geweigerd", + "permission-denied-text": "U hebt geen toestemming om deze bewerking uit te voeren!" + }, + "action": { + "activate": "Activeren", + "suspend": "Opschorten", + "save": "Opslaan", + "saveAs": "Opslaan als", + "cancel": "Annuleren", + "ok": "OK", + "delete": "Verwijderen", + "add": "Toevoegen", + "yes": "Ja", + "no": "Nee", + "update": "Update", + "remove": "Verwijderen", + "search": "Zoeken", + "clear-search": "Zoekopdracht wissen", + "assign": "Toewijzen", + "unassign": "Toewijzing ongedaan maken", + "share": "Delen", + "make-private": "Privé maken", + "make-public": "Openbaar maken", + "apply": "Toepassen", + "apply-changes": "Wijzigingen toepassen", + "edit-mode": "Bewerk modus", + "enter-edit-mode": "Ga naar de bewerkingsmodus", + "decline-changes": "Wijzigingen weigeren", + "open": "Open", + "close": "Sluiten", + "back": "Terug", + "run": "Uitvoeren", + "sign-in": "Aanmelden", + "edit": "Bewerken", + "view": "Bekijken", + "create": "Aanmaken", + "drag": "Slepen", + "refresh": "Vernieuwen", + "undo": "Ongedaan maken", + "copy": "Kopiëren", + "paste": "Plakken", + "copy-reference": "Referentie kopiëren", + "paste-reference": "Referentie plakken", + "import": "Importeren", + "export": "Exporteren", + "share-via": "Delen via {{provider}}", + "move": "Verplaatsen", + "select": "Selecteren", + "continue": "Voortzetten", + "discard-changes": "Wijzigingen negeren", + "download": "Downloaden", + "next": "Volgend", + "next-with-label": "Volgende: {{label}}", + "read-more": "Lees meer", + "hide": "Verbergen", + "done": "Klaar", + "print": "Afdrukken", + "restore": "Herstellen", + "confirm": "Bevestigen", + "more": "Meer", + "less": "Minder", + "skip": "Overslaan", + "send": "Verzenden" + }, + "aggregation": { + "aggregation": "Aggregatie", + "function": "Functie voor gegevensaggregatie", + "limit": "Maximale waarden", + "group-interval": "Groeperingsinterval", + "min": "Min", + "max": "Max", + "avg": "Gemiddeld", + "sum": "Som", + "count": "Tellen", + "none": "Geen" + }, + "admin": { + "settings": "Instellingen", + "general": "Algemeen", + "general-settings": "Algemene instellingen", + "home-settings": "Home-instellingen", + "home": "Thuis", + "outgoing-mail": "E-mailserver", + "outgoing-mail-settings": "Serverinstellingen voor uitgaande e-mail", + "system-settings": "Systeeminstellingen", + "test-mail-sent": "Testmail is succesvol verzonden!", + "base-url": "Basis-URL", + "base-url-required": "Basis-URL is vereist.", + "prohibit-different-url": "Verbieden om de hostnaam te gebruiken in de headers van de clientaanvraag", + "prohibit-different-url-hint": "Deze instelling moet zijn ingeschakeld voor productieomgevingen. Kan beveiligingsproblemen veroorzaken wanneer deze is uitgeschakeld", + "mail-from": "E-mail van", + "mail-from-required": "Mail Van is vereist.", + "smtp-protocol": "SMTP-protocol", + "smtp-host": "SMTP-host", + "smtp-host-required": "SMTP-host is vereist.", + "smtp-port": "SMTP-poort", + "smtp-port-required": "U moet een smtp-poort opgeven.", + "smtp-port-invalid": "Dat ziet er niet uit als een geldige smtp-poort.", + "timeout-msec": "Time-out (msec)", + "timeout-required": "Er is een time-out vereist.", + "timeout-invalid": "Dat ziet er niet uit als een geldige time-out.", + "enable-tls": "TLS inschakelen", + "tls-version": "TLS-versie", + "enable-proxy": "Proxy inschakelen", + "proxy-host": "Proxy-host", + "proxy-host-required": "Proxyhost is vereist.", + "proxy-port": "Proxy-poort", + "proxy-port-required": "Proxypoort is vereist.", + "proxy-port-range": "De proxypoort moet tussen 1 en 65535 liggen.", + "proxy-user": "Proxy-gebruiker", + "proxy-password": "Proxy wachtwoord", + "change-password": "Wachtwoord wijzigen", + "send-test-mail": "Testmail versturen", + "use-system-mail-settings": "Systeeminstellingen voor e-mailserver gebruiken", + "mail-templates": "E-mail sjablonen", + "mail-template-settings": "Instellingen voor e-mailsjablonen", + "use-system-mail-template-settings": "Systeem-e-mailsjablonen gebruiken", + "mail-template": { + "mail-template": "E-mail sjabloon", + "test": "E-mailbericht testen", + "activation": "Bericht over accountactivering", + "account-activated": "Bericht over account geactiveerd", + "account-lockout": "Bericht over accountvergrendeling", + "reset-password": "Bericht over wachtwoord opnieuw instellen", + "password-was-reset": "Bericht Wachtwoord is opnieuw ingesteld", + "user-activated": "Door de gebruiker geactiveerd bericht", + "user-registered": "Geregistreerd bericht van de gebruiker", + "api-usage-state-enabled": "Api-gebruiksstatus ingeschakeld", + "api-usage-state-warning": "Waarschuwing voor API-gebruiksstatus", + "api-usage-state-disabled": "Api-gebruiksstatus uitgeschakeld", + "two-fa-verification": "2FA-verificatiebericht" + }, + "mail-subject": "Onderwerp van de e-mail", + "mail-body": "Hoofdtekst van e-mail", + "sms-provider": "SMS-provider", + "sms-provider-settings": "Instellingen sms-provider", + "use-system-sms-settings": "De instellingen van de systeem-sms-provider gebruiken", + "sms-provider-type": "Type sms-provider", + "sms-provider-type-required": "Het type sms-provider is vereist.", + "sms-provider-type-aws-sns": "Amazone SNS", + "sms-provider-type-twilio": "Twilio", + "sms-provider-type-smpp": "SMPP", + "aws-access-key-id": "AWS-toegangssleutel-ID", + "aws-access-key-id-required": "AWS Access Key ID is vereist", + "aws-secret-access-key": "AWS geheime toegangssleutel", + "aws-secret-access-key-required": "AWS geheime toegangssleutel is vereist", + "aws-region": "AWS-regio", + "aws-region-required": "AWS-regio is vereist", + "number-from": "Telefoonnummer van", + "number-from-required": "Telefoonnummer van is vereist.", + "number-to": "Telefoonnummer naar", + "number-to-required": "Telefoonnummer Naar is vereist.", + "phone-number-hint": "Telefoonnummer in E.164-formaat, bijv. +19995550123", + "phone-number-hint-twilio": "Telefoonnummer in E.164-formaat/SID van telefoonnummer/SID-berichtenservice, bijv. +19995550123/PNXXX/MGXXX", + "phone-number-pattern": "Ongeldig telefoonnummer. Moet in E.164-formaat zijn, bijv. +19995550123.", + "phone-number-pattern-twilio": "Ongeldig telefoonnummer. Moet in E.164-indeling/SID/Berichtenservice SID van het telefoonnummer/telefoonnummer zijn, bijv. +19995550123/PNXXX/MGXXX.", + "sms-message": "SMS-bericht", + "sms-message-required": "SMS-bericht is vereist.", + "sms-message-max-length": "SMS-bericht mag niet langer zijn dan 1600 tekens", + "twilio-account-sid": "TSID voor Twilio-account", + "twilio-account-sid-required": "TSID voor Twilio-account is vereist", + "twilio-account-token": "Twilio-accounttoken", + "twilio-account-token-required": "Twilio-accounttoken is vereist", + "send-test-sms": "Test-sms verzenden", + "test-sms-sent": "Test-sms is succesvol verzonden!", + "security-settings": "Beveiligingsinstellingen", + "password-policy": "Wachtwoord beleid", + "minimum-password-length": "Minimale wachtwoordlengte", + "minimum-password-length-required": "Minimale wachtwoordlengte is vereist", + "minimum-password-length-range": "De minimale wachtwoordlengte moet tussen 5 en 50 liggen", + "minimum-uppercase-letters": "Minimum aantal hoofdletters", + "minimum-uppercase-letters-range": "Minimum aantal hoofdletters mag niet negatief zijn", + "minimum-lowercase-letters": "Minimum aantal kleine letters", + "minimum-lowercase-letters-range": "Minimum aantal kleine letters mag niet negatief zijn", + "minimum-digits": "Minimum aantal cijfers", + "minimum-digits-range": "Minimum aantal cijfers mag niet negatief zijn", + "minimum-special-characters": "Minimum aantal speciale tekens", + "minimum-special-characters-range": "Minimum aantal speciale tekens mag niet negatief zijn", + "password-expiration-period-days": "Vervalperiode van wachtwoord in dagen", + "password-expiration-period-days-range": "De vervalperiode van het wachtwoord in dagen mag niet negatief zijn", + "password-reuse-frequency-days": "Frequentie van hergebruik van wachtwoorden in dagen", + "password-reuse-frequency-days-range": "De frequentie van hergebruik van wachtwoorden in dagen mag niet negatief zijn", + "allow-whitespace": "Witruimte toestaan", + "general-policy": "Algemeen beleid", + "max-failed-login-attempts": "Maximum aantal mislukte inlogpogingen voordat het account wordt vergrendeld", + "minimum-max-failed-login-attempts-range": "Het maximale aantal mislukte aanmeldingspogingen mag niet negatief zijn", + "user-lockout-notification-email": "Als het gebruikersaccount is vergrendeld, stuurt u een melding naar e-mail", + "domain-name": "Domeinnaam", + "domain-name-unique": "Domeinnaam en protocol moeten uniek zijn.", + "domain-name-max-length": "Domeinnaam moet kleiner zijn dan 256 tekens tekens", + "error-verification-url": "Een domeinnaam mag geen symbolen '/' en ':' bevatten. Voorbeeld: thingsboard.io", + "oauth2": { + "access-token-uri": "Toegangstoken URI", + "access-token-uri-required": "De URI van het toegangstoken is vereist.", + "activate-user": "Gebruiker activeren", + "add-domain": "Domein toevoegen", + "delete-domain": "Domein verwijderen", + "add-provider": "Provider toevoegen", + "delete-provider": "Provider verwijderen", + "allow-user-creation": "Aanmaken van gebruikers toestaan", + "always-fullscreen": "Altijd volledig scherm", + "authorization-uri": "Autorisatie-URI", + "authorization-uri-required": "Autorisatie-URI is vereist.", + "client-authentication-method": "Verificatiemethode voor clients", + "client-id": "Client-ID", + "client-id-required": "Client-ID is vereist.", + "client-id-max-length": "Client-ID lengte moet kleiner zijn dan 256 tekens tekens", + "client-secret": "Geheim van de klant", + "client-secret-required": "Clientgeheim is vereist.", + "client-secret-max-length": "Clientgeheim lengte moet kleiner zijn dan 2049 tekens", + "custom-setting": "Aangepaste instellingen", + "customer-name-pattern": "Patroon klantnaam", + "customer-name-pattern-max-length": "Het patroon van de klantnaam moet kleiner zijn dan 256 tekens tekens", + "parent-customer-name-pattern": "Patroon voor bovenliggende klantnaam", + "user-groups-name-pattern": "Naampatroon van gebruikersgroepen", + "default-dashboard-name": "Standaard dashboardnaam", + "default-dashboard-name-max-length": "De standaardnaam van het dashboard moet kleiner zijn dan 256 tekens tekens", + "delete-domain-text": "Let op, na de bevestiging zijn dit domein en alle providergegevens niet meer beschikbaar.", + "delete-domain-title": "Weet u zeker dat u instellingen wilt verwijderen van dit domein '{{domainName}}'?", + "delete-registration-text": "Let op, na de bevestiging zijn de gegevens van deze provider niet meer beschikbaar.", + "delete-registration-title": "Weet u zeker dat u de provider '{{name}}' wilt verwijderen?", + "email-attribute-key": "E-mail attribuut sleutel", + "email-attribute-key-required": "E-mailkenmerksleutel is vereist.", + "email-attribute-key-max-length": "De e-mailkenmerksleutel moet kleiner zijn dan 32 tekens", + "first-name-attribute-key": "Voornaam attribuutsleutel", + "first-name-attribute-key-max-length": "De kenmerksleutel voor de voornaam moet kleiner zijn dan 32 tekens", + "general": "Algemeen", + "jwk-set-uri": "JSON-websleutel-URI", + "last-name-attribute-key": "Sleutel voor het kenmerk achternaam", + "last-name-attribute-key-max-length": "De kenmerksleutel voor de achternaam moet kleiner zijn dan 32 tekens", + "login-button-icon": "Pictogram van de inlogknop", + "login-button-label": "Label van de aanbieder", + "login-button-label-placeholder": "Log in met $(Providerlabel)", + "login-button-label-required": "Label is vereist.", + "login-provider": "Login provider", + "mapper": "Mapper", + "new-domain": "Nieuw domein", + "oauth2": "OAuth2", + "password-max-length": "Wachtwoord moet kleiner zijn dan 256 tekens tekens", + "redirect-uri-template": "Sjabloon voor omleidings-URI", + "copy-redirect-uri": "Omleidings-URI kopiëren", + "registration-id": "Registratie-ID", + "registration-id-required": "Registratie-ID is vereist.", + "registration-id-unique": "Registratie-ID moet uniek zijn voor het systeem.", + "scope": "Draagwijdte", + "scope-required": "Scope is vereist.", + "tenant-name-pattern": "Patroon van tenantnaam", + "tenant-name-pattern-required": "Het patroon van de naam van de tenant is vereist.", + "tenant-name-pattern-max-length": "Patroon van tenantnaam imoet kleiner zijn dan 256 tekens tekens", + "tenant-name-strategy": "Strategie voor naam tenant", + "type": "Type mapper", + "uri-pattern-error": "Ongeldige URI-indeling.", + "url": "URL", + "url-pattern": "Ongeldige URL-indeling.", + "url-required": "URL is vereist.", + "url-max-length": "URL moet kleiner zijn dan 256 tekens tekens", + "user-info-uri": "Gebruikersinfo URI", + "user-info-uri-required": "URI voor gebruikersinformatie is vereist.", + "username-max-length": "Gebruikersnaam moet kleiner zijn dan 256 tekens tekens", + "user-name-attribute-name": "Sleutel voor gebruikersnaamattribuut", + "user-name-attribute-name-required": "De kenmerksleutel van de gebruikersnaam is vereist", + "protocol": "Protocol", + "domain-schema-http": "HTTP (HTTP)", + "domain-schema-https": "HTTPS", + "domain-schema-mixed": "HTTP+HTTPS", + "enable": "OAuth2-instellingen inschakelen", + "domains": "Domeinen", + "mobile-apps": "Mobiele applicaties", + "no-mobile-apps": "Geen applicaties geconfigureerd", + "mobile-package": "Applicatie pakket", + "mobile-package-placeholder": "Vb.: my.example.app", + "mobile-package-hint": "Voor Android: uw eigen unieke applicatie-ID. Voor iOS: ID van productbundel.", + "mobile-package-unique": "Het toepassingspakket moet uniek zijn.", + "mobile-app-secret": "Geheim van de toepassing", + "invalid-mobile-app-secret": "Het geheim van de toepassing mag alleen alfanumerieke tekens bevatten en moet tussen de 16 en 2048 tekens lang zijn.", + "copy-mobile-app-secret": "Toepassingsgeheim kopiëren", + "add-mobile-app": "Applicatie toevoegen", + "delete-mobile-app": "Toepassingsgegevens verwijderen", + "providers": "Providers", + "platform-web": "Web", + "platform-android": "Android", + "platform-ios": "iOS", + "all-platforms": "Alle platformen", + "allowed-platforms": "Toegestane platforms" + }, + "smpp-provider": { + "smpp-version": "SMPP-versie", + "smpp-host": "SMPP-host", + "smpp-host-required": "SMPP-host is vereist", + "smpp-port": "SMPP-poort", + "smpp-port-required": "SMPP-poort is vereist", + "system-id": "Systeem-ID", + "system-id-required": "Systeem-ID is vereist", + "password": "Wachtwoord", + "password-required": "Wachtwoord is vereist", + "type-settings": "Typ instellingen", + "source-settings": "Bron-instellingen", + "destination-settings": "Bestemming instellingen", + "additional-settings": "Aanvullende instellingen", + "system-type": "Type systeem", + "bind-type": "Type binden", + "service-type": "Soort dienst", + "source-address": "Bronadres", + "source-ton": "Bron: TON", + "source-npi": "Bron: NPI", + "destination-ton": "Bestemming TON (type nummer)", + "destination-npi": "Bestemming NPI (Nummerplan Identificatie)", + "address-range": "Adres bereik", + "coding-scheme": "Coderingsschema", + "bind-type-tx": "Zender", + "bind-type-rx": "Ontvanger", + "bind-type-trx": "Transciever", + "ton-unknown": "Onbekend", + "ton-international": "Internationaal", + "ton-national": "Nationaal", + "ton-network-specific": "Netwerk Specifiek", + "ton-subscriber-number": "Abonneenummer", + "ton-alphanumeric": "Alfanumeriek", + "ton-abbreviated": "Afgekort", + "npi-unknown": "0 - Onbekend", + "npi-isdn": "1 - ISDN/telefoonnummerplan (E163/E164)", + "npi-data-numbering-plan": "3 - Nummeringsplan voor gegevens (X.121)", + "npi-telex-numbering-plan": "4 - Telexnummerplan (F.69)", + "npi-land-mobile": "6 - Landmobiel (E.212)", + "npi-national-numbering-plan": "8 - Nationaal nummerplan", + "npi-private-numbering-plan": "9 - Privé nummerplan", + "npi-ermes-numbering-plan": "10 - ERMES-nummerplan (ETSI DE/PS 3 01-3)", + "npi-internet": "13 - Internet (IP)", + "npi-wap-client-id": "18 - WAP-client-ID (te definiëren door WAP-forum)", + "scheme-smsc": "0 - SMSC Default Alphabet (ASCII voor korte en lange code en naar GSM voor gratis)", + "scheme-ia5": "1 - IA5 (ASCII voor korte en lange code, Latijnse 9 voor gratis code (ISO-8859-9))", + "scheme-octet-unspecified-2": "2 - Octet Niet gespecificeerd (8-bits binair)", + "scheme-latin-1": "3 - Latijn 1 (ISO-8859-1)", + "scheme-octet-unspecified-4": "4 - Octet Niet gespecificeerd (8-bits binair)", + "scheme-jis": "5 - JIS (X 0208-1990)", + "scheme-cyrillic": "6 - Cyrillisch (ISO-8859-5)", + "scheme-latin-hebrew": "7 - Latijn/Hebreeuws (ISO-8859-8)", + "scheme-ucs-utf": "8 - UCS2/UTF-16 (ISO/IEC-10646)", + "scheme-pictogram-encoding": "9 - Codering van pictogrammen", + "scheme-music-codes": "10 - Muziek Codes (ISO-2022-JP)", + "scheme-extended-kanji-jis": "13 - Uitgebreide Kanji JIS (X 0212-1990)", + "scheme-korean-graphic-character-set": "14 - Koreaanse grafische tekenset (KS C 5601/KS X 1001)" + }, + "queue-select-name": "Selecteer de naam van de queue", + "queue-name": "Naam", + "queue-name-required": "Queue naam is verplicht!", + "queues": "Queues", + "queue-partitions": "Partities", + "queue-submit-strategy": "Strategie indienen", + "queue-processing-strategy": "Verwerkingsstrategie", + "queue-configuration": "Configuratie van queues", + "repository-settings": "Repository-instellingen", + "repository": "Opslagplaats", + "repository-url": "URL van opslagplaats", + "repository-url-required": "De URL van de opslagplaats is vereist.", + "default-branch": "Standaard filiaalnaam", + "repository-read-only": "Alleen-lezen", + "show-merge-commits": "Samenvoegcommits weergeven", + "authentication-settings": "Authenticatie-instellingen", + "auth-method": "Verificatiemethode", + "auth-method-username-password": "Wachtwoord / toegangstoken", + "auth-method-username-password-hint": "GitHub-gebruikers moeten toegang tokens gebruiken met schrijfmachtigingen voor de opslagplaats.", + "auth-method-private-key": "Privé-sleutel", + "password-access-token": "Wachtwoord / toegangstoken", + "change-password-access-token": "Wachtwoord / toegangstoken wijzigen", + "private-key": "Privé-sleutel", + "drop-private-key-file-or": "Sleep een bestand met een persoonlijke sleutel of", + "passphrase": "Wachtwoordzin", + "enter-passphrase": "Voer wachtwoordzin in", + "change-passphrase": "Wachtwoordzin wijzigen", + "check-access": "Toegang controleren", + "check-repository-access-success": "Toegang tot repository succesvol geverifieerd!", + "delete-repository-settings-title": "Weet je zeker dat je repository-instellingen wilt verwijderen?", + "delete-repository-settings-text": "Opgelet, na de bevestiging worden de instellingen van het archief verwijderd en is de versiebeheerfunctie niet meer beschikbaar.", + "auto-commit-settings": "Instellingen voor automatisch vastleggen", + "auto-commit": "Automatisch vastleggen", + "auto-commit-entities": "Entiteiten automatisch vastleggen", + "no-auto-commit-entities-prompt": "Geen entiteiten geconfigureerd voor automatisch vastleggen", + "delete-auto-commit-settings-title": "Weet je zeker dat je instellingen voor automatisch vastleggen wilt verwijderen?", + "delete-auto-commit-settings-text": "Opgelet, na de bevestiging worden de instellingen voor automatisch vastleggen verwijderd en wordt automatisch vastleggen uitgeschakeld voor alle entiteiten.", + "2fa": { + "2fa": "Twee-factor-authenticatie", + "available-providers": "Beschikbare providers", + "issuer-name": "Naam van de uitgever", + "issuer-name-required": "De naam van de uitgever is vereist.", + "max-verification-failures-before-user-lockout": "Max. aantal verificatiefouten vóór gebruikersvergrendeling", + "max-verification-failures-before-user-lockout-pattern": "Het maximum aantal mislukte verificaties moet een positief geheel getal zijn.", + "number-of-checking-attempts": "Aantal controlepogingen", + "number-of-checking-attempts-pattern": "Het aantal controlepogingen moet een positief geheel getal zijn.", + "number-of-checking-attempts-required": "Het aantal controlepogingen is vereist.", + "number-of-codes": "Aantal codes", + "number-of-codes-pattern": "Het aantal codes moet een positief geheel getal zijn.", + "number-of-codes-required": "Aantal codes is vereist.", + "provider": "Aanbieder", + "retry-verification-code-period": "Verificatiecodeperiode opnieuw proberen (sec)", + "retry-verification-code-period-pattern": "Minimale tijdsduur is 5 sec", + "retry-verification-code-period-required": "De periode van de verificatiecode voor opnieuw proberen is vereist.", + "total-allowed-time-for-verification": "Totale toegestane tijd voor verificatie (sec)", + "total-allowed-time-for-verification-pattern": "Minimaal toegestane tijd is 60 sec", + "total-allowed-time-for-verification-required": "De totale toegestane tijd is vereist.", + "use-system-two-factor-auth-settings": "Tweefactorauthenticatie-instellingen van het systeem gebruiken", + "verification-code-check-rate-limit": "Snelheidslimiet voor verificatiecodecontrole", + "verification-code-lifetime": "Levensduur verificatiecode (sec)", + "verification-code-lifetime-pattern": "De levensduur van de verificatiecode moet een positief geheel getal zijn.", + "verification-code-lifetime-required": "De levensduur van de verificatiecode is vereist.", + "verification-message-template": "Sjabloon voor verificatiebericht", + "verification-limitations": "Beperkingen voor verificatie", + "verification-message-template-pattern": "Verificatiebericht moet patroon bevatten: ${code}", + "verification-message-template-required": "Verificatieberichtsjabloon is vereist.", + "within-time": "Binnen de tijd (sec)", + "within-time-pattern": "Tijd moet een positief geheel getal zijn.", + "within-time-required": "Er is tijd nodig." + }, + "jwt": { + "security-settings": "JWT-beveiligingsinstellingen", + "issuer-name": "Naam van de uitgever", + "issuer-name-required": "De naam van de uitgever is vereist.", + "signings-key": "Sleutel ondertekenen", + "signings-key-hint": "Base64-gecodeerde tekenreeks die ten minste 256 bits aan gegevens vertegenwoordigt.", + "signings-key-required": "Ondertekeningssleutel is vereist.", + "signings-key-min-length": "De ondertekeningssleutel moet ten minste 256 bits aan gegevens bevatten.", + "signings-key-base64": "De ondertekeningssleutel moet de base64-indeling hebben.", + "expiration-time": "Vervaltijd token (sec)", + "expiration-time-required": "De vervaltijd van het token is vereist.", + "expiration-time-pattern": "De vervaltijd van het token is een positief geheel getal.", + "expiration-time-min": "De minimale tijd is 60 seconden (1 minuut).", + "refresh-expiration-time": "Vervaltijd token vernieuwen (sec)", + "refresh-expiration-time-required": "De vervaltijd van het vernieuwingstoken is vereist.", + "refresh-expiration-time-pattern": "De vervaltijd van het token vernieuwen is een positief geheel getal.", + "refresh-expiration-time-min": "De minimale tijd is 900 seconden (15 minuten).", + "refresh-expiration-time-less-token": "De tokentijd voor vernieuwen moet een grotere tokentijd zijn.", + "generate-key": "Sleutel genereren", + "info-header": "Alle gebruikers moeten opnieuw inloggen", + "info-message": "Wijziging van de JWT-ondertekeningssleutel zorgt ervoor dat alle uitgegeven tokens ongeldig zijn. Alle gebruikers moeten opnieuw inloggen. Dit heeft ook gevolgen voor scripts die gebruikmaken van Rest API/Websockets." + }, + "resources": "Weg", + "notifications": "Meldingen", + "notifications-settings": "Instellingen voor meldingen", + "slack-api-token": "Slack API-token", + "slack": "Los", + "slack-settings": "Slack-instellingen" + }, + "alarm": { + "alarm": "Alarm", + "alarms": "Alarmen", + "all-alarms": "Alle alarmen", + "select-alarm": "Selecteer alarm", + "no-alarms-matching": "Er zijn geen alarmen gevonden die overeenkomen met '{{entity}}'.", + "alarm-required": "Alarm is vereist", + "alarm-filter": "Alarm filter", + "filter": "Filter", + "alarm-status": "Alarmstatus", + "alarm-status-list": "Lijst met alarmstatussen", + "any-status": "Elke status", + "search-status": { + "ANY": "Enig", + "ACTIVE": "Actief", + "CLEARED": "Uitgeschakeld", + "ACK": "Erkend", + "UNACK": "Niet erkend" + }, + "display-status": { + "ACTIVE_UNACK": "Actief Niet erkend", + "ACTIVE_ACK": "Actief Erkend", + "CLEARED_UNACK": "Gewist Niet bevestigd", + "CLEARED_ACK": "Gewist Erkend" + }, + "no-alarms-prompt": "Geen alarmen gevonden", + "created-time": "Gecreëerde tijd", + "type": "Type", + "severity": "Strengheid", + "originator": "Opdrachtgever", + "originator-type": "Type opsteller", + "details": "Details", + "originator-label": "Etiket van de afzender", + "assign": "Toewijzen", + "assignments": "Toewijzingen", + "assignee": "Gevolmachtigde", + "assignee-id": "Verantwoordelijke ID", + "assignee-first-name": "Voornaam van de gevolmachtigde", + "assignee-last-name": "Achternaam van de cessionaris", + "assignee-email": "E-mailadres van de verantwoordelijke", + "unassigned": "Toegewezen", + "assignee-not-set": "Alle", + "status": "Status", + "alarm-details": "Details van het alarm", + "start-time": "Begintijd", + "assign-time": "Tijd toewijzen", + "end-time": "Eindtijd", + "ack-time": "Erkende tijd", + "clear-time": "Vrijgemaakte tijd", + "duration": "Duur", + "alarm-severity-list": "Lijst met alarmernst", + "any-severity": "Elke ernst", + "severity-critical": "Kritisch", + "severity-major": "Majoor", + "severity-minor": "Minderjarige", + "severity-warning": "Waarschuwing", + "severity-indeterminate": "Onbepaalde", + "acknowledge": "Erkennen", + "clear": "Duidelijk", + "search": "Alarmen zoeken", + "selected-alarms": "{ count, plural, =1 {1 alarm} andere {# alarms} } geselecteerd", + "no-data": "Geen gegevens om weer te geven", + "polling-interval": "Polling-interval alarmen (sec)", + "polling-interval-required": "Alarmen polling-interval is vereist.", + "min-polling-interval-message": "Minimaal 1 sec polling-interval is toegestaan.", + "aknowledge-alarms-title": "Erken { count, plural, =1 {1 alarm} andere {# alarms} }", + "aknowledge-alarms-text": "Weet je zeker dat je { count, plural, =1 {1 alarm} andere {# alarms} } wilt erkennen?", + "aknowledge-alarm-title": "Alarm bevestigen", + "aknowledge-alarm-text": "Weet u zeker dat u Alarm wilt erkennen?", + "clear-alarms-title": "Wis { count, plural, =1 {1 alarm} andere {# alarms} }", + "clear-alarms-text": "Weet u zeker dat u { count, plural, =1 {1 alarm} andere {# alarms} } wilt wissen?", + "clear-alarm-title": "Alarm wissen", + "clear-alarm-text": "Weet u zeker dat u Alarm wilt wissen?", + "alarm-status-filter": "Alarm Status Filter", + "alarm-filter-title": "Alarm Filter", + "assigned": "Toegewezen", + "filter-title": "Filter", + "max-count-load": "Maximaal aantal te laden alarmen (0 - onbeperkt)", + "max-count-load-required": "Er is een maximaal aantal alarmen vereist om te laden.", + "max-count-load-error-min": "Minimale waarde is 0.", + "fetch-size": "Ophaal grootte", + "fetch-size-required": "Ophaal grootte is vereist.", + "fetch-size-error-min": "Minimale waarde is 10.", + "alarm-type-list": "Lijst met alarmtypen", + "any-type": "Elk type", + "assigned-to-current-user": "Toegewezen aan huidige gebruiker", + "assigned-to-me": "Aan mij toegewezen", + "search-propagated-alarms": "Doorgegeven alarmen zoeken", + "comments": "Alarm opmerkingen", + "show-more": "Meer tonen", + "additional-info": "Aanvullende informatie" + }, + "alarm-activity": { + "add": "Voeg een reactie...", + "alarm-comment": "Alarm commentaar", + "comments": "Opmerkingen", + "delete-alarm-comment": "Wil je deze reactie verwijderen?", + "refresh": "Vernieuwen", + "oldest-first": "Oudste eerst", + "newest-first": "Nieuwste eerst", + "activity": "Activiteit", + "export": "Exporteren naar CSV", + "author": "Auteur", + "created-date": "Datum van aanmaak", + "edited-date": "Bewerkte datum", + "text": "Sms", + "system": "Systeem" + }, + "alias": { + "add": "Alias toevoegen", + "edit": "Alias bewerken", + "name": "Alias naam", + "name-required": "Aliasnaam is vereist", + "duplicate-alias": "Alias met dezelfde naam bestaat al.", + "filter-type-single-entity": "Eén entiteit", + "filter-type-entity-group": "Entiteiten van de groep", + "filter-type-entity-list": "Lijst met entiteiten", + "filter-type-entity-name": "Naam van de entiteit", + "filter-type-entity-type": "Type entiteit", + "filter-type-entity-group-list": "Lijst met entiteitsgroepen", + "filter-type-entity-group-name": "Naam van entiteitsgroep", + "filter-type-entities-by-group-name": "Entiteiten op groepsnaam", + "filter-type-state-entity": "Entiteit vanuit dashboardstatus", + "filter-type-state-entity-description": "Entiteit die is overgenomen uit de statusparameters van het dashboard", + "filter-type-state-entity-owner": "Eigenaar van entiteit vanuit dashboardstatus", + "filter-type-state-entity-owner-description": "Eigenaar van entiteit die is overgenomen uit de parameters van de dashboardstatus", + "filter-type-asset-type": "Type asset", + "filter-type-asset-type-description": "Asset van het type \"{{assetTypes}}\"", + "filter-type-asset-type-and-name-description": "Asset van het type '{{assetTypes}}' en waarvan de naam begint met '{{prefix}}'", + "filter-type-device-type": "Type device", + "filter-type-device-type-description": "Devices van het type \"{{deviceTypes}}\"", + "filter-type-device-type-and-name-description": "Devices van het type '{{deviceTypes}}' en waarvan de naam begint met '{{prefix}}'", + "filter-type-entity-view-type": "Type entiteitsweergave", + "filter-type-entity-view-type-description": "Entiteitsweergaven van het type '{{entityViewTypes}}'", + "filter-type-entity-view-type-and-name-description": "Entiteitsweergaven van het type '{{entityViewTypes}}' en met een naam die begint met '{{prefix}}'", + "filter-type-edge-type": "Type edge", + "filter-type-edge-type-description": "Edge van het type \"{{edgeTypes}}\"", + "filter-type-edge-type-and-name-description": "Edge van het type '{{edgeTypes}}' en met de naam beginnend met '{{prefix}}'", + "filter-type-relations-query": "Query op relaties", + "filter-type-relations-query-description": "{{entities}} die {{relationType}} relatie hebben {{direction}} {{rootEntity}}", + "filter-type-edge-search-query": "Edge-zoekopdracht", + "filter-type-edge-search-query-description": "Edge met types {{edgeTypes}} die {{relationType}} relatie hebben {{direction}} {{rootEntity}}", + "filter-type-scheduler-event": "Scheduler-events", + "filter-type-scheduler-event-type-description": "Scheduler-events met het type '{{eventType}}'", + "filter-type-scheduler-event-originator-description": "Scheduler-gebeurtenissen van een specifieke afzender", + "filter-type-scheduler-event-type-originator-description": "Scheduler-gebeurtenissen van een specifieke afzender met '{{eventType}}'", + "filter-type-asset-search-query": "Zoekquery voor asset", + "filter-type-asset-search-query-description": "Asset met types {{assetTypes}} die {{relationType}} relatie hebben {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Zoekquery voor device", + "filter-type-device-search-query-description": "Devices met types {{deviceTypes}} die {{relationType}} relatie hebben {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Zoekquery voor entiteitsweergave", + "filter-type-entity-view-search-query-description": "Entiteitsweergaven met types {{entityViewTypes}} die {{relationType}} relatie hebben {{direction}} {{rootEntity}}", + "filter-type-apiUsageState": "Api-gebruiksstatus", + "entity-filter": "Entiteit filteren", + "resolve-multiple": "Oplossen als meerdere entiteiten", + "filter-type": "Filter type", + "filter-type-required": "Filtertype is vereist.", + "entity-filter-no-entity-matched": "Er zijn geen entiteiten gevonden die overeenkomen met het opgegeven filter.", + "no-entity-filter-specified": "Geen entiteitsfilter opgegeven", + "root-state-entity": "Dashboardstatusentiteit gebruiken als hoofdmap", + "group-state-entity": "Dashboardstatusentiteit gebruiken als entiteitsgroep", + "group-state-entity-owner": "Dashboardstatusentiteit gebruiken als eigenaar van entiteitsgroep", + "last-level-relation": "Alleen relatie op het laatste niveau ophalen", + "root-entity": "Hoofdentiteit", + "state-entity-parameter-name": "Naam van de parameter van de entiteit van de staat", + "default-state-entity": "Entiteit met standaardstatus", + "default-state-entity-group": "Entiteitsgroep standaardstatus", + "default-entity-parameter-name": "Standaard", + "max-relation-level": "Max. relatieniveau", + "unlimited-level": "Onbeperkt niveau", + "state-entity": "Entiteit voor dashboardstatus", + "entities-of-group-state-entity": "Entiteiten uit de entiteitsgroep van de dashboardstatus", + "all-entities": "Alle entiteiten", + "any-relation": "enig", + "originator": "Opdrachtgever", + "originator-state-entity": "Dashboardstatusentiteit gebruiken als afzender", + "scheduler-event-type": "Gebeurtenistype planner" + }, + "asset": { + "all": "Alle", + "all-assets": "Alle assets", + "groups": "Groepen", + "shared": "Gedeeld", + "asset": "Asset", + "assets": "Assets", + "management": "Vermogensbeheer", + "view-assets": "Assets bekijken", + "add": "Asset toevoegen", + "asset-type-max-length": "Het type asset moet kleiner zijn dan 256 tekens tekens", + "assign-to-customer": "Toewijzen aan klant", + "assign-asset-to-customer": "Asset(s) toewijzen aan klant", + "assign-asset-to-customer-text": "Selecteer de asset die u aan de klant wilt toewijzen", + "no-assets-text": "Geen asset gevonden", + "assign-to-customer-text": "Selecteer de klant om de asset toe te wijzen", + "public": "Publiek", + "assignedToCustomer": "Toegewezen aan klant", + "make-public": "Assets openbaar maken", + "make-private": "Assets privé maken", + "unassign-from-customer": "Toewijzing van klant ongedaan maken", + "delete": "Asset verwijderen", + "asset-public": "Asset is openbaar", + "asset-type": "Type asset", + "asset-type-required": "Het type asset is vereist.", + "select-asset-type": "Selecteer asset type", + "enter-asset-type": "Asset type invoeren", + "any-asset": "Elke asset", + "no-asset-types-matching": "Er zijn geen asset types gevonden die overeenkomen met '{{entitySubtype}}'.", + "asset-type-list-empty": "Er zijn geen asset types geselecteerd.", + "asset-types": "Soorten asset", + "name": "Naam", + "name-required": "Naam is verplicht.", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens tekens", + "label-max-length": "Etiket moet kleiner zijn dan 256 tekens tekens", + "description": "Omschrijving: __________", + "type": "Type", + "type-required": "Type is vereist.", + "details": "Detail", + "events": "Events", + "add-asset-text": "Nieuw asset toevoegen", + "asset-details": "Details van het bedrijfsmiddel", + "assign-assets": "Middelen toewijzen", + "assign-assets-text": "Wijs { count, plural, =1 {1 asset} andere {# assets} } toe aan de klant", + "assign-asset-to-edge-title": "Asset(s) toewijzen aan Edge", + "assign-asset-to-edge-text": "Selecteer de assets die u aan de edge wilt toewijzen", + "delete-assets": "Assets verwijderen", + "unassign-assets": "Toewijzing van assets ongedaan maken", + "unassign-assets-action-title": "Toewijzing { count, plural, =1 {1 asset} andere {# assets} } van klant ongedaan maken", + "assign-new-asset": "Nieuw asset toewijzen", + "delete-asset-title": "Weet je zeker dat je de asset '{{assetName}}' wilt verwijderen?", + "delete-asset-text": "Opgelet, na de bevestiging worden de asset en alle gerelateerde gegevens onherstelbaar.", + "delete-assets-title": "Weet u zeker dat u { count, plural, =1 {1 asset} andere {# assets} } wilt verwijderen?", + "delete-assets-action-title": "Verwijder { count, plural, =1 {1 asset} andere {# assets} }", + "delete-assets-text": "Opgelet, na de bevestiging worden alle geselecteerde assets verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", + "make-public-asset-title": "Weet je zeker dat je de asset '{{assetName}}' openbaar wilt maken?", + "make-public-asset-text": "Na de bevestiging worden de asset en alle bijbehorende gegevens openbaar en toegankelijk gemaakt voor anderen.", + "make-private-asset-title": "Weet je zeker dat je de asset '{{assetName}}' privé wilt maken?", + "make-private-asset-text": "Na de bevestiging worden de asset en al zijn gegevens privé gemaakt en zijn ze niet toegankelijk voor anderen.", + "unassign-asset-title": "Weet je zeker dat je de toewijzing van de asset '{{assetName}}' ongedaan wilt maken?", + "unassign-asset-text": "Na de bevestiging wordt de toewijzing van de asset ongedaan gemaakt en is het niet toegankelijk voor de klant.", + "unassign-asset": "Toewijzing van asset ongedaan maken", + "unassign-assets-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 asset} andere {# assets} } wilt opheffen?", + "unassign-assets-text": "Na de bevestiging worden alle geselecteerde assets ongedaan gemaakt en zijn ze niet toegankelijk voor de klant.", + "copyId": "Item-ID kopiëren", + "idCopiedMessage": "Item-ID is gekopieerd naar het klembord", + "select-asset": "Selecteer asset", + "no-assets-matching": "Er zijn geen assets gevonden die overeenkomen met '{{entity}}'.", + "asset-required": "Asset is vereist", + "name-starts-with": "Expressie van itemnaam", + "help-text": "Gebruik '%' naar behoefte: '%asset_name_contains%', '%asset_name_ends', 'asset_starts_with'.", + "search": "Assets zoeken", + "select-group-to-add": "Selecteer doelgroep om geselecteerde assets toe te voegen", + "select-group-to-move": "Selecteer de doelgroep om geselecteerde assets te verplaatsen", + "remove-assets-from-group": "Weet je zeker dat je { count, plural, =1 {1 asset} andere {# assets} } uit groep '{{entityGroup}}' wilt verwijderen?", + "group": "Groep van assets", + "list-of-groups": "{ count, plural, =1 {One asset group} andere {List of # asset groups} }", + "group-name-starts-with": "Asset groepen waarvan de naam begint met '{{prefix}}'", + "import": "Asset importeren", + "asset-file": "Asset bestand", + "label": "Etiket", + "assign-asset-to-edge": "Asset(s) toewijzen aan Edge", + "unassign-asset-from-edge": "Toewijzing van asset ongedaan maken", + "unassign-asset-from-edge-title": "Weet je zeker dat je de toewijzing van de asset '{{assetName}}' ongedaan wilt maken?", + "unassign-asset-from-edge-text": "Na de bevestiging wordt de toewijzing van de asset ongedaan gemaakt en is het niet toegankelijk via de edge.", + "unassign-assets-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 asset} andere {# assets} } wilt opheffen?", + "unassign-assets-from-edge-text": "Na de bevestiging worden alle geselecteerde assets ongedaan gemaakt en zijn ze niet toegankelijk via de edge.", + "selected-assets": "{ count, plural, =1 {1 asset} andere {# assets} } geselecteerd" + }, + "attribute": { + "attributes": "Attributen", + "latest-telemetry": "Nieuwste telemetrie", + "attributes-scope": "Bereik van entiteits attributen", + "scope-latest-telemetry": "Nieuwste telemetrie", + "scope-client": "Attributen van de klant", + "scope-server": "Server-attributen", + "scope-shared": "Gedeelde attributen", + "add": "Attribuut toevoegen", + "add-attribute-prompt": "Voeg attribuut toe", + "key": "Sleutel", + "key-max-length": "Sleutel moet kleiner zijn dan 256 tekens tekens", + "last-update-time": "Laatste update tijd", + "key-required": "Attribuutsleutel is vereist.", + "value": "Waarde", + "value-required": "Kenmerkwaarde is vereist.", + "delete-attributes-title": "Weet u zeker dat u { count, plural, =1 {1 attribute} andere {# attributes} } wilt verwijderen?", + "delete-attributes-text": "Opgelet, na de bevestiging worden alle geselecteerde attributen verwijderd.", + "delete-attributes": "Attributen verwijderen", + "enter-attribute-value": "Kenmerkwaarde invoeren", + "show-on-widget": "Toon op widget", + "widget-mode": "Widget-modus", + "next-widget": "Volgende widget", + "prev-widget": "Vorige widget", + "add-to-dashboard": "Toevoegen aan dashboard", + "add-widget-to-dashboard": "Widget toevoegen aan dashboard", + "selected-attributes": "{ count, plural, =1 {1 attribute} andere {# attributes} } geselecteerd", + "selected-telemetry": "{ count, plural, =1 {1 telemetry unit} andere {# telemetry units} } geselecteerd", + "no-attributes-text": "Geen attributen gevonden", + "no-telemetry-text": "Geen telemetrie gevonden", + "copy-key": "Kopieer sleutel", + "copy-value": "Waarde kopiëren" + }, + "api-usage": { + "api-features": "API-functies", + "api-usage": "API-gebruik", + "alarm": "Alarm", + "alarms-created": "Alarmen gemaakt", + "alarms-created-daily-activity": "Alarmen creëerden dagelijkse activiteit", + "alarms-created-hourly-activity": "Alarmen creëerden activiteit per uur", + "alarms-created-monthly-activity": "Alarmen creëerden maandelijkse activiteit", + "data-points": "Datapunten", + "data-points-storage-days": "Opslagdagen voor datapunten", + "device-api": "Device-API", + "email": "E-mail", + "email-messages": "E-mailberichten", + "email-messages-daily-activity": "Dagelijkse activiteit e-mailberichten", + "email-messages-monthly-activity": "Maandelijkse activiteit e-mailberichten", + "exceptions": "Uitzonderingen", + "executions": "Executies", + "javascript": "JavaScript", + "javascript-executions": "JavaScript-uitvoeringen", + "javascript-functions": "JavaScript-functies", + "javascript-functions-daily-activity": "JavaScript-functies dagelijkse activiteit", + "javascript-functions-hourly-activity": "JavaScript-functies activiteit per uur", + "javascript-functions-monthly-activity": "JavaScript-functies maandelijkse activiteit", + "latest-error": "Laatste error", + "messages": "Berichten", + "notifications": "Meldingen", + "notifications-email-sms": "Meldingen (e-mail/sms)", + "notifications-hourly-activity": "Meldingen activiteit per uur", + "permanent-failures": "${entityName} Permanente storingen", + "permanent-timeouts": "Permanente time-outs van $ {entityName}", + "processing-failures": "${entityName} Verwerkingsfouten", + "processing-failures-and-timeouts": "Verwerkingsfouten en time-outs", + "processing-timeouts": "${entityName} time-outs voor verwerking", + "queue-stats": "Queue Statistieken", + "rule-chain": "Rule chain", + "rule-engine": "Rule engine", + "rule-engine-daily-activity": "Dagelijkse activiteit van de rule engine", + "rule-engine-executions": "Uitvoeringen van rule engine", + "rule-engine-hourly-activity": "Rule engine uurlijkse activiteit", + "rule-engine-monthly-activity": "Maandelijkse activiteit van Rule Engine", + "rule-engine-statistics": "Rule Engine-statistieken", + "rule-node": "Regel Knooppunt", + "sms": "SMS", + "sms-messages": "SMS-berichten", + "sms-messages-daily-activity": "Dagelijkse activiteit SMS-berichten", + "sms-messages-monthly-activity": "SMS-berichten maandelijkse activiteit", + "successful": "${entityName} Succesvol", + "telemetry": "Telemetrie", + "telemetry-persistence": "Persistentie van telemetrie", + "telemetry-persistence-daily-activity": "Dagelijkse activiteit met behoud van telemetrie", + "telemetry-persistence-hourly-activity": "Telemetriepersistentie activiteit per uur", + "telemetry-persistence-monthly-activity": "Maandelijkse activiteit voor telemetriepersistentie", + "transport": "Vervoer", + "transport-daily-activity": "Vervoer dagelijkse activiteit", + "transport-data-points": "Datapunten voor transport", + "transport-hourly-activity": "Vervoer per uur", + "transport-messages": "Berichten vervoeren", + "transport-monthly-activity": "Maandelijkse activiteit Transport", + "view-details": "Bekijk details", + "view-statistics": "Statistieken bekijken" + }, + "audit-log": { + "audit": "Audit", + "audit-logs": "Audit Logboeken", + "timestamp": "Tijdstempel", + "entity-type": "Type entiteit", + "entity-name": "Naam van de entiteit", + "user": "Gebruiker", + "type": "Type", + "status": "Status", + "details": "Details", + "type-added": "Toegevoegd", + "type-deleted": "Verwijderd", + "type-updated": "Bijgewerkt", + "type-attributes-updated": "Attributen geüpdatet", + "type-attributes-deleted": "Attributen verwijderd", + "type-rpc-call": "RPC-oproep", + "type-credentials-updated": "Inloggegevens bijgewerkt", + "type-assigned-to-customer": "Toegewezen aan klant", + "type-unassigned-from-customer": "Toewijzing van klant ongedaan gemaakt", + "type-assigned-to-edge": "Toegewezen aan Edge", + "type-unassigned-from-edge": "Toewijzing van Edge ongedaan gemaakt", + "type-activated": "Geactiveerd", + "type-suspended": "Latent", + "type-credentials-read": "Referenties gelezen", + "type-attributes-read": "Gelezen attributen", + "type-added-to-entity-group": "Toegevoegd aan groep", + "type-removed-from-entity-group": "Verwijderd uit groep", + "type-relation-add-or-update": "Relatie geüpdatet", + "type-relation-delete": "Relatie verwijderd", + "type-relations-delete": "Alle relaties verwijderd", + "type-alarm-ack": "Erkend", + "type-alarm-clear": "Uitgeschakeld", + "type-alarm-assign": "Toegewezen", + "type-alarm-unassign": "Toegewezen", + "type-added-comment": "Reactie toegevoegd", + "type-updated-comment": "Bijgewerkte reactie", + "type-deleted-comment": "Verwijderde reactie", + "type-rest-api-rule-engine-call": "REST API-aanroep van rule engine", + "type-made-public": "Openbaar gemaakt", + "type-made-private": "Privé gemaakt", + "type-login": "Inloggen", + "type-logout": "Logout", + "type-lockout": "Lockout", + "status-success": "Succes", + "status-failure": "Fout", + "audit-log-details": "Details van het auditlogboek", + "no-audit-logs-prompt": "Geen logboeken gevonden", + "action-data": "Actie gegevens", + "failure-details": "Details van de fout", + "search": "Auditlogboeken doorzoeken", + "clear-search": "Zoekopdracht wissen", + "type-assigned-from-tenant": "Toegewezen door tenant", + "type-assigned-to-tenant": "Toegewezen aan tenant", + "type-provision-success": "Device ingericht", + "type-provision-failure": "Het inrichten van devices is mislukt", + "type-timeseries-updated": "Telemetrie bijgewerkt", + "type-timeseries-deleted": "Telemetrie verwijderd", + "type-owner-changed": "Eigenaar gewijzigd" + }, + "confirm-on-exit": { + "message": "U hebt niet-opgeslagen wijzigingen. Weet je zeker dat je deze pagina wilt verlaten?", + "html-message": "U hebt niet-opgeslagen wijzigingen.
Weet je zeker dat je deze pagina wilt verlaten?", + "title": "Niet-opgeslagen wijzigingen" + }, + "contact": { + "country": "Land", + "city": "Stad", + "state": "Staat / Provincie", + "postal-code": "Postcode", + "postal-code-invalid": "Ongeldige postcode notatie.", + "address": "Adres", + "address2": "Adres 2", + "phone": "Telefoon", + "email": "E-mail", + "no-address": "Geen adres", + "state-max-length": "De lengte van de staat moet kleiner zijn dan 256 tekens tekens", + "phone-max-length": "Telefoonnummer moet minder zijn dan 256 tekens tekens", + "city-max-length": "De opgegeven stad moet kleiner zijn dan 256 tekens tekens" + }, + "common": { + "username": "Gebruikersnaam", + "password": "Wachtwoord", + "enter-username": "Gebruikersnaam invoeren", + "enter-password": "Wachtwoord invoeren", + "enter-search": "Zoekopdracht invoeren", + "created-time": "Gecreëerde tijd", + "loading": "Laden...", + "proceed": "Doorgaan", + "open-details-page": "Detailpagina openen" + }, + "converter": { + "converter": "Gegevens converteren", + "converters": "Data-converters", + "select-converter": "Selecteer gegevensomzetter", + "no-converters-matching": "Er zijn geen gegevensconverters gevonden die overeenkomen met '{{entity}}'.", + "no-converters-found": "Geen dataconverters gevonden.", + "converter-required": "Dataconverter is vereist", + "delete": "Converter verwijderen", + "management": "Beheer van dataconverters", + "add-converter-text": "Nieuwe gegevensconverter toevoegen", + "no-converters-text": "Geen dataconverters gevonden", + "selected-converters": "{ count, plural, =1 {1 data converter} andere {# data converters} } geselecteerd", + "delete-converter-title": "Weet u zeker dat u de dataconverter '{{converterName}}' wilt verwijderen?", + "delete-converter-text": "Opgelet, na de bevestiging worden de gegevensomzetter en alle gerelateerde gegevens onherstelbaar.", + "delete-converters-title": "Weet u zeker dat u { count, plural, =1 {1 data converter} andere {# data converters} } wilt verwijderen?", + "delete-converters-action-title": "Verwijder { count, plural, =1 {1 data converter} andere {# data converters} }", + "delete-converters-text": "Opgelet, na de bevestiging worden alle geselecteerde gegevensconverters verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", + "events": "Events", + "add": "Gegevensconverter toevoegen", + "search": "Gegevensconverters zoeken", + "converter-details": "Details van de gegevensconverter", + "details": "Details", + "copyId": "Kopieer converter-ID", + "idCopiedMessage": "Converter-ID is gekopieerd naar klembord", + "debug-mode": "Foutopsporingsmodus", + "created-time": "Gecreëerde tijd", + "name": "Naam", + "name-required": "Naam is verplicht.", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens", + "description": "Omschrijving: __________", + "decoder": "Decoder", + "encoder": "Coderingsprogramma", + "test-decoder-fuction": "Test decoder functie", + "test-encoder-fuction": "Encoderfunctie testen", + "decoder-input-params": "Ingangsparameters decoder", + "encoder-input-params": "Ingangsparameters van de encoder", + "payload": "Nettolading", + "payload-content-type": "Inhoudstype payload", + "payload-content": "Inhoud van payload", + "message": "Bericht", + "message-type": "Soort bericht", + "message-type-required": "Berichttype is vereist", + "test": "Test", + "metadata": "Metagegevens", + "metadata-required": "Metagegevensvermeldingen mogen niet leeg zijn.", + "integration-metadata": "Integratie metadata", + "integration-metadata-required": "Vermeldingen in integratiemetagegevens mogen niet leeg zijn.", + "output": "Uitvoer", + "import": "Converter importeren", + "export": "Converter exporteren", + "export-failed-error": "Kan converter niet exporteren: {{error}}", + "create-new-converter": "Nieuwe converter maken", + "converter-file": "Converter bestand", + "invalid-converter-file-error": "Kan converter niet importeren: Ongeldige gegevensstructuur van converter.", + "type": "Type", + "type-required": "Type is vereist.", + "type-uplink": "Uplink", + "type-downlink": "Downlink" + }, + "content-type": { + "json": "Json", + "text": "Sms", + "binary": "Binair (Base64)" + }, + "color": { + "color-picker": "Kleurkiezer", + "primary-colors": "Primaire kleuren", + "accent-colors": "Accent kleuren", + "no-color-selected": "Geen kleur geselecteerd" + }, + "customer": { + "all": "Alle", + "all-customers": "Alle klanten", + "groups": "Groepen", + "shared": "Gedeeld", + "hierarchy": "Hiërarchie", + "customer": "Klant", + "customers": "Klanten", + "management": "Klantenbeheer", + "dashboard": "Klantendashboard", + "dashboards": "Dashboards voor klanten", + "devices": "Devices van klanten", + "entity-views": "Weergaven van klantentiteiten", + "assets": "Assets van klanten", + "public-dashboards": "Openbare dashboards", + "public-devices": "Openbare devices", + "public-assets": "Publieke assets", + "public-entity-views": "Weergaven van openbare entiteit-views", + "add": "Klant toevoegen", + "delete": "Klant verwijderen", + "manage-customer-user-groups": "Gebruikersgroepen van klanten beheren", + "manage-customer-groups": "Klantgroepen beheren", + "manage-customer-device-groups": "Asset groepen van klanten beheren", + "manage-customer-asset-groups": "Asset groepen van klanten beheren", + "manage-customer-entity-view-groups": "Weergavegroepen voor klantentiteiten beheren", + "manage-customer-edge-groups": "Edge-groepen voor klanten beheren", + "manage-customer-dashboard-groups": "Groepen klantendashboards beheren", + "manage-customer-users": "Klantgebruikers beheren", + "manage-customers": "Klanten beheren", + "manage-customer-devices": "Devices van klanten beheren", + "manage-customer-entity-views": "Weergaven van klantentiteiten beheren", + "manage-customer-dashboards": "Klantdashboards beheren", + "manage-public-devices": "Openbare devices beheren", + "manage-public-dashboards": "Openbare dashboards beheren", + "manage-customer-assets": "Klant devices beheren", + "manage-customer-edges": "Beheer klant edges", + "manage-public-assets": "Openbare asset beheren", + "add-customer-text": "Nieuwe klant toevoegen", + "no-customers-text": "Geen klanten gevonden", + "customer-details": "Klantgegevens", + "delete-customer-title": "Weet je zeker dat je de '{{customerTitle}}' van de klant wilt verwijderen?", + "delete-customer-text": "Let op, na de bevestiging worden de klant en alle gerelateerde gegevens onherstelbaar.", + "delete-customers-title": "Weet u zeker dat u { count, plural, =1 {1 customer} andere {# customers} } wilt verwijderen?", + "delete-customers-action-title": "Verwijder { count, plural, =1 {1 customer} andere {# customers} }", + "delete-customers-text": "Opgelet, na de bevestiging worden alle geselecteerde klanten verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", + "manage-user-groups": "Gebruikersgroepen beheren", + "manage-asset-groups": "Asset groepen beheren", + "manage-device-groups": "Asset groepen beheren", + "manage-dashboard-groups": "Dashboardgroepen beheren", + "manage-entity-view-groups": "Entiteitsweergavegroepen beheren", + "manage-edge-groups": "Edge-groepen beheren", + "manage-users": "Gebruikers beheren", + "manage-assets": "Assets beheren", + "manage-devices": "Devices beheren", + "manage-dashboards": "Dashboards beheren", + "manage-entity-views": "Entiteitsweergaven beheren", + "title": "Titel", + "title-required": "Titel is vereist.", + "title-max-length": "Titel moet kleiner zijn dan 256 tekens", + "description": "Omschrijving: __________", + "details": "Details", + "events": "Events", + "copyId": "Kopieer klant-ID", + "idCopiedMessage": "Klant-ID is gekopieerd naar klembord", + "select-customer": "Selecteer klant", + "no-customers-matching": "Er zijn geen klanten gevonden die overeenkomen met '{{entity}}'.", + "customer-required": "Klant is verplicht", + "select-group-to-add": "Selecteer doelgroep om geselecteerde klanten toe te voegen", + "select-group-to-move": "Selecteer doelgroep om geselecteerde klanten te verhuizen", + "remove-customers-from-group": "Weet je zeker dat je { count, plural, =1 {1 customer} andere {# customers} } uit groep '{{entityGroup}}' wilt verwijderen?", + "group": "Groep klanten", + "list-of-groups": "{ count, plural, =1 {One customer group} andere {List of # customer groups} }", + "group-name-starts-with": "Klantgroepen waarvan de naam begint met '{{prefix}}'", + "select-default-customer": "Selecteer standaardklant", + "default-customer": "Standaard klant", + "default-customer-required": "Standaardklant is vereist om fouten op te sporen in het dashboard op tenantniveau", + "allow-white-labeling": "White Labeling toestaan", + "search": "Klanten zoeken", + "selected-customers": "{ count, plural, =1 {1 customer} andere {# customers} } geselecteerd", + "edges": "Edge-instanties van de klant", + "manage-edges": "Edge beheren" + }, + "customers-hierarchy": { + "customers-hierarchy": "Hiërarchie van klanten", + "open-nav-tree": "Navigatiestructuur openen", + "return-to-top-level": "Terug naar het hoogste niveau" + }, + "custom-menu": { + "custom-menu": "Aangepast menu", + "custom-menu-hint": "Definieer hieronder de JSON van het aangepaste menu. Deze JSON bevat een lijst met aangepaste menu-items." + }, + "custom-translation": { + "custom-translation": "Aangepaste vertaling", + "translation-map": "Vertaling kaart", + "key": "Vertaling sleutel", + "import": "Vertaling importeren", + "export": "Vertaling exporteren", + "export-data": "Vertaalgegevens exporteren", + "import-data": "Vertaalgegevens importeren", + "translation-file": "Vertaal bestand", + "invalid-translation-file-error": "Kan vertaalbestand niet importeren: Ongeldige gegevensstructuur voor vertalingen.", + "custom-translation-hint": "Definieer hieronder de JSON voor aangepaste vertalingen. Deze JSON overschrijft de standaardvertaling. Klik op 'Download locale file' om een bestaande vertaling te krijgen. U kunt het gedownloade bestand ook gebruiken als referentie voor beschikbare sleutel-waardeparen voor vertalingen.", + "download-locale-file": "Landinstellingsbestand downloaden" + }, + "datetime": { + "date-from": "Datum van", + "time-from": "Tijd van", + "date-to": "Datum tot", + "time-to": "Tijd om" + }, + "dashboard": { + "all": "Alle", + "all-dashboards": "Alle dashboards", + "groups": "Groepen", + "shared": "Gedeeld", + "dashboard": "Dashboard", + "dashboards": "Dashboards", + "management": "Beheer van dashboards", + "view-dashboards": "Dashboards bekijken", + "add": "Dashboard toevoegen", + "assign-dashboard-to-customer": "Dashboard(s) toewijzen aan klant", + "assign-dashboard-to-customer-text": "Selecteer de dashboards die u aan de klant wilt toewijzen", + "assign-to-customer-text": "Selecteer de klant om de dashboard(s) toe te wijzen", + "assign-to-customer": "Toewijzen aan klant", + "unassign-from-customer": "Toewijzing van klant ongedaan maken", + "make-public": "Dashboard openbaar maken", + "make-private": "Dashboard privé maken", + "manage-assigned-customers": "Toegewezen klanten beheren", + "assigned-customers": "Toegewezen klanten", + "assign-to-customers": "Dashboard(s) toewijzen aan klanten", + "assign-to-customers-text": "Selecteer de klanten om de dashboard(s) toe te wijzen", + "unassign-from-customers": "Toewijzing van dashboard(s) van klanten ongedaan maken", + "unassign-from-customers-text": "Selecteer de klanten die u wilt opheffen in de dashboard(s)", + "no-dashboards-text": "Geen dashboards gevonden", + "no-widgets": "Geen widgets geconfigureerd", + "add-widget": "Nieuwe widget toevoegen", + "title": "Titel", + "image": "Dashboard afbeelding", + "mobile-app-settings": "Instellingen voor mobiele applicaties", + "mobile-order": "Dashboardbestelling in mobiele applicatie", + "mobile-hide": "Dashboard verbergen in mobiele applicatie", + "update-image": "Dashboardafbeelding bijwerken", + "take-screenshot": "Screenshot maken", + "select-widget-title": "Selecteer widget", + "select-widget-value": "{{title}}: Widget selecteren", + "select-widget-subtitle": "Lijst met beschikbare widgettypen", + "delete": "Dashboard verwijderen", + "title-required": "Titel is vereist.", + "title-max-length": "Titel moet kleiner zijn dan 256 tekens tekens", + "description": "Omschrijving: __________", + "details": "Details", + "dashboard-details": "Details van het dashboard", + "add-dashboard-text": "Nieuw dashboard toevoegen", + "assign-dashboards": "Dashboards toewijzen", + "assign-new-dashboard": "Nieuw dashboard toewijzen", + "assign-dashboards-text": "Wijs { count, plural, =1 {1 dashboard} andere {# dashboards} } toe aan klanten", + "unassign-dashboards-action-text": "Toewijzing { count, plural, =1 {1 dashboard} andere {# dashboards} } van klanten ongedaan maken", + "delete-dashboards": "Dashboards verwijderen", + "unassign-dashboards": "Toewijzing van dashboards ongedaan maken", + "unassign-dashboards-action-title": "Toewijzing { count, plural, =1 {1 dashboard} andere {# dashboards} } van klant ongedaan maken", + "delete-dashboard-title": "Weet je zeker dat je de '{{dashboardTitle}}' van het dashboard wilt verwijderen?", + "delete-dashboard-text": "Opgelet, na de bevestiging worden het dashboard en alle gerelateerde gegevens onherstelbaar.", + "delete-dashboards-title": "Weet u zeker dat u { count, plural, =1 {1 dashboard} andere {# dashboards} } wilt verwijderen?", + "delete-dashboards-action-title": "Verwijder { count, plural, =1 {1 dashboard} andere {# dashboards} }", + "delete-dashboards-text": "Opgelet, na de bevestiging worden alle geselecteerde dashboards verwijderd en worden alle gerelateerde gegevens onherstelbaar.", + "unassign-dashboard-title": "Weet u zeker dat u de toewijzing van het dashboard '{{dashboardTitle}}' wilt opheffen?", + "unassign-dashboard-text": "Na de bevestiging wordt de toewijzing van het dashboard ongedaan gemaakt en is het niet toegankelijk voor de klant.", + "unassign-dashboard": "Toewijzing dashboard ongedaan maken", + "unassign-dashboards-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 dashboard} andere {# dashboards} } wilt opheffen?", + "unassign-dashboards-text": "Na de bevestiging worden alle geselecteerde dashboards ongedaan gemaakt en zijn ze niet toegankelijk voor de klant.", + "public-dashboard-title": "Dashboard is nu openbaar", + "public-dashboard-text": "De {{dashboardTitle}} van uw dashboard is nu openbaar en toegankelijk via de volgende openbare link:", + "public-dashboard-notice": "notitie: Vergeet niet om gerelateerde devices openbaar te maken om toegang te krijgen tot hun gegevens.", + "public-dashboard-link": "Koppeling naar openbaar dashboard", + "public-dashboard-link-text": "Uw openbare dashboard {{dashboardTitle}} is toegankelijk via de volgende openbare link:", + "public-dashboard-link-notice": "notitie: Vergeet niet om gerelateerde Devices, asset en entiteitsweergaven openbaar te maken om toegang te krijgen tot hun gegevens.", + "make-private-dashboard-title": "Weet je zeker dat je het dashboard '{{dashboardTitle}}' privé wilt maken?", + "make-private-dashboard-text": "Na de bevestiging wordt het dashboard privé gemaakt en is het niet toegankelijk voor anderen.", + "make-private-dashboard": "Dashboard privé maken", + "socialshare-text": "'{{dashboardTitle}}' mogelijk gemaakt door ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' mogelijk gemaakt door ThingsBoard", + "select-dashboard": "Selecteer dashboard", + "no-dashboards-matching": "Er zijn geen dashboards gevonden die overeenkomen met '{{entity}}'.", + "dashboard-required": "Dashboard is vereist.", + "select-existing": "Bestaand dashboard selecteren", + "create-new": "Nieuw dashboard maken", + "new-dashboard-title": "Nieuwe dashboardtitel", + "open-dashboard": "Dashboard openen", + "set-background": "Achtergrond instellen", + "background-color": "Achtergrondkleur", + "background-image": "Achtergrondafbeelding", + "background-size-mode": "Modus voor achtergrondgrootte", + "no-image": "Geen afbeelding geselecteerd", + "empty-image": "Geen afbeelding", + "drop-image": "Zet een afbeelding neer of klik om een bestand te selecteren om te uploaden.", + "maximum-upload-file-size": "Maximale bestandsgrootte uploaden: {{ size }}", + "cannot-upload-file": "Kan bestand niet uploaden", + "settings": "Instellingen", + "layout-settings": "Lay-out instellingen", + "columns-count": "Aantal kolommen", + "columns-count-required": "Het aantal kolommen is vereist.", + "min-columns-count-message": "Er is slechts een minimum van 10 kolommen toegestaan.", + "max-columns-count-message": "Er is slechts een maximum van 1000 kolommen toegestaan.", + "widgets-margins": "Marge tussen widgets", + "margin-required": "Margewaarde is vereist.", + "min-margin-message": "Alleen 0 is toegestaan als minimale margewaarde.", + "max-margin-message": "Slechts 50 is toegestaan als maximale margewaarde.", + "horizontal-margin": "Horizontale marge", + "horizontal-margin-required": "Horizontale margewaarde is vereist.", + "min-horizontal-margin-message": "Alleen 0 is toegestaan als minimale horizontale margewaarde.", + "max-horizontal-margin-message": "Slechts 50 is toegestaan als maximale horizontale margewaarde.", + "vertical-margin": "Verticale marge", + "vertical-margin-required": "Verticale margewaarde is vereist.", + "min-vertical-margin-message": "Alleen 0 is toegestaan als minimale verticale margewaarde.", + "max-vertical-margin-message": "Slechts 50 is toegestaan als maximale verticale margewaarde.", + "apply-outer-margin": "Marges toepassen op de zijkanten van de lay-out", + "autofill-height": "Lay-outhoogte automatisch vullen", + "mobile-layout": "Mobiele lay-outinstellingen", + "mobile-row-height": "Mobiele rijhoogte, px", + "mobile-row-height-required": "De waarde van de rijhoogte van de mobiele rij is vereist.", + "min-mobile-row-height-message": "Er zijn slechts 5 pixels toegestaan als minimale waarde voor de rijhoogte van mobiele rijen.", + "max-mobile-row-height-message": "Er zijn slechts 200 pixels toegestaan als maximale waarde voor de rijhoogte van mobiele Devices.", + "title-settings": "Titelinstellingen", + "display-title": "Dashboardtitel weergeven", + "title-color": "Titel kleur", + "toolbar-settings": "Werkbalk-instellingen", + "hide-toolbar": "Werkbalk verbergen", + "toolbar-always-open": "Werkbalk open houden", + "display-dashboards-selection": "Dashboardselectie weergeven", + "display-entities-selection": "Selectie van weergave-entiteiten", + "display-filters": "Filters weergeven", + "display-dashboard-timewindow": "Tijdvenster weergeven", + "display-dashboard-export": "Export weergeven", + "display-update-dashboard-image": "Afbeelding van updatedashboard weergeven", + "dashboard-logo-settings": "Instellingen voor dashboardlogo's", + "display-dashboard-logo": "Logo weergeven in volledig scherm dashboard", + "dashboard-logo-image": "Afbeelding van het dashboardlogo", + "advanced-settings": "Geavanceerde instellingen", + "dashboard-css": "Dashboard CSS", + "import": "Dashboard importeren", + "export": "Dashboard exporteren", + "export-failed-error": "Dashboard kan niet worden geëxporteerd: {{error}}", + "export-pdf": "Exporteren als PDF", + "export-png": "Exporteren als PNG", + "export-jpg": "Exporteren als JPEG", + "export-json-config": "JSON-configuratie exporteren", + "download-dashboard-progress": "Dashboard genereren {{reportType}} ...", + "create-new-dashboard": "Nieuw dashboard maken", + "dashboard-file": "Dashboard bestand", + "invalid-dashboard-file-error": "Dashboard kan niet worden geïmporteerd: Ongeldige gegevensstructuur van het dashboard.", + "dashboard-import-missing-aliases-title": "Aliassen configureren die worden gebruikt door geïmporteerd dashboard", + "create-new-widget": "Nieuwe widget maken", + "import-widget": "Widget importeren", + "widget-file": "Widget bestand", + "invalid-widget-file-error": "Widget kan niet worden geïmporteerd: Ongeldige gegevensstructuur van de widget.", + "widget-import-missing-aliases-title": "Aliassen configureren die worden gebruikt door geïmporteerde widgets", + "open-toolbar": "Dashboardwerkbalk openen", + "close-toolbar": "Werkbalk sluiten", + "configuration-error": "Configuratie fout", + "alias-resolution-error-title": "Configuratiefout voor dashboardaliassen", + "invalid-aliases-config": "Kan geen devices vinden die overeenkomen met sommige aliassenfilter.
Neem contact op met uw beheerder om dit probleem op te lossen.", + "select-devices": "Devices selecteren", + "assignedToCustomer": "Toegewezen aan klant", + "assignedToCustomers": "Toegewezen aan klanten", + "public": "Publiek", + "copyId": "Dashboard-id kopiëren", + "idCopiedMessage": "Dashboard-id is gekopieerd naar klembord", + "public-link": "Openbare link", + "copy-public-link": "Kopieer openbare link", + "public-link-copied-message": "De openbare koppeling naar het dashboard is gekopieerd naar het klembord", + "manage-states": "Dashboardstatussen beheren", + "states": "Statussen van dashboards", + "search-states": "Statussen van zoekdashboard", + "selected-states": "{ count, plural, =1 {1 dashboard state} andere {# dashboard states} } geselecteerd", + "edit-state": "Dashboardstatus bewerken", + "delete-state": "Dashboardstatus verwijderen", + "add-state": "Dashboardstatus toevoegen", + "no-states-text": "Geen staten gevonden", + "state": "Status van het dashboard", + "state-name": "Naam", + "state-name-required": "De naam van de dashboardstatus is vereist.", + "state-id": "Staats-ID", + "state-id-required": "Status-id van het dashboard is vereist.", + "state-id-exists": "De status van het dashboard met dezelfde id bestaat al.", + "is-root-state": "Wortel staat", + "delete-state-title": "Dashboardstatus verwijderen", + "delete-state-text": "Weet u zeker dat u de status van het dashboard met de naam '{{stateName}}' wilt verwijderen?", + "show-details": "Details weergeven", + "hide-details": "Verberg details", + "select-state": "Selecteer de doelstatus", + "state-controller": "Controle van de staat", + "search": "Dashboards doorzoeken", + "selected-dashboards": "{ count, plural, =1 {1 dashboard} andere {# dashboards} } geselecteerd", + "home-dashboard": "Startpagina dashboard", + "home-dashboard-hide-toolbar": "Verberg de werkbalk van het startdashboard", + "select-group-to-add": "Selecteer doelgroep om geselecteerde dashboards toe te voegen", + "select-group-to-move": "Selecteer de doelgroep om geselecteerde dashboards te verplaatsen", + "remove-dashboards-from-group": "Weet je zeker dat je { count, plural, =1 {1 dashboard} andere {# dashboards} } uit groep '{{entityGroup}}' wilt verwijderen?", + "group": "Groep dashboards", + "list-of-groups": "{ count, plural, =1 {One dashboard group} andere {List of # dashboard groups} }", + "group-name-starts-with": "Dashboardgroepen waarvan de naam begint met '{{prefix}}'", + "unassign-dashboard-from-edge-text": "Na de bevestiging wordt de toewijzing van het dashboard ongedaan gemaakt en is het niet toegankelijk voor de edge.", + "unassign-dashboards-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 dashboard} andere {# dashboards} } wilt opheffen?", + "unassign-dashboards-from-edge-text": "Na de bevestiging worden alle geselecteerde dashboards ongedaan gemaakt en zijn ze niet toegankelijk voor de edge.", + "assign-dashboard-to-edge": "Dashboard(s) toewijzen aan Edge", + "assign-dashboard-to-edge-text": "Selecteer de dashboards die u aan de edge wilt toewijzen", + "non-existent-dashboard-state-error": "Dashboardstatus met id '{{ stateId }}' is niet gevonden" + }, + "datakey": { + "settings": "Instellingen", + "advanced": "Geavanceerd", + "label": "Etiket", + "color": "Kleur", + "units": "Speciaal symbool om naast waarde weer te geven", + "decimals": "Aantal cijfers na drijvende komma", + "data-generation-func": "Functie voor het genereren van gegevens", + "use-data-post-processing-func": "Gebruik de functie voor het nabewerken van gegevens", + "configuration": "Configuratie van gegevenssleutels", + "timeseries": "Timeseries", + "attributes": "Attributen", + "entity-field": "Entiteit veld", + "alarm": "Alarmvelden", + "timeseries-required": "Timeseries van entiteiten zijn vereist.", + "timeseries-or-attributes-required": "Timeseries/attributen van entiteiten zijn vereist.", + "alarm-fields-timeseries-or-attributes-required": "Alarmvelden of tijdreeksen/attributen van entiteiten zijn vereist.", + "maximum-timeseries-or-attributes": "Maximaal { count, plural, =1 {1 timeseries/attribute is allowed.} andere {# timeseries/attributes are allowed} }", + "alarm-fields-required": "Alarmvelden zijn verplicht.", + "function-types": "Soorten functies", + "function-type": "Soort functie", + "function-types-required": "Functietypes zijn vereist.", + "alarm-keys": "Alarmgegevens toetsen", + "alarm-key": "Sleutel voor alarmgegevens", + "alarm-key-functions": "Functies van de alarmtoets", + "alarm-key-function": "Alarmtoets functie", + "latest-keys": "Nieuwste gegevenssleutels", + "latest-key": "Meest recente gegevenssleutel", + "latest-key-functions": "Nieuwste sleutelfuncties", + "latest-key-function": "Nieuwste toetsfunctie", + "timeseries-keys": "Gegevenssleutels voor tijdreeksen", + "timeseries-key": "Gegevenssleutel tijdreeksen", + "timeseries-key-functions": "Timeseries belangrijkste functies", + "timeseries-key-function": "Tijdreeks-sleutelfunctie", + "maximum-function-types": "Maximaal { count, plural, =1 {1 function type is allowed.} andere {# function types are allowed} }", + "time-description": "tijdstempel van de huidige waarde;", + "value-description": "de huidige waarde;", + "prev-value-description": "resultaat van de vorige functieaanroep;", + "time-prev-description": "tijdstempel van de vorige waarde;", + "prev-orig-value-description": "oorspronkelijke vorige waarde;", + "aggregation": "Aggregatie", + "aggregation-type-hint-common": "Om prestatieredenen is de berekening van de geaggregeerde waarden alleen beschikbaar voor vaste tijdsintervallen zoals 'huidige dag', 'huidige maand', enz., en niet voor schuifvensterintervallen zoals 'laatste 30 minuten' of 'laatste 24 uur'.", + "aggregation-type-none-hint": "Neem de laatste waarde.", + "aggregation-type-min-hint": "Zoek de minimumwaarde tussen gegevenspunten binnen een geselecteerd tijdvenster.", + "aggregation-type-max-hint": "Zoek de maximale waarde tussen gegevenspunten binnen een geselecteerd tijdvenster.", + "aggregation-type-avg-hint": "Bereken een gemiddelde waarde tussen gegevenspunten binnen een geselecteerd tijdvenster.", + "aggregation-type-sum-hint": "Tel alle waarden van de gegevenspunten binnen een geselecteerd tijdvenster bij elkaar op.", + "aggregation-type-count-hint": "Het totale aantal gegevenspunten binnen een geselecteerd tijdvenster.", + "delta-calculation": "Delta-berekening", + "enable-delta-calculation": "Deltaberekening inschakelen", + "enable-delta-calculation-hint": "Als deze optie is ingeschakeld, wordt de waarde van de gegevenssleutel berekend op basis van de geaggregeerde waarden voor een geselecteerd tijdvenster en een opgegeven vergelijkingsperiode. Om prestatieredenen is de deltaberekening alleen beschikbaar voor historische tijdvensters en niet voor realtimewaarden. U kunt bijvoorbeeld de delta berekenen tussen het energieverbruik van gisteren ten opzichte van het energieverbruik van eergisteren.", + "delta-calculation-result": "Resultaat van de deltaberekening", + "delta-calculation-result-previous-value": "Vorige waarde", + "delta-calculation-result-delta-absolute": "Delta (absoluut)", + "delta-calculation-result-delta-percent": "Delta (procent)" + }, + "datasource": { + "type": "Type gegevensbron", + "name": "Naam", + "label": "Etiket", + "add-datasource-prompt": "Voeg gegevensbron toe" + }, + "details": { + "details": "Details", + "edit-mode": "Modus bewerken", + "edit-json": "JSON bewerken", + "toggle-edit-mode": "Bewerkingsmodus in-/uitschakelen" + }, + "device": { + "all": "Alle", + "all-devices": "Alle Devices", + "groups": "Groepen", + "shared": "Gedeeld", + "device": "Device", + "device-required": "Device is vereist.", + "devices": "Devices", + "management": "Device beheer", + "view-devices": "Devices bekijken", + "device-alias": "Alias van het device", + "device-type-max-length": "Device type moet kleiner zijn dan 256 tekens", + "aliases": "Aliassen van Devices", + "no-alias-matching": "'{{alias}}' niet gevonden.", + "no-aliases-found": "Geen aliassen gevonden.", + "no-key-matching": "'{{key}}' niet gevonden.", + "no-keys-found": "Geen sleutels gevonden.", + "create-new-alias": "Maak een nieuwe aan!", + "create-new-key": "Maak een nieuwe aan!", + "duplicate-alias-error": "Dubbele alias gevonden '{{alias}}'.
Apparaataliassen moeten uniek zijn binnen het dashboard.", + "configure-alias": "Configureer de alias '{{alias}}'", + "no-devices-matching": "Er zijn geen devices gevonden die overeenkomen met '{{entity}}'.", + "alias": "Alias", + "alias-required": "Device alias is vereist.", + "remove-alias": "Device alias verwijderen", + "add-alias": "Device alias toevoegen", + "name-starts-with": "Expressie van device naam", + "help-text": "Gebruik '%' naar behoefte: '%device_name_contains%', '%device_name_ends', 'device_starts_with'.", + "device-list": "Lijst met Devices", + "use-device-name-filter": "Filter gebruiken", + "device-list-empty": "Geen devices geselecteerd.", + "device-name-filter-required": "Apparaatnaamfilter is vereist.", + "device-name-filter-no-device-matched": "Er zijn geen devices gevonden die beginnen met '{{device}}'.", + "add": "Device toevoegen", + "assign-to-customer": "Toewijzen aan klant", + "assign-device-to-customer": "Device(en) toewijzen aan klant", + "assign-device-to-customer-text": "Selecteer de devices die u aan de klant wilt toewijzen", + "make-public": "Device openbaar maken", + "make-private": "Device privé maken", + "no-devices-text": "Geen devices gevonden", + "assign-to-customer-text": "Selecteer de klant om het/het device(en) toe te wijzen", + "device-details": "Gegevens van het device", + "add-device-text": "Nieuw device toevoegen", + "credentials": "Geloofsbrief", + "manage-credentials": "Inloggegevens beheren", + "delete": "Device verwijderen", + "assign-devices": "Devices toewijzen", + "assign-devices-text": "Wijs { count, plural, =1 {1 device} andere {# devices} } toe aan de klant", + "delete-devices": "Devices verwijderen", + "unassign-from-customer": "Toewijzing van klant ongedaan maken", + "unassign-devices": "Toewijzing van devices ongedaan maken", + "unassign-devices-action-title": "Toewijzing { count, plural, =1 {1 device} andere {# devices} } van klant ongedaan maken", + "unassign-device-from-edge-title": "Weet u zeker dat u de toewijzing van het device '{{deviceName}}' wilt opheffen?", + "unassign-device-from-edge-text": "Na de bevestiging wordt de toewijzing van het device ongedaan gemaakt en is het niet toegankelijk via de edge.", + "unassign-devices-from-edge": "Toewijzing van devices aan de edge ongedaan maken", + "assign-new-device": "Nieuw device toewijzen", + "make-public-device-title": "Weet je zeker dat je het device '{{deviceName}}' openbaar wilt maken?", + "make-public-device-text": "Na de bevestiging worden het device en al zijn gegevens openbaar gemaakt en toegankelijk gemaakt voor anderen.", + "make-private-device-title": "Weet je zeker dat je het device '{{deviceName}}' privé wilt maken?", + "make-private-device-text": "Na de bevestiging worden het device en al zijn gegevens privé gemaakt en zijn ze niet toegankelijk voor anderen.", + "view-credentials": "Inloggegevens bekijken", + "delete-device-title": "Weet u zeker dat u de '{{deviceName}}' van het device wilt verwijderen?", + "delete-device-text": "Opgelet, na de bevestiging worden het device en alle gerelateerde gegevens onherstelbaar.", + "delete-devices-title": "Weet u zeker dat u { count, plural, =1 {1 device} andere {# devices} } wilt verwijderen?", + "delete-devices-action-title": "Verwijder { count, plural, =1 {1 device} andere {# devices} }", + "delete-devices-text": "Opgelet, na de bevestiging worden alle geselecteerde devices verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", + "unassign-device-title": "Weet u zeker dat u de toewijzing van het device '{{deviceName}}' wilt opheffen?", + "unassign-device-text": "Na de bevestiging wordt de toewijzing van het device ongedaan gemaakt en is het niet toegankelijk voor de klant.", + "unassign-device": "Toewijzing van device ongedaan maken", + "unassign-devices-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 device} andere {# devices} } wilt opheffen?", + "unassign-devices-text": "Na de bevestiging worden alle geselecteerde devices ongedaan gemaakt en zijn ze niet toegankelijk voor de klant.", + "device-credentials": "Inloggegevens van het device", + "loading-device-credentials": "Devicereferenties worden geladen...", + "credentials-type": "Type referenties", + "access-token": "Toegang token", + "access-token-required": "Toegangstoken is vereist.", + "access-token-invalid": "De lengte van het toegangstoken moet tussen de 1 en 32 tekens zijn.", + "certificate-pem-format": "Certificaat in PEM-formaat", + "certificate-pem-format-required": "Certificaat is vereist.", + "copy-access-token": "Toegangstoken kopiëren", + "copy-certificate": "Kopie certificaat", + "copy-client-id": "Client-ID kopiëren", + "copy-user-name": "Gebruikersnaam kopiëren", + "copy-password": "Kopieer wachtwoord", + "generate-client-id": "Client-ID genereren", + "generate-user-name": "Gebruikersnaam genereren", + "generate-password": "Wachtwoord genereren", + "generate-access-token": "Toegangstoken genereren", + "lwm2m-security-config": { + "identity": "Identiteit van de klant", + "identity-required": "Clientidentiteit is vereist.", + "identity-tooltip": "De PSK-identifier is een willekeurige PSK-identifier van maximaal 128 bytes, zoals beschreven in de standaard [RFC7925].\nDe PSK-id MOET eerst worden geconverteerd naar een tekenreeks en vervolgens worden gecodeerd in octetten met behulp van UTF-8.", + "client-key": "Client-sleutel", + "client-key-required": "Clientsleutel is vereist.", + "client-key-tooltip-prk": "RPK openbare sleutel of id moet in de standaard [RFC7250] en gecodeerd zijn naar Base64-indeling!", + "client-key-tooltip-psk": "PSK-sleutel moet in het standaard [RFC4279] en HexDec-formaat zijn: 32, 64, 128 tekens!", + "endpoint": "Naam van endpoint", + "endpoint-required": "De naam van de endpoint is vereist.", + "client-public-key": "Openbare sleutel van client", + "client-public-key-hint": "Als de openbare sleutel van de client leeg is, wordt het vertrouwde certificaat gebruikt", + "client-public-key-tooltip": "De openbare sleutel X509 moet in DER-gecodeerd X509v3-formaat zijn en uitsluitend het EC-algoritme ondersteunen en vervolgens worden gecodeerd naar het Base64-formaat!", + "mode": "Beveiligingsconfiguratiemodus", + "client-tab": "Configuratie voor clientbeveiliging", + "client-certificate": "Certificaat van de klant", + "bootstrap-tab": "Bootstrap-client", + "bootstrap-server": "Bootstrap-server", + "lwm2m-server": "LwM2M-server", + "client-publicKey-or-id": "Openbare sleutel of id van de client", + "client-publicKey-or-id-required": "De openbare sleutel of id van de client is vereist.", + "client-publicKey-or-id-tooltip-psk": "De PSK-identifier is een willekeurige PSK-identifier van maximaal 128 bytes, zoals beschreven in de standaard [RFC7925].\nDe PSK-id MOET eerst worden geconverteerd naar een tekenreeks en vervolgens worden gecodeerd in octetten met behulp van UTF-8.", + "client-publicKey-or-id-tooltip-rpk": "RPK openbare sleutel of id moet in de standaard [RFC7250] en gecodeerd zijn naar Base64-indeling!", + "client-publicKey-or-id-tooltip-x509": "De openbare X509-sleutel moet in DER-gecodeerd X509v3-formaat zijn en uitsluitend het EC-algoritme ondersteunen en vervolgens worden gecodeerd in Base64-indeling", + "client-secret-key": "Geheime sleutel van de client", + "client-secret-key-required": "Client Secret Key is vereist.", + "client-secret-key-tooltip-psk": "PSK-sleutel moet in het standaard [RFC4279] en HexDec-formaat zijn: 32, 64, 128 tekens!", + "client-secret-key-tooltip-prk": "De geheime RPK-sleutel moet in PKCS_8 formaat zijn (DER-codering, standaard [RFC5958]) en vervolgens worden gecodeerd in Base64-formaat!", + "client-secret-key-tooltip-x509": "De geheime sleutel X509 moet in PKCS_8 formaat zijn (DER-codering, standaard [RFC5958]) en vervolgens worden gecodeerd in Base64-formaat!" + }, + "client-id": "Client-ID", + "client-id-pattern": "Bevat een ongeldig teken.", + "user-name": "Gebruikersnaam", + "user-name-required": "Gebruikersnaam is vereist.", + "client-id-or-user-name-necessary": "Client-ID en/of gebruikersnaam zijn noodzakelijk", + "password": "Wachtwoord", + "secret": "Geheim", + "secret-required": "Geheim is vereist.", + "device-type": "Device type", + "device-type-required": "Device type is vereist.", + "select-device-type": "Selecteer device type", + "enter-device-type": "Voer het device type in", + "any-device": "Elk device", + "no-device-types-matching": "Er zijn geen device typen gevonden die overeenkomen met '{{entitySubtype}}'.", + "device-type-list-empty": "Geen device typen geselecteerd.", + "device-types": "Device sorrten", + "name": "Naam", + "name-required": "Naam is verplicht.", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens tekens", + "label-max-length": "Etiket moet kleiner zijn dan 256 tekens tekens", + "description": "Omschrijving: __________", + "label": "Etiket", + "events": "Events", + "details": "Details", + "copyId": "Device-ID kopiëren", + "copyAccessToken": "Toegangstoken kopiëren", + "copy-mqtt-authentication": "MQTT-inloggegevens kopiëren", + "idCopiedMessage": "Device-ID is gekopieerd naar klembord", + "accessTokenCopiedMessage": "Token voor apparaattoegang is gekopieerd naar het klembord", + "mqtt-authentication-copied-message": "MQTT-verificatie van device is gekopieerd naar klembord", + "assignedToCustomer": "Toegewezen aan klant", + "unable-delete-device-alias-title": "Kan apparaatalias niet verwijderen", + "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' kan niet worden verwijderd omdat deze wordt gebruikt door de volgende widget(s):
{{widgetsList}}", + "is-gateway": "Is gateway", + "overwrite-activity-time": "Activiteitstijd overschrijven voor verbonden device", + "device-filter": "Device filter", + "device-filter-title": "Device filter", + "filter-title": "Filter", + "device-state": "Status van het device", + "state": "Staat", + "any": "Enig", + "active": "Actief", + "inactive": "Inactief", + "public": "Publiek", + "device-public": "Device is openbaar", + "select-device": "Selecteer device", + "select-group-to-add": "Selecteer de doelgroep om geselecteerde devices toe te voegen", + "select-group-to-move": "Selecteer de doelgroep om geselecteerde devices te verplaatsen", + "remove-devices-from-group": "Weet je zeker dat je { count, plural, =1 {1 device} andere {# devices} } uit groep '{{entityGroup}}' wilt verwijderen?", + "group": "Device groep", + "list-of-groups": "{ count, plural, =1 {One device group} andere {List of # device groups} }", + "group-name-starts-with": "Asset groepen waarvan de naam begint met '{{prefix}}'", + "import": "Device importeren", + "device-file": "Device bestand", + "search": "Devices zoeken", + "selected-devices": "{ count, plural, =1 {1 device} andere {# devices} } geselecteerd", + "device-configuration": "Configuratie van het device", + "transport-configuration": "Configuratie van het transport", + "wizard": { + "device-wizard": "Device Wizard", + "device-details": "Gegevens van het device", + "new-device-profile": "Nieuw device profiel maken", + "existing-device-profile": "Bestaand device profiel selecteren", + "specific-configuration": "Specifieke configuratie", + "customer-to-assign-device": "Klant om het device toe te wijzen", + "add-credentials": "Inloggegevens toevoegen" + }, + "unassign-devices-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 device} andere {# devices} } wilt opheffen?", + "unassign-devices-from-edge-text": "Na de bevestiging worden alle geselecteerde devices ongedaan gemaakt en zijn ze niet toegankelijk via de edge." + }, + "asset-profile": { + "asset-profile": "Asset profiel", + "asset-profiles": "Asset profielen", + "all-asset-profiles": "Alle", + "add": "Asset profiel toevoegen", + "edit": "Asset profiel bewerken", + "asset-profile-details": "Details van de asset profiel", + "no-asset-profiles-text": "Geen assetprofielen gevonden", + "search": "Assetprofielen zoeken", + "selected-asset-profiles": "{ count, plural, =1 {1 asset profile} andere {# asset profiles} } geselecteerd", + "no-asset-profiles-matching": "Er zijn geen asset profielen gevonden die overeenkomen met '{{entity}}'.", + "asset-profile-required": "Asset profiel is vereist", + "idCopiedMessage": "De ID van het itemprofiel is gekopieerd naar het klembord", + "set-default": "Itemprofiel instellen als standaard", + "delete": "Asset profiel verwijderen", + "copyId": "Itemprofiel-ID kopiëren", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens", + "new-device-profile-name": "Naam van itemprofiel", + "new-device-profile-name-required": "De naam van de asset profiel is vereist.", + "name": "Naam", + "name-required": "Naam is verplicht.", + "image": "Afbeelding van assetprofiel", + "description": "Omschrijving: __________", + "default": "Verstek", + "default-rule-chain": "Keten van standaardregels", + "default-edge-rule-chain": "Standaard rule chain", + "default-edge-rule-chain-hint": "Wordt aan de edge gebruikt als rule chain om inkomende gegevens voor asset van dit asset profiel te verwerken", + "mobile-dashboard": "Mobiel dashboard", + "mobile-dashboard-hint": "Gebruikt door mobiele applicatie als dashboard met activagegevens", + "select-queue-hint": "Maak een keuze uit een vervolgkeuzelijst.", + "delete-asset-profile-title": "Weet je zeker dat je de asset profiel '{{assetProfileName}}' wilt verwijderen?", + "delete-asset-profile-text": "Let op, na de bevestiging worden de asset profiel en alle gerelateerde gegevens onherstelbaar.", + "delete-asset-profiles-title": "Weet u zeker dat u { count, plural, =1 {1 asset profile} andere {# asset profiles} } wilt verwijderen?", + "delete-asset-profiles-text": "Opgelet, na de bevestiging worden alle geselecteerde asset profielen verwijderd en worden alle gerelateerde gegevens onherstelbaar.", + "set-default-asset-profile-title": "Weet u zeker dat u de asset profiel standaard op '{{assetProfileName}}' wilt zetten?", + "set-default-asset-profile-text": "Na de bevestiging wordt de asset profiel gemarkeerd als standaard en wordt het gebruikt voor nieuwe asset waarvoor geen profiel is opgegeven.", + "no-asset-profiles-found": "Geen assetprofielen gevonden.", + "create-new-asset-profile": "Maak een nieuwe aan!", + "create-asset-profile": "Nieuw asset profiel maken", + "import": "Asset profiel importeren", + "export": "Asset profiel exporteren", + "export-failed-error": "Kan itemprofiel niet exporteren: {{error}}", + "asset-profile-file": "Asset profiel bestand", + "invalid-asset-profile-file-error": "Kan itemprofiel niet importeren: Ongeldige gegevensstructuur van asset profiel." + }, + "device-profile": { + "device-profile": "Device profiel", + "device-profiles": "Device profielen", + "all-device-profiles": "Alle", + "add": "Device profiel toevoegen", + "edit": "Device profiel bewerken", + "device-profile-details": "Details van device profiel", + "no-device-profiles-text": "Geen device profielen gevonden", + "search": "Device profielen zoeken", + "selected-device-profiles": "{ count, plural, =1 {1 device profile} andere {# device profiles} } geselecteerd", + "no-device-profiles-matching": "Er zijn geen device profielen gevonden die overeenkomen met '{{entity}}'.", + "device-profile-required": "Device profiel is vereist", + "idCopiedMessage": "Device profiel-ID is gekopieerd naar klembord", + "set-default": "Device profiel standaard instellen", + "delete": "Device profiel verwijderen", + "copyId": "Device profiel-ID kopiëren", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens tekens", + "new-device-profile-name": "Profielnaam device", + "new-device-profile-name-required": "De naam van het device profiel is vereist.", + "name": "Naam", + "name-required": "Naam is verplicht.", + "type": "Type profiel", + "type-required": "Profieltype is vereist.", + "type-default": "Verstek", + "image": "Profielafbeelding van device", + "transport-type": "Soort vervoer", + "transport-type-required": "Transporttype is vereist.", + "transport-type-default": "Verstek", + "transport-type-default-hint": "Ondersteunt standaard MQTT-, HTTP- en CoAP-transport", + "transport-type-mqtt": "MQTT", + "transport-type-mqtt-hint": "Maakt geavanceerde MQTT-transportinstellingen mogelijk", + "transport-type-coap": "CoAP", + "transport-type-coap-hint": "Maakt geavanceerde CoAP-transportinstellingen mogelijk", + "transport-type-lwm2m": "LWM2M", + "transport-type-lwm2m-hint": "LWM2M transporttype", + "transport-type-snmp": "SNMP", + "transport-type-snmp-hint": "SNMP-transportconfiguratie opgeven", + "description": "Omschrijving: __________", + "default": "Verstek", + "profile-configuration": "Profiel configuratie", + "transport-configuration": "Configuratie van het transport", + "default-rule-chain": "Standaard rule chain", + "default-edge-rule-chain": "Standaard edge rule chain", + "default-edge-rule-chain-hint": "Wordt aan de edge rule chain om inkomende gegevens te verwerken voor devices met dit device profiel", + "mobile-dashboard": "Mobiel dashboard", + "mobile-dashboard-hint": "Gebruikt door mobiele applicatie als dashboard met device details", + "select-queue-hint": "Maak een keuze uit een vervolgkeuzelijst.", + "delete-device-profile-title": "Weet u zeker dat u het device profiel '{{deviceProfileName}}' wilt verwijderen?", + "delete-device-profile-text": "Opgelet, na de bevestiging worden het device profiel en alle gerelateerde gegevens, inclusief bijbehorende OTA-updates, onherstelbaar.", + "delete-device-profiles-title": "Weet u zeker dat u { count, plural, =1 {1 device profile} andere {# device profiles} } wilt verwijderen?", + "delete-device-profiles-text": "Opgelet, na de bevestiging worden alle geselecteerde device profielen verwijderd en kunnen alle gerelateerde gegevens, inclusief bijbehorende OTA-updates, niet meer worden hersteld.", + "set-default-device-profile-title": "Weet u zeker dat u het device profiel standaard '{{deviceProfileName}}' wilt maken?", + "set-default-device-profile-text": "Na de bevestiging wordt het device profiel als standaard gemarkeerd en wordt het gebruikt voor nieuwe devices waarvoor geen profiel is opgegeven.", + "no-device-profiles-found": "Geen device profielen gevonden.", + "create-new-device-profile": "Maak een nieuwe aan!", + "mqtt-device-topic-filters": "Onderwerpfilters voor MQTT-Devices", + "mqtt-device-topic-filters-unique": "Onderwerpfilters voor MQTT-Devices moeten uniek zijn.", + "mqtt-device-topic-filters-spark-plug": "MQTT Bougie B Edge van het netwerk (EoN) knooppunt.", + "mqtt-device-topic-filters-spark-plug-hint": "Sta verbindingen toe vanaf EoN-rule nodes met Sparkplug B-payload en onderwerpindeling.", + "mqtt-device-topic-filters-spark-plug-attribute-metric-names": "SparkPlug-statistieken om op te slaan als attributen.", + "mqtt-device-topic-filters-spark-plug-attribute-metric-names-hint": "Namen van SparkPlug-statistieken die worden opgeslagen als device attributen. Alle andere metrische gegevens worden opgeslagen als device telemetrie.", + "mqtt-device-payload-type": "Nuttige waarde van het MQTT-device", + "mqtt-device-payload-type-json": "JSON", + "mqtt-device-payload-type-proto": "Protobuf", + "mqtt-enable-compatibility-with-json-payload-format": "Schakel compatibiliteit met andere payload-indelingen in.", + "mqtt-enable-compatibility-with-json-payload-format-hint": "Indien ingeschakeld, gebruikt het platform standaard een Protobuf payload-indeling. Als parsen mislukt, probeert het platform de JSON-payloadindeling te gebruiken. Handig voor achterwaartse compatibiliteit tijdens firmware-updates. De eerste release van de firmware gebruikt bijvoorbeeld Json, terwijl de nieuwe release Protobuf gebruikt. Tijdens het proces van firmware-update voor de vloot van devices, is het vereist om zowel Protobuf als JSON tegelijkertijd te ondersteunen. De compatibiliteitsmodus introduceert een lichte prestatievermindering, dus het wordt aanbevolen om deze modus uit te schakelen zodra alle devices zijn bijgewerkt.", + "mqtt-use-json-format-for-default-downlink-topics": "Json-indeling gebruiken voor standaard downlink-onderwerpen", + "mqtt-use-json-format-for-default-downlink-topics-hint": "Indien ingeschakeld, gebruikt het platform de Json-payloadindeling om attributen en RPC te pushen via de volgende onderwerpen: v1/devices/me/attributes/response/$request_id, v1/devices/me/attributes, v1/devices/me/rpc/request/$request_id, v1/devices/me/rpc/response/$request_id. Deze instelling heeft geen invloed op kenmerk- en rpc-abonnementen die worden verzonden met behulp van nieuwe (v2) onderwerpen: v2/a/res/$request_id, v2/a, v2/r/req/$request_id, v2/r/res/$request_id. Waarbij $request_id een id van een geheel getal is.", + "mqtt-send-ack-on-validation-exception": "PUBACK verzenden bij mislukte validatie van PUBLISH-bericht", + "mqtt-send-ack-on-validation-exception-hint": "Standaard sluit het platform de MQTT-sessie bij mislukte berichtvalidatie. Indien ingeschakeld, stuurt het platform een publicatiebevestiging in plaats van de sessie te sluiten.", + "snmp-add-mapping": "SNMP-toewijzing toevoegen", + "snmp-mapping-not-configured": "Geen toewijzing voor OID aan tijdreeks/telemetrie geconfigureerd", + "snmp-timseries-or-attribute-name": "Tijdreeks-/kenmerknaam voor toewijzing", + "snmp-timseries-or-attribute-type": "Tijdreeks/kenmerktype voor toewijzing", + "snmp-method-pdu-type-get-request": "GetRequest (GetAanvraag)", + "snmp-method-pdu-type-get-next-request": "VolgendeAanvraag", + "snmp-oid": "OID", + "transport-device-payload-type-json": "JSON", + "transport-device-payload-type-proto": "Protobuf", + "mqtt-payload-type-required": "Payload-type is vereist.", + "coap-device-type": "Type CoAP-device", + "coap-device-payload-type": "Nuttige lading van CoAP-device", + "coap-device-type-required": "Het type CoAP-device is vereist.", + "coap-device-type-default": "Verstek", + "coap-device-type-efento": "Efento NB-IoT", + "support-level-wildcards": "Enkele [+] en multi-level [#] jokertekens worden ondersteund.", + "telemetry-topic-filter": "Onderwerpfilter voor telemetrie", + "telemetry-topic-filter-required": "Telemetrie-onderwerpfilter is vereist.", + "attributes-topic-filter": "Attributen topic filter", + "attributes-subscribe-topic-filter": "Attributen topic subscribe filter", + "attributes-topic-filter-required": "Attributen topic filter is vereist.", + "attributes-subscribe-topic-filter-required": "Attributen topic subscribe is vereist", + "telemetry-proto-schema": "Telemetrie proto-schema", + "telemetry-proto-schema-required": "Telemetrie proto-schema is vereist.", + "attributes-proto-schema": "Attributen proto-schema", + "attributes-proto-schema-required": "Attributen proto-schema is vereist.", + "rpc-response-proto-schema": "RPC-respons proto-schema", + "rpc-response-proto-schema-required": "RPC-respons proto-schema is vereist.", + "rpc-response-topic-filter": "Onderwerpfilter voor RPC-reacties", + "rpc-response-topic-filter-required": "RPC-responsonderwerpfilter is vereist.", + "rpc-request-proto-schema": "RPC-aanvraag proto-schema", + "rpc-request-proto-schema-required": "RPC-aanvraag proto-schema is vereist.", + "rpc-request-proto-schema-hint": "RPC-aanvraagbericht moet altijd velden bevatten: tekenreeksmethode = 1; int32 requestId = 2; en params = 3 van elk gegevenstype.", + "not-valid-pattern-topic-filter": "Ongeldig topic pattern filter", + "not-valid-single-character": "Ongeldig gebruik van een jokerteken op één niveau", + "not-valid-multi-character": "Ongeldig gebruik van een jokerteken met meerdere niveaus", + "single-level-wildcards-hint": "[+] is geschikt voor elk topic filter niveau. Bijv.: v1/devices/+/telemetry of +/devices/+/attributen.", + "multi-level-wildcards-hint": "[#] kan de topic filter zelf vervangen en moet het laatste symbool van de topic zijn. Bijv.: # of v1/Devices/ik/#.", + "alarm-rules": "Alarm regels", + "alarm-rules-with-count": "Alarmregels ({{count}})", + "no-alarm-rules": "Geen alarmregels geconfigureerd", + "add-alarm-rule": "Alarmregel toevoegen", + "edit-alarm-rule": "Alarmregel bewerken", + "alarm-type": "Soort alarm", + "alarm-type-required": "Alarmtype is vereist.", + "alarm-type-unique": "Het alarmtype moet uniek zijn binnen de alarmregels van het device profiel.", + "alarm-type-max-length": "Alarmtype moet kleiner zijn dan 256 tekens tekens", + "create-alarm-pattern": "Creëer {{alarmType}} alarm", + "create-alarm-rules": "Alarmregels maken", + "no-create-alarm-rules": "Geen voorwaarden voor het creëren van alarmen+ geconfigureerd", + "add-create-alarm-rule-prompt": "Voeg een alarmregel toe", + "clear-alarm-rule": "Duidelijke alarmregel", + "no-clear-alarm-rule": "Geen duidelijke voorwaarde geconfigureerd", + "add-create-alarm-rule": "Voorwaarde voor maken toevoegen", + "add-clear-alarm-rule": "Duidelijke voorwaarde toevoegen", + "select-alarm-severity": "Selecteer de ernst van het alarm", + "alarm-severity-required": "De ernst van het alarm is vereist.", + "condition-duration": "Duur van de conditie", + "condition-duration-value": "Duur waarde", + "condition-duration-time-unit": "Tijdseenheid", + "condition-duration-value-range": "De duurwaarde moet tussen 1 en 2147483647 liggen.", + "condition-duration-value-pattern": "De waarde van de duur moet gehele getallen zijn.", + "condition-duration-value-required": "Duurwaarde is vereist.", + "condition-duration-time-unit-required": "Tijdseenheid is vereist.", + "advanced-settings": "Geavanceerde instellingen", + "alarm-rule-details": "Details", + "alarm-rule-details-hint": "Tip: gebruik ${keyName} om waarden van de attribuut- of telemetriesleutels te vervangen die worden gebruikt in de alarmregelvoorwaarde.", + "add-alarm-rule-details": "Voeg details toe", + "alarm-rule-mobile-dashboard": "Mobiel dashboard", + "alarm-rule-mobile-dashboard-hint": "Gebruikt door mobiele applicatie als dashboard met alarmdetails", + "alarm-rule-no-mobile-dashboard": "Geen dashboard geselecteerd", + "propagate-alarm": "Alarm doorgeven aan verwante entiteiten", + "alarm-rule-relation-types-list": "Relatietypen die moeten worden gepropageerd", + "alarm-rule-relation-types-list-hint": "Als Relatietypen doorgeven niet is geselecteerd, worden alarmen doorgegeven zonder te filteren op relatietype.", + "propagate-alarm-to-owner": "Alarm doorgeven aan entiteitseigenaar (klant of tenant)", + "propagate-alarm-to-owner-hierarchy": "Alarm doorgeven aan de hiërarchie van entiteitseigenaren", + "propagate-alarm-to-tenant": "Alarm doorgeven aan tenant", + "alarm-details": "Details van het alarm", + "alarm-rule-condition": "Voorwaarde van de alarmregel", + "enter-alarm-rule-condition-prompt": "Voeg de voorwaarde van de alarmregel toe", + "edit-alarm-rule-condition": "Voorwaarde van de alarmregel bewerken", + "device-provisioning": "Inrichting van Devices", + "provision-strategy": "Provisioning-strategie", + "provision-strategy-required": "Provisioning-strategie is vereist.", + "provision-strategy-disabled": "Provisioning-strategie uitgeschakeld ", + "provision-strategy-created-new": "Toestaan om nieuwe provisioning-strategie aan te maken", + "provision-strategy-check-pre-provisioned": "Controleren op vooraf geprovisioneerde devices", + "provision-device-key": "Provision device sleutel", + "provision-device-key-required": "Provision device sleutel is vereist.", + "copy-provision-key": "Provisioningsleutel kopiëren", + "provision-key-copied-message": "Provisioningsleutel is gekopieerd naar klembord", + "provision-device-secret": "Provion device geheim", + "provision-device-secret-required": "Provision device geheim is vereist.", + "copy-provision-secret": "Provisioning geheim kopiëren", + "provision-secret-copied-message": "Provisioningsgeheim is gekopieerd naar klembord", + "provision-strategy-x509": { + "certificate-chain": "X509 Certificaten Keten", + "certificate-chain-hint": "De X.509-certificatenstrategie wordt gebruikt om devices in te richten op clientcertificaten in tweerichtings-TLS-communicatie.", + "allow-create-new-devices": "Nieuwe devices maken", + "allow-create-new-devices-hint": "Indien geselecteerd, worden nieuwe devices gemaakt en wordt het clientcertificaat gebruikt als apparaatreferenties.", + "certificate-value": "Certificaat in PEM-formaat", + "certificate-value-required": "Certificaat in PEM-formaat is vereist", + "cn-regex-variable": "CN Reguliere expressie variabele", + "cn-regex-variable-required": "CN Regular Expression variabele is vereist", + "cn-regex-variable-hint": "Vereist om de device naam op te halen uit de algemene naam van het X509-certificaat van het device." + }, + "condition": "Conditie", + "condition-type": "Conditie type", + "condition-type-simple": "Eenvoudig", + "condition-type-duration": "Duur", + "condition-during": "Tijdens {{during}}", + "condition-during-dynamic": "Tijdens \"{{ attribute }}\" ({{during}})", + "condition-type-repeating": "Herhalende", + "condition-type-required": "Conditietype is vereist.", + "condition-repeating-value": "Aantal gebeurtenissen", + "condition-repeating-value-range": "Het aantal gebeurtenissen moet tussen 1 en 2147483647 liggen.", + "condition-repeating-value-pattern": "Het aantal gebeurtenissen moet gehele getallen zijn.", + "condition-repeating-value-required": "Het aantal gebeurtenissen is vereist.", + "condition-repeat-times": "Herhalingen { count, plural, =1 {1 time} andere {# times} }", + "condition-repeat-times-dynamic": "Herhaalt \"{ attribute }\" ({ count, plural, =1 {1 time} andere {# times} })", + "schedule-type": "Type planner", + "schedule-type-required": "Het type planner is vereist.", + "schedule": "Rooster", + "edit-schedule": "Alarmschema bewerken", + "schedule-any-time": "Altijd actief", + "schedule-specific-time": "Actief op een bepaald tijdstip", + "schedule-custom": "Gewoonte", + "schedule-day": { + "monday": "Maandag", + "tuesday": "Dinsdag", + "wednesday": "Woensdag", + "thursday": "Donderdag", + "friday": "Vrijdag", + "saturday": "Zaterdag", + "sunday": "Zondag" + }, + "schedule-days": "Dagen", + "schedule-time": "Tijd", + "schedule-time-from": "Van", + "schedule-time-to": "Aan", + "schedule-days-of-week-required": "Er moet ten minste één dag van de week worden geselecteerd.", + "create-device-profile": "Nieuw device profiel maken", + "import": "Device profiel importeren", + "export": "Device profiel exporteren", + "export-failed-error": "Device profiel kan niet worden geëxporteerd: {{error}}", + "device-profile-file": "Bestand met device profiel", + "invalid-device-profile-file-error": "Kan device profiel niet importeren: Ongeldige gegevensstructuur van het device profiel.", + "power-saving-mode": "Energiebesparende modus", + "power-saving-mode-type": { + "default": "Energiebesparende modus voor device profiel gebruiken", + "psm": "Energiebesparende modus (PSM)", + "drx": "Discontinue ontvangst (DRX)", + "edrx": "Uitgebreide discontinue ontvangst (eDRX)" + }, + "edrx-cycle": "eDRX-cyclus", + "edrx-cycle-required": "eDRX-cyclus is vereist.", + "edrx-cycle-pattern": "De eDRX-cyclus moet een positief geheel getal zijn.", + "edrx-cycle-min": "Het minimale aantal eDRX-cycli is {{ min }} seconden.", + "paging-transmission-window": "Transmissievenster voor oproepen", + "paging-transmission-window-required": "Oproeptransmissievenster is vereist.", + "paging-transmission-window-pattern": "Het zendvenster van de paging-transmissie moet een positief geheel getal zijn.", + "paging-transmission-window-min": "Het minimale aantal paging-transmissievensters is {{ min }} seconden.", + "psm-activity-timer": "PSM-activiteitstimer", + "psm-activity-timer-required": "PSM-activiteitstimer is vereist.", + "psm-activity-timer-pattern": "PSM-activiteitstimer moet een positief geheel getal zijn.", + "psm-activity-timer-min": "Het minimale aantal PSM-activiteitstimers is {{ min }} seconden.", + "lwm2m": { + "object-list": "Lijst met objecten", + "object-list-empty": "Geen objecten geselecteerd.", + "no-objects-found": "Geen objecten gevonden.", + "no-objects-matching": "Er zijn geen objecten gevonden die overeenkomen met '{{object}}'.", + "model-tab": "LWM2M Model", + "add-new-instances": "Nieuwe instanties toevoegen", + "instances-list": "Lijst met instanties", + "instances-list-required": "Een lijst met instanties is vereist.", + "instance-id-pattern": "Instantie-id moet een positief geheel getal zijn.", + "instance-id-max": "Maximale exemplaar-id-waarde {{max}}.", + "instance": "Voorbeeld", + "resource-label": "#ID Naam van de bron", + "observe-label": "Waarnemen", + "attribute-label": "Attribuut", + "telemetry-label": "Telemetrie", + "edit-observe-select": "Telemetrie of attribuut bewerken om te bewerken, te observeren", + "edit-attributes-select": "Als u attributen wilt bewerken, selecteert u telemetrie of kenmerk", + "no-attributes-set": "Geen attributen ingesteld", + "key-name": "Naam van de sleutel", + "key-name-required": "Sleutelnaam is vereist", + "attribute-name": "Naam attribuut", + "attribute-name-required": "Naamattribuut is vereist.", + "attribute-value": "Attribuutwaarde", + "attribute-value-required": "Kenmerkwaarde is vereist.", + "attribute-value-pattern": "De kenmerkwaarde moet een positief geheel getal zijn.", + "edit-attributes": "Attributen bewerken: {{ name }}", + "view-attributes": "Bekijk attributen: {{ name }}", + "add-attribute": "Attribuut toevoegen", + "edit-attribute": "Attribuut bewerken", + "view-attribute": "Bekijk attribuut", + "remove-attribute": "Attribuut verwijderen", + "delete-server-text": "Opgelet, na de bevestiging wordt de serverconfiguratie onherstelbaar.", + "delete-server-title": "Weet je zeker dat je de server wilt verwijderen?", + "mode": "Beveiligingsconfiguratiemodus", + "bootstrap-tab": "Opstarten", + "bootstrap-server-legend": "Bootstrap-server (ShortId...)", + "lwm2m-server-legend": "LwM2M-server (ShortId...)", + "server": "Server", + "short-id": "Korte server-ID", + "short-id-tooltip": "Server-id kort Id. Wordt gebruikt als koppeling naar het Object-exemplaar van de koppeling.\nDeze id identificeert elke LwM2M-server die is geconfigureerd voor de LwM2M-client op unieke wijze.\nResource MOET worden ingesteld wanneer de Bootstrap-Server Resource een waarde van 'false' heeft.\nDe waarden ID:0 en ID:65535 mogen NIET worden gebruikt voor het identificeren van de LwM2M-server.", + "short-id-required": "Een korte server-ID is vereist.", + "short-id-range": "Korte server-ID moet tussen 1 en 65534 liggen.", + "short-id-pattern": "Korte server-ID moet een positief geheel getal zijn.", + "lifetime": "Levensduur klantregistratie", + "lifetime-required": "Levenslange registratie van klanten is vereist.", + "lifetime-pattern": "De levensduur van clientregistratie moet een positief geheel getal zijn.", + "default-min-period": "Min. periode tussen twee kennisgeving(en)", + "default-min-period-tooltip": "De standaardwaarde die de LwM2M-client moet gebruiken voor de minimale periode van een waarneming als deze parameter niet in een waarneming is opgenomen.", + "default-min-period-required": "Minimumperiode is vereist.", + "default-min-period-pattern": "De minimumperiode moet een positief geheel getal zijn.", + "notification-storing": "Melding opslaan wanneer uitgeschakeld of offline", + "binding": "Bindend", + "binding-type": { + "u": "U: Cliënt is te allen tijde bereikbaar via de UDP-binding.", + "m": "M: Opdrachtgever is te allen tijde bereikbaar via de MQTT-binding.", + "h": "H: Client is op elk moment bereikbaar via de HTTP-binding.", + "t": "T: Cliënt is te allen tijde bereikbaar via de TCP-binding.", + "s": "S: Cliënt is te allen tijde bereikbaar via de SMS-binding.", + "n": "N: Client MOET het antwoord op een dergelijk verzoek verzenden via de niet-IP-binding (wordt ondersteund sinds LWM2M 1.1).", + "uq": "UQ: UDP-verbinding in wachtrijmodus (wordt niet meer ondersteund sinds LWM2M 1.1)", + "uqs": "UQS: zowel UDP- als SMS-verbindingen actief; UDP in wachtrijmodus, SMS in standaardmodus (wordt niet ondersteund sinds LWM2M 1.1)", + "tq": "TQ: TCP-verbinding in wachtrijmodus (wordt niet meer ondersteund sinds LWM2M 1.1)", + "tqs": "TQS: zowel TCP- als SMS-verbindingen actief; TCP in wachtrijmodus, SMS in standaardmodus (wordt niet ondersteund sinds LWM2M 1.1)", + "sq": "SQ: SMS-verbinding in wachtrijmodus (wordt niet meer ondersteund sinds LWM2M 1.1)" + }, + "binding-tooltip": "Dit is de lijst in de \"bindende\" bron van het LwM2M-serverobject - /1/x/7.\nGeeft de ondersteunde bindingsmodi in de LwM2M-client aan.\nDeze waarde MOET hetzelfde zijn als de waarde in de bron \"Ondersteunde bindingen en modi\" in het apparaatobject (/3/0/16).\nHoewel meerdere transporten worden ondersteund, kan er slechts één transportbinding worden gebruikt tijdens de gehele transportsessie.\nWanneer bijvoorbeeld UDP en SMS beide worden ondersteund, kunnen de LwM2M-client en de LwM2M-server ervoor kiezen om tijdens de gehele transportsessie via UDP of sms te communiceren.", + "bootstrap-server": "Bootstrap-server", + "lwm2m-server": "LwM2M-server", + "include-bootstrap-server": "Bootstrap-serverupdates opnemen", + "bootstrap-update-title": "U hebt Bootstrap Server al geconfigureerd. Weet je zeker dat je de updates wilt uitsluiten?", + "bootstrap-update-text": "Opgelet, na de bevestiging worden de configuratiegegevens van de Bootstrap-server onherstelbaar.", + "server-host": "Server host", + "server-host-required": "Server host is vereist.", + "server-port": "Haven", + "server-port-required": "Poort is vereist.", + "server-port-pattern": "Poort moet een positief geheel getal zijn.", + "server-port-range": "De poort moet tussen 1 en 65535 liggen.", + "server-public-key": "Openbare sleutel van de server", + "server-public-key-required": "De openbare sleutel van de server is vereist.", + "client-hold-off-time": "Wacht met de tijd", + "client-hold-off-time-required": "Wachttijd is vereist.", + "client-hold-off-time-pattern": "Hold Off Time moet een positief geheel getal zijn.", + "client-hold-off-time-tooltip": "Client Hold Off Time alleen voor gebruik met een Bootstrap-Server", + "account-after-timeout": "Account na de time-out", + "account-after-timeout-required": "Account na de time-out is vereist.", + "account-after-timeout-pattern": "Account na de time-out moet een positief geheel getal zijn.", + "account-after-timeout-tooltip": "Bootstrap-Server-account na de time-outwaarde die door deze resource wordt opgegeven.", + "server-type": "Type server", + "add-new-server-title": "Nieuwe serverconfiguratie toevoegen", + "add-server-config": "Serverconfiguratie toevoegen", + "add-lwm2m-server-config": "LwM2M-server toevoegen", + "no-config-servers": "Geen servers geconfigureerd", + "others-tab": "Overige instellingen", + "client-strategy": "Klantstrategie bij het verbinden", + "client-strategy-label": "Strategie", + "client-strategy-only-observe": "Observeer het verzoek aan de client pas na de eerste verbinding", + "client-strategy-read-all": "Lees alle bronnen en observeer het verzoek aan de klant na registratie", + "fw-update": "Firmware-update", + "fw-update-strategy": "Strategie voor firmware-updates", + "fw-update-strategy-data": "Firmware-update pushen als binair bestand met behulp van Object 19 en Resource 0 (gegevens)", + "fw-update-strategy-package": "Firmware-update pushen als binair bestand met behulp van Object 5 en Resource 0 (pakket)", + "fw-update-strategy-package-uri": "Genereer automatisch een unieke CoAP-URL om het pakket te downloaden en de firmware-update te pushen als Object 5 en Resource 1 (pakket-URI)", + "sw-update": "Bijwerken van de software", + "sw-update-strategy": "Strategie voor software-updates", + "sw-update-strategy-package": "Binair bestand pushen met Object 9 en Resource 2 (pakket)", + "sw-update-strategy-package-uri": "Genereer automatisch een unieke CoAP-URL om het pakket te downloaden en software-update te pushen met behulp van Object 9 en Resource 3 (pakket-URI)", + "fw-update-resource": "CoAP-bron voor firmware-update", + "fw-update-resource-required": "CoAP-bron voor firmware-update is vereist.", + "sw-update-resource": "CoAP-bron voor software-update", + "sw-update-resource-required": "Software-update CoAP-bron is vereist.", + "config-json-tab": "Json Config Profile-device", + "attributes-name": { + "min-period": "Minimale periode", + "max-period": "Maximale periode", + "greater-than": "Groter dan", + "less-than": "Minder dan", + "step": "Stap", + "min-evaluation-period": "Minimale evaluatieperiode", + "max-evaluation-period": "Maximale evaluatieperiode" + }, + "composite-operations-support": "Ondersteunt samengestelde lees-/schrijf-/observatiebewerkingen" + }, + "snmp": { + "add-communication-config": "Communicatieconfiguratie toevoegen", + "add-mapping": "Toewijzing toevoegen", + "authentication-passphrase": "Wachtwoordzin voor verificatie", + "authentication-passphrase-required": "Wachtwoordzin voor verificatie is vereist.", + "authentication-protocol": "Authenticatie protocol", + "authentication-protocol-required": "Verificatieprotocol is vereist.", + "communication-configs": "Communicatie configs", + "community": "Tekenreeks van de gemeenschap", + "community-required": "Community-tekenreeks is vereist.", + "context-name": "Naam van de context", + "data-key": "Gegevenssleutel", + "data-key-required": "Gegevenssleutel is vereist.", + "data-type": "Soort gegevens", + "data-type-required": "Gegevenstype is vereist.", + "engine-id": "Motor-ID", + "host": "Server host", + "host-required": "Server host is vereist.", + "oid": "OID", + "oid-pattern": "Ongeldige OID-indeling.", + "oid-required": "OID is vereist.", + "please-add-communication-config": "Gelieve communicatieconfiguratie toe te voegen", + "please-add-mapping-config": "Voeg a.u.b. toewijzingsconfiguratie toe", + "port": "Haven", + "port-format": "Ongeldige poortindeling.", + "port-required": "Poort is vereist.", + "privacy-passphrase": "Wachtwoordzin voor privacy", + "privacy-passphrase-required": "Privacywachtwoordzin is vereist.", + "privacy-protocol": "Privacybeleid protocol", + "privacy-protocol-required": "Privacyprotocol is vereist.", + "protocol-version": "Protocolversie", + "protocol-version-required": "Protocolversie is vereist.", + "querying-frequency": "Frequentie van vragen, ms", + "querying-frequency-invalid-format": "De frequentie van query's moet een positief geheel getal zijn.", + "querying-frequency-required": "Queryfrequentie is vereist.", + "retries": "Pogingen", + "retries-invalid-format": "Pogingen voor nieuwe pogingen moeten een positief geheel getal zijn.", + "retries-required": "Opnieuw proberen is vereist.", + "scope": "Draagwijdte", + "scope-required": "Scope is vereist.", + "security-name": "Naam van de beveiliging", + "security-name-required": "Beveiligingsnaam is vereist.", + "timeout-ms": "Time-out, ms", + "timeout-ms-invalid-format": "Time-out moet een positief geheel getal zijn.", + "timeout-ms-required": "Er is een time-out vereist.", + "user-name": "Gebruikersnaam", + "user-name-required": "Gebruikersnaam is vereist." + } + }, + "dialog": { + "close": "Dialoogvenster sluiten", + "error-message-title": "Foutmelding:", + "error-details-title": "Details van de fout" + }, + "direction": { + "column": "Kolom", + "row": "Rijen" + }, + "edge": { + "all": "Alle", + "all-edges": "Alle edges", + "groups": "Groepen", + "shared": "Gedeeld", + "edge": "Edge", + "edge-instances": "Edge-instanties", + "instances": "Exemplaren", + "edge-file": "Edge-bestand", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens", + "label-max-length": "Etiket moet kleiner zijn dan 256 tekens", + "type-max-length": "Type moet kleiner zijn dan 256 tekens", + "management": "Edge-beheer", + "no-edges-matching": "Er zijn geen edges gevonden die overeenkomen met '{{entity}}'.", + "add": "Edge toevoegen", + "no-edges-text": "Geen edges gevonden", + "edge-details": "Details van de edge", + "add-edge-text": "Nieuwe edge toevoegen", + "delete": "Edge verwijderen", + "delete-edge-title": "Weet je zeker dat je de edge '{{edgeName}}' wilt verwijderen?", + "delete-edge-text": "Opgelet, na de bevestiging worden de edge en alle gerelateerde gegevens onherstelbaar.", + "delete-edges-title": "Weet je zeker dat je { count, plural, =1 {1 edge} andere {# edges} } wilt bevoordelen?", + "delete-edges-text": "Opgelet, na de bevestiging worden alle geselecteerde edges verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", + "name": "Naam", + "name-starts-with": "Edge naam begint met", + "name-required": "Naam is verplicht.", + "edge-license-key": "Edge-licentiesleutel", + "edge-license-key-required": "Edge-licentiecode is vereist.", + "edge-license-key-max-length": "Edge-licentiesleutel moet kleiner zijn dan 31", + "edge-license-key-hint": "BELANGRIJKE OPMERKING: standaard edge-licentiesleutel wordt alleen verstrekt voor evaluatiedoeleinden.
Deze sleutel is slechts 30 dagen na activering actief. De whitelabeling-functie is uitgeschakeld.
Om uw licentiesleutel te verkrijgen, navigeert u naar de pagina pricing en selecteert u de beste licentieoptie voor uw geval.", + "cloud-endpoint": "Cloud-eindpunt", + "cloud-endpoint-required": "Cloud Endpoint is vereist.", + "cloud-endpoint-max-length": "Cloud-eindpunt moet kleiner zijn dan 256 tekens", + "cloud-endpoint-hint": "Edge vereist HTTP(s)-toegang tot Cloud (ThingsBoard PE) om de licentiesleutel te verifiëren. Geef de cloud-URL op waarmee Edge verbinding kan maken.", + "description": "Omschrijving: __________", + "details": "Details", + "events": "Events", + "copy-id": "Edge-ID kopiëren", + "id-copied-message": "Edge Id is gekopieerd naar klembord", + "sync": "Edge synchroniseren", + "edge-required": "Edge vereist", + "edge-type": "Type edge", + "edge-type-required": "Edge type is vereist.", + "event-action": "Actie voor event", + "entity-id": "Entiteit-ID", + "select-edge-type": "Edge type selecteren", + "assign-to-customer": "Toewijzen aan klant", + "assign-to-customer-text": "Selecteer de klant om de edge(en) toe te wijzen", + "assign-edge-to-customer": "Edge(s) toewijzen aan klant", + "assign-edge-to-customer-text": "Selecteer de edges die u aan de klant wilt toewijzen", + "assignedToCustomer": "Toegewezen aan klant", + "edge-public": "Edge is openbaar", + "assigned-to-customer": "Toegewezen aan: {{customerTitle}}", + "unassign-from-customer": "Toewijzing van klant ongedaan maken", + "unassign-edge-title": "Weet u zeker dat u de toewijzing van de edge '{{edgeName}}' wilt opheffen?", + "unassign-edge-text": "Na de bevestiging wordt de toewijzing van de edge ongedaan gemaakt en is deze niet toegankelijk voor de klant.", + "unassign-edges-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 edge} andere {# edges} } wilt opheffen?", + "unassign-edges-text": "Na de bevestiging worden alle geselecteerde edges ongedaan gemaakt en zijn ze niet toegankelijk voor de klant.", + "make-public": "Edge openbaar maken", + "make-public-edge-title": "Weet je zeker dat je de edge '{{edgeName}}' openbaar wilt maken?", + "make-public-edge-text": "Na de bevestiging worden de edge en al zijn gegevens openbaar gemaakt en toegankelijk gemaakt voor anderen.", + "make-private": "Maak edge privé", + "public": "Publiek", + "make-private-edge-title": "Weet je zeker dat je de edge '{{edgeName}}' privé wilt maken?", + "make-private-edge-text": "Na de bevestiging worden de edge en al zijn gegevens privé gemaakt en zijn ze niet toegankelijk voor anderen.", + "import": "Edge importeren", + "install-connect-instructions": "Installatie- en verbindingsinstructies", + "label": "Etiket", + "load-entity-error": "Gegevens kunnen niet worden geladen. Entiteit is verwijderd.", + "assign-new-edge": "Nieuwe edge toewijzen", + "unassign-from-edge": "Toewijzing van edge ongedaan maken", + "edge-key": "Edge sleutel", + "copy-edge-key": "Edge sleutel kopiëren", + "edge-key-copied-message": "Edge sleutel is gekopieerd naar klembord", + "edge-secret": "Het geheim van de edge", + "copy-edge-secret": "Kopieer Edge geheim", + "edge-secret-copied-message": "Edge-geheim is gekopieerd naar klembord", + "manage-assets": "Assets beheren", + "manage-devices": "Devices beheren", + "manage-entity-views": "Entiteitsweergaven beheren", + "manage-dashboards": "Dashboards beheren", + "manage-rulechains": "Rule chains beheren", + "assets": "Edge-asset", + "devices": "Edge-Devices", + "entity-views": "Edge-entiteitsweergaven", + "dashboard": "Edge-dashboard", + "dashboards": "Edge-dashboards", + "rulechain-templates": "Sjablonen voor rule chains", + "edge-rulechain-templates": "Sjablonen voor edge rule chains", + "converter-templates": "Converter sjablonen", + "edge-converter-templates": "Sjablonen voor edge-converters", + "integration-templates": "Integratie sjablonen", + "edge-integration-templates": "Sjablonen voor edge-integratie", + "rulechains": "Edge rule chains", + "integrations": "Integraties", + "search": "Edge zoeken", + "selected-edges": "{ count, plural, =1 {1 edge} andere {# edges} } geselecteerd", + "any-edge": "Elke edge", + "no-edge-types-matching": "Er zijn geen edge typen gevonden die overeenkomen met '{{entitySubtype}}'.", + "edge-type-list-empty": "Geen edge typen geselecteerd.", + "edge-types": "Soorten edges", + "enter-edge-type": "Voer edge type in", + "deployed": "Ingezet", + "pending": "In behandeling", + "downlinks": "Downlinks", + "no-downlinks-prompt": "Geen downlinks gevonden", + "sync-process-started-successfully": "Het synchronisatieproces is succesvol gestart!", + "missing-related-rule-chains-title": "Edge heeft ontbrekende gerelateerde rule chain(s)", + "missing-related-rule-chains-text": "Toegewezen aan rule chain(s) Gebruik rule nodes die bericht(en) doorsturen naar rule chain(s) die niet aan deze edge zijn toegewezen.

Lijst met ontbrekende rule chain(s):
{{missingRuleChains}}", + "widget-datasource-error": "Deze widget ondersteunt alleen de gegevensbron van de EDGE-entiteit", + "assign-to-edge": "Toewijzen aan edge", + "assign-to-edge-title": "Entiteitsgroep(en) toewijzen aan Edge", + "manage-edge-user-groups": "Edge-gebruikersgroepen beheren", + "manage-edge-asset-groups": "Edge-activagroepen beheren", + "manage-edge-device-groups": "Edge-apparaatgroepen beheren", + "manage-edge-entity-view-groups": "Edge-entiteitsweergavegroepen beheren", + "manage-edge-dashboard-groups": "Edge-dashboardgroepen beheren", + "manage-edge-rule-chains": "Ketens van randregels beheren", + "manage-edge-integrations": "Edge-integraties beheren", + "manage-edge-scheduler-events": "Edge-scheduler events beheren", + "select-group-to-add": "Selecteer de doelgroep om geselecteerde edges toe te voegen", + "select-group-to-move": "Selecteer de doelgroep om geselecteerde edges te verplaatsen", + "remove-edges-from-group": "Weet je zeker dat je { count, plural, =1 {1 edge} andere {# edges} } uit groep '{entityGroup}' wilt verwijderen?", + "group": "Groep edges", + "list-of-groups": "{ count, plural, =1 {One edge group} andere {List of # edge groups} }", + "group-name-starts-with": "Edge-groepen waarvan de naam begint met '{{prefix}}'", + "unassign-entity-group-from-edge-title": "Weet u zeker dat u de toewijzing van de entiteitsgroep '{{ entityGroupName }}' wilt opheffen?", + "unassign-entity-group-from-edge-text": "Na de bevestiging wordt de toewijzing van de entiteitsgroep ongedaan gemaakt en is deze niet toegankelijk via de edge.", + "unassign-entity-group-from-edge": "Toewijzing van entiteitsgroep aan edge ongedaan maken", + "unassign-entity-groups-from-edge-title": "Weet u zeker dat u de toewijzing van {{count}} entiteitsgroepen wilt opheffen?", + "unassign-entity-groups-from-edge-text": "Na de bevestiging wordt de toewijzing van entiteitsgroepen ongedaan gemaakt en zijn ze niet toegankelijk via de edge.", + "unassign-entity-groups-from-edge": "Toewijzing van entiteitsgroepen aan de edge ongedaan maken", + "unassign-scheduler-events-from-edge": "Toewijzing van scheduler events van edge ongedaan maken", + "unassign-scheduler-event-from-edge-title": "Weet u zeker dat u de toewijzing van de '{{schedulerEventName}}' van de planningsgebeurtenis wilt intrekken?", + "unassign-scheduler-event-from-edge-text": "Na de bevestiging wordt de toewijzing van de planningsgebeurtenis ongedaan gemaakt en is deze niet toegankelijk via de edge.", + "unassign-scheduler-events-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 scheduler event} andere {# scheduler events} } wilt opheffen?", + "unassign-scheduler-events-from-edge-text": "Na de bevestiging worden alle geselecteerde scheduler events ongedaan gemaakt en zijn ze niet toegankelijk voor de edge.", + "manage-user-groups": "Gebruikersgroepen beheren", + "manage-asset-groups": "Asset groepen beheren", + "manage-device-groups": "Asset groepen beheren", + "manage-dashboard-groups": "Dashboardgroepen beheren", + "manage-entity-view-groups": "Entiteitsweergavegroepen beheren", + "manage-scheduler-events": "Scheduler-events beheren", + "manage-integrations": "Integraties beheren", + "assign-scheduler-event-to-edge-title": "Scheduler-gebeurtenissen toewijzen aan Edge", + "assign-scheduler-event-to-edge-text": "Selecteer de scheduler events die u aan de edge wilt toewijzen", + "assign-entity-groups-to-edge-text": "Selecteer de entiteitsgroepen die u aan de edge wilt toewijzen", + "assign-integration-to-edge-title": "Integraties toewijzen aan Edge", + "assign-integration-to-edge-text": "Selecteer de integraties die u aan de edge wilt toewijzen", + "missing-attributes-title": "Edge heeft ontbrekende attribuut(en)", + "missing-attributes-text": "Integratie(s) die aan de edge zijn toegewezen, hebben tijdelijke aanduidingen voor attributen die niet beschikbaar zijn aan deze edge.

Lijst met ontbrekende edge kenmerken:
{{missingEdgeAttributes}}", + "missing-all-related-attributes-title": "Edge(en) hebben ontbrekende attribuut(en)", + "missing-all-related-attributes-text": "Edge(s), gerelateerd aan integratie, hebben geen attributen die aanwezig zijn in de integratieconfiguratie als tijdelijke aanduidingen voor attributen.

Lijst met ontbrekende edge kenmerken:
{{missingEdgeAttributes}}", + "quick-overview-widget-header": "{{edgeName}} Snel overzicht" + }, + "edge-event": { + "type-dashboard": "Dashboard", + "type-asset": "Asset", + "type-device": "Device", + "type-device-profile": "Device Profiel", + "type-asset-profile": "Asset Profiel", + "type-entity-view": "Entiteit Weergave", + "type-alarm": "Alarm", + "type-entity-group": "Entiteitsgroep", + "type-rule-chain": "Rule chain", + "type-rule-chain-metadata": "Metagegevens van rule chain", + "type-edge": "Edge", + "type-user": "Gebruiker", + "type-customer": "Klant", + "type-relation": "Relatie", + "type-widgets-bundle": "Widgets Bundel", + "type-widgets-type": "Widgets Type", + "type-admin-settings": "Admin-instellingen", + "type-scheduler-event": "Scheduler Evenement", + "type-white-labeling": "White labelling", + "type-login-white-labeling": "Inloggen White Labeling", + "type-custom-translation": "Aangepaste vertaling", + "type-role": "Rol", + "type-group-permission": "Toestemming voor groepen", + "type-integration": "Integratie", + "type-converter": "Conversie type", + "action-type-added": "Toegevoegd", + "action-type-deleted": "Verwijderd", + "action-type-updated": "Bijgewerkt", + "action-type-post-attributes": "Attributen posten", + "action-type-attributes-updated": "Attributen bijgewerkt", + "action-type-attributes-deleted": "Attributen verwijderd", + "action-type-timeseries-updated": "Tijdreeks bijgewerkt", + "action-type-credentials-updated": "Inloggegevens bijgewerkt", + "action-type-assigned-to-customer": "Toegewezen aan klant", + "action-type-unassigned-from-customer": "Toewijzing van klant ongedaan gemaakt", + "action-type-relation-add-or-update": "Relatie toevoegen of bijwerken", + "action-type-relation-deleted": "Relatie verwijderd", + "action-type-rpc-call": "RPC-oproep", + "action-type-alarm-ack": "Alarm Ack", + "action-type-alarm-clear": "Alarm wissen", + "action-type-assigned-to-edge": "Toegewezen aan Edge", + "action-type-unassigned-from-edge": "Toewijzing van Edge ongedaan gemaakt", + "action-type-credentials-request": "Inloggegevens aanvragen", + "action-type-entity-merge-request": "Aanvraag voor samenvoeging van entiteiten", + "action-type-added-to-entity-group": "Toegevoegd aan entiteitsgroep", + "action-type-removed-from-entity-group": "Verwijderd uit entiteitsgroep", + "action-type-change-owner": "Eigenaar wijzigen", + "action-type-relations-deleted": "Relaties verwijderd" + }, + "error": { + "unable-to-connect": "Kan geen verbinding maken met de server! Controleer uw internetverbinding.", + "unhandled-error-code": "Onverwerkte foutcode: {{errorCode}}", + "unknown-error": "Onbekende fout" + }, + "entity": { + "entity": "Entiteit", + "entities": "Entiteiten", + "entities-count": "Aantal entiteiten", + "alarms-count": "Aantal alarmen", + "aliases": "Aliassen van entiteiten", + "entity-alias": "Alias van entiteit", + "unable-delete-entity-alias-title": "Entiteitsalias kan niet worden verwijderd", + "unable-delete-entity-alias-text": "Entiteitsalias '{{entityAlias}}' kan niet worden verwijderd omdat deze wordt gebruikt door de volgende widget(s):
{{widgetsList}}", + "duplicate-alias-error": "Dubbele alias gevonden '{{alias}}'.
Aliassen van entiteiten moeten uniek zijn binnen het dashboard.", + "missing-entity-filter-error": "Filter ontbreekt voor alias '{{alias}}'.", + "configure-alias": "Configureer de alias '{{alias}}'", + "alias": "Alias", + "alias-required": "Entiteitsalias is vereist.", + "remove-alias": "Entiteitsalias verwijderen", + "add-alias": "Entiteitsalias toevoegen", + "entity-list": "Lijst met entiteiten", + "entity-type": "Type entiteit", + "entity-types": "Soorten entiteiten", + "entity-type-list": "Lijst met entiteitstypen", + "any-entity": "Elke entiteit", + "enter-entity-type": "Entiteitstype invoeren", + "no-entities-matching": "Er zijn geen entiteiten gevonden die overeenkomen met '{{entity}}'.", + "no-entity-types-matching": "Er zijn geen entiteitstypen gevonden die overeenkomen met '{{entityType}}'.", + "name-starts-with": "Naam expressie", + "help-text": "Gebruik '%' naar behoefte: '%entity_name_contains%', '%entity_name_ends', 'entity_starts_with'.", + "use-entity-name-filter": "Filter gebruiken", + "entity-list-empty": "Geen entiteiten geselecteerd.", + "entity-type-list-empty": "Er zijn geen entiteitstypen geselecteerd.", + "entity-name-filter-required": "Filter voor entiteitsnamen is vereist.", + "entity-name-filter-no-entity-matched": "Er zijn geen entiteiten gevonden die beginnen met '{{entity}}'.", + "all-subtypes": "Alle", + "select-entities": "Entiteiten selecteren", + "no-aliases-found": "Geen aliassen gevonden.", + "no-alias-matching": "'{{alias}}' niet gevonden.", + "create-new-alias": "Maak een nieuwe aan!", + "key": "Sleutel", + "key-name": "Naam van de sleutel", + "no-keys-found": "Geen sleutels gevonden.", + "no-key-matching": "'{{key}}' niet gevonden.", + "create-new-key": "Maak een nieuwe aan!", + "type": "Type", + "type-required": "Entiteitstype is vereist.", + "type-device": "Device", + "type-devices": "Devices", + "list-of-devices": "{ count, plural, =1 {One device} andere {List of # devices} }", + "device-name-starts-with": "Devices waarvan de naam begint met '{{prefix}}'", + "type-device-profile": "Device profiel", + "type-device-profiles": "Device profielen", + "list-of-device-profiles": "{ count, plural, =1 {One device profile} andere {List of # device profiles} }", + "device-profile-name-starts-with": "Device profielen waarvan de naam begint met '{{prefix}}'", + "type-asset-profile": "Asset profiel", + "type-asset-profiles": "Asset profielen", + "list-of-asset-profiles": "{ count, plural, =1 {One asset profile} andere {List of # asset profiles} }", + "asset-profile-name-starts-with": "Assetprofielen waarvan de naam begint met '{{prefix}}'", + "type-asset": "Asset", + "type-assets": "Assets", + "list-of-assets": "{ count, plural, =1 {One asset} andere {List of # assets} }", + "asset-name-starts-with": "Assets waarvan de naam begint met '{{prefix}}'", + "type-entity-view": "Entiteit Weergave", + "type-entity-views": "Entiteitsweergaven", + "list-of-entity-views": "{ count, plural, =1 {One entity view} andere {List of # entity views} }", + "entity-view-name-starts-with": "Entiteitsweergaven waarvan de naam begint met '{{prefix}}'", + "type-rule": "Regel", + "type-rules": "Reglement", + "list-of-rules": "{ count, plural, =1 {One rule} andere {List of # rules} }", + "rule-name-starts-with": "Regels waarvan de naam begint met '{{prefix}}'", + "type-plugin": "Plug-in", + "type-plugins": "Insteekplaatsen", + "list-of-plugins": "{ count, plural, =1 {One plugin} andere {List of # plugins} }", + "plugin-name-starts-with": "Plug-ins waarvan de naam begint met '{{prefix}}'", + "type-tenant": "Tenant", + "type-tenants": "Tenants", + "list-of-tenants": "{ count, plural, =1 {One tenant} andere {List of # tenants} }", + "tenant-name-starts-with": "Tenants van wie de naam begint met '{{prefix}}'", + "type-tenant-profile": "Profiel van de tenant", + "type-tenant-profiles": "Profielen van tenants", + "list-of-tenant-profiles": "{ count, plural, =1 {One tenant profile} andere {List of # tenant profiles} }", + "tenant-profile-name-starts-with": "Tentant profielen waarvan de naam begint met '{{prefix}}'", + "type-customer": "Klant", + "type-customers": "Klanten", + "list-of-customers": "{ count, plural, =1 {One customer} andere {List of # customers} }", + "customer-name-starts-with": "Klanten van wie de naam begint met '{{prefix}}'", + "type-user": "Gebruiker", + "type-users": "Gebruikers", + "list-of-users": "{ count, plural, =1 {One user} andere {List of # users} }", + "user-name-starts-with": "Gebruikers van wie de naam begint met '{{prefix}}'", + "type-dashboard": "Dashboard", + "type-dashboards": "Dashboards", + "list-of-dashboards": "{ count, plural, =1 {One dashboard} andere {List of # dashboards} }", + "dashboard-name-starts-with": "Dashboards waarvan de naam begint met '{{prefix}}'", + "type-alarm": "Alarm", + "type-alarms": "Alarmen", + "list-of-alarms": "{ count, plural, =1 {One alarm} andere {List of # alarms} }", + "alarm-name-starts-with": "Alarmen waarvan de naam begint met '{{prefix}}'", + "type-rulechain": "Rule chains", + "type-rulechains": "Rule chains", + "list-of-rulechains": "{ count, plural, =1 {One rule chain} andere {List of # rule chains} }", + "rulechain-name-starts-with": "Rule chains waarvan de naam begint met '{{prefix}}'", + "type-scheduler-event": "Scheduler-event", + "type-scheduler-events": "Scheduler-events", + "list-of-scheduler-events": "{ count, plural, =1 {One scheduler event} andere {List of # scheduler events} }", + "scheduler-event-name-starts-with": "Scheduler-events waarvan de naam begint met '{{prefix}}'", + "type-blob-entity": "Blob-entiteit", + "type-blob-entities": "Blob-entiteiten", + "list-of-blob-entities": "{ count, plural, =1 {One blob entity} andere {List of # blob entities} }", + "blob-entity-name-starts-with": "Blob-entiteiten waarvan de naam begint met '{{prefix}}'", + "type-rulenode": "Rule node", + "type-rulenodes": "Rule nodes", + "list-of-rulenodes": "{ count, plural, =1 {One rule node} andere {List of # rule nodes} }", + "rulenode-name-starts-with": "Rule node waarvan de naam begint met '{{prefix}}'", + "type-current-customer": "Huidige klant", + "type-current-tenant": "Huidige tenant", + "type-current-user": "Huidige gebruiker", + "type-current-user-owner": "Huidige gebruikerseigenaar", + "type-widgets-bundle": "Widgets bundel", + "type-widgets-bundles": "Widgets bundels", + "list-of-widgets-bundles": "{ count, plural, =1 {One widgets bundle} andere {List of # widget bundles} }", + "search": "Entiteiten zoeken", + "selected-entities": "{ count, plural, =1 {1 entity} andere {# entities} } geselecteerd", + "entity-name": "Naam van de entiteit", + "entity-label": "Entiteitslabel", + "details": "Gegevens van de entiteit", + "no-entities-prompt": "Geen entiteiten gevonden", + "no-data": "Geen gegevens om weer te geven", + "columns-to-display": "Weer te geven kolommen", + "type-api-usage-state": "Api-gebruiksstatus", + "type-entity-group": "Entiteitsgroep", + "type-converter": "Gegevens converteren", + "type-converters": "Data-converters", + "list-of-converters": "{ count, plural, =1 {One data converter} andere {List of # data converters} }", + "converter-name-starts-with": "Dataconverters waarvan de naam begint met \"{{prefix}}\"", + "type-integration": "Integratie", + "type-integrations": "Integraties", + "list-of-integrations": "{ count, plural, =1 {One integration} andere {List of # integrations} }", + "integration-name-starts-with": "Integraties waarvan de naam begint met '{{prefix}}'", + "type-role": "Rol", + "type-roles": "Rollen", + "list-of-roles": "{ count, plural, =1 {One role} andere {List of # roles} }", + "role-name-starts-with": "Rollen waarvan de naam begint met '{{prefix}}'", + "type-group-permission": "Toestemming voor groepen", + "type-edge": "Edge", + "type-edges": "Edge", + "list-of-edges": "{ count, plural, =1 {One edge} andere {List of # edges} }", + "edge-name-starts-with": "Edge waarvan de naam begint met '{{prefix}}'", + "type-tb-resource": "Hulpbron", + "type-ota-package": "OTA-pakket", + "type-rpc": "RPC", + "type-queue": "Rij", + "type-notification": "Bekendmaking", + "type-notification-rule": "Meldingsregel", + "type-notification-target": "Ontvanger van de melding", + "type-notification-request": "Verzoek om kennisgeving", + "type-notification-template": "Kennisgeving sjabloon", + "customer-name": "Naam van de klant", + "sub-customer-name": "Naam van de subklant", + "parent-customer-name": "Naam van bovenliggende klant", + "parent-sub-customer-name": "Naam van bovenliggende subklant", + "include-customer-entities": "Klantentiteiten opnemen", + "include-sub-customer-entities": "Entiteiten van subklanten opnemen", + "groups": "Groepen" + }, + "entity-group": { + "entity-group": "Entiteitsgroep", + "details": "Details", + "columns": "Kolommen", + "add-column": "Kolom toevoegen", + "column-value": "Waarde", + "column-value-required": "Kolomwaarde is vereist.", + "column-title": "Titel", + "default-sort-order": "Standaard sorteervolgorde", + "default-sort-order-required": "De standaard sorteervolgorde is vereist.", + "hide-in-mobile-view": "Mobiel verborgen", + "use-cell-style-function": "Celstijlfunctie gebruiken", + "use-cell-content-function": "De functie voor celinhoud gebruiken", + "edit-column": "Kolom bewerken", + "column-details": "Kolom details", + "actions": "Acties", + "settings": "Instellingen", + "search": "Entiteitsgroepen zoeken", + "delete": "Entiteitsgroep verwijderen", + "name": "Naam", + "name-required": "Naam is verplicht.", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens", + "description": "Omschrijving: __________", + "add": "Entiteitsgroep toevoegen", + "open-entity-group": "Entiteitsgroep openen", + "add-entity-group-text": "Nieuwe entiteitsgroep toevoegen", + "no-entity-groups-text": "Geen entiteitsgroepen gevonden", + "entity-group-details": "Details van entiteitsgroep", + "selected-entity-groups": "{ count, plural, =1 {1 entity group} andere {# entity groups} } geselecteerd", + "delete-entity-groups": "Entiteitsgroepen verwijderen", + "delete-entity-group-title": "Weet u zeker dat u de entiteitsgroep '{{entityGroupName}}' wilt verwijderen?", + "delete-entity-group-text": "Let op, na de bevestiging worden de entiteitsgroep en alle gerelateerde gegevens onherstelbaar.", + "delete-entity-groups-title": "Weet u zeker dat u { count, plural, =1 {1 entity group} andere {# entity groups} } wilt verwijderen?", + "delete-entity-groups-action-title": "Verwijder { count, plural, =1 {1 entity group} andere {# entity groups} }", + "delete-entity-groups-text": "Opgelet, na de bevestiging worden alle geselecteerde entiteitsgroepen verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", + "device-groups": "Asset groepen", + "shared-device-groups": "Groepen met gedeelde devices", + "asset-groups": "Asset groepen", + "shared-asset-groups": "Gedeelde activagroepen", + "customer-groups": "Klantengroepen", + "shared-customer-groups": "Gedeelde klantgroepen", + "device-group": "Apparaatgroep", + "asset-group": "Activagroep", + "user-group": "Gebruikersgroep", + "user-groups": "Gebruikersgroepen", + "customer-group": "Klantengroep", + "entity-view-groups": "Groepen voor entiteitsweergaven", + "shared-entity-view-groups": "Groepen met gedeelde entiteitsweergave", + "entity-view-group": "Groep voor entiteitsweergave", + "edge-groups": "Randgroepen", + "shared-edge-groups": "Gedeelde randgroepen", + "edge-group": "Edge groep", + "dashboard-groups": "Dashboard groepen", + "shared-dashboard-groups": "Gedeelde dashboardgroepen", + "dashboard-group": "Dashboard groep", + "fetch-more": "Haal meer op", + "column-type": { + "column-type": "Type kolom", + "client-attribute": "Cliënt attribuut", + "shared-attribute": "Gedeeld attribuut", + "server-attribute": "Server-attribuut", + "timeseries": "Timeseries", + "entity-field": "Entiteit veld" + }, + "column-type-required": "Kolomtype is vereist.", + "entity-field": { + "created-time": "Gecreëerde tijd", + "name": "Naam", + "type": "Type", + "device_profile": "Device profiel", + "asset_profile": "Asset profiel", + "assigned_customer": "Toegewezen klant", + "authority": "Autoriteit", + "first_name": "Voornaam", + "last_name": "Achternaam", + "email": "E-mail", + "title": "Titel", + "country": "Land", + "state": "Staat", + "city": "Stad", + "address": "Adres", + "address2": "Adres 2", + "zip": "Ritssluiting", + "phone": "Telefoon", + "label": "Etiket" + }, + "sort-order": { + "asc": "Oplopend", + "desc": "Aflopende", + "none": "Geen" + }, + "details-mode": { + "on-row-click": "Klik op rij", + "on-action-button-click": "Klik op de knop Details", + "disabled": "Invalide" + }, + "change-owner": "Eigenaar wijzigen", + "select-target-owner": "Selecteer doeleigenaar", + "no-owners-matching": "Er zijn geen eigenaren gevonden die overeenkomen met '{{owner}}'.", + "target-owner-required": "Doeleigenaar is vereist.", + "confirm-change-owner-title": "Weet u zeker dat u van eigenaar wilt veranderen voor { count, plural, =1 {1 selected entity} andere {# selected entities} }?", + "confirm-change-owner-text": "Opgelet, na de bevestiging worden alle geselecteerde entiteiten verwijderd van de huidige eigenaar en worden ze geplaatst in de groep 'Alle' van de doeleigenaar.", + "add-to-group": "Toevoegen aan groep", + "move-to-group": "Verplaatsen naar groep", + "select-entity-group": "Entiteitsgroep selecteren", + "no-entity-groups-matching": "Er zijn geen entiteitsgroepen gevonden die overeenkomen met '{{entityGroup}}'.", + "create-new-entity-group": "Maak een nieuwe aan!", + "target-entity-group-required": "Doelgroep is vereist.", + "select-user-group": "Selecteer gebruikersgroep", + "no-user-groups-matching": "Er zijn geen gebruikersgroepen gevonden die overeenkomen met '{{entityGroup}}'.", + "target-user-group-required": "Doelgroep is vereist.", + "remove-from-group": "Verwijderen uit groep", + "group-table-title": "Titel van groepstabel", + "enable-search": "Zoeken naar entiteiten inschakelen", + "enable-add": "Entiteiten toevoegen inschakelen", + "enable-delete": "Entiteiten verwijderen inschakelen", + "enable-selection": "Selectie van entiteiten inschakelen", + "enable-group-transfer": "Acties voor groepsoverdracht inschakelen", + "display-pagination": "Paginering weergeven", + "default-page-size": "Standaard paginaformaat", + "enable-assignment-actions": "Toewijzingsacties inschakelen", + "enable-credentials-management": "Beheer van referenties inschakelen", + "enable-login-as-user": "Inloggen als gebruikersactie inschakelen", + "enable-users-management": "Gebruikersbeheer inschakelen", + "enable-customers-management": "Klantenbeheer inschakelen", + "enable-assets-management": "Asset beheer inschakelen", + "enable-devices-management": "Device beheer inschakelen", + "enable-entity-views-management": "Beheer van entiteitsweergaven inschakelen", + "enable-edges-management": "Edge beheer inschakelen", + "enable-dashboards-management": "Dashboardbeheer inschakelen", + "enable-scheduler-events-management": "Beheer van scheduler events inschakelen", + "open-details-on": "Entiteitsdetails openen op", + "select-existing": "Bestaande entiteitsgroep selecteren", + "create-new": "Nieuwe entiteitsgroep maken", + "new-entity-group-name": "Naam van nieuwe entiteitsgroep", + "entity-group-list": "Lijst met entiteitsgroepen", + "entity-group-list-empty": "Geen entiteitsgroepen geselecteerd.", + "name-starts-with": "De naam van de entiteitsgroep begint met", + "entity-group-name-filter-required": "Filter voor de naam van de entiteitsgroep is vereist.", + "roles": "Rollen", + "permissions": "Machtigingen", + "public": "Publiek", + "entity-group-public": "Entiteitsgroep is openbaar", + "make-public": "Entiteitsgroep openbaar maken", + "make-private": "Entiteitsgroep privé maken", + "make-public-entity-group-title": "Weet u zeker dat u de entiteitsgroep '{{entityGroupName}}' openbaar wilt maken?", + "make-public-entity-group-text": "Na de bevestiging worden de entiteitengroep en al haar entiteiten openbaar gemaakt en toegankelijk gemaakt voor anderen.", + "make-private-entity-group-title": "Weet u zeker dat u de entiteitsgroep '{{entityGroupName}}' privé wilt maken?", + "make-private-entity-group-text": "Na de bevestiging worden de entiteitengroep en al zijn entiteiten privé gemaakt en zijn ze niet toegankelijk voor anderen.", + "share": "Entiteitsgroep delen", + "copyId": "Entiteitsgroep-id kopiëren", + "idCopiedMessage": "Entiteitsgroep-id is gekopieerd naar klembord", + "entity-group-name": "Naam van entiteitsgroep", + "all-users": "Alle gebruikers", + "owner": "Eigenaar", + "owner-required": "Eigenaar is verplicht.", + "manage-owner-and-groups": "Eigenaar en groepen beheren", + "owner-and-groups": "Eigenaar en groepen" + }, + "entity-field": { + "created-time": "Gecreëerde tijd", + "name": "Naam", + "type": "Type", + "first-name": "Voornaam", + "last-name": "Achternaam", + "email": "E-mail", + "title": "Titel", + "country": "Land", + "state": "Staat", + "city": "Stad", + "address": "Adres", + "address2": "Adres 2", + "zip": "Ritssluiting", + "phone": "Telefoon", + "label": "Etiket", + "configuration": "Configuratie", + "schedule": "Rooster", + "originatorId": "Initiator-ID", + "originatorType": "Type opsteller" + }, + "entity-view": { + "all": "Alle", + "all-entity-views": "Alle entiteitsweergaven", + "groups": "Groepen", + "shared": "Gedeeld", + "entity-view": "Entiteit Weergave", + "entity-view-required": "Entiteitsweergave is vereist.", + "entity-views": "Entiteitsweergaven", + "management": "Beheer van entiteitsweergaven", + "view-entity-views": "Entiteitsweergaven weergeven", + "entity-view-alias": "Alias voor entiteitsweergave", + "aliases": "Aliassen voor entiteitsweergave", + "no-alias-matching": "'{{alias}}' niet gevonden.", + "no-aliases-found": "Geen aliassen gevonden.", + "no-key-matching": "'{{key}}' niet gevonden.", + "no-keys-found": "Geen sleutels gevonden.", + "create-new-alias": "Maak een nieuwe aan!", + "create-new-key": "Maak een nieuwe aan!", + "duplicate-alias-error": "Dubbele alias gevonden '{{alias}}'.
Aliassen voor entiteitsweergave moeten uniek zijn binnen het dashboard.", + "configure-alias": "Configureer de alias '{{alias}}'", + "no-entity-views-matching": "Er zijn geen entiteitsweergaven gevonden die overeenkomen met '{{entity}}'.", + "public": "Publiek", + "alias": "Alias", + "alias-required": "Alias voor entiteitsweergave is vereist.", + "remove-alias": "Alias voor entiteitsweergave verwijderen", + "add-alias": "Alias voor entiteitsweergave toevoegen", + "name-starts-with": "Naamexpressie entiteitsweergave", + "help-text": "Gebruik '%' naar behoefte: '%entity-view_name_contains%', '%entity-view_name_ends', 'entity-view_starts_with'.", + "entity-view-list": "Lijst met entiteitsweergaven", + "use-entity-view-name-filter": "Filter gebruiken", + "entity-view-list-empty": "Geen entiteitsweergaven geselecteerd.", + "entity-view-name-filter-required": "Het naamfilter voor de entiteitsweergave is vereist.", + "entity-view-name-filter-no-entity-view-matched": "Er zijn geen entiteitsweergaven gevonden die beginnen met '{{entityView}}'.", + "add": "Entiteitsweergave toevoegen", + "entity-view-public": "Entiteitsweergave is openbaar", + "assign-to-customer": "Toewijzen aan klant", + "assign-entity-view-to-customer": "Entiteitsweergave(n) toewijzen aan klant", + "assign-entity-view-to-customer-text": "Selecteer de entiteitsweergaven die u aan de klant wilt toewijzen", + "no-entity-views-text": "Geen entiteitsweergaven gevonden", + "assign-to-customer-text": "Selecteer de klant om de entiteitsweergave(n) toe te wijzen", + "entity-view-details": "Details van entiteitsweergave", + "add-entity-view-text": "Nieuwe entiteitsweergave toevoegen", + "delete": "Entiteitsweergave verwijderen", + "assign-entity-views": "Entiteitsweergaven toewijzen", + "assign-entity-views-text": "Wijs { count, plural, =1 {1 entity view} andere {# entity views} } toe aan de klant", + "delete-entity-views": "Entiteitsweergaven verwijderen", + "make-public": "Entiteitsweergave openbaar maken", + "make-private": "Entiteitsweergave privé maken", + "unassign-from-customer": "Toewijzing van klant ongedaan maken", + "unassign-entity-views": "Toewijzing van entiteitsweergaven intrekken", + "unassign-entity-views-action-title": "Toewijzing { count, plural, =1 {1 entity view} andere {# entity views} } van klant ongedaan maken", + "assign-new-entity-view": "Nieuwe entiteitsweergave toewijzen", + "delete-entity-view-title": "Weet u zeker dat u de entiteitsweergave '{{entityViewName}}' wilt verwijderen?", + "delete-entity-view-text": "Opgelet, na de bevestiging worden de entiteitsweergave en alle gerelateerde gegevens onherstelbaar.", + "delete-entity-views-title": "Weet u zeker dat u { count, plural, =1 {1 entity view} andere {# entity views} } wilt verwijderen?", + "delete-entity-views-action-title": "Verwijder { count, plural, =1 {1 entity view} andere {# entity views} }", + "delete-entity-views-text": "Opgelet, na de bevestiging worden alle geselecteerde entiteitsweergaven verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", + "make-public-entity-view-title": "Weet u zeker dat u de entiteitsweergave '{{entityViewName}}' openbaar wilt maken?", + "make-public-entity-view-text": "Na de bevestiging worden de entiteitsweergave en al haar gegevens openbaar en toegankelijk gemaakt voor anderen.", + "make-private-entity-view-title": "Weet u zeker dat u de entiteitsweergave '{{entityViewName}}' privé wilt maken?", + "make-private-entity-view-text": "Na de bevestiging worden de entiteitsweergave en al zijn gegevens privé gemaakt en zijn ze niet toegankelijk voor anderen.", + "unassign-entity-view-title": "Weet u zeker dat u de toewijzing van de entiteitsweergave '{{entityViewName}}' wilt opheffen?", + "unassign-entity-view-text": "Na de bevestiging wordt de toewijzing van de entiteitsweergave ongedaan gemaakt en is deze niet toegankelijk voor de klant.", + "unassign-entity-view": "Toewijzing van entiteitsweergave ongedaan maken", + "unassign-entity-views-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 entity view} andere {# entity views} } wilt opheffen?", + "unassign-entity-views-text": "Na de bevestiging worden alle geselecteerde entiteitsweergaven ongedaan gemaakt en zijn ze niet toegankelijk voor de klant.", + "entity-view-type": "Type entiteitsweergave", + "entity-view-type-required": "Het type Entiteitsweergave is vereist.", + "select-entity-view-type": "Weergavetype entiteit selecteren", + "enter-entity-view-type": "Entiteitsweergavetype invoeren", + "any-entity-view": "Elke entiteitsweergave", + "no-entity-view-types-matching": "Er zijn geen entiteitsweergavetypen gevonden die overeenkomen met '{{entitySubtype}}'.", + "entity-view-type-list-empty": "Er zijn geen types entiteitsweergaven geselecteerd.", + "entity-view-types": "Typen entiteitsweergaven", + "created-time": "Gecreëerde tijd", + "name": "Naam", + "name-required": "Naam is verplicht.", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens", + "type-max-length": "Het type entiteitsweergave moet kleiner zijn dan 256 tekens", + "description": "Omschrijving: __________", + "events": "Events", + "details": "Details", + "copyId": "Entiteitsweergave-id kopiëren", + "idCopiedMessage": "Entiteitsweergave-id is gekopieerd naar klembord", + "assignedToCustomer": "Toegewezen aan klant", + "unable-entity-view-device-alias-title": "Kan alias voor entiteitsweergave niet verwijderen", + "unable-entity-view-device-alias-text": "Entiteitsweergave device alias '{{entityViewAlias}}' kan niet worden verwijderd omdat deze wordt gebruikt door de volgende widget(s):
{{widgetsList}}", + "select-entity-view": "Entiteitsweergave selecteren", + "start-date": "Begindatum", + "start-ts": "Begintijd", + "end-date": "Einddatum", + "end-ts": "Eindtijd", + "date-limits": "Datumlimieten", + "client-attributes": "Attributen van de klant", + "shared-attributes": "Gedeelde attributen", + "server-attributes": "Server-attributen", + "timeseries": "Timeseries", + "client-attributes-placeholder": "Attributen van de klant", + "shared-attributes-placeholder": "Gedeelde attributen", + "server-attributes-placeholder": "Server-attributen", + "timeseries-placeholder": "Timeseries", + "target-entity": "Doelentiteit", + "attributes-propagation": "Doorgifte van attributen", + "attributes-propagation-hint": "Entiteitsweergave kopieert automatisch gespecificeerde attributen van doelentiteit telkens wanneer u deze entiteitsweergave opslaat of bijwerkt. Om prestatieredenen worden de attributen van de doelentiteit niet doorgegeven aan de entiteitsweergave bij elke kenmerkwijziging. U kunt automatische doorgifte inschakelen door het rulenode 'kopiëren naar weergave' in uw rule chain te configureren en berichten 'Attributen posten' en 'Attributen bijgewerkt' te koppelen aan het nieuwe rule node.", + "timeseries-data": "Tijdreeksgegevens", + "timeseries-data-hint": "Configureer timeseries sleutels van de doelentiteit die toegankelijk zijn voor de entiteitsweergave. Deze timeseries gegevens zijn niet editeerbaar.", + "select-group-to-add": "Selecteer de doelgroep om geselecteerde entiteitsweergaven toe te voegen", + "select-group-to-move": "Selecteer de doelgroep om geselecteerde entiteitsweergaven te verplaatsen", + "remove-entity-views-from-group": "Weet je zeker dat je { count, plural, =1 {1 entity view} andere {# entity views} } uit groep '{{entityGroup}}' wilt verwijderen?", + "group": "Groep entiteitsweergaven", + "list-of-groups": "{ count, plural, =1 {One entity view group} andere {List of # entity view groups} }", + "group-name-starts-with": "Entiteitsweergavegroepen waarvan de naam begint met '{{prefix}}'", + "search": "Entiteitsweergaven zoeken", + "selected-entity-views": "{ count, plural, =1 {1 entity view} andere {# entity views} } geselecteerd", + "assign-entity-view-to-edge": "Entiteitsweergave(n) toewijzen aan edge", + "assign-entity-view-to-edge-text": "Selecteer de entiteitsweergaven die u aan de edge wilt toewijzen", + "unassign-entity-view-from-edge-title": "Weet u zeker dat u de toewijzing van de entiteitsweergave '{{entityViewName}}' wilt opheffen?", + "unassign-entity-view-from-edge-text": "Na de bevestiging wordt de toewijzing van de entiteitsweergave ongedaan gemaakt en is deze niet toegankelijk via de edge.", + "unassign-entity-views-from-edge-action-title": "Toewijzing { count, plural, =1 {1 entity view} andere {# entity views} } van edge ongedaan maken", + "unassign-entity-view-from-edge": "Toewijzing van entiteitsweergave ongedaan maken", + "unassign-entity-views-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 entity view} andere {# entity views} } wilt opheffen?", + "unassign-entity-views-from-edge-text": "Na de bevestiging worden alle geselecteerde entiteitsweergaven ongedaan gemaakt en zijn ze niet toegankelijk via de edge." + }, + "event": { + "events": "Events", + "event-type": "Type event", + "events-filter": "Events filter", + "clean-events": "Events wissen", + "type-error": "Fout", + "type-lc-event": "Levens cyclus event", + "type-stats": "Statistiek", + "type-debug-converter": "Debuggen", + "type-debug-integration": "Debuggen", + "type-debug-rule-node": "Debuggen", + "type-debug-rule-chain": "Debuggen", + "no-events-prompt": "Geen events gevonden", + "error": "Fout", + "alarm": "Alarm", + "event-time": "Tijd voor de event", + "server": "Server", + "body": "Body", + "method": "Methode", + "type": "Type", + "in": "In", + "out": "Buiten", + "metadata": "Metagegevens", + "message": "Bericht", + "entity": "Entiteit", + "message-id": "Bericht-ID", + "copy-message-id": "Kopieer bericht-ID", + "message-type": "Type bericht", + "data-type": "Soort gegevens", + "relation-type": "Relatie Type", + "data": "Data", + "event": "Event", + "status": "Status", + "success": "Succes", + "failed": "Mislukt", + "messages-processed": "Verwerkte berichten", + "max-messages-processed": "Maximum aantal verwerkte berichten", + "min-messages-processed": "Minimaal aantal verwerkte berichten", + "errors-occurred": "Er zijn fouten opgetreden", + "max-errors-occurred": "Maximale fouten opgetreden", + "min-errors-occurred": "Er zijn minimale fouten opgetreden", + "min-value": "Minimale waarde is 0.", + "all-events": "Alle", + "has-error": "Heeft fout", + "entity-id": "Entiteit-ID", + "copy-entity-id": "Entiteits-ID kopiëren", + "entity-type": "Type entiteit", + "clear-filter": "Filter wissen", + "clear-request-title": "Wis alle events", + "clear-request-text": "Weet je zeker dat je alle events wilt wissen?", + "started": "Begon", + "updated": "Bijgewerkt", + "stopped": "Gestopt" + }, + "extension": { + "extensions": "Extensies", + "selected-extensions": "{ count, plural, =1 {1 extension} andere {# extensions} } geselecteerd", + "type": "Type", + "key": "Sleutel", + "value": "Waarde", + "id": "Legitimatiebewijs", + "extension-id": "Extensie-id", + "extension-type": "Type extensie", + "transformer-json": "JSON *", + "unique-id-required": "De huidige extensie-id bestaat al.", + "delete": "Extensie verwijderen", + "add": "Extensie toevoegen", + "edit": "Extensie bewerken", + "view": "Bekijk extensie", + "delete-extension-title": "Weet je zeker dat je de extensie '{{extensionId}}' wilt verwijderen?", + "delete-extension-text": "Opgelet, na de bevestiging worden de extensie en alle gerelateerde gegevens onherstelbaar.", + "delete-extensions-title": "Weet u zeker dat u { count, plural, =1 {1 extension} andere {# extensions} } wilt verwijderen?", + "delete-extensions-text": "Opgelet, na de bevestiging worden alle geselecteerde extensies verwijderd.", + "converters": "Converters", + "converter-id": "Converter-id", + "configuration": "Configuratie", + "converter-configurations": "Converter configuraties", + "token": "Beveiligingstoken", + "add-converter": "Converter toevoegen", + "add-config": "Converterconfiguratie toevoegen", + "device-name-expression": "Expressie van device naam", + "device-type-expression": "Expressie van device type", + "custom": "Gewoonte", + "to-double": "Verdubbelen", + "transformer": "Transformator", + "json-required": "Transformator json is vereist.", + "json-parse": "Kan transformator json niet parseren.", + "attributes": "Attributen", + "add-attribute": "Attribuut toevoegen", + "add-map": "Toewijzingselement toevoegen", + "timeseries": "Timeseries", + "add-timeseries": "Timeseries toevoegen", + "field-required": "Veld is verplicht", + "brokers": "Brokers", + "add-broker": "Broker toevoegen", + "host": "Server host", + "port": "Haven", + "port-range": "De poort moet tussen 1 en 65535 liggen.", + "ssl": "Ssl", + "credentials": "Inloggegevens", + "username": "Gebruikersnaam", + "password": "Wachtwoord", + "retry-interval": "Interval voor opnieuw proberen in milliseconden", + "sas": "Handtekening voor gedeelde toegang", + "anonymous": "Anoniem", + "basic": "Basis", + "pem": "PEM", + "ca-cert": "CA-certificaatbestand *", + "private-key": "Bestand met privésleutel *", + "cert": "Certificaat bestand *", + "no-file": "Geen bestand geselecteerd.", + "drop-file": "Zet een bestand neer of klik om een bestand te selecteren om te uploaden.", + "mapping": "Kartering", + "topic-filter": "Onderwerp filter", + "converter-type": "Type converter", + "converter-json": "Json", + "json-name-expression": "JSON-expressie voor device naam", + "topic-name-expression": "Topic expressie device naam", + "json-type-expression": "json-expressie van device type", + "topic-type-expression": "Topic expressie van device type", + "attribute-key-expression": "Expressie van kenmerksleutel", + "attr-json-key-expression": "Json-expressie van kenmerksleutel", + "attr-topic-key-expression": "Topic expressie van kenmerk", + "request-id-expression": "ID-expressie aanvragen", + "request-id-json-expression": "Aanvraag-id json-expressie", + "request-id-topic-expression": "Topic expressie voor aanvraag-id", + "response-topic-expression": "Expressie van topic response", + "value-expression": "Waarde expressie", + "topic": "Onderwerp", + "timeout": "Time-out in milliseconden", + "converter-json-required": "Converter json is vereist.", + "converter-json-parse": "Kan converter json niet parseren.", + "filter-expression": "Expressie filteren", + "connect-requests": "Verbind aanvragen", + "add-connect-request": "Verbindingsverzoek toevoegen", + "disconnect-requests": "Verzoeken om de verbinding te verbreken", + "add-disconnect-request": "Verzoek tot verbroken verbinding toevoegen", + "attribute-requests": "Attribuut aanvragen", + "add-attribute-request": "Attribuutaanvraag toevoegen", + "attribute-updates": "Updates van attributen", + "add-attribute-update": "Attribuutupdate toevoegen", + "server-side-rpc": "RPC aan de serverzijde", + "add-server-side-rpc-request": "RPC-verzoek aan serverzijde toevoegen", + "device-name-filter": "Filter voor device naam", + "attribute-filter": "Attribuut filter", + "method-filter": "Methode filter", + "request-topic-expression": "Topic expressie aanvragen", + "response-timeout": "Time-out voor respons in milliseconden", + "topic-expression": "Onderwerp expressie", + "client-scope": "Toepassingsgebied van de klant", + "add-device": "Device toevoegen", + "opc-server": "Servers", + "opc-add-server": "Server toevoegen", + "opc-add-server-prompt": "Gelieve server toe te voegen", + "opc-application-name": "Naam van de toepassing", + "opc-application-uri": "Applicatie uri", + "opc-scan-period-in-seconds": "Scanperiode in seconden", + "opc-security": "Veiligheid", + "opc-identity": "Identiteit", + "opc-keystore": "Sleutel opslag", + "opc-type": "Type", + "opc-keystore-type": "Type", + "opc-keystore-location": "Plaats*", + "opc-keystore-password": "Wachtwoord", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Sleutel wachtwoord", + "opc-device-node-pattern": "Knooppuntpatroon van het device", + "opc-device-name-pattern": "Patroon van device naam", + "modbus-server": "Servers", + "modbus-add-server": "Server toevoegen", + "modbus-add-server-prompt": "Gelieve server toe te voegen", + "modbus-transport": "Vervoer", + "modbus-tcp-reconnect": "Automatisch opnieuw verbinding maken", + "modbus-rtu-over-tcp": "RTU via TCP", + "modbus-port-name": "Naam seriële poort", + "modbus-encoding": "Codering", + "modbus-parity": "Pariteit", + "modbus-baudrate": "Baudrate", + "modbus-databits": "Data bits gegevens", + "modbus-stopbits": "Data bits stop", + "modbus-databits-range": "Data bits moeten tussen 7 en 8 liggen.", + "modbus-stopbits-range": "Stop bits moeten in een bereik van 1 tot 2 liggen.", + "modbus-unit-id": "Eenheid-ID", + "modbus-unit-id-range": "De eenheids-ID moet tussen 1 en 247 liggen.", + "modbus-device-name": "Naam van het device", + "modbus-poll-period": "Enquête periode (ms)", + "modbus-attributes-poll-period": "Attributen enquêteperiode (ms)", + "modbus-timeseries-poll-period": "Tijdreeks-enquêteperiode (ms)", + "modbus-poll-period-range": "De enquêteperiode moet een positieve waarde hebben.", + "modbus-tag": "Label", + "modbus-function": "Functie", + "modbus-register-address": "Adres registreren", + "modbus-register-address-range": "Het registratieadres moet tussen 0 en 65535 liggen.", + "modbus-register-bit-index": "De index van het bit", + "modbus-register-bit-index-range": "De bitindex moet tussen 0 en 15 liggen.", + "modbus-register-count": "Aantal registreren", + "modbus-register-count-range": "Het aantal registers moet een positieve waarde zijn.", + "modbus-byte-order": "Byte volgorde", + "sync": { + "status": "Status", + "sync": "Sync", + "not-sync": "Niet synchroniseren", + "last-sync-time": "Laatste synchronisatietijd", + "not-available": "Niet beschikbaar" + }, + "export-extensions-configuration": "Configuratie van extensies exporteren", + "import-extensions-configuration": "Configuratie van extensies importeren", + "import-extensions": "Extensies importeren", + "import-extension": "Extensie importeren", + "export-extension": "Export extensie", + "file": "Extensies bestand", + "invalid-file-error": "Ongeldig extensiebestand", + "text": "SMS", + "json": "JSON", + "binary": "BINAIR", + "hex": "HEX" + }, + "feature": { + "advanced-features": "Geavanceerde functies" + }, + "filter": { + "add": "Filter toevoegen", + "edit": "Filter bewerken", + "name": "Naam van filter", + "name-required": "Filternaam is vereist.", + "duplicate-filter": "Filter met dezelfde naam bestaat al.", + "filters": "Filters", + "unable-delete-filter-title": "Filter kan niet worden verwijderd", + "unable-delete-filter-text": "Filter '{{filter}}' kan niet worden verwijderd omdat het wordt gebruikt door de volgende widget(s):
{{widgetsList}}", + "duplicate-filter-error": "Duplicaatfilter gevonden '{{filter}}'.
Filters moeten uniek zijn binnen het dashboard.", + "missing-key-filters-error": "Sleutelfilters ontbreken voor filter '{{filter}}'.", + "filter": "Filter", + "editable": "Bewerkbaar", + "no-filters-found": "Geen filters gevonden.", + "no-filter-text": "Geen filter gespecificeerd", + "add-filter-prompt": "Filter toevoegen", + "no-filter-matching": "'{{filter}}' niet gevonden.", + "create-new-filter": "Maak een nieuwe aan!", + "filter-required": "Filter is vereist.", + "operation": { + "operation": "Operatie", + "equal": "gelijk", + "not-equal": "Niet gelijk", + "starts-with": "begint met", + "ends-with": "eindigt met", + "contains": "Bevat", + "not-contains": "Bevat niet", + "greater": "groter dan", + "less": "minder dan", + "greater-or-equal": "groter of gelijk", + "less-or-equal": "minder of gelijk aan", + "and": "en", + "or": "of", + "in": "in", + "not-in": "Niet in" + }, + "ignore-case": "Hoofdlettergebruik negeren", + "value": "Waarde", + "remove-filter": "Filter verwijderen", + "duplicate-filter-action": "Filter dupliceren", + "preview": "Voorbeeld van filter", + "no-filters": "Geen filters geconfigureerd", + "add-filter": "Filter toevoegen", + "add-complex-filter": "Complex filter toevoegen", + "add-complex": "Complex toevoegen", + "complex-filter": "Complex filter", + "edit-complex-filter": "Complex filter bewerken", + "edit-filter-user-params": "Gebruikersparameters voor filter predicate bewerken", + "filter-user-params": "Gebruikersparameters voor predicaten filteren", + "user-parameters": "Parameters van de gebruiker", + "display-label": "Label om weer te geven", + "autogenerated-label": "Automatisch label genereren", + "order-priority": "Prioriteit voor veldorders", + "key-filter": "Toets filter", + "key-filters": "Belangrijkste filters", + "key-name": "Naam van de sleutel", + "key-name-required": "De naam van de sleutel is vereist.", + "key-type": { + "key-type": "Type sleutel", + "attribute": "Attribuut", + "timeseries": "Timeseries", + "entity-field": "Entiteit veld", + "constant": "Constant" + }, + "value-type": { + "value-type": "Soort waarde", + "string": "Snaar", + "numeric": "Numeriek", + "boolean": "Booleaans", + "date-time": "Datum/tijd" + }, + "value-type-required": "Het type sleutelwaarde is vereist.", + "key-value-type-change-title": "Weet u zeker dat u het type sleutelwaarde wilt wijzigen?", + "key-value-type-change-message": "Als u het nieuwe waardetype bevestigt, worden alle ingevoerde toetsfilters verwijderd.", + "no-key-filters": "Geen toetsfilters geconfigureerd", + "add-key-filter": "Sleutelfilter toevoegen", + "remove-key-filter": "Sleutelfilter verwijderen", + "edit-key-filter": "Sleutelfilter bewerken", + "date": "Datum", + "time": "Tijd", + "current-tenant": "Huidige tenant", + "current-customer": "Huidige klant", + "current-user": "Huidige gebruiker", + "current-device": "Huidig device", + "default-value": "Standaardwaarde", + "dynamic-source-type": "Dynamisch brontype", + "dynamic-value": "Dynamische waarde", + "no-dynamic-value": "Geen dynamische waarde", + "source-attribute": "Bron attribuut", + "switch-to-dynamic-value": "Overschakelen naar dynamische waarde", + "switch-to-default-value": "Overschakelen naar standaardwaarde", + "inherit-owner": "Erven van eigenaar", + "source-attribute-not-set": "Als het bronkenmerk niet is ingesteld" + }, + "fullscreen": { + "expand": "Uitvouwen naar volledig scherm", + "exit": "Volledig scherm afsluiten", + "toggle": "Modus voor volledig scherm in- en uitschakelen", + "fullscreen": "Volledig scherm" + }, + "function": { + "function": "Functie" + }, + "gateway": { + "add-entry": "Configuratie toevoegen", + "connector-add": "Nieuwe connector toevoegen", + "connector-enabled": "Connector inschakelen", + "connector-name": "Naam van de connector", + "connector-name-required": "De naam van de connector is vereist.", + "connector-type": "Type aansluiting", + "connector-type-required": "Het type connector is vereist.", + "connectors": "Configuratie van connectoren", + "create-new-gateway": "Een nieuwe gateway maken", + "create-new-gateway-text": "Weet u zeker dat u een nieuwe gateway wilt maken met de naam: '{{gatewayName}}'?", + "delete": "Configuratie verwijderen", + "download-tip": "Configuratiebestand downloaden", + "gateway": "Gateway", + "gateway-exists": "Device met dezelfde naam bestaat al.", + "gateway-name": "Naam van de gateway", + "gateway-name-required": "De naam van de gateway is vereist.", + "gateway-saved": "Gatewayconfiguratie succesvol opgeslagen.", + "json-parse": "Ongeldige JSON.", + "json-required": "Het veld mag niet leeg zijn.", + "no-connectors": "Geen connectoren", + "no-data": "Geen configuraties", + "no-gateway-found": "Geen gateway gevonden.", + "no-gateway-matching": "'{{item}}' niet gevonden.", + "path-logs": "Pad naar logbestanden", + "path-logs-required": "Pad is vereist.", + "remote": "Configuratie op afstand", + "remote-logging-level": "Registratie niveau", + "remove-entry": "Configuratie verwijderen", + "save-tip": "Configuratiebestand opslaan", + "security-type": "Soort beveiliging", + "security-types": { + "access-token": "Toegang tot token", + "tls": "TLS (TLS)" + }, + "storage": "Opslag", + "storage-max-file-records": "Maximum aantal records in bestand", + "storage-max-files": "Maximaal aantal bestanden", + "storage-max-files-min": "Minimum aantal is 1.", + "storage-max-files-pattern": "Nummer is niet geldig.", + "storage-max-files-required": "Nummer is vereist.", + "storage-max-records": "Maximum aantal records in opslag", + "storage-max-records-min": "Minimum aantal records is 1.", + "storage-max-records-pattern": "Nummer is niet geldig.", + "storage-max-records-required": "Maximale records zijn vereist.", + "storage-pack-size": "Maximale pakketgrootte voor events", + "storage-pack-size-min": "Minimum aantal is 1.", + "storage-pack-size-pattern": "Nummer is niet geldig.", + "storage-pack-size-required": "De maximale pakketgrootte van het event is vereist.", + "storage-path": "Opslag pad", + "storage-path-required": "Opslagpad is vereist.", + "storage-type": "Type opslag", + "storage-types": { + "file-storage": "Opslag van bestanden", + "memory-storage": "Geheugen opslag" + }, + "thingsboard": "Dingen Bord", + "thingsboard-host": "ThingsBoard-gastheer", + "thingsboard-host-required": "Server host is vereist.", + "thingsboard-port": "ThingsBoard-poort", + "thingsboard-port-max": "Het maximale poortnummer is 65535.", + "thingsboard-port-min": "Het minimale poortnummer is 1.", + "thingsboard-port-pattern": "Poort is niet geldig.", + "thingsboard-port-required": "Poort is vereist.", + "tidy": "Ordelijk", + "tidy-tip": "Opgeruimde configuratie JSON", + "title-connectors-json": "Configuratie van connector {{typeName}}", + "tls-path-ca-certificate": "Pad naar CA-certificaat op gateway", + "tls-path-client-certificate": "Pad naar clientcertificaat op gateway", + "tls-path-private-key": "Pad naar privésleutel op gateway", + "toggle-fullscreen": "Volledig scherm in- en uitschakelen", + "transformer-json-config": "Configuratie JSON*", + "update-config": "Configuratie JSON toevoegen/bijwerken" + }, + "grid": { + "delete-item-title": "Weet u zeker dat u dit item wilt verwijderen?", + "delete-item-text": "Opgelet, na de bevestiging zullen dit item en alle gerelateerde gegevens onherstelbaar worden.", + "delete-items-title": "Weet u zeker dat u { count, plural, =1 {1 item} andere {# items} } wilt verwijderen?", + "delete-items-action-title": "Verwijder { count, plural, =1 {1 item} andere {# items} }", + "delete-items-text": "Opgelet, na de bevestiging worden alle geselecteerde items verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", + "add-item-text": "Nieuw item toevoegen", + "no-items-text": "Geen items gevonden", + "item-details": "Details van het artikel", + "delete-item": "Item verwijderen", + "delete-items": "Items verwijderen", + "scroll-to-top": "Scroll naar bovenzijde" + }, + "help": { + "goto-help-page": "Ga naar de helppagina", + "show-help": "Hulp tonen" + }, + "home": { + "home": "Thuis", + "profile": "Profiel", + "logout": "Logout", + "menu": "Menu", + "avatar": "Avatar", + "open-user-menu": "Gebruikersmenu openen" + }, + "file-input": { + "browse-file": "Blader door het bestand", + "browse-files": "Blader door bestanden" + }, + "image-input": { + "drop-image-or": "Een afbeelding slepen en neerzetten of", + "drop-images-or": "Sleep een afbeelding of", + "no-images": "Geen afbeeldingen geselecteerd", + "images": "beelden" + }, + "import": { + "no-file": "Geen bestand geselecteerd", + "drop-file": "Zet een JSON-bestand neer of klik om een bestand te selecteren om te uploaden.", + "drop-csv-file": "Zet een CSV-bestand neer of klik om een bestand te selecteren om te uploaden.", + "drop-json-file-or": "Een JSON-bestand slepen en neerzetten of", + "drop-file-csv": "Zet een CSV-bestand neer of klik om een bestand te selecteren om te uploaden.", + "drop-file-csv-or": "Een CSV-bestand slepen en neerzetten of", + "column-value": "Waarde", + "column-title": "Titel", + "column-example": "Voorbeeld van waardegegevens", + "column-key": "Kenmerk-/telemetriesleutel", + "credentials": "Geloofsbrief", + "csv-delimiter": "CSV-scheidingsteken", + "csv-first-line-header": "De eerste rule bevat kolomnamen", + "csv-update-data": "Attributen/telemetrie bijwerken", + "details": "Details", + "import-csv-number-columns-error": "Een bestand moet ten minste twee kolommen bevatten", + "import-csv-invalid-format-error": "Ongeldige bestandsindeling. Regel: '{{line}}'", + "column-type": { + "name": "Naam", + "type": "Type", + "label": "Etiket", + "column-type": "Type kolom", + "client-attribute": "Cliënt attribuut", + "shared-attribute": "Gedeeld attribuut", + "server-attribute": "Server-attribuut", + "timeseries": "Timeseries", + "entity-field": "Entiteit veld", + "access-token": "Toegang token", + "x509": "X.509", + "mqtt": { + "client-id": "MQTT-client-id", + "user-name": "MQTT-gebruikersnaam", + "password": "MQTT-wachtwoord" + }, + "lwm2m": { + "client-endpoint": "Naam van de LwM2M-endpoint", + "security-config-mode": "LwM2M-modus voor beveiligingsconfiguratie", + "client-identity": "Identiteit van LwM2M-client", + "client-key": "LwM2M-clientsleutel", + "client-cert": "Openbare sleutel van LwM2M-client", + "bootstrap-server-security-mode": "LwM2M bootstrap-serverbeveiligingsmodus", + "bootstrap-server-secret-key": "LwM2M bootstrap server geheime sleutel", + "bootstrap-server-public-key-id": "LwM2M opstarttrap, server openbare sleutel of id", + "lwm2m-server-security-mode": "LwM2M-server beveiligingsmodus", + "lwm2m-server-secret-key": "Geheime sleutel van de LwM2M-server", + "lwm2m-server-public-key-id": "Openbare sleutel of id van de LwM2M-server" + }, + "isgateway": "Is Gateway", + "activity-time-from-gateway-device": "Activiteitstijd vanaf gateway-device", + "description": "Omschrijving: __________", + "edge-license-key": "Licentiesleutel", + "cloud-endpoint": "Cloud-eindpunt", + "routing-key": "Edge routing sleutel", + "secret": "Edge geheim" + }, + "stepper-text": { + "select-file": "Selecteer een bestand", + "configuration": "Configuratie importeren", + "column-type": "Kolomtype selecteren", + "creat-entities": "Nieuwe entiteiten maken" + }, + "message": { + "create-entities": "{{count}} zijn met succes nieuwe entiteiten gecreëerd.", + "update-entities": "{{count}} entiteiten zijn bijgewerkt.", + "error-entities": "Er is een fout opgetreden bij het maken van {{count}} entiteiten." + } + }, + "integration": { + "integration": "Integratie", + "integrations": "Integraties", + "integrations-center": "Integratiecentrum", + "select-integration": "Selecteer integratie", + "no-integrations-matching": "Er zijn geen integraties gevonden die overeenkomen met '{{entity}}'.", + "integration-required": "Integratie is vereist", + "delete": "Integratie verwijderen", + "management": "Beheer van integraties", + "add-integration-text": "Nieuwe integratie toevoegen", + "no-integrations-text": "Geen integraties gevonden", + "selected-integrations": "{ count, plural, =1 {1 integration} andere {# integrations} } geselecteerd", + "delete-integration-title": "Weet je zeker dat je de integratie '{{integrationName}}' wilt verwijderen?", + "delete-integration-text": "Opgelet, na de bevestiging worden de integratie en alle gerelateerde gegevens onherstelbaar.", + "delete-integrations-title": "Weet u zeker dat u { count, plural, =1 {1 integration} andere {# integrations} } wilt verwijderen?", + "delete-integrations-action-title": "Verwijder { count, plural, =1 {1 integration} andere {# integrations} }", + "delete-integrations-text": "Opgelet, na de bevestiging worden alle geselecteerde integraties verwijderd en worden alle gerelateerde gegevens onherstelbaar.", + "events": "Events", + "enabled": "Integratie inschakelen", + "allow-create-devices-or-assets": "Devices of assets maken toestaan", + "all-types": "Alle integratietypes", + "add": "Integratie toevoegen", + "search": "Integraties zoeken", + "integration-details": "Details van de integratie", + "details": "Details", + "copyId": "Integratie-ID kopiëren", + "idCopiedMessage": "Integratie-id is gekopieerd naar klembord", + "debug-mode": "Foutopsporingsmodus", + "enable-security": "Beveiliging inschakelen", + "enable-security-new": "Beveiliging inschakelen voor automatische tokenupdates", + "headers-filter": "Filter voor kopteksten", + "header": "Rubriek", + "no-headers-filter": "Geen headers filter", + "downlink-url": "Downlink URL", + "downlink-url-required": "Downlink-URL is vereist", + "create-loriot-output": "Uitvoer van Loriot-toepassingen maken", + "send-downlink": "Downlink verzenden", + "server": "Server", + "server-required": "Server is vereist", + "domain": "Domein", + "app-id": "Toepassings-ID", + "app-id-required": "Toepassings-ID is vereist", + "app-token": "Token voor toegang tot toepassing", + "app-token-required": "Application Access Token is vereist", + "email": "E-mail", + "email-required": "E-mail is verplicht", + "application-uri": "Toepassings-URI", + "as-id": "AS ID", + "as-id-required": "AS-ID is vereist.", + "as-key": "AS-toets", + "as-key-required": "AS-sleutel is vereist.", + "client-id-new": "Client-ID", + "client-id-new-required": "Client-ID (login) is vereist (login).", + "client-secret": "Geheim van de klant", + "client-secret-required": "Client Secret (wachtwoord) is vereist (wachtwoord).", + "max-time-diff-in-seconds": "Maximaal tijdsverschil (seconden)", + "max-time-diff-in-seconds-required": "Maximaal tijdsverschil is vereist.", + "created-time": "Gecreëerde tijd", + "name": "Naam", + "name-required": "Naam is verplicht.", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens", + "description": "Omschrijving: __________", + "base-url": "Basis-URL", + "base-url-required": "Basis-URL is vereist", + "security-key": "Beveiligingssleutel", + "http-endpoint": "URL van HTTP-eindpunt", + "replace-no-content-to-ok": "Vervang de reactiestatus van 'Geen inhoud' naar 'OK'", + "copy-http-endpoint-url": "Kopieer de URL van het HTTP-eindpunt", + "http-endpoint-url-copied-message": "De URL van het HTTP-eindpunt is gekopieerd naar het klembord", + "host": "Server host", + "host-required": "Server host is vereist.", + "host-private": "De host moet zich in het openbare netwerk bevinden.", + "url-private": "De URL moet zich in het openbare netwerk bevinden.", + "host-type": "Type host", + "host-type-required": "Hosttype is vereist.", + "api-version": "API v3 gebruiken", + "custom-host": "Aangepaste host", + "custom-host-required": "Aangepaste host is vereist.", + "port": "Poort", + "port-required": "Poort is vereist.", + "port-range": "De poort moet tussen 1 en 65535 liggen.", + "connect-timeout": "Time-out verbinding (sec)", + "connect-timeout-required": "Er is een time-out voor de verbinding vereist.", + "connect-timeout-range": "De time-out van de verbinding moet tussen 1 en 200 liggen.", + "client-id": "Client-ID", + "client-id-required": "Client-ID is vereist.", + "client-id-range": "Client-ID moet tussen de 1 en 23 tekens lang zijn. [MQTT-3.1.3-5]", + "client-id-pattern": "Client-ID moet bestaan uit cijfers, hoofdletters en kleine letters. [MQTT-3.1.3-5]", + "client-id-hint": "Tip: Optioneel. Laat dit veld leeg voor automatisch gegenereerde client-ID. Opgelet bij het opgeven van de Client ID. De meerderheid van de MQTT-brokers staat niet toe dat meerdere verbindingen met dezelfde client-ID worden gebruikt. Om verbinding te maken met dergelijke makelaars, moet uw MQTT-klant-ID uniek zijn.", + "max-bytes-in-message": "Max. aantal bytes in bericht", + "max-bytes-in-message-range": "Het maximale aantal bytes in een bericht moet tussen 1 en 256000000 liggen.", + "device-id": "Device-ID", + "device-id-required": "Device-ID is vereist.", + "device-id-range": "Device-ID moet tussen 1 en 65535 tekens bestaan.", + "device-id-pattern": "Device-ID moet bestaan uit cijfers, hoofdletters en kleine letters. [MQTT-3.1.3-5]", + "group-id": "Groeps-ID", + "group-id-required": "Groeps-ID is vereist.", + "topics": "Onderwerpen", + "topics-required": "Onderwerp is vereist.", + "routing-keys": "Routing sleutels", + "routing-keys-required": "Routingsleutel is vereist.", + "queues": "Queues", + "queues-required": "De naam van de queue is vereist.", + "durable": "Duurzaam", + "exclusive": "Exclusief", + "autoDelete": "Automatisch verwijderen", + "exchange-name": "Naam van de uitwisseling", + "exchange-name-required": "De naam van de uitwisseling is vereist.", + "downlink-topic": "Downlink-onderwerp", + "connection-timeout": "Time-out van de verbinding, ms", + "connection-timeout-min": "Ongeldige time-outwaarde voor verbinding.", + "handshake-timeout": "Time-out voor handdruk, ms", + "handshake-timeout-min": "Ongeldige time-outwaarde voor handdruk.", + "virtual-host": "Virtuele gastheer", + "rabbit-mq-poll-interval": "Polling-interval, ms", + "rabbit-mq-poll-interval-min": "Ongeldige waarde voor de polling-interval.", + "application-server-url": "URL van applicatieserver", + "application-server-url-required": "De URL van de toepassingsserver is vereist.", + "application-server-api-token": "API-token voor toepassingsserver", + "application-server-api-token-required": "API-token van toepassingsserver is vereist.", + "bootstrap-servers": "Bootstrap-servers", + "bootstrap-servers-required": "Bootstrap-servers zijn vereist.", + "poll-interval": "Poll-interval", + "poll-interval-required": "Poll-interval is vereist.", + "poll-interval-min-value": "De waarde van het pollinterval mag niet kleiner zijn dan 1", + "auto-create-topics": "Onderwerpen automatisch maken", + "clean-session": "Vernieuwde sessie", + "enable-ssl": "SSL inschakelen", + "credentials": "Inloggegevens", + "credentials-type": "Type referenties", + "credentials-type-required": "Het type referentie is vereist.", + "username": "Gebruikersnaam", + "username-required": "Gebruikersnaam is vereist.", + "password": "Wachtwoord", + "password-required": "Wachtwoord is vereist.", + "azure-ca-cert": "CA-certificaatbestand", + "ca-cert": "CA-certificaatbestand", + "private-key": "Bestand met privésleutel", + "private-key-password": "Wachtwoord voor privésleutel", + "cert": "Certificaat bestand", + "no-file": "Geen bestand geselecteerd.", + "drop-file": "Zet een bestand neer of klik om een bestand te selecteren om te uploaden.", + "drop-file-or": "Een bestand slepen en neerzetten of", + "check-connection": "Verbinding controleren", + "check-success": "Verbinding tot stand gebracht.", + "topic-filters": "Onderwerp filters", + "remove-topic-filter": "Onderwerpfilter verwijderen", + "add-topic-filter": "Onderwerpfilter toevoegen", + "add-topic-filter-prompt": "Voeg een onderwerpfilter toe", + "topic": "Onderwerp", + "mqtt-qos": "QoS", + "mqtt-qos-at-most-once": "Hoogstens één keer", + "mqtt-qos-at-least-once": "Minstens één keer", + "mqtt-qos-exactly-once": "Precies één keer", + "downlink-topic-pattern": "Patroon voor downlinkonderwerpen", + "downlink-topic-pattern-required": "Downlink-onderwerppatroon is vereist.", + "retained-message": "Behouden", + "aws-access-key-id": "AWS-toegangssleutel-ID", + "aws-secret-access-key": "Geheime AWS-toegangssleutel", + "aws-region": "Regio", + "aws-iot-endpoint": "AWS IoT-eindpunt", + "aws-iot-endpoint-required": "AWS IoT Endpoint is vereist.", + "aws-iot-credentials": "AWS IoT-referenties", + "aws-sqs-polling-period-in-seconds": "Pollingperiode in seconden", + "aws-sqs-queue-url": "URL van SQS-queue", + "aws-sqs-queue-url-required": "SQS Queue URL is vereist", + "aws-sqs-access-key-id-required": "Toegangssleutel-ID is vereist", + "aws-sqs-secret-access-key-required": "Geheime toegangssleutel is vereist", + "application-credentials": "Aanmeldingsgegevens van de toepassing", + "api-key": "API-sleutel", + "api-key-required": "API-sleutel is vereist.", + "api-key-format": "Ongeldige API-sleutelindeling.", + "auth-token": "Authenticatie token", + "auth-token-required": "Verificatietoken is vereist.", + "region": "Regio", + "region-required": "Regio is vereist.", + "application-id": "Toepassings-ID", + "application-id-required": "Toepassings-ID is vereist.", + "access-id": "Toegangs-ID", + "access-id-required": "Toegangs-ID is vereist.", + "access-key": "Toegangssleutel", + "access-key-required": "Toegangssleutel is vereist.", + "access-key-min-length": "Toegangssleutel is te kort.", + "access-key-max-length": "Toegangssleutel is te lang.", + "connection-parameters": "Verbinding parameters", + "service-bus-namespace-name": "Naam van Service Bus-namespace", + "service-bus-namespace-name-required": "Service Bus-namespace naam is vereist.", + "connection-string": "Connectionstring", + "consumer-group": "Consumentengroep", + "connection-string-required": "Aansluitreeks vereist!", + "event-hub-name": "Naam Event Hub", + "event-hub-name-required": "Event Hub-naam is vereist.", + "event-iot-hub-name-required": "Iot Hub-naam is vereist voor downlink", + "sas-key-name": "SAS-sleutelnaam", + "sas-key-name-required": "SAS-sleutelnaam is vereist.", + "sas-key": "SAS-toets", + "sas-key-required": "SAS-sleutel is vereist.", + "iot-hub-name": "IoT Hub-naam (vereist voor downlink)", + "hostname": "Hostnaam", + "hostname-required": "Hostnaam is vereist", + "integration-clazz": "Integratie klasse", + "integration-clazz-required": "Integratie klasse is vereist", + "integration-configuration": "Integratie JSON-configuratie", + "metadata": "Metagegevens", + "type": "Type integratie", + "select-integration-type": "Selecteer integratietype", + "type-required": "Integratietype is vereist.", + "type-not-found": "Integratietype niet gevonden", + "uplink-converter": "Uplink data converter", + "uplink-converter-required": "Uplink-gegevensconverter is vereist.", + "downlink-converter": "Downlink data converter", + "type-http": "HTTP (HTTP)", + "type-http-description": "Protocol voor het overbrengen van tekst- of multimediabestanden.", + "type-ocean-connect": "Oceaan verbinden", + "type-ocean-connect-description": "Integratie van devices met verschillende mogelijkheden met het IoT-platform.", + "type-sigfox": "SigFox", + "type-sigfox-description": "Lichtgewicht protocol voor het verwerken van kleine gegevensoverdrachten.", + "type-thingpark": "ThingPark", + "type-thingpark-description": "Berichtgeoriënteerd protocol voor transportlagen.", + "type-loriot": "Loriot", + "type-loriot-description": "Gedistribueerde LoRaWAN-infrastructuur.", + "type-thingpark-enterprise": "ThingParkEnterprise", + "type-thingpark-enterprise-description": "Berichtenplatform voor het LPWA-netwerk.", + "type-tmobile-iot-cdp": "iotcreators.com (T-Mobile – IoT CDP)", + "type-tmobile-iot-cdp-description": "Mobiele IoT-connectiviteitsservice.", + "type-mqtt": "MQTT", + "type-mqtt-description": "Netwerkprotocol van machine naar machine.", + "type-aws-iot": "AWS IoT", + "type-aws-iot-description": "Beheer-, opslag- en analyseservice.", + "type-aws-sqs": "AWS SQS", + "type-aws-sqs-description": "Volledig beheerde message queuing-service.", + "type-aws-kinesis": "AWS-kinesis", + "type-aws-kinesis-description": "Service voor real-time video- en datastreams.", + "type-ibm-watson-iot": "IBM Watson IoT", + "type-ibm-watson-iot-description": "Volledig beheerde, in de cloud gehoste service.", + "type-ttn": "De Things Stack-gemeenschap", + "type-ttn-description": "Open en gedecentraliseerd LoRaWAN-netwerk.", + "type-tti": "De dingen stapelen industrieën", + "type-tti-description": "Software om private LoRaWAN-netwerken op grote schaal te exploiteren.", + "type-chirpstack": "ChirpStack", + "type-chirpstack-description": "LoRaWAN-netwerkconfiguratieserver.", + "type-azure-event-hub": "Azure Event Hub", + "type-azure-event-hub-description": "Big data-streamingplatform en service voor het opnemen van events.", + "type-azure-iot-hub": "Azure IoT Hub", + "type-azure-iot-hub-description": "In de cloud gehoste berichtenservice.", + "type-opc-ua": "OPC-UA", + "type-opc-ua-description": "Cross-platform voor gegevensuitwisseling van sensoren naar cloud-apps.", + "type-custom": "Gewoonte", + "type-coap": "CoAP", + "type-coap-description": "Netwerkprotocol van machine naar machine voor beperkte Devices.", + "type-udp": "UDP", + "type-udp-description": "Berichtgeoriënteerd protocol voor transportlagen.", + "type-tcp": "TCP", + "type-tcp-description": "Verbindingsgericht protocol.", + "type-kafka": "Kafka", + "type-kafka-description": "Streaming analytics, data-integratie streaming platform.", + "type-rabbitmq": "KonijnMQ", + "type-rabbitmq-description": "Berichtenbroker die ondersteuning biedt voor meerdere berichtenprotocollen.", + "type-pubsub": "Kroeg/Sub", + "type-pubsub-description": "Service voor asynchrone berichten tussen apps.", + "type-apache-pulsar": "Apache Pulsar", + "type-apache-pulsar-description": "Gedistribueerd pub-sub berichten- en streamingplatform.", + "type-tuya": "Tuya", + "type-tuya-description": "In de cloud gehoste berichten. platform van het device.", + "type-azure-service-bus": "Azure Service Bus", + "type-azure-service-bus-description": "Volledig beheerde berichtenmakelaar voor ondernemingen met berichtwachtrijen en onderwerpen voor publiceren en abonneren.", + "opc-ua-application-name": "Naam van de toepassing", + "opc-ua-application-uri": "Applicatie uri", + "opc-ua-scan-period-in-seconds": "Scanperiode in seconden", + "opc-ua-scan-period-in-seconds-required": "Scanperiode is vereist", + "opc-ua-timeout": "Time-out in milliseconden", + "opc-ua-timeout-required": "Time-out is vereist", + "opc-ua-security": "Veiligheid", + "opc-ua-security-required": "Beveiliging is vereist", + "opc-ua-identity": "Identiteit", + "opc-ua-identity-required": "Legitimatie is vereist", + "opc-ua-keystore": "Sleutel opslag", + "add-opc-ua-keystore-prompt": "Gelieve keystore-bestand toe te voegen", + "opc-ua-keystore-required": "Keystore is vereist", + "opc-ua-type": "Type", + "opc-ua-keystore-type": "Type", + "opc-ua-keystore-type-required": "Type is vereist", + "opc-ua-keystore-location": "Plaats*", + "opc-ua-keystore-password": "Wachtwoord", + "opc-ua-keystore-password-required": "Wachtwoord is vereist", + "opc-ua-keystore-alias": "Alias", + "opc-ua-keystore-alias-required": "Alias is vereist", + "opc-ua-keystore-key-password": "Sleutel wachtwoord", + "opc-ua-keystore-key-password-required": "Sleutelwachtwoord is vereist", + "opc-ua-mapping": "Kartering", + "add-opc-ua-mapping-prompt": "Gelieve kaarten toe te voegen", + "opc-ua-mapping-type": "Type toewijzing", + "opc-ua-mapping-type-required": "Toewijzingstype is vereist", + "opc-ua-device-node-pattern": "Patroon van apparaatknooppunten", + "opc-ua-device-node-pattern-required": "Apparaatknooppuntpatroon is vereist", + "opc-ua-namespace": "Naamruimte", + "opc-ua-add-map": "Toewijzingselement toevoegen", + "kinesis-stream-name": "Naam van de stream", + "kinesis-stream-name-required": "Streamnaam is vereist", + "kinesis-region": "Regio", + "kinesis-region-required": "Regio is vereist", + "kinesis-access-key-id": "Toegangssleutel-ID", + "kinesis-access-key-id-required": "Toegangssleutel-ID is vereist", + "kinesis-secret-access-key": "Geheime toegangssleutel", + "kinesis-secret-access-key-required": "Geheime toegangssleutel is vereist", + "kinesis-use-consumers-with-enhanced-fan-out": "Gebruik consumenten met verbeterde fan-out", + "kinesis-use-credentials-from-instance-metadata": "Referenties van de Amazon EC2 Instance Metadata Service gebruiken", + "kinesis-application-name": "Toepassingsnaam (standaard gelijk aan Streamnaam)", + "kinesis-initial-position-in-stream": "Beginpositie in stream", + "kinesis-initial-position-in-stream-required": "Beginpositie in stream vereist", + "kinesis-max-records": "Max. records", + "kinesis-max-records-required": "Max. records is vereist", + "kinesis-max-records-length-range": "De maximale recordlengte moet tussen 1 en 10000 liggen", + "kinesis-request-timeout": "Time-out aanvragen in enkele seconden", + "kinesis-request-timeout-required": "Time-out voor aanvraag is vereist", + "other-properties": "Overige eigenschappen", + "subscription-tags": "Tags voor abonnementen", + "remove-subscription-tag": "Abonnementstag verwijderen", + "add-subscription-tag": "Abonnementstag toevoegen", + "add-subscription-tag-prompt": "Voeg een abonnementstag toe", + "key": "Sleutel", + "path": "Pad", + "required": "Vereist", + "integration-key": "Integratiesleutel", + "copy-integration-key": "Integratiesleutel kopiëren", + "integration-key-copied-message": "Integratiesleutel is gekopieerd naar klembord", + "integration-secret": "Integratie geheim", + "copy-integration-secret": "Integratiegeheim kopiëren", + "integration-secret-copied-message": "Integratiegeheim is gekopieerd naar klembord", + "execute-remotely": "Op afstand uitvoeren", + "remote": "Afgelegen", + "handler-configuration": "Configuratie van de handler", + "handler-configuration-type": "Handler Type", + "so-broadcast": "Uitzending inschakelen - integratie accepteert uitzendadrespakketten", + "so-keepalive-option": "Verzenden van keep-alive-berichten op verbindingsgeoriënteerde stopcontacten mogelijk maken", + "so-reuse-addr": "Bind het proces aan een poort", + "tcp-no-delay": "Dwingt een socket om de gegevens te verzenden zonder buffering (schakel het bufferalgoritme van Nagle uit)", + "fail-fast": "Gegooide uitzondering zodra de decoder merkt dat de lengte van het frame de maximale grootte zal overschrijden", + "strip-delimiter": "Scheidingsteken voor stroken", + "length-field-offset": "Lengte veld offset", + "length-field-offset-required": "Lengte veldverschuiving is vereist.", + "length-field-offset-range": "De lengteveldverschuiving moet tussen 0 en 8 liggen.", + "length-field-length": "Lengte Veldlengte", + "length-field-length-required": "Lengte Veldlengte is vereist.", + "length-field-length-range": "Lengte Veldlengte moet tussen 0 en 8 liggen.", + "length-adjustment": "Lengteaanpassing (de compensatiewaarde die moet worden toegevoegd aan de waarde van het lengteveld)", + "length-adjustment-required": "Lengteaanpassing is vereist.", + "length-adjustment-range": "De lengte-aanpassing moet tussen 0 en 8 liggen.", + "byte-order": "Byte Volgorde van het lengteveld", + "initial-bytes-to-strip": "Aantal eerste bytes dat uit het gedecodeerde frame moet worden verwijderd", + "initial-bytes-to-strip-required": "Het aantal eerste bytes dat uit het gedecodeerde frame moet worden verwijderd, is vereist.", + "initial-bytes-to-strip-range": "Het aantal eerste bytes dat uit het gedecodeerde frame moet worden verwijderd, moet tussen 0 en 8 liggen.", + "so-backlog-option": "Max. aantal in behandeling zijnde verbindingen op het stopcontact", + "so-backlog-option-required": "Het maximale aantal in behandeling zijnde verbindingen op het stopcontact is vereist.", + "so-backlog-option-range": "Het maximale aantal in behandeling zijnde verbindingen op het stopcontact moet tussen 1 en 65535 liggen.", + "so-rcv-buf": "Grootte van de buffer voor inkomende socket (in KB)", + "so-rcv-buf-required": "De grootte van de buffer voor de inkomende socket (in KB) is vereist.", + "so-rcv-buf-range": "De grootte van de buffer voor inkomende socket (in KB) moet tussen 1 en 65535 liggen.", + "so-snd-buf": "Grootte van de buffer voor uitgaande socket (in KB)", + "so-snd-buf-required": "De grootte van de buffer voor uitgaande socket (in KB) is vereist.", + "so-snd-buf-range": "De grootte van de buffer voor uitgaande socket (in KB) moet tussen 1 en 65535 liggen.", + "charset-name": "Naam van de set", + "charset-name-required": "Charset-naam is vereist.", + "message-separator": "Scheidingsteken voor berichten", + "message-separator-required": "Berichtenscheidingsteken is vereist.", + "character-sequence": "Karakter Volgorde", + "character-sequence-required": "Tekenvolgorde is vereist.", + "max-frame-length": "Maximale framelengte (in bytes)", + "max-frame-length-required": "Maximale framelengte (in bytes) is vereist.", + "max-frame-length-range": "De maximale framelengte (in bytes) moet tussen 1 en 65535 liggen.", + "handler-type": "Handler Type", + "message-size": "Grootte van het bericht", + "message-size-required": "Berichtgrootte is vereist.", + "service-url": "URL van de dienst", + "service-url-required": "Service-URL is vereist.", + "subscription-name": "Naam van het abonnement", + "subscription-name-required": "De naam van het abonnement is vereist.", + "max-num-messages": "Max. aantal berichten", + "max-num-messages-required": "Het maximale aantal berichten is vereist.", + "max-num-bytes": "Max. aantal bytes", + "max-num-bytes-required": "Maximaal aantal bytes vereist.", + "timeout-in-ms": "Time-out in milliseconden", + "timeout-in-ms-required": "Time-out in milliseconden is vereist.", + "user-id": "Gebruikersnaam", + "user-id-required": "Gebruikersnaam is vereist.", + "token": "Teken", + "token-required": "Token is vereist.", + "project-id": "Project-ID", + "project-id-required": "Project-ID is vereist.", + "subscription-id": "Abonnements-ID", + "subscription-id-required": "Abonnements-ID is vereist.", + "service-account-key": "Sleutelbestand voor serviceaccount", + "service-account-key-required": "Het sleutelbestand van het serviceaccount is vereist.", + "tcp": { + "system-line-separator": "Systeem Lijnscheider", + "nul-delimiter": "Nul-scheidingsteken", + "byte-order-little-endian": "Kleine Endian", + "byte-order-big-endian": "Grote Endian" + }, + "cache-size": "Cache-grootte", + "cache-time-to-live": "Cachetijd om in minuten te leven", + "min-cache-size": "Cachegrootte kan niet lager zijn 0", + "min-cache-time-to-live": "Cache tijd om te leven kan niet lager zijn 0", + "max-cache-time-to-live": "Ongeldige tijd van cache live, kies tussen 0 en 525600", + "coap-security-mode": "Beveiligingsmodus", + "coap-security-mode-required": "CoAP-beveiligingsmodus is vereist", + "coap-security-mode-no-secure": "NIET VEILIG", + "coap-security-mode-dtls": "DTLS", + "coap-security-mode-mixed": "GEMENGD", + "coap-endpoint": "URL van CoAP-eindpunt", + "coap-endpoint-url-copied-message": "De URL van het CoAP-eindpunt is gekopieerd naar het klembord", + "copy-coap-endpoint-url": "Kopieer de URL van het CoAP-eindpunt", + "copy-coap-dtls-endpoint-url": "Kopieer de URL van het CoAP DTLS-eindpunt", + "coap-dtls-base-url": "DTLS-basis-URL", + "coap-dtls-base-url-required": "DTLS-basis-URL is vereist", + "coap-dtls-endpoint": "URL van CoAP DTLS-eindpunt", + "coap-dtls-endpoint-url-copied-message": "De URL van het CoAP DTLS-eindpunt is gekopieerd naar het klembord", + "unassign-integration-title": "Weet je zeker dat je de toewijzing van de integratie '{{integrationName}}' ongedaan wilt maken?", + "unassign-integration-from-edge-text": "Na de bevestiging wordt de toewijzing van de integratie ongedaan gemaakt en is deze niet toegankelijk voor de edge.", + "unassign-integrations-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 integration} andere {# integrations} } wilt opheffen?", + "unassign-integrations-from-edge-text": "Na de bevestiging worden alle geselecteerde integraties ongedaan gemaakt en zijn ze niet toegankelijk voor de edge.", + "unassign-integrations": "Toewijzing van integraties ongedaan maken", + "edge-placeholder-hint": "U kunt tijdelijke aanduiding ${{ATTRIBUTE_KEY}} gebruiken om het integratieveld te vervangen door de kenmerkwaarde van een specifieke Edge-entiteit. 'Edge A' heeft bijvoorbeeld het kenmerk 'baseUrl' dat gelijk is aan 'http://localhost:9999'. U kunt $ {{baseUrl}} instellen als een van de integratievelden, en het wordt vervangen door 'http://localhost:9999' tijdens de toewijzing van deze integratie aan 'Edge A'. Bovendien, als 'Edge A' attribuut 'baseUrl' wordt bijgewerkt, wordt de integratie met de nieuwe bijgewerkte waarde automatisch ingericht voor 'Edge A'.", + "status": { + "status": "Status", + "active": "Actief", + "disabled": "Invalide", + "failed": "Mislukt", + "pending": "In behandeling" + }, + "daily-activity": "Dagelijkse activiteit", + "enable-debug-mode": "Foutopsporingsmodus inschakelen", + "disable-debug-mode": "Schakel de foutopsporingsmodus uit", + "advanced-settings": "Geavanceerde instellingen", + "basic-settings": "Basisinstellingen", + "existing-uplink-converter": "Selecteer een bestaande uplink-gegevensconverter", + "new-uplink-converter": "Maak een nieuwe uplink-gegevensconverter", + "existing-downlink-converter": "Selecteer een bestaande downlink-gegevensconverter", + "new-downlink-converter": "Maak een nieuwe downlink-gegevensconverter", + "connection": "Verbinding", + "connected": "Verbonden", + "not-connected": "Niet verbonden", + "environment": "Milieu", + "environment-required": "Omgeving is vereist.", + "region-cn": "China", + "region-us": "Verenigde Staten", + "region-eu": "Europa", + "region-in": "India", + "topic-name": "Naam van het onderwerp", + "topic-name-required": "Onderwerpnaam is verplicht", + "sub-name": "Naam van het abonnement", + "sub-name-required": "De naam van het abonnement is vereist", + "downlink-connection-string": "Downlink-verbindingsreeks", + "downlink-connection-string-required": "Downlink-verbindingsreeks is vereist", + "downlink-topic-name": "Naam van downlink-onderwerp", + "downlink-topic-name-required": "De naam van het downlink-onderwerp is vereist" + }, + "item": { + "selected": "Geselecteerd" + }, + "js-func": { + "no-return-error": "Functie moet waarde teruggeven.", + "return-type-mismatch": "Functie moet waarde van het type '{{type}}' teruggeven.", + "tidy": "Ordelijk", + "mini": "Mini" + }, + "key-val": { + "key": "Sleutel", + "value": "Waarde", + "remove-entry": "Invoer verwijderen", + "add-entry": "Invoer toevoegen", + "no-data": "Geen inzendingen" + }, + "layout": { + "layout": "Indeling", + "manage": "Lay-outs beheren", + "settings": "Lay-out instellingen", + "color": "Kleur", + "main": "Voornaamste", + "right": "Rechts", + "left": "Links", + "select": "Doellay-out selecteren", + "percentage-width": "Percentage breedte (%)", + "fixed-width": "Vaste breedte (px)", + "left-width": "Linkerkolom (%)", + "right-width": "Rechterkolom (%)", + "pick-fixed-side": "Vaste zijde:", + "layout-fixed-width": "Vaste breedte (px)", + "value-min-error": "Waarde moet meer zijn dan {{min}}{{unit}}", + "value-max-error": "De waarde moet lager zijn dan {{max}}{{unit}}", + "layout-fixed-width-required": "Vaste breedte is vereist", + "right-width-percentage-required": "Het juiste percentage is vereist", + "left-width-percentage-required": "Linker percentage is vereist", + "divider": "Scheidingslijn", + "right-side": "Indeling rechterkant", + "left-side": "Lay-out aan de linkerkant" + }, + "legend": { + "direction": "Richting van de legende", + "position": "Legende positie", + "sort-legend": "Datakeys sorteren in legende", + "show-max": "Maximale waarde weergeven", + "show-min": "Minimale waarde weergeven", + "show-avg": "Gemiddelde waarde weergeven", + "show-total": "Totale waarde weergeven", + "show-latest": "Toon laatste waarde", + "settings": "Legende-instellingen", + "min": "Min", + "max": ".max", + "avg": "Avg", + "total": "totaal", + "latest": "laatst", + "comparison-time-ago": { + "previousInterval": "(vorig interval)", + "customInterval": "(aangepast interval)", + "days": "(dag geleden)", + "weeks": "(week geleden)", + "months": "(maand geleden)", + "years": "(jaar geleden)" + } + }, + "login": { + "login": "Inloggen", + "request-password-reset": "Verzoek om wachtwoordreset", + "reset-password": "Wachtwoord opnieuw instellen", + "create-password": "Wachtwoord maken", + "two-factor-authentication": "Twee-factor-authenticatie", + "passwords-mismatch-error": "Ingevoerde wachtwoorden moeten hetzelfde zijn.", + "password-again": "Wachtwoord opnieuw ingeven", + "sign-in": "Gelieve in te loggen", + "username": "Gebruikersnaam (e-mail)", + "remember-me": "Onthoud mij", + "forgot-password": "Wachtwoord vergeten?", + "password-reset": "Wachtwoord opnieuw instellen", + "expired-password-reset-message": "Uw inloggegevens zijn verlopen. Gelieve een nieuwe wachtwoord aan te maken.", + "new-password": "Nieuw wachtwoord", + "new-password-again": "Bevestig nieuw wachtwoord", + "password-link-sent-message": "Reset-link is verzonden", + "email": "E-mail", + "no-account": "Heb je nog geen account?", + "create-account": "Maak een account aan", + "login-with": "Log in met {{name}}", + "or": "of", + "error": "Fout bij inloggen", + "verify-your-identity": "Verifieer uw identiteit", + "select-way-to-verify": "Selecteer een manier om te verifiëren", + "resend-code": "Code opnieuw verzenden", + "resend-code-wait": "Code opnieuw verzenden in { time, plural, =1 {1 second} andere {# seconds} }", + "try-another-way": "Probeer een andere manier", + "totp-auth-description": "Voer de beveiligingscode van je authenticator-app in.", + "totp-auth-placeholder": "Code", + "sms-auth-description": "Er is een beveiligingscode naar je telefoon gestuurd om {{contact}}.", + "sms-auth-placeholder": "SMS-code", + "email-auth-description": "Er is een beveiligingscode verzonden naar uw e-mailadres op {{contact}}.", + "email-auth-placeholder": "E-mail code", + "backup-code-auth-description": "Voer een van uw back-upcodes in.", + "backup-code-auth-placeholder": "Back-up code" + }, + "signup": { + "firstname": "Voornaam", + "lastname": "Achternaam", + "email": "E-mail", + "signup": "Inschrijven", + "create-password": "Maak een wachtwoord aan", + "repeat-password": "Herhaal je wachtwoord", + "have-account": "Heb je al een account?", + "signin": "Aanmelden", + "no-captcha-message": "U moet bevestigen dat u geen robot bent", + "password-length-message": "Uw wachtwoord moet minimaal 6 tekens lang zijn", + "email-verification": "E-mail verificatie", + "email-verification-message": "Er is een e-mail met verificatiegegevens verzonden naar het opgegeven e-mailadres.
Volg de instructies in de e-mail om uw aanmeldingsprocedure te voltooien.
Let op: als je de e-mail al een tijdje niet hebt gezien, controleer dan je map 'spam' of probeer de e-mail opnieuw te verzenden door op de knop 'Opnieuw verzenden' te klikken.", + "account-activation-title": "Account activeren", + "account-activated": "Account succesvol geactiveerd!", + "account-activated-text": "Gefeliciteerd!
Uw account is geactiveerd.", + "resend": "Verzenden", + "inactive-user-exists-title": "Inactieve gebruiker bestaat al", + "inactive-user-exists-text": "Er is al een geregistreerde gebruiker met een niet-geverifieerd e-mailadres.
Klik op de knop 'Opnieuw verzenden' als u de verificatie-e-mail opnieuw wilt verzenden.", + "activating-account": "Account activeren...", + "activating-account-text": "Uw account wordt momenteel geactiveerd. Een ogenblik geduld...", + "accept-privacy-policy": "Accepteer het privacybeleid", + "accept": "Accepteren", + "privacy-policy": "Privacybeleid", + "accept-privacy-policy-message": "U moet ons privacybeleid accepteren", + "recaptcha-title": "reCAPTCHA", + "terms-of-use": "Gebruiksvoorwaarden", + "accept-terms-of-use-message": "U moet onze Gebruiksvoorwaarden accepteren" + }, + "notification": { + "action-button": "Actieknop", + "action-type": "Soort actie", + "active": "Actief", + "add-notification-recipients-group": "Groep voor ontvangers van meldingen toevoegen", + "add-notification-template": "Meldingssjabloon toevoegen", + "add-recipient": "Ontvanger toevoegen", + "add-recipients": "Geadresseerden toevoegen", + "add-rule": "Regel toevoegen", + "add-stage": "Fase toevoegen", + "add-template": "Sjabloon toevoegen", + "after": "Na", + "alarm-assignment-trigger-settings": "Triggerinstellingen voor alarmtoewijzing", + "alarm-comment-trigger-settings": "Instellingen voor triggers voor alarmreacties", + "alarm-trigger-settings": "Instellingen alarmtrigger", + "all": "Alle", + "api-feature-hint": "Als het veld leeg is, wordt de trigger toegepast op alle api-functies", + "api-usage-trigger-settings": "Instellingen voor API-gebruikstrigger", + "new-platform-version-trigger-settings": "Triggerinstellingen voor nieuwe platformversie", + "at-least-one-should-be-selected": "Er moet er ten minste één worden geselecteerd", + "basic-settings": "Basisinstellingen", + "button-text": "Tekst van de knop", + "button-text-required": "Knoptekst is vereist", + "button-text-max-length": "De tekst van de knop moet kleiner zijn dan of gelijk zijn aan {{ length }} tekens", + "compose": "Samenstellen", + "conversation": "Gesprek", + "conversation-required": "Gesprek is vereist", + "copy-notification-template": "Meldingssjabloon kopiëren", + "copy-rule": "Regel kopiëren", + "copy-template": "Kopieer sjabloon", + "create-new": "Nieuwe maken", + "created": "Geschapen", + "delete-notification-text": "Opgelet, na de bevestiging wordt de melding onherstelbaar.", + "delete-notification-title": "Weet je zeker dat je de melding wilt verwijderen?", + "delete-notifications-text": "Opgelet, na de bevestiging worden meldingen onherstelbaar.", + "delete-notifications-title": "Weet u zeker dat u { count, plural, =1 {1 notification} andere {# notifications} } wilt verwijderen?", + "delete-recipient-text": "Let op, na de bevestiging wordt de ontvanger onherstelbaar.", + "delete-recipient-title": "Weet u zeker dat u de '{{recipientName}}' van de ontvanger wilt verwijderen?", + "delete-recipients-text": "Opgelet, na de bevestiging worden ontvangers onherstelbaar.", + "delete-recipients-title": "Weet u zeker dat u { count, plural, =1 {1 recipient} andere {# recipients} } wilt verwijderen?", + "delete-request-text": "Opgelet, na het bevestigingsverzoek wordt het onherstelbaar.", + "delete-request-title": "Weet je zeker dat je een verzoek wilt verwijderen?", + "delete-requests-text": "Opgelet, na de bevestiging worden verzoeken onherstelbaar.", + "delete-requests-title": "Weet u zeker dat u { count, plural, =1 {1 request} andere {# requests} } wilt verwijderen?", + "delete-rule-text": "Opgelet, nadat de bevestigingsregel onherstelbaar wordt.", + "delete-rule-title": "Weet u zeker dat u rule '{{ruleName}}' wilt verwijderen?", + "delete-rules-text": "Opgelet, na de bevestiging worden de regels onherstelbaar.", + "delete-rules-title": "Weet u zeker dat u { count, plural, =1 {1 rule} andere {# rules} } wilt verwijderen?", + "delete-template-text": "Opgelet, nadat de bevestigingssjabloon onherstelbaar wordt.", + "delete-template-title": "Weet u zeker dat u sjabloon '{{templateName}}' wilt verwijderen?", + "delete-templates-text": "Opgelet, nadat de bevestigingssjablonen onherstelbaar zijn geworden.", + "delete-templates-title": "Weet u zeker dat u { count, plural, =1 {1 template} andere {# templates} } wilt verwijderen?", + "deleted": "Verwijderd", + "delivery-method": { + "delivery-method": "Wijze van levering", + "email": "E-mail", + "email-preview": "Voorbeeld van e-mailmelding", + "slack": "Los", + "slack-preview": "Voorbeeld van Slack-melding", + "sms": "SMS", + "sms-preview": "Voorbeeld van sms-melding", + "web": "Web", + "web-preview": "Voorbeeld van webmelding" + }, + "delivery-method-not-configure-click": "De leveringsmethode is niet geconfigureerd. Klik om in te stellen.", + "delivery-method-not-configure-contact": "De leveringsmethode is niet geconfigureerd. Neem contact op met uw systeembeheerder.", + "delivery-methods": "Levering methodes", + "description": "Omschrijving: __________", + "device-activity-trigger-settings": "Triggerinstellingen voor actieve devices", + "device-list-rule-hint": "Als het veld leeg is, wordt de trigger toegepast op alle devices", + "device-profiles-list-rule-hint": "Als het veld leeg is, wordt de trigger toegepast op alle device profielen", + "disabled": "Uitgeschakeld", + "edit-notification-recipients-group": "Groep geadresseerden van meldingen bewerken", + "edit-notification-template": "Meldingssjabloon bewerken", + "edit-rule": "Regel bewerken", + "edit-template": "Sjabloon bewerken", + "enabled": "Ingeschakeld", + "entities-limit-trigger-settings": "Entiteiten beperken triggerinstellingen", + "entity-action-trigger-settings": "Instellingen voor triggers voor entiteitsacties", + "entity-type": "Type entiteit", + "escalation-chain": "Escalatie keten", + "failed-send": "Fouten verzenden", + "fails": "{ count, plural, =1 {1 fail} andere {# fails} }", + "filter": "Filter", + "first-recipient": "Eerste ontvanger", + "inactive": "Inactief", + "inbox": "Postvak IN", + "notification-inbox": "Meldingen / Postvak IN", + "input-field-support-templatization": "Ondersteuning voor invoervelden sjabloonisatie.", + "input-fields-support-templatization": "Invoervelden ondersteunen sjabloonisatie.", + "integration-action-trigger-settings": "Activeringsinstellingen voor integratiegebeurtenissen", + "integration-list-rule-hint": "Als het veld leeg is, wordt de trigger toegepast op alle integraties", + "link": "Verbinden", + "link-required": "Link is verplicht", + "link-type": { + "dashboard": "Dashboard openen", + "link": "URL-link openen" + }, + "management": "Beheer van meldingen", + "mark-all-as-read": "Alles markeren als gelezen", + "mark-as-read": "Markeren als gelezen", + "message": "Bericht", + "message-required": "Bericht is vereist", + "message-max-length": "Het bericht moet kleiner zijn dan of gelijk zijn aan {{ length }} tekens", + "name": "Naam", + "name-required": "Naam is verplicht", + "new-notification": "Nieuwe melding", + "no-inbox-notification": "Geen melding gevonden", + "no-notification-request": "Geen meldingsverzoek", + "no-notification-templates": "Geen meldingssjablonen gevonden", + "no-notifications-yet": "Nog geen meldingen", + "no-recipients-notification": "Geen melding van ontvangers", + "no-rule": "Geen rule geconfigureerd", + "no-rules-notification": "Geen melding van regels", + "no-severity-found": "Geen ernst gevonden", + "no-severity-matching": "'{{severity}}' niet gevonden.", + "no-template-matching": "Er zijn geen '{{template}}' gevonden die overeenkomen met de bron.", + "not-found-slack-recipient": "Slack-ontvanger niet gevonden", + "notification": "Bekendmaking", + "notification-center": "Meldingscentrum", + "notify": "aankondigen", + "notify-again": "Opnieuw melden", + "notify-alarm-action": { + "acknowledged": "Alarm bevestigd", + "assigned": "Alarm toegewezen", + "cleared": "Alarm gewist", + "created": "Alarm gemaakt", + "severity-changed": "Alarmernst gewijzigd", + "unassigned": "Alarm niet toegewezen" + }, + "notify-on": "Melden op", + "notify-on-comment-update": "Bericht over commentaarupdate", + "notify-on-required": "Melden op is vereist", + "notify-on-unassign": "Melden bij toewijzing ongedaan maken", + "notify-only-integrations-errors": "Alleen melden bij fout", + "notify-only-user-comments": "Alleen opmerkingen van gebruikers op de hoogte stellen", + "only-rule-chain-lifecycle-failures": "Alleen fouten in de levenscyclus van de rule chain", + "only-rule-node-lifecycle-failures": "Alleen fouten in de levenscyclus van rule nodes", + "platform-users": "Gebruikers van het platform", + "recipient": "Ontvanger", + "recipient-group": "Groep ontvangers", + "recipient-type": { + "affected-tenant-administrators": "Betrokken tenantbeheerders", + "affected-user": "Betrokken gebruiker", + "all-users": "Alle gebruikers", + "customer-users": "Klant-gebruikers", + "system-administrators": "Systeembeheerders", + "tenant-administrators": "Tenant beheerders", + "user-filters": "Gebruiker filter", + "user-group-list": "Lijst met gebruikersgroepen", + "user-list": "Lijst met gebruikers", + "user-role": "Rol van de gebruiker", + "users-entity-owner": "Gebruikers van de entiteitseigenaar" + }, + "recipients": "Ontvangers", + "notification-recipients": "Meldingen / Ontvangers", + "recipients-count": "{ count, plural, =1 {1 recipient} andere {# recipients} }", + "recipients-required": "Ontvangers zijn verplicht", + "refresh-allow-delivery-method": "Vernieuwen Bezorgmethode toestaan", + "request-search": "Zoekopdracht aanvragen", + "request-status": { + "processing": "Verwerking", + "scheduled": "Gepland", + "sent": "Verzonden" + }, + "review": "Recensie", + "rule": "Regel", + "rule-chain-list-rule-hint": "Als het veld leeg is, wordt de trigger toegepast op alle rule chains", + "rule-engine-events-trigger-settings": "Events in de rule engine activeren instellingen", + "rule-engine-filter": "Rule engine filter", + "rule-name": "Naam van rule", + "rule-name-required": "Naam is verplicht", + "rule-node-filter": "Filter voor rule nodes", + "rules": "Reglement", + "notification-rules": "Meldingen / Regels", + "scheduler-later": "Plannen voor later", + "search-notification": "Meldingen zoeken", + "search-recipients": "Ontvangers zoeken", + "search-rules": "Zoekregels", + "search-templates": "Sjablonen zoeken", + "see-documentation": "Zie documentatie", + "selected-notifications": "{ count, plural, =1 {1 notification} andere {# notifications} } geselecteerd", + "selected-recipients": "{ count, plural, =1 {1 recipient} andere {# recipients} } geselecteerd", + "selected-requests": "{ count, plural, =1 {1 request} andere {# requests} } geselecteerd", + "selected-rules": "{ count, plural, =1 {1 rule} andere {# rules} } geselecteerd", + "selected-template": "{ count, plural, =1 {1 template} andere {# templates} } geselecteerd", + "send-notification": "Notificatie versturen", + "sent": "Verzonden", + "notification-sent": "Notificaties / Verzonden", + "set-entity-from-notification": "Entiteit instellen van melding naar dashboardstatus", + "slack-chanel-type": "Type Slack-kanaal", + "slack-chanel-types": { + "direct": "Direct bericht", + "private-channel": "Privé-kanaal", + "public-channel": "Publieke zender" + }, + "start-from-scratch": "Begin vanaf nul", + "status": "Status", + "stop-escalation-alarm-status-become": "Stop de escalatie van de alarmstatus wordt:", + "subject": "Onderwerp", + "subject-required": "Onderwerp is verplicht", + "template": "Sjabloon", + "template-name": "Naam van sjabloon", + "template-required": "Sjabloon is vereist", + "template-type": { + "alarm": "Alarm", + "alarm-assignment": "Alarm toewijzing", + "alarm-comment": "Alarm commentaar", + "api-usage-limit": "Limiet voor API-gebruik", + "device-activity": "Activiteit van het device", + "entities-limit": "Limieten voor entiteiten", + "entity-action": "Actie van entiteit", + "general": "Algemeen", + "integration-lifecycle-event": "Levens cyclus event van integratie", + "rule-engine-lifecycle-event": "Levens cyclus event van rule engine", + "rule-node": "Regel knooppunt", + "new-platform-version": "Nieuwe platformversie" + }, + "templates": "Sjablonen", + "notification-templates": "Meldingen / Sjablonen", + "tenant-profiles-list-rule-hint": "Als het veld leeg is, wordt de trigger toegepast op alle tenantprofielen", + "tenants-list-rule-hint": "Als het veld leeg is, wordt de trigger toegepast op alle tenants", + "threshold": "Drempel", + "time": "Tijd", + "track-rule-node-events": "Regelknooppuntgebeurtenissen bijhouden", + "trigger": { + "alarm": "Alarm", + "alarm-assignment": "Alarm toewijzing", + "alarm-comment": "Alarm commentaar", + "api-usage-limit": "Limiet voor API-gebruik", + "device-activity": "Activiteit van het device", + "entities-limit": "Limieten voor entiteiten", + "entity-action": "Actie van entiteit", + "integration-lifecycle-event": "Levens cyclus event van integratie", + "rule-engine-lifecycle-event": "Levens cyclus event van rule engine", + "new-platform-version": "Nieuwe platformversie", + "trigger": "Trigger", + "trigger-required": "Trigger is vereist" + }, + "type": "Type", + "unread": "Ongelezen", + "updated": "Bijgewerkt", + "use-template": "Sjabloon gebruiken", + "view-all": "Alles weergeven", + "view-notification-recipients-group": "Groep geadresseerden van meldingen weergeven", + "view-notification-template": "Meldingssjabloon weergeven", + "view-rule": "Bekijk rule", + "warning": "Waarschuwing" + }, + "ota-update": { + "add": "Pakket toevoegen", + "assign-firmware": "Toegewezen firmware", + "assign-firmware-required": "Toegewezen firmware is vereist", + "assign-software": "Toegewezen software", + "assign-software-required": "Toegewezen software is vereist", + "auto-generate-checksum": "Automatisch controlesom genereren", + "cant-applied-group-all": "Kan niet worden aangevraagd voor groep Alle", + "checksum": "Checksum", + "checksum-hint": "Als de checksum leeg is, wordt deze automatisch gegenereerd", + "checksum-algorithm": "Checksum-algoritme", + "checksum-copied-message": "De controlesom van het pakket is gekopieerd naar het klembord", + "change-firmware": "Wijziging van de firmware kan leiden tot een update van { count, plural, =1 {1 device} andere {# devices} }.", + "change-software": "Wijziging van de software kan leiden tot een update van { count, plural, =1 {1 device} andere {# devices} }.", + "chose-compatible-device-profile": "Het geüploade pakket is alleen beschikbaar voor devices met het gekozen profiel.", + "chose-firmware-distributed-device": "Kies firmware die naar de devices wordt gedistribueerd", + "chose-software-distributed-device": "Kies software die naar de devices wordt gedistribueerd", + "content-type": "Inhoudstype", + "copy-checksum": "Controlesom kopiëren", + "copy-direct-url": "Directe URL kopiëren", + "copyId": "Kopieer pakket-ID", + "copied": "Gekopieerd!", + "delete": "Pakket verwijderen", + "delete-ota-update-text": "Opgelet, na de bevestiging wordt de OTA-update onherstelbaar.", + "delete-ota-update-title": "Weet je zeker dat je de OTA-update '{{title}}' wilt verwijderen?", + "delete-ota-updates-text": "Opgelet, na de bevestiging worden alle geselecteerde OTA-updates verwijderd.", + "delete-ota-updates-title": "Weet u zeker dat u { count, plural, =1 {1 OTA update} andere {# OTA updates} } wilt verwijderen?", + "description": "Omschrijving: __________", + "direct-url": "Directe URL", + "direct-url-copied-message": "De directe URL van het pakket is gekopieerd naar het klembord", + "direct-url-required": "Directe URL is vereist", + "download": "Pakket downloaden", + "drop-file": "Zet een pakketbestand neer of klik om een bestand te selecteren om te uploaden.", + "drop-package-file-or": "Sleep een pakketbestand of", + "file-name": "Bestandsnaam", + "file-size": "Bestandsgrootte", + "file-size-bytes": "Bestandsgrootte in bytes", + "idCopiedMessage": "Pakket-ID is gekopieerd naar klembord", + "no-firmware-matching": "Er zijn geen compatibele OTA-updatepakketten gevonden die overeenkomen met '{{entity}}'.", + "no-firmware-text": "Er zijn geen compatibele OTA-updatepakketten voor firmware ingericht.", + "no-packages-text": "Geen pakketten gevonden", + "no-software-matching": "Er zijn geen compatibele Software OTA Update-pakketten gevonden die overeenkomen met '{{entity}}'.", + "no-software-text": "Er zijn geen compatibele OTA-updatepakketten voor software geleverd.", + "ota-update": "OTA-update", + "ota-update-details": "Details van OTA-update", + "ota-updates": "OTA-updates", + "package-type": "Soort verpakking", + "packages-repository": "Pakketten repository", + "search": "Pakketten zoeken", + "selected-package": "{ count, plural, =1 {1 package} andere {# packages} } geselecteerd", + "title": "Titel", + "title-required": "Titel is vereist.", + "title-max-length": "Titel moet kleiner zijn dan 256 tekens", + "types": { + "firmware": "Firmware", + "software": "Software" + }, + "upload-binary-file": "Binair bestand uploaden", + "use-external-url": "Externe URL gebruiken", + "version": "Versie", + "version-required": "Versie is vereist.", + "version-tag": "Versie Tag", + "version-tag-hint": "De aangepaste tag moet overeenkomen met de pakketversie die door uw device wordt gerapporteerd.", + "version-max-length": "Versie moet kleiner zijn dan 256 tekens", + "warning-after-save-no-edit": "Zodra het pakket is geüpload, kunt u de titel, versie, device profiel en pakkettype niet meer wijzigen." + }, + "position": { + "top": "Boven", + "bottom": "Bodem", + "left": "Links", + "right": "Rechts" + }, + "profile": { + "profile": "Profiel", + "last-login-time": "Laatste keer inloggen", + "change-password": "Wachtwoord wijzigen", + "current-password": "Huidig wachtwoord", + "copy-jwt-token": "JWT-token kopiëren", + "jwt-token": "JWT-token", + "token-valid-till": "Token is geldig tot", + "tokenCopiedSuccessMessage": "JWT-token is gekopieerd naar klembord", + "tokenCopiedWarnMessage": "JWT-token is verlopen! Vernieuw de pagina." + }, + "profiles": { + "profiles": "Profielen" + }, + "security": { + "security": "Veiligheid", + "general-settings": "Algemene beveiligingsinstellingen", + "2fa": { + "2fa": "Twee-factor-authenticatie", + "2fa-description": "Tweefactorauthenticatie beschermt uw account tegen ongeoorloofde toegang. Het enige wat je hoeft te doen is een beveiligingscode in te voeren wanneer je inlogt.", + "authenticate-with": "U kunt zich authenticeren met:", + "disable-2fa-provider-text": "Als u {{name}} uitschakelt, wordt uw account minder veilig", + "disable-2fa-provider-title": "Weet u zeker dat u {{name}} wilt uitschakelen?", + "get-new-code": "Nieuwe code ophalen", + "main-2fa-method": "Gebruik als belangrijkste tweefactorauthenticatiemethode", + "dialog": { + "activation-step-description-email": "De volgende keer dat u inlogt, wordt u gevraagd de beveiligingscode in te voeren die naar uw e-mailadres wordt verzonden.", + "activation-step-description-sms": "De volgende keer dat u inlogt, wordt u gevraagd de beveiligingscode in te voeren die naar het telefoonnummer wordt verzonden.", + "activation-step-description-totp": "De volgende keer dat u inlogt, moet u een tweefactorauthenticatiecode opgeven.", + "activation-step-label": "Activering", + "backup-code-description": "Print de codes uit, zodat je ze bij de hand hebt wanneer je ze moet gebruiken om in te loggen op je account. U kunt elke back-upcode één keer gebruiken.", + "backup-code-warn": "Zodra u deze pagina verlaat, kunnen deze codes niet meer worden weergegeven. Bewaar ze veilig met behulp van de onderstaande opties.", + "download-txt": "Downloaden (txt)", + "email-step-description": "Voer een e-mailadres in dat u als authenticator wilt gebruiken.", + "email-step-label": "E-mail", + "enable-email-title": "E-mailauthenticator inschakelen", + "enable-sms-title": "SMS-authenticator inschakelen", + "enable-totp-title": "Authenticator-app inschakelen", + "enter-verification-code": "Vul hier de 6-cijferige code in", + "get-backup-code-title": "Back-upcode ophalen", + "next": "Volgend", + "scan-qr-code": "Scan deze QR-code met je verificatie-app", + "send-code": "Verstuur code", + "sms-step-description": "Voer een telefoonnummer in dat u als authenticator wilt gebruiken.", + "sms-step-label": "Telefoonnummer", + "success": "Succes!", + "totp-step-description-install": "U kunt apps zoals Google Authenticator, Authy of Duo installeren.", + "totp-step-description-open": "Open de authenticator-app op je mobiele telefoon.", + "totp-step-label": "Download de app", + "verification-code": "6-cijferige code", + "verification-code-invalid": "Ongeldige indeling van de verificatiecode", + "verification-code-incorrect": "Verificatiecode is onjuist", + "verification-code-many-request": "Te veel verzoeken om verificatiecode te controleren", + "verification-step-description": "Voer een 6-cijferige code in die we zojuist naar {{address}} hebben gestuurd", + "verification-step-label": "Verificatie" + }, + "provider": { + "email": "E-mail", + "email-description": "Gebruik een beveiligingscode die naar uw e-mailadres is verzonden om u te verifiëren.", + "email-hint": "Authenticatiecodes worden per e-mail verzonden naar {{ info }}", + "sms": "SMS", + "sms-description": "Gebruik je telefoon om je te authenticeren. We sturen je een beveiligingscode via sms wanneer je inlogt.", + "sms-hint": "Authenticatiecodes worden per sms verzonden naar {{ info }}", + "totp": "Authenticator-app", + "totp-description": "Gebruik apps zoals Google Authenticator, Authy of Duo op je telefoon om te verifiëren. Het genereert een beveiligingscode om in te loggen.", + "totp-hint": "De Authenticator-app is ingesteld voor uw account", + "backup_code": "Back-up code", + "backup-code-description": "Met deze afdrukbare eenmalige toegangscodes kunt u zich aanmelden wanneer u uw telefoon niet bij de hand hebt, bijvoorbeeld wanneer u op reis bent.", + "backup-code-hint": "{{ info }} codes voor eenmalig gebruik zijn op dit moment actief" + } + }, + "password-requirement": { + "at-least": "Minstens:", + "character": "{ count, plural, =1 {1 character} andere {# characters} }", + "digit": "{ count, plural, =1 {1 digit} andere {# digits} }", + "incorrect-password-try-again": "Onjuist wachtwoord. Probeer het opnieuw", + "lowercase-letter": "{ count, plural, =1 {1 lowercase letter} andere {# lowercase letters} }", + "new-passwords-not-match": "Nieuw wachtwoord komt niet overeen", + "password-should-not-contain-spaces": "Uw wachtwoord mag geen spaties bevatten", + "password-not-meet-requirements": "Wachtwoord voldeed niet aan de vereisten", + "password-requirements": "Vereisten voor wachtwoorden", + "password-should-difference": "Het nieuwe wachtwoord moet anders zijn dan het huidige", + "special-character": "{ count, plural, =1 {1 special character} andere {# special characters} }", + "uppercase-letter": "{ count, plural, =1 {1 uppercase letter} andere {# uppercase letters} }" + } + }, + "relation": { + "relations": "Betrekkingen", + "direction": "Richting", + "search-direction": { + "FROM": "Van", + "TO": "Aan" + }, + "direction-type": { + "FROM": "Van", + "TO": "Aan" + }, + "from-relations": "Uitgaande relaties", + "to-relations": "Inkomende relaties", + "selected-relations": "{ count, plural, =1 {1 relation} andere {# relations} } geselecteerd", + "type": "Type", + "to-entity-type": "Naar entiteitstype", + "to-entity-name": "Naar entiteitsnaam", + "from-entity-type": "Van entiteitstype", + "from-entity-name": "Van entiteitsnaam", + "to-entity": "Naar entiteit", + "from-entity": "Van entiteit", + "delete": "Relatie verwijderen", + "relation-type": "Relatie type", + "relation-type-required": "Relatietype is vereist.", + "relation-type-max-length": "Relatietype moet kleiner zijn dan 256 tekens", + "any-relation-type": "Elk type", + "add": "Relatie toevoegen", + "edit": "Relatie bewerken", + "view": "Bekijk relatie", + "delete-to-relation-title": "Weet u zeker dat u de relatie met de entiteit '{{entityName}}' wilt verwijderen?", + "delete-to-relation-text": "Let op, na de bevestiging is de entiteit '{{entityName}}' niet meer gerelateerd aan de huidige entiteit.", + "delete-to-relations-title": "Weet u zeker dat u { count, plural, =1 {1 relation} andere {# relations} } wilt verwijderen?", + "delete-to-relations-text": "Opgelet, na de bevestiging worden alle geselecteerde relaties verwijderd en worden de bijbehorende entiteiten losgekoppeld van de huidige entiteit.", + "delete-from-relation-title": "Weet u zeker dat u de relatie uit de entiteit '{{entityName}}' wilt verwijderen?", + "delete-from-relation-text": "Opgelet, na de bevestiging zal de huidige entiteit niet meer gerelateerd zijn aan de entiteit '{{entityName}}'.", + "delete-from-relations-title": "Weet u zeker dat u { count, plural, =1 {1 relation} andere {# relations} } wilt verwijderen?", + "delete-from-relations-text": "Opgelet, na de bevestiging worden alle geselecteerde relaties verwijderd en wordt de huidige entiteit losgekoppeld van de corresponderende entiteiten.", + "remove-relation-filter": "Relatiefilter verwijderen", + "add-relation-filter": "Relatiefilter toevoegen", + "any-relation": "Elke relatie", + "relation-filters": "Relatie filters", + "additional-info": "Aanvullende informatie (JSON)", + "invalid-additional-info": "Kan aanvullende informatie json niet parseren.", + "no-relations-text": "Geen relaties gevonden" + }, + "resource": { + "add": "Bron toevoegen", + "copyId": "Resource-ID kopiëren", + "delete": "Bron verwijderen", + "delete-resource-text": "Opgelet, na de bevestiging wordt de bron onherstelbaar.", + "delete-resource-title": "Weet u zeker dat u de bron '{{resourceTitle}}' wilt verwijderen?", + "delete-resources-action-title": "Verwijder { count, plural, =1 {1 resource} andere {# resources} }", + "delete-resources-text": "Houd er rekening mee dat de geselecteerde bronnen, zelfs als ze in device profielen worden gebruikt, worden verwijderd.", + "delete-resources-title": "Weet u zeker dat u { count, plural, =1 {1 resource} andere {# resources} } wilt verwijderen?", + "download": "Bron downloaden", + "drop-file": "Zet een bronbestand neer of klik om een bestand te selecteren om te uploaden.", + "drop-resource-file-or": "Sleep een bronbestand of", + "empty": "Bron is leeg", + "file-name": "Bestandsnaam", + "idCopiedMessage": "Bron-id is gekopieerd naar klembord", + "no-resource-matching": "Er zijn geen '{{widgetsBundle}}' gevonden die overeenkomen met de bron.", + "no-resource-text": "Geen bronnen gevonden", + "open-widgets-bundle": "Widgets-bundel openen", + "resource": "Hulpbron", + "resource-library-details": "Details van de bron", + "resource-type": "Type bron", + "resources-library": "Bibliotheek met bronnen", + "search": "Bronnen zoeken", + "selected-resources": "{ count, plural, =1 {1 resource} andere {# resources} } geselecteerd", + "system": "Systeem", + "title": "Titel", + "title-required": "Titel is vereist.", + "title-max-length": "Titel moet kleiner zijn dan 256 tekens" + }, + "rulechain": { + "rulechain": "Rule chains", + "rulechain-events": "Events in de rule chain", + "rulechains": "Rule chains", + "root": "Bron", + "delete": "Rule chain verwijderen", + "name": "Naam", + "name-required": "Naam is verplicht.", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens", + "description": "Omschrijving: __________", + "add": "Rule chain toevoegen", + "set-root": "Bron rule chain aanmaken", + "set-root-rulechain-title": "Weet u zeker dat u de rule chain '{{ruleChainName}}' als bron wilt maken?", + "set-root-rulechain-text": "Na de bevestiging wordt de rule chain de bron en worden alle inkomende transportberichten afgehandeld.", + "delete-rulechain-title": "Weet u zeker dat u de rule chain '{{ruleChainName}}' wilt verwijderen?", + "delete-rulechain-text": "Opgelet, na de bevestiging worden de rule chain en alle gerelateerde gegevens onherstelbaar.", + "delete-rulechains-title": "Weet u zeker dat u { count, plural, =1 {1 rule chain} andere {# rule chains} } wilt verwijderen?", + "delete-rulechains-action-title": "Verwijder { count, plural, =1 {1 rule chain} andere {# rule chains} }", + "delete-rulechains-text": "Opgelet, na de bevestiging worden alle geselecteerde rule chains verwijderd en worden alle gerelateerde gegevens onherstelbaar.", + "add-rulechain-text": "Nieuwe rule chain toevoegen", + "no-rulechains-text": "Geen rule chains gevonden", + "rulechain-details": "Details van de rule chain", + "details": "Details", + "events": "Events", + "system": "Systeem", + "import": "Rule chain importeren", + "export": "Rule chain exporteren", + "export-failed-error": "Rule chain kan niet worden geëxporteerd: {{error}}", + "create-new-rulechain": "Nieuwe rule chain maken", + "rulechain-file": "Rule chain bestand", + "invalid-rulechain-file-error": "Rule chain kan niet worden geïmporteerd: Ongeldige gegevensstructuur van de rule chain.", + "copyId": "Rule chain-id kopiëren", + "idCopiedMessage": "Rule chain-id is gekopieerd naar klembord", + "select-rulechain": "Rule chain selecteren", + "no-rulechains-matching": "Er zijn geen rule chains gevonden die overeenkomen met '{{entity}}'.", + "rulechain-required": "Rule chain is vereist", + "management": "Beheer van rule chains", + "debug-mode": "Foutopsporingsmodus", + "search": "Rule chains zoeken", + "selected-rulechains": "{ count, plural, =1 {1 rule chain} andere {# rule chains} } geselecteerd", + "open-rulechain": "Rule chain openen", + "edge-template-root": "Sjabloon Wortel", + "assign-to-edge": "Toewijzen aan Edge", + "edge-rulechain": "Edge rule chain", + "unassign-rulechain-from-edge-text": "Na de bevestiging wordt de toewijzing van de rule chain ongedaan gemaakt en is deze niet toegankelijk voor de edge.", + "unassign-rulechains-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 rulechain} andere {# rulechains} } wilt opheffen?", + "unassign-rulechains-from-edge-text": "Na de bevestiging worden alle geselecteerde rule chains ongedaan gemaakt en zijn ze niet toegankelijk via de edge.", + "assign-rulechain-to-edge-title": "Regel Chain(s) toewijzen aan Edge", + "assign-rulechain-to-edge-text": "Selecteer de rule chains die u aan de edge wilt toewijzen", + "set-edge-template-root-rulechain": "Rule chain maken als hoofdmap van de randsjabloon", + "set-edge-tscheduler-eventemplate-root-rulechain": "Rule chain maken als hoofdmap van de randsjabloon", + "set-edge-template-root-rulechain-title": "Weet u zeker dat u de rule chain '{{ruleChainName}}' randsjabloon root wilt maken?", + "set-edge-template-root-rulechain-text": "Na de bevestiging wordt de rule chain de hoofdstructuur van de randsjabloon en wordt deze de hoofdregelketen voor een nieuw aangemaakte edges.", + "invalid-rulechain-type-error": "Kan rule chain niet importeren: Ongeldig type rule chain. Verwacht type is {{expectedRuleChainType}}.", + "set-auto-assign-to-edge": "Rule chain toewijzen aan edge(en) bij het maken", + "set-auto-assign-to-edge-title": "Weet u zeker dat u de rule chain '{{ruleChainName}}' wilt toewijzen aan edge(en) bij het maken?", + "set-auto-assign-to-edge-text": "Na de bevestiging wordt de rule chain automatisch toegewezen aan edge(en) bij het aanmaken.", + "unset-auto-assign-to-edge": "Wijs geen rule chain toe aan edge(en) bij het maken", + "unset-auto-assign-to-edge-title": "Weet u zeker dat u de rule chain '{{ruleChainName}}' niet wilt toewijzen aan edge(en) bij het maken?", + "unset-auto-assign-to-edge-text": "Na de bevestiging wordt de rule chain niet langer automatisch toegewezen aan edge(en) bij het aanmaken.", + "unassign-rulechain-title": "Weet u zeker dat u de toewijzing van de rule chain '{{ruleChainName}}' wilt opheffen?", + "unassign-rulechains": "Toewijzing van rule chains ongedaan maken" + }, + "rulenode": { + "rule-node-events": "Events met rule nodes", + "details": "Details", + "events": "Events", + "search": "Rule nodes zoeken", + "open-node-library": "Knooppuntbibliotheek openen", + "add": "Rule node toevoegen", + "name": "Naam", + "name-required": "Naam is verplicht.", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens", + "type": "Type", + "description": "Omschrijving: __________", + "delete": "Rule node verwijderen", + "select-all-objects": "Selecteer alle rule nodes en verbindingen", + "deselect-all-objects": "Deselecteer alle rule nodes en verbindingen", + "delete-selected-objects": "Geselecteerde rule nodes en verbindingen verwijderen", + "delete-selected": "Selectie verwijderen", + "create-nested-rulechain": "Geneste rule chain maken", + "select-all": "Alles selecteren", + "copy-selected": "Geselecteerde tekst kopiëren", + "deselect-all": "Alles deselecteren", + "rulenode-details": "Details van rule nodes", + "debug-mode": "Foutopsporingsmodus", + "singleton-mode": "Singleton-modus", + "configuration": "Configuratie", + "link": "Verbinden", + "link-details": "Details van rule nodes links", + "add-link": "Link toevoegen", + "link-label": "Label koppelen", + "link-label-required": "Linklabel is vereist.", + "custom-link-label": "Aangepast linklabel", + "custom-link-label-required": "Aangepast linklabel is vereist.", + "link-labels": "Labels koppelen", + "link-labels-required": "Labels koppelen is vereist.", + "no-link-labels-found": "Geen linklabels gevonden", + "no-link-label-matching": "'{{label}}' niet gevonden.", + "create-new-link-label": "Maak een nieuwe aan!", + "type-filter": "Filter", + "type-filter-details": "Inkomende berichten filteren met geconfigureerde voorwaarden", + "type-enrichment": "Verrijking", + "type-enrichment-details": "Aanvullende informatie toevoegen aan de metagegevens van berichten", + "type-transformation": "Transformatie", + "type-transformation-details": "De payload en metagegevens van berichten wijzigen", + "type-action": "Actie", + "type-action-details": "Speciale actie uitvoeren", + "type-analytics": "Analytics", + "type-analytics-details": "Analyse uitvoeren van gestreamde of persistente gegevens", + "type-external": "Extern", + "type-external-details": "Werkt samen met extern systeem", + "type-rule-chain": "Rule chain", + "type-rule-chain-details": "Inkomende berichten worden doorgestuurd naar een opgegeven rule chain", + "type-flow": "Vloeien", + "type-flow-details": "Organiseert de berichtenstroom", + "type-input": "Invoer", + "type-input-details": "Logische invoer van rule chain, stuurt inkomende berichten door naar het volgende gerelateerde rule node", + "type-unknown": "Onbekend", + "type-unknown-details": "Onopgelost rule node", + "directive-is-not-loaded": "Gedefinieerde configuratierichtlijn '{{directiveName}}' is niet beschikbaar.", + "ui-resources-load-error": "Het laden van de bronnen van de configuratie-gebruikersinterface is mislukt.", + "invalid-target-rulechain": "Kan doelregelketen niet oplossen!", + "test-script-function": "Test script functie", + "script-lang-java-script": "Java-schrift", + "script-lang-tbel": "TBEL", + "message": "Bericht", + "message-type": "Soort bericht", + "select-message-type": "Selecteer berichttype", + "message-type-required": "Berichttype is vereist", + "metadata": "Metagegevens", + "metadata-required": "Metagegevensvermeldingen mogen niet leeg zijn.", + "output": "Uitvoer", + "test": "Test", + "help": "Help", + "reset-debug-mode": "Foutopsporingsmodus resetten in alle rule nodes" + }, + "role": { + "role": "Rol", + "roles": "Rollen", + "management": "Rollen beheer", + "view-roles": "Rollen weergeven", + "no-roles-matching": "Er zijn geen rollen gevonden die overeenkomen met '{{entity}}'.", + "role-list": "Lijst met rollen", + "role-list-required": "Rollenlijst is verplicht", + "add": "Rol toevoegen", + "view": "Bekijk rol", + "search": "Rollen zoeken", + "selected-roles": "{ count, plural, =1 {1 role} andere {# roles} } geselecteerd", + "no-roles-text": "Geen rollen gevonden", + "role-details": "Details van de rol", + "add-role-text": "Nieuwe rol toevoegen", + "delete": "Rol verwijderen", + "delete-roles": "Rollen verwijderen", + "delete-role-title": "Weet je zeker dat je de rol '{{roleName}}' wilt verwijderen?", + "delete-role-text": "Opgelet, na de bevestiging worden de rol en alle gerelateerde gegevens onherstelbaar.", + "delete-roles-title": "Weet u zeker dat u { count, plural, =1 {1 role} andere {# roles} } wilt verwijderen?", + "delete-roles-action-title": "Verwijder { count, plural, =1 {1 role} andere {# roles} }", + "delete-roles-text": "Opgelet, na de bevestiging worden alle geselecteerde rollen verwijderd en worden alle gerelateerde gegevens onherstelbaar.", + "role-type": "Soort rol", + "role-type-required": "Roltype is vereist.", + "select-role-type": "Roltype selecteren", + "enter-role-type": "Roltype invoeren", + "any-role": "Elke rol", + "no-role-types-matching": "Er zijn geen roltypen gevonden die overeenkomen met '{{entitySubtype}}'.", + "role-type-list-empty": "Geen roltypen geselecteerd.", + "role-types": "Soorten rollen", + "created-time": "Gecreëerde tijd", + "name": "Naam", + "name-required": "Naam is verplicht.", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens", + "description": "Omschrijving: __________", + "events": "Events", + "details": "Details", + "copyId": "Rol-ID kopiëren", + "idCopiedMessage": "Rol-ID is gekopieerd naar het klembord", + "permissions": "Machtigingen", + "role-required": "Vereiste rol", + "roles-required": "Vereiste rollen", + "display-type": { + "GENERIC": "Generiek", + "GROUP": "Groep" + } + }, + "group-permission": { + "user-group-roles": "Rollen voor gebruikersgroepen", + "entity-group-permissions": "Machtigingen voor entiteitsgroepen", + "role-type": "Soort rol", + "role-name": "Naam van de rol", + "group-type": "Soort groep", + "group-name": "Naam van de groep", + "group-owner": "Eigenaar van de groep", + "user-group-name": "Naam van de gebruikersgroep", + "user-group-owner": "Eigenaar van gebruikersgroep", + "edit": "Machtigingen bewerken", + "delete": "Machtigingen verwijderen", + "selected-group-permissions": "{ count, plural, =1 {1 group permission} andere {# group permissions} } geselecteerd", + "delete-group-permission-title": "Weet je zeker dat je de groepsrechten '{{roleName}}' wilt verwijderen?", + "delete-group-permission-text": "Opgelet, na de bevestiging worden de groepstoestemming en alle gerelateerde gegevens onherstelbaar.", + "delete-group-permission": "Groepsmachtiging verwijderen", + "delete-group-permissions-title": "Weet u zeker dat u { count, plural, =1 {1 group permission} andere {# group permission} } wilt verwijderen?", + "delete-group-permissions-text": "Opgelet, na de bevestiging worden alle geselecteerde groepsmachtigingen verwijderd en verliezen de bijbehorende gebruikers de toegang tot gespecificeerde bronnen.", + "delete-group-permissions": "Groepsmachtigingen verwijderen", + "add-group-permission": "Groepsmachtiging toevoegen", + "edit-group-permission": "Groepsmachtiging bewerken", + "entity-group": "Entiteitsgroep", + "user-group": "Gebruikersgroep", + "no-owners-matching": "Er zijn geen eigenaren gevonden die overeenkomen met '{{owner}}'.", + "target-owner-required": "Eigenaar van entiteitsgroep is vereist.", + "target-user-group-owner-required": "Eigenaar van gebruikersgroep is vereist.", + "no-group-permissions-text": "Geen groepsmachtigingen gevonden" + }, + "permission": { + "permissions-required": "Er moet ten minste één machtigingsvermelding worden opgegeven.", + "remove-permission": "Machtigingsinvoer verwijderen", + "add-permission": "Machtigingsinvoer toevoegen", + "other": "Anders __________", + "resource": { + "resource": "Hulpbron", + "select-resource": "Bron selecteren", + "resource-required": "Resource is vereist", + "no-resources-matching": "Er zijn geen bronnen gevonden die overeenkomen met '{{resource}}'.", + "display-type": { + "ALL": "Alle", + "PROFILE": "Profiel", + "ADMIN_SETTINGS": "Admin-instellingen", + "ALARM": "Alarm", + "DEVICE": "Device", + "DEVICE_PROFILE": "Device profiel", + "ASSET": "Asset", + "CUSTOMER": "Klant", + "DASHBOARD": "Dashboard", + "ENTITY_VIEW": "Entiteit Weergave", + "EDGE": "Edge", + "TENANT": "Tenant", + "TENANT_PROFILE": "Profiel van de tenant", + "RULE_CHAIN": "Rule chain", + "USER": "Gebruiker", + "WIDGETS_BUNDLE": "Widgets Bundel", + "WIDGET_TYPE": "Widget Type", + "CONVERTER": "Conversieprogramma", + "INTEGRATION": "Integratie", + "SCHEDULER_EVENT": "Scheduler Evenement", + "BLOB_ENTITY": "Blob-entiteit", + "CUSTOMER_GROUP": "Klantengroep", + "DEVICE_GROUP": "Device Groep", + "ASSET_GROUP": "Activagroep", + "USER_GROUP": "Gebruikersgroep", + "ENTITY_VIEW_GROUP": "Groep Entiteitsweergave", + "DASHBOARD_GROUP": "Dashboard Groep", + "ROLE": "Rol", + "GROUP_PERMISSION": "Toestemming voor groepen", + "WHITE_LABELING": "White labelling", + "AUDIT_LOG": "Controle logboek", + "API_USAGE_STATE": "Status API-gebruik", + "TB_RESOURCE": "Hulpbron", + "EDGE_GROUP": "Edge-groep", + "OTA_PACKAGE": "Ota-pakket", + "QUEUE": "Rij", + "VERSION_CONTROL": "Versiebeheer", + "ASSET_PROFILE": "Asset Profiel", + "NOTIFICATION": "Bekendmaking" + } + }, + "operation": { + "operation": "Operatie", + "operations": "Operaties", + "operations-required": "Er moet ten minste één bewerking worden opgegeven.", + "enter-operation": "Operatie invoeren", + "no-operations-matching": "Er zijn geen bewerkingen gevonden die overeenkomen met '{{operation}}'.", + "display-type": { + "ALL": "Alle", + "CREATE": "Nieuw", + "READ": "Lezen", + "WRITE": "Schrijven", + "DELETE": "Verwijderen", + "ASSIGN_TO_CUSTOMER": "Toewijzen aan klant", + "UNASSIGN_FROM_CUSTOMER": "Toewijzing van klant ongedaan maken", + "RPC_CALL": "RPC-oproep", + "READ_CREDENTIALS": "Inloggegevens lezen", + "WRITE_CREDENTIALS": "Referenties schrijven", + "READ_ATTRIBUTES": "Attributen lezen", + "WRITE_ATTRIBUTES": "Attributen schrijven", + "READ_TELEMETRY": "Telemetrie lezen", + "WRITE_TELEMETRY": "Telemetrie schrijven", + "CLAIM_DEVICES": "Devices claimen", + "IMPERSONATE": "Imiteren", + "CHANGE_OWNER": "Eigenaar wijzigen", + "ADD_TO_GROUP": "Voeg toe aan groep", + "REMOVE_FROM_GROUP": "Verwijderen uit groep", + "SHARE_GROUP": "Groep delen", + "ASSIGN_TO_TENANT": "Toewijzen aan tenant" + } + } + }, + "scheduler": { + "scheduler": "Scheduler", + "scheduler-event": "Scheduler-event", + "select-scheduler-event": "Scheduler event selecteren", + "no-scheduler-events-matching": "Er zijn geen scheduler events gevonden die overeenkomen met '{{entity}}'.", + "scheduler-event-required": "Scheduler-event is vereist", + "management": "Beheer van de planning", + "scheduler-events": "Scheduler-events", + "add-scheduler-event": "Scheduler-event toevoegen", + "search-scheduler-events": "Scheduler-events zoeken", + "created-time": "Gecreëerde tijd", + "name": "Naam", + "name-required": "Naam is verplicht", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens", + "type": "Type", + "created_customer": "Gemaakt door de klant", + "edit-scheduler-event": "Scheduler-event bewerken", + "view-scheduler-event": "Scheduler event weergeven", + "delete-scheduler-event": "Scheduler-event verwijderen", + "no-scheduler-events": "Geen planner-gebeurtenissen gevonden", + "selected-scheduler-events": "{ count, plural, =1 {1 scheduler event} andere {# scheduler events} } geselecteerd", + "delete-scheduler-event-title": "Weet u zeker dat u de '{{schedulerEventName}}' van de planningsgebeurtenis wilt verwijderen?", + "delete-scheduler-event-text": "Opgelet, na de bevestiging worden de event van de planner en alle gerelateerde gegevens onherstelbaar.", + "delete-scheduler-events-title": "Weet u zeker dat u { count, plural, =1 {1 scheduler event} andere {# scheduler events} } wilt verwijderen?", + "delete-scheduler-events-text": "Opgelet, na de bevestiging worden alle geselecteerde scheduler events verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", + "create": "Scheduler-event maken", + "edit": "Scheduler-event bewerken", + "view": "Scheduler event weergeven", + "configuration": "Configuratie", + "schedule": "Rooster", + "start-time": "Begintijd", + "repeat": "Herhalen", + "repeats": "Herhaalt", + "daily": "Dagelijks", + "every-n-days": "Elke N dagen", + "every-n-days-text": "Elke { days, plural, =1 {day} andere {# days} }", + "weekly": "Wekelijks", + "every-n-weeks": "Elke N weken", + "every-n-weeks-text": "Elke { weeks, plural, =1 {week} andere {# weeks} }", + "monthly": "Maandelijks", + "yearly": "Jaarlijks", + "timer": "Op basis van een timer", + "repeats-required": "Herhalingen zijn vereist.", + "repeat-on": "Herhaal op", + "repeat-every": "Herhaal elke", + "repeat-every-n-days": "Herhaal dit elke N dagen", + "repeat-days-required": "Het aantal dagen om te herhalen is vereist.", + "invalid-repeat-days-value": "Het aantal te herhalen dagen moet een positief geheel getal zijn.", + "repeat-every-n-weeks": "Herhaal elke N weken", + "repeat-weeks-required": "Aantal weken om te herhalen is vereist.", + "invalid-repeat-weeks-value": "Het aantal te herhalen weken moet een positief geheel getal zijn.", + "ends-on": "Eindigt op", + "sunday-label": "S", + "monday-label": "M", + "tuesday-label": "T", + "wednesday-label": "W", + "thursday-label": "T", + "friday-label": "F", + "saturday-label": "S", + "repeat-on-sunday": "Herhaling op zondag", + "repeat-on-monday": "Herhaal op maandag", + "repeat-on-tuesday": "Herhaal op dinsdag", + "repeat-on-wednesday": "Herhaal op woensdag", + "repeat-on-thursday": "Herhaal op donderdag", + "repeat-on-friday": "Herhaal op vrijdag", + "repeat-on-saturday": "Herhaling op zaterdag", + "event-type": "Type event", + "select-event-type": "Selecteer evenementtype", + "event-type-required": "Gebeurtenistype is vereist.", + "event-type-max-length": "Het gebeurtenistype moet kleiner zijn dan 256 tekens.", + "list-mode": "Lijst weergave", + "calendar-mode": "Kalenderweergave", + "calendar-view-type": "Type kalenderweergave", + "month": "Maand", + "week": "Week", + "day": "Dag", + "agenda-week": "Agenda Week", + "agenda-day": "Dag van de agenda", + "list-year": "Lijst Jaar", + "list-month": "Lijst Maand", + "list-week": "Lijst Week", + "list-day": "Lijst Dag", + "today": "Vandaag", + "navigate-before": "Navigeer voor", + "navigate-next": "Navigeer volgende", + "starting-from": "Vanaf", + "until": "totdat", + "on": "op", + "sunday": "Zondag", + "monday": "Maandag", + "tuesday": "Dinsdag", + "wednesday": "Woensdag", + "thursday": "Donderdag", + "friday": "Vrijdag", + "saturday": "Zaterdag", + "originator": "Opdrachtgever", + "single-entity": "Eén entiteit", + "group-of-entities": "Groep van entiteiten", + "entities-group-owner": "Eigenaar van entiteitengroep", + "single-device": "Eén device", + "group-of-devices": "Groep Devices", + "devices-group-owner": "Eigenaar van de Devicesgroep", + "message-body": "Hoofdtekst van bericht", + "target": "Doel", + "rpc-method": "Methode", + "rpc-method-required": "Methode is vereist", + "rpc-method-white-space": "Witruimte is niet toegestaan.", + "rpc-params": "Params", + "select-dashboard-state": "Dashboardstatus selecteren", + "hours": "Uren", + "minutes": "Notulen", + "seconds": "Seconden", + "time-interval-required": "Tijdsinterval is vereist", + "time-unit-required": "Tijdseenheid is vereist", + "every-hour": "elke { count, plural, =1 {hour} andere {# hours} }", + "every-minute": "elke { count, plural, =1 {minute} andere {# minutes} }", + "every-second": "elke { count, plural, =1 {second} andere {# seconds} }", + "invalid-time": "Ongeldige tijd" + }, + "report": { + "report-config": "Configuratie van rapporten", + "email-config": "E-mail configuratie", + "dashboard-state-param": "Parameterwaarde dashboardstatus", + "base-url": "Basis-URL", + "base-url-required": "Basis-URL is vereist.", + "use-dashboard-timewindow": "Dashboard tijdvenster gebruiken", + "timewindow": "Tijdvenster", + "name-pattern": "Patroon van rapportnaam", + "name-pattern-required": "Rapportnaam patroon is vereist", + "type": "Soort rapport", + "use-current-user-credentials": "Huidige gebruikersreferenties gebruiken", + "customer-user-credentials": "Gebruikersgegevens van de klant", + "customer-user-credentials-required": "Gebruikersgegevens van de klant zijn vereist", + "generate-test-report": "Testrapport genereren", + "send-email": "Stuur een e-mail", + "from": "Van", + "from-required": "Van is vereist.", + "to": "Aan", + "to-required": "Naar is vereist.", + "cc": "CC", + "bcc": "Bcc", + "subject": "Onderwerp", + "subject-required": "Onderwerp is verplicht.", + "body": "Body", + "body-required": "Body is vereist." + }, + "blob-entity": { + "blob-entity": "Blob-entiteit", + "select-blob-entity": "Blob-entiteit selecteren", + "no-blob-entities-matching": "Er zijn geen blob-entiteiten gevonden die overeenkomen met '{{entity}}'.", + "blob-entity-required": "Blob-entiteit is vereist", + "files": "Archief", + "search": "Bestanden zoeken", + "clear-search": "Zoekopdracht wissen", + "no-blob-entities-prompt": "Geen bestanden gevonden", + "report": "Rapport", + "created-time": "Gecreëerde tijd", + "name": "Naam", + "type": "Type", + "created_customer": "Gemaakt door de klant", + "selected-blob-entities": "{ count, plural, =1 {1 file} andere {# files} } geselecteerd", + "download-blob-entity": "Bestand downloaden", + "delete-blob-entity": "Bestand verwijderen", + "delete-blob-entity-title": "Weet u zeker dat u bestand '{{blobEntityName}}' wilt verwijderen?", + "delete-blob-entity-text": "Opgelet, na de bevestiging worden de gegevens van het bestand onherstelbaar.", + "delete-blob-entities-title": "Weet u zeker dat u { count, plural, =1 {1 file} andere {# files} } wilt verwijderen?", + "delete-blob-entities-text": "Opgelet, na de bevestiging worden alle geselecteerde bestanden verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld." + }, + "timezone": { + "timezone": "Tijdzone", + "select-timezone": "Selecteer tijdzone", + "no-timezones-matching": "Er zijn geen tijdzones gevonden die overeenkomen met '{{timezone}}'.", + "timezone-required": "Tijdzone is vereist.", + "browser-time": "Browser Tijd" + }, + "queue": { + "queue-name": "Rij", + "no-queues-found": "Geen queues gevonden.", + "no-queues-matching": "Er zijn geen queues gevonden die overeenkomen met '{{queue}}'.", + "select-name": "Selecteer de naam van de queue", + "name": "Naam", + "name-required": "Queue naam is verplicht!", + "name-unique": "De naam van de queue is niet uniek!", + "name-pattern": "De naam van de queue bevat een ander teken dan ASCII-alfanumerieke tekens, '.', '_' en '-'!", + "queue-required": "Queue is verplicht!", + "topic-required": "Wachtrijonderwerp is vereist!", + "poll-interval-required": "Poll-interval is vereist!", + "poll-interval-min-value": "De waarde van het pollinterval mag niet kleiner zijn dan 1", + "partitions-required": "Partities is vereist!", + "partitions-min-value": "De waarde van partities mag niet kleiner zijn dan 1", + "pack-processing-timeout-required": "Time-out voor verwerking is vereist", + "pack-processing-timeout-min-value": "De time-outwaarde voor verwerking mag niet kleiner zijn dan 1", + "batch-size-required": "Batchgrootte is vereist!", + "batch-size-min-value": "De waarde van de batchgrootte mag niet kleiner zijn dan 1", + "retries-required": "Opnieuw proberen is vereist!", + "retries-min-value": "De waarde voor nieuwe pogingen mag niet negatief zijn", + "failure-percentage-required": "Error percentage is vereist!", + "failure-percentage-min-value": "De waarde van het error percentage mag niet kleiner zijn dan 0", + "failure-percentage-max-value": "De waarde van het error percentage mag niet hoger zijn dan 100", + "pause-between-retries-required": "Pauze tussen nieuwe pogingen is vereist!", + "pause-between-retries-min-value": "De waarde van de pauze tussen nieuwe pogingen mag niet kleiner zijn dan 1", + "max-pause-between-retries-required": "Maximale pauze tussen nieuwe pogingen is vereist!", + "max-pause-between-retries-min-value": "De maximale pauze tussen de waarde voor nieuwe pogingen mag niet minder zijn dan 1", + "submit-strategy-type-required": "Strategietype indienen is vereist!", + "processing-strategy-type-required": "Het type verwerkingsstrategie is vereist!", + "queues": "Queues", + "selected-queues": "{ count, plural, =1 {1 queue} andere {# queues} } geselecteerd", + "delete-queue-title": "Weet je zeker dat je de queue '{{queueName}}' wilt verwijderen?", + "delete-queues-title": "Weet u zeker dat u { count, plural, =1 {1 queue} andere {# queues} } wilt verwijderen?", + "delete-queue-text": "Opgelet, na de bevestiging worden de queue en alle gerelateerde gegevens onherstelbaar.", + "delete-queues-text": "Na de bevestiging worden alle geselecteerde queues verwijderd en zijn ze niet meer toegankelijk.", + "search": "Queue zoeken", + "add": "Queue toevoegen", + "details": "Details van de queue", + "topic": "Onderwerp", + "submit-settings": "Instellingen verzenden", + "submit-strategy": "Soort strategie *", + "grouping-parameter": "Parameter groeperen", + "processing-settings": "Verwerkingsinstellingen opnieuw proberen", + "processing-strategy": "Soort verwerking *", + "retries-settings": "Instellingen voor nieuwe pogingen", + "polling-settings": "Polling-instellingen", + "batch-processing": "Batchverwerking", + "poll-interval": "Poll-interval", + "partitions": "Partities", + "immediate-processing": "Onmiddellijke verwerking", + "consumer-per-partition": "Stuur een berichtenquête voor elke consument", + "consumer-per-partition-hint": "Schakel afzonderlijke verbruiker(s) in voor elke partitie", + "processing-timeout": "Verwerking binnen, ms", + "batch-size": "Grootte van de partij", + "retries": "Aantal nieuwe pogingen (0 – onbeperkt)", + "failure-percentage": "Percentage mislukte berichten voor het overslaan van nieuwe pogingen", + "pause-between-retries": "Probeer het opnieuw binnen, sec", + "max-pause-between-retries": "Extra nieuwe poging binnen, sec", + "delete": "Queue verwijderen", + "copyId": "Queue-ID kopiëren", + "idCopiedMessage": "Queue-ID is gekopieerd naar klembord", + "description": "Omschrijving: __________", + "description-hint": "Deze tekst wordt weergegeven in de wachtrijbeschrijving in plaats van de geselecteerde strategie", + "alt-description": "Strategie indienen: {{submitStrategy}}, verwerkingsstrategie: {{processingStrategy}}", + "strategies": { + "sequential-by-originator-label": "Opeenvolgend door opsteller", + "sequential-by-originator-hint": "Het nieuwe bericht voor bijvoorbeeld device A wordt pas verzonden als het vorige bericht voor device A is bevestigd", + "sequential-by-tenant-label": "Sequentieel per tenant", + "sequential-by-tenant-hint": "Nieuw bericht voor bijvoorbeeld tenant A wordt pas verzonden als het vorige bericht voor tenant A is bevestigd", + "sequential-label": "Sequentieel", + "sequential-hint": "Nieuw bericht wordt pas verzonden als het vorige bericht is bevestigd", + "burst-label": "Barsten", + "burst-hint": "Alle berichten worden verzonden naar de rule chains in de volgorde waarin ze binnenkomen", + "batch-label": "Batch", + "batch-hint": "De nieuwe batch wordt pas ingediend als de vorige batch is bevestigd", + "skip-all-failures-label": "Sla alle storingen over", + "skip-all-failures-hint": "Negeer alle fouten", + "skip-all-failures-and-timeouts-label": "Sla alle storingen en time-outs over", + "skip-all-failures-and-timeouts-hint": "Negeer alle storingen en time-outs", + "retry-all-label": "Probeer alles opnieuw", + "retry-all-hint": "Probeer alle berichten uit het verwerkingspakket opnieuw uit te voeren", + "retry-failed-label": "Opnieuw proberen mislukt", + "retry-failed-hint": "Probeer alle mislukte berichten opnieuw uit het verwerkingspakket", + "retry-timeout-label": "Time-out opnieuw proberen", + "retry-timeout-hint": "Probeer alle time-outberichten opnieuw uit het verwerkingspakket", + "retry-failed-and-timeout-label": "Opnieuw proberen mislukt en time-out", + "retry-failed-and-timeout-hint": "Probeer alle mislukte en getimede berichten opnieuw uit het verwerkingspakket" + } + }, + "server-error": { + "general": "Algemene serverfout", + "authentication": "Authenticatie fout", + "jwt-token-expired": "JWT-token is verlopen", + "tenant-trial-expired": "Proefperiode tenant verlopen", + "credentials-expired": "Inloggegevens verlopen", + "permission-denied": "Toestemming geweigerd", + "invalid-arguments": "Ongeldige argumenten", + "bad-request-params": "Ongeldige aanvraagparameters", + "item-not-found": "Object niet gevonden", + "too-many-requests": "Te veel aanvragen", + "too-many-updates": "Te veel updates" + }, + "tenant": { + "tenant": "Tenant", + "tenants": "Tenants", + "management": "Beheer van tenants", + "add": "Tenant toevoegen", + "admins": "Beheerders", + "manage-tenant-admins": "Tenant beheerders beheren", + "delete": "Tenant verwijderen", + "add-tenant-text": "Nieuwe tenant toevoegen", + "no-tenants-text": "Geen tenants gevonden", + "tenant-details": "Gegevens tenant", + "title-max-length": "Titel moet kleiner zijn dan 256 tekens", + "delete-tenant-title": "Weet u zeker dat u de tenant '{{tenantTitle}}' wilt verwijderen?", + "delete-tenant-text": "Let op, na de bevestiging worden de tenant en alle gerelateerde gegevens onherstelbaar.", + "delete-tenants-title": "Weet u zeker dat u { count, plural, =1 {1 tenant} andere {# tenants} } wilt verwijderen?", + "delete-tenants-action-title": "Verwijder { count, plural, =1 {1 tenant} andere {# tenants} }", + "delete-tenants-text": "Let op, na de bevestiging worden alle geselecteerde tenants verwijderd en worden alle gerelateerde gegevens onherstelbaar.", + "title": "Titel", + "title-required": "Titel is vereist.", + "description": "Omschrijving: __________", + "details": "Details", + "events": "Events", + "copyId": "Tenant-id kopiëren", + "idCopiedMessage": "Tenant-id is gekopieerd naar klembord", + "select-tenant": "Selecteer tenant", + "no-tenants-matching": "Er zijn geen tenants gevonden die overeenkomen met '{{entity}}'.", + "tenant-required": "Tenant is verplicht", + "allow-white-labeling": "White Labeling toestaan", + "allow-customer-white-labeling": "White labeling van klanten toestaan", + "search": "Tenants zoeken", + "selected-tenants": "{ count, plural, =1 {1 tenant} andere {# tenants} } geselecteerd", + "isolated-tb-rule-engine": "Verwerking in geïsoleerde ThingsBoard Rule Engine-container", + "isolated-tb-rule-engine-details": "Vereist afzonderlijke microservice(s) per geïsoleerde tenant" + }, + "tenant-profile": { + "tenant-profile": "Profiel van de tenant", + "tenant-profiles": "Profielen van tenants", + "add": "Huurdersprofiel toevoegen", + "add-profile": "Profiel toevoegen", + "edit": "Huurdersprofiel bewerken", + "tenant-profile-details": "Details van tenantsprofiel", + "no-tenant-profiles-text": "Geen tenantsprofielen gevonden", + "name-max-length": "Naam moet kleiner zijn dan 256 tekens", + "search": "Tentant profielen zoeken", + "selected-tenant-profiles": "{ count, plural, =1 {1 tenant profile} andere {# tenant profiles} } geselecteerd", + "no-tenant-profiles-matching": "Er zijn geen tenantprofielen gevonden die overeenkomen met '{{entity}}'.", + "tenant-profile-required": "Huurdersprofiel is vereist", + "idCopiedMessage": "De profiel-id van de tenant is gekopieerd naar het klembord", + "set-default": "Tenantprofiel standaard instellen", + "delete": "Tenantprofiel verwijderen", + "copyId": "Tenantprofiel-id kopiëren", + "name": "Naam", + "name-required": "Naam is verplicht.", + "data": "Profiel gegevens", + "profile-configuration": "Profiel configuratie", + "description": "Omschrijving: __________", + "default": "Verstek", + "delete-tenant-profile-title": "Weet u zeker dat u het tenantsprofiel '{{tenantProfileName}}' wilt verwijderen?", + "delete-tenant-profile-text": "Let op, na de bevestiging wordt het tenantsprofiel en alle gerelateerde gegevens onherstelbaar.", + "delete-tenant-profiles-title": "Weet u zeker dat u { count, plural, =1 {1 tenant profile} andere {# tenant profiles} } wilt verwijderen?", + "delete-tenant-profiles-text": "Opgelet, na de bevestiging worden alle geselecteerde tenantsprofielen verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", + "set-default-tenant-profile-title": "Weet u zeker dat u het tenantsprofiel standaard '{{tenantProfileName}}' wilt maken?", + "set-default-tenant-profile-text": "Na de bevestiging wordt het tenantsprofiel als standaard gemarkeerd en wordt het gebruikt voor nieuwe tenants waarvoor geen profiel is opgegeven.", + "no-tenant-profiles-found": "Er zijn geen tenantsprofielen gevonden.", + "create-new-tenant-profile": "Maak een nieuwe aan!", + "create-tenant-profile": "Nieuw tenantsprofiel maken", + "import": "Tenantprofiel importeren", + "export": "Tenantprofiel exporteren", + "export-failed-error": "Kan tenantprofiel niet exporteren: {{error}}", + "tenant-profile-file": "Bestand met tenantsprofiel", + "invalid-tenant-profile-file-error": "Kan tenantprofiel niet importeren: Ongeldige gegevensstructuur van tenantprofiel.", + "advanced-settings": "Geavanceerde instellingen", + "entities": "Entiteiten", + "rule-engine": "Rule engine", + "time-to-live": "Tijd om te leven", + "alarms-and-notifications": "Alarmen en meldingen", + "ota-files-in-bytes": "OTA-bestanden in bytes", + "ws-title": "WS", + "unlimited": "(0 - onbeperkt)", + "maximum-devices": "Maximaal aantal Devices", + "maximum-devices-required": "Het maximale aantal devices is vereist.", + "maximum-devices-range": "Het maximale aantal devices mag niet negatief zijn", + "maximum-assets": "Maximaal aantal asset", + "maximum-assets-required": "Het maximale aantal asset is vereist.", + "maximum-assets-range": "Het maximumaantal items mag niet negatief zijn", + "maximum-customers": "Maximaal aantal klanten", + "maximum-customers-required": "Het maximale aantal klanten is vereist.", + "maximum-customers-range": "Het maximale aantal klanten mag niet negatief zijn", + "maximum-users": "Maximaal aantal gebruikers", + "maximum-users-required": "Het maximale aantal gebruikers is vereist.", + "maximum-users-range": "Het maximumaantal gebruikers mag niet negatief zijn", + "maximum-dashboards": "Dashboards maximaal aantal", + "maximum-dashboards-required": "Het maximale aantal dashboards is vereist.", + "maximum-dashboards-range": "Het maximumaantal dashboards mag niet negatief zijn", + "maximum-rule-chains": "Maximaal aantal rule chains", + "maximum-rule-chains-required": "Het maximale aantal rule chains is vereist.", + "maximum-rule-chains-range": "Het maximale aantal rule chains mag niet negatief zijn", + "maximum-integrations": "Integraties maximaal aantal", + "maximum-integrations-required": "Integraties maximaal aantal is vereist.", + "maximum-integrations-range": "Het maximale aantal integraties mag niet negatief zijn", + "maximum-converters": "Converters maximaal aantal", + "maximum-converters-required": "Het maximale aantal converters is vereist.", + "maximum-converters-range": "Het maximale aantal converters mag niet negatief zijn", + "maximum-scheduler-events": "Maximaal aantal scheduler events", + "maximum-scheduler-events-required": "Het maximale aantal scheduler events is vereist.", + "maximum-scheduler-events-range": "Het maximale aantal scheduler events mag niet negatief zijn", + "maximum-resources-sum-data-size": "Totale grootte van bronbestanden", + "maximum-resources-sum-data-size-required": "De somgrootte van bronbestanden is vereist.", + "maximum-resources-sum-data-size-range": "De somgrootte van resourcebestanden mag niet negatief zijn", + "maximum-ota-packages-sum-data-size": "Totale grootte van OTA-pakketbestanden", + "maximum-ota-package-sum-data-size-required": "De somgrootte van OTA-pakketbestanden is vereist.", + "maximum-ota-package-sum-data-size-range": "De somgrootte van OTA-pakketbestanden mag niet negatief zijn", + "rest-requests-for-tenant": "REST-aanvragen voor tenant", + "transport-tenant-telemetry-msg-rate-limit": "Telemetrieberichten van tenants vervoeren", + "transport-tenant-telemetry-data-points-rate-limit": "Telemetrie gegevenspunten voor transporttenants", + "transport-device-msg-rate-limit": "Berichten over transportmiddelen", + "transport-device-telemetry-msg-rate-limit": "Telemetrieberichten van transport devices", + "transport-device-telemetry-data-points-rate-limit": "Telemetrie gegevenspunten voor transport devices", + "tenant-entity-export-rate-limit": "Aanmaken van entiteitsversies", + "tenant-entity-import-rate-limit": "Laden van entiteitsversie", + "tenant-notification-request-rate-limit": "Verzoeken om kennisgeving", + "tenant-notification-requests-per-rule-rate-limit": "Notificatieverzoeken per notificatieregel", + "max-transport-messages": "Maximaal aantal transportberichten", + "max-transport-messages-required": "Transportberichten maximaal aantal is vereist.", + "max-transport-messages-range": "Het maximale aantal transportberichten mag niet negatief zijn", + "max-transport-data-points": "Maximaal aantal transportdatapunten", + "max-transport-data-points-required": "Er is een maximaal aantal transportdatapunten vereist.", + "max-transport-data-points-range": "Het maximale aantal transport gegevenspunten mag niet negatief zijn", + "max-r-e-executions": "Maximaal aantal uitvoeringen van rule engine", + "max-r-e-executions-required": "Het maximale aantal uitvoeringen van de rule engine is vereist.", + "max-r-e-executions-range": "Het maximale aantal uitvoeringen van de rule engine mag niet negatief zijn", + "max-j-s-executions": "Maximaal aantal JavaScript-uitvoeringen", + "max-j-s-executions-required": "Het maximale aantal JavaScript-uitvoeringen is vereist.", + "max-j-s-executions-range": "Het maximale aantal JavaScript-uitvoeringen mag niet negatief zijn", + "max-d-p-storage-days": "Maximaal aantal bewaardagen datapunten", + "max-d-p-storage-days-required": "Het maximale aantal opslagdagen voor datapunten is vereist.", + "max-d-p-storage-days-range": "Het maximale aantal opslagdagen voor gegevenspunten kan niet negatief zijn", + "default-storage-ttl-days": "Opslag TTL-dagen standaard", + "default-storage-ttl-days-required": "Standaard zijn TTL-dagen voor opslag vereist.", + "default-storage-ttl-days-range": "Opslag TTL-dagen kunnen standaard niet negatief zijn", + "alarms-ttl-days": "Alarmen TTL-dagen", + "alarms-ttl-days-required": "Alarmen TTL-dagen vereist", + "alarms-ttl-days-days-range": "Alarmen TTL-dagen kunnen niet negatief zijn", + "rpc-ttl-days": "RPC TTL-dagen", + "rpc-ttl-days-required": "RPC TTL-dagen vereist", + "rpc-ttl-days-days-range": "RPC TTL-dagen kunnen niet negatief zijn", + "max-rule-node-executions-per-message": "Maximaal aantal uitvoeringen van rule node per bericht", + "max-rule-node-executions-per-message-required": "MRule node per bericht voert het maximale aantal uit dat wordt uitgevoerd.", + "max-rule-node-executions-per-message-range": "Rule node per bericht dat wordt uitgevoerd, het maximale aantal kan niet negatief zijn", + "max-emails": "Maximaal aantal verzonden e-mails", + "max-emails-required": "Het maximale aantal verzonden e-mails is vereist.", + "max-emails-range": "Het maximale aantal verzonden e-mails mag niet negatief zijn", + "max-sms": "Maximaal aantal sms-berichten", + "max-sms-required": "Het maximale aantal sms-berichten is vereist.", + "max-sms-range": "Het maximale aantal verzonden sms'jes mag niet negatief zijn", + "max-created-alarms": "Maximaal aantal alarmen gemaakt", + "max-created-alarms-required": "Het maximale aantal alarmen is vereist.", + "max-created-alarms-range": "Alarmen gemaakt maximaal aantal negatief zijn", + "no-queue": "Geen queue geconfigureerd", + "add-queue": "Queue toevoegen", + "queues-with-count": "Queues ({{count}})", + "tenant-rest-limits": "REST-aanvragen voor tenant", + "customer-rest-limits": "REST-aanvragen voor klant", + "incorrect-pattern-for-rate-limits": "Het formaat bestaat uit door komma's gescheiden paren van capaciteit en punt (in seconden) met een dubbele punt ertussen, bijvoorbeeld 100:1,2000:60", + "too-small-value-zero": "De waarde moet groter zijn dan 0", + "too-small-value-one": "De waarde moet groter zijn dan 1", + "queue-size-is-limited-by-system-configuration": "De grootte van de queue wordt ook beperkt door de systeemconfiguratie.", + "cassandra-tenant-limits-configuration": "Cassandra-query voor tenant", + "ws-limit-max-sessions-per-tenant": "Sessies per tenant maximaal aantal", + "ws-limit-max-sessions-per-customer": "Maximaal aantal sessies per klant", + "ws-limit-max-sessions-per-regular-user": "Sessies per reguliere gebruiker maximaal aantal", + "ws-limit-max-sessions-per-public-user": "Maximaal aantal sessies per openbare gebruiker", + "ws-limit-queue-per-session": "Maximale grootte van berichtenwachtrij per sessie", + "ws-limit-max-subscriptions-per-tenant": "Maximaal aantal abonnementen per tenant", + "ws-limit-max-subscriptions-per-customer": "Abonnementen per klant maximum aantal", + "ws-limit-max-subscriptions-per-regular-user": "Abonnementen per gewone gebruiker maximum aantal", + "ws-limit-max-subscriptions-per-public-user": "Abonnementen per openbare gebruiker maximum aantal", + "ws-limit-updates-per-session": "WS-updates per sessie", + "rate-limits": { + "add-limit": "Limiet toevoegen", + "advanced-settings": "Geavanceerde instellingen", + "edit-limit": "Limiet bewerken", + "but-less-than": "maar minder dan", + "edit-transport-tenant-msg-title": "Snelheidslimieten voor berichten van transporttenants bewerken", + "edit-transport-tenant-telemetry-msg-title": "Snelheidslimieten voor telemetrie berichten van transporttenants bewerken", + "edit-transport-tenant-telemetry-data-points-title": "Snelheidslimieten voor telemetrie gegevenspunten van transporttenants bewerken", + "edit-transport-device-msg-title": "Snelheidslimieten voor berichten van transport devices bewerken", + "edit-transport-device-telemetry-msg-title": "Snelheidslimieten voor telemetrie berichten van transport devices bewerken", + "edit-transport-device-telemetry-data-points-title": "Snelheidslimieten voor telemetriegegevenspunten van transport devices bewerken", + "edit-tenant-rest-limits-title": "REST-aanvragen voor tarieflimieten voor tenants bewerken", + "edit-customer-rest-limits-title": "REST-aanvragen voor snelheidslimieten voor klanten bewerken", + "edit-ws-limit-updates-per-session-title": "Snelheidslimieten voor WS-updates per sessie bewerken", + "edit-cassandra-tenant-limits-configuration-title": "Cassandra-query bewerken voor tarieflimieten voor tenants", + "edit-tenant-entity-export-rate-limit-title": "Limieten voor het maken van entiteitsversies bewerken", + "edit-tenant-entity-import-rate-limit-title": "Limieten voor de laadsnelheid van entiteitsversies bewerken", + "edit-tenant-notification-request-rate-limit-title": "Snelheidslimieten voor meldingsverzoeken bewerken", + "edit-tenant-notification-requests-per-rule-rate-limit-title": "Snelheidslimieten voor meldingsverzoeken per meldingsregel bewerken", + "messages-per": "berichten per", + "not-set": "Niet ingesteld", + "number-of-messages": "Aantal berichten", + "number-of-messages-required": "Het aantal berichten is vereist.", + "number-of-messages-min": "Minimale waarde is 1.", + "preview": "Voorbeeld", + "per-seconds": "Per seconde", + "per-seconds-required": "Tijdtarief is vereist.", + "per-seconds-min": "Minimale waarde is 1.", + "rate-limits": "Limieten voor tarieven", + "remove-limit": "Limiet verwijderen", + "transport-tenant-msg": "Berichten van tenants vervoeren", + "transport-tenant-telemetry-msg": "Telemetrieberichten van tenants vervoeren", + "transport-tenant-telemetry-data-points": "Telemetriege gevenspunten voor transport tenants", + "transport-device-msg": "Berichten over transportmiddelen", + "transport-device-telemetry-msg": "Telemetrieberichten van transport devices", + "transport-device-telemetry-data-points": "Telemetriegegevenspunten voor transport devices", + "sec": "seconde" + } + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, =1 {1 second} andere {# seconds} }", + "minutes-interval": "{ minutes, plural, =1 {1 minute} andere {# minutes} }", + "hours-interval": "{ hours, plural, =1 {1 hour} andere {# hours} }", + "days-interval": "{ days, plural, =1 {1 day} andere {# days} }", + "days": "Dagen", + "hours": "Uren", + "minutes": "Notulen", + "seconds": "Seconden", + "advanced": "Geavanceerd", + "predefined": { + "yesterday": "Gisteren", + "day-before-yesterday": "Eergisteren", + "this-day-last-week": "Deze dag vorige week", + "previous-week": "Vorige week (zo - za)", + "previous-week-iso": "Vorige week (ma - zo)", + "previous-month": "Vorige maand", + "previous-quarter": "Vorig kwartaal", + "previous-half-year": "Vorig halfjaar", + "previous-year": "Vorig jaar", + "current-hour": "Huidig uur", + "current-day": "Huidige dag", + "current-day-so-far": "Huidige dag tot nu toe", + "current-week": "Huidige week (zo - za)", + "current-week-iso": "Huidige week (ma - zo)", + "current-week-so-far": "Huidige week tot nu toe (zo - za)", + "current-week-iso-so-far": "Huidige week tot nu toe (ma - zo)", + "current-month": "Huidige maand", + "current-month-so-far": "Huidige maand tot nu toe", + "current-quarter": "Huidig kwartaal", + "current-quarter-so-far": "Huidig kwartaal tot nu toe", + "current-half-year": "Huidig halfjaar", + "current-half-year-so-far": "Huidig halfjaar tot nu toe", + "current-year": "Huidig jaar", + "current-year-so-far": "Huidig jaar tot nu toe" + } + }, + "timeunit": { + "milliseconds": "Milliseconden", + "seconds": "Seconden", + "minutes": "Notulen", + "hours": "Uren", + "days": "Dagen" + }, + "timewindow": { + "years": "{ years, plural, =1 { year } andere {# years } }", + "months": "{ months, plural, =1 { month } andere {# months } }", + "weeks": "{ weeks, plural, =1 { week } andere {# weeks } }", + "days": "{ days, plural, =1 { day } andere {# days } }", + "hours": "{ hours, plural, =0 { hour } =1 {1 hour } andere {# hours } }", + "hr": "{{ hr }} uur", + "minutes": "{ minutes, plural, =0 { minute } =1 {1 minute } andere {# minutes } }", + "min": "{{ min }} min", + "seconds": "{ seconds, plural, =0 { second } =1 {1 second } andere {# seconds } }", + "sec": "{{ sec }} sec", + "short": { + "days": "{ days, plural, =1 {1 day } andere {# days } }", + "hours": "{ hours, plural, =1 {1 hour } andere {# hours } }", + "minutes": "{{minutes}} min", + "seconds": "{{seconds}} sec" + }, + "realtime": "Realtime", + "history": "Geschiedenis", + "last-prefix": "laatste", + "period": "van {{ startTime }} tot {{ endTime }}", + "edit": "Tijdvenster bewerken", + "date-range": "Periode", + "for-all-time": "Permanent", + "last": "Laatste", + "time-period": "Periode", + "hide": "Verbergen", + "interval": "Interval", + "just-now": "Daarnet", + "ago": "geleden" + }, + "user": { + "all": "Alle", + "all-users": "Alle gebruikers", + "groups": "Groepen", + "user": "Gebruiker", + "users": "Gebruikers", + "management": "Gebruikersbeheer", + "customer-users": "Gebruikers van klanten", + "tenant-admins": "Tenant Admins", + "sys-admin": "Systeembeheerder", + "tenant-admin": "Tenant beheerder", + "customer": "Klant", + "anonymous": "Anoniem", + "add": "Gebruiker toevoegen", + "delete": "Gebruiker verwijderen", + "add-user-text": "Nieuwe gebruiker toevoegen", + "no-users-text": "Geen gebruikers gevonden", + "user-details": "Gegevens van de gebruiker", + "delete-users": "Gebruikers verwijderen", + "delete-user-title": "Weet u zeker dat u de '{{userEmail}}' van de gebruiker wilt verwijderen?", + "delete-user-text": "Opgelet, na de bevestiging worden de gebruiker en alle gerelateerde gegevens onherstelbaar.", + "delete-users-title": "Weet u zeker dat u { count, plural, =1 {1 user} andere {# users} } wilt verwijderen?", + "delete-users-action-title": "Verwijder { count, plural, =1 {1 user} andere {# users} }", + "delete-users-text": "Opgelet, na de bevestiging worden alle geselecteerde gebruikers verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", + "activation-email-sent-message": "Activeringsmail is succesvol verzonden!", + "resend-activation": "Activering opnieuw verzenden", + "email": "E-mail", + "email-required": "E-mail is vereist.", + "invalid-email-format": "Ongeldig e-mailformaat.", + "first-name": "Voornaam", + "last-name": "Achternaam", + "description": "Omschrijving: __________", + "default-dashboard": "Standaard dashboard", + "always-fullscreen": "Altijd volledig scherm", + "select-user": "Selecteer gebruiker", + "no-users-matching": "Er zijn geen gebruikers gevonden die overeenkomen met '{{entity}}'.", + "user-required": "Gebruiker is vereist", + "activation-method": "Activeringsmethode", + "display-activation-link": "Activeringslink weergeven", + "send-activation-mail": "Activeringsmail versturen", + "activation-link": "Activeringslink voor gebruiker", + "activation-link-text": "Om de gebruiker te activeren gebruikt u de volgende activation link:", + "copy-activation-link": "Activeringslink kopiëren", + "activation-link-copied-message": "De activeringslink van de gebruiker is gekopieerd naar het klembord", + "details": "Details", + "login-as-tenant-admin": "Aanmelden als tenantbeheerder", + "login-as-customer-user": "Inloggen als klantgebruiker", + "select-group-to-add": "Selecteer doelgroep om geselecteerde gebruikers toe te voegen", + "select-group-to-move": "Selecteer de doelgroep om geselecteerde gebruikers te verplaatsen", + "remove-users-from-group": "Weet je zeker dat je { count, plural, =1 {1 user} andere {# users} } uit groep '{{entityGroup}}' wilt verwijderen?", + "group": "Groep gebruikers", + "list-of-groups": "{ count, plural, =1 {One user group} andere {List of # user groups} }", + "group-name-starts-with": "Gebruikersgroepen waarvan de naam begint met '{{prefix}}'", + "search": "Gebruikers zoeken", + "selected-users": "{ count, plural, =1 {1 user} andere {# users} } geselecteerd", + "disable-account": "Gebruikersaccount uitschakelen", + "enable-account": "Gebruikersaccount inschakelen", + "enable-account-message": "Gebruikersaccount is succesvol ingeschakeld!", + "disable-account-message": "Gebruikersaccount is met succes uitgeschakeld!", + "copyId": "Kopieer gebruikers-ID", + "idCopiedMessage": "Gebruikers-ID is gekopieerd naar klembord", + "user-list": "Lijst met gebruikers", + "user-list-required": "Gebruikerslijst is vereist" + }, + "value": { + "type": "Soort waarde", + "string": "Snaar", + "string-value": "Tekenreeks waarde", + "string-value-required": "Tekenreekswaarde is vereist", + "integer": "Geheel getal", + "integer-value": "Gehele waarde", + "integer-value-required": "Geheel getal is vereist", + "invalid-integer-value": "Ongeldige waarde van een geheel getal", + "double": "Dubbel", + "double-value": "Dubbele waarde", + "double-value-required": "Dubbele waarde is vereist", + "boolean": "Booleaans", + "boolean-value": "Booleaanse waarde", + "false": "Vals", + "true": "Waar", + "long": "Lang", + "json": "JSON", + "json-value": "JSON-waarde", + "json-value-invalid": "JSON-waarde heeft een ongeldige indeling", + "json-value-required": "JSON-waarde is vereist." + }, + "version-control": { + "version-control": "Versiebeheer", + "management": "Beheer van versiebeheer", + "search": "Versies zoeken", + "branch": "Tak", + "default": "Verstek", + "select-branch": "Selecteer filiaal", + "branch-required": "Filiaal is vereist", + "create-entity-version": "Entiteitsversie maken", + "version-name": "Versienaam", + "version-name-required": "Versienaam is vereist", + "author": "Auteur", + "export-relations": "Exportrelaties", + "export-attributes": "Attributen exporteren", + "export-credentials": "Inloggegevens exporteren", + "export-group-entities": "Groepsentiteiten exporteren", + "export-roles": "Rollen exporteren", + "entity-versions": "Versies van entiteiten", + "versions": "Versies", + "created-time": "Gecreëerde tijd", + "version-id": "Versie-ID", + "no-entity-versions-text": "Geen entiteitsversies gevonden", + "no-versions-text": "Geen versies gevonden", + "copy-full-version-id": "Kopieer de volledige versie-id", + "create-version": "Versie maken", + "creating-version": "Versie maken... Een ogenblik geduld", + "nothing-to-commit": "Geen wijzigingen om vast te leggen", + "restore-version": "Versie herstellen", + "restore-entity-from-version": "Entiteit herstellen vanaf versie '{{versionName}}'", + "restoring-entity-version": "Entiteitsversie herstellen... Een ogenblik geduld", + "load-relations": "Relaties met belasting", + "load-attributes": "Attributen laden", + "load-credentials": "Inloggegevens laden", + "load-group-entities": "Groepsentiteiten laden", + "load-roles": "Rollen laden", + "compare-with-current": "Vergelijk met de huidige", + "diff-entity-with-version": "Verschil met entiteitsversie '{{versionName}}'", + "previous-difference": "Vorig verschil", + "next-difference": "Volgende Verschil", + "current": "Actueel", + "differences": "{ count, plural, =1 {1 difference} andere {# differences} }", + "create-entities-version": "Versie van entiteiten maken", + "default-sync-strategy": "Standaard synchronisatiestrategie", + "sync-strategy-merge": "Fuseren", + "sync-strategy-overwrite": "Overschrijven", + "entities-to-export": "Te exporteren entiteiten", + "entities-to-restore": "Entiteiten die moeten worden hersteld", + "sync-strategy": "Synchronisatie strategie", + "all-entities": "Alle entiteiten", + "no-entities-to-export-prompt": "Geef entiteiten op die u wilt exporteren", + "no-entities-to-restore-prompt": "Geef entiteiten op die u wilt herstellen", + "add-entity-type": "Entiteitstype toevoegen", + "remove-all": "Alles verwijderen", + "version-create-result": "{ added, plural, =0 {No entities} =1 {1 entity} andere {# entities} } toegevoegd.
{ modified, plural, =0 {No entities} =1 {1 entity} andere {# entities} } gewijzigd.
{ removed, plural, =0 {No entities} =1 {1 entity} andere {# entities} } verwijderd.", + "remove-other-entities": "Andere entiteiten verwijderen", + "find-existing-entity-by-name": "Bestaande entiteit zoeken op naam", + "restore-entities-from-version": "Entiteiten herstellen vanaf versie '{{versionName}}'", + "restoring-entities-from-version": "Entiteiten herstellen... Een ogenblik geduld", + "no-entities-restored": "Geen entiteiten hersteld", + "created": "{{created}} gemaakt", + "updated": "{{updated}} geüpdatet", + "deleted": "{{deleted}} geschrapt", + "groups-created": "{ created, plural, =1 {1 group} andere {# groups} } gemaakt", + "groups-updated": "{ updated, plural, =1 {1 group} andere {# groups} } bijgewerkt", + "groups-deleted": "{ deleted, plural, =1 {1 group} andere {# groups} } verwijderd", + "remove-other-entities-confirm-text": "Opgelet! Hiermee worden alle huidige entiteiten
die niet aanwezig zijn in de versie die u wilt herstellen, permanent verwijderd.

Typ andere entiteiten verwijderen om te bevestigen.", + "auto-commit-to-branch": "Automatisch vastleggen op {{ branch }} filiaal", + "default-create-entity-version-name": "{{entityName}} update", + "sync-strategy-merge-hint": "Hiermee maakt of actualiseert u geselecteerde entiteiten in de opslagplaats. Alle andere opslagplaatsentiteiten worden niet gewijzigd.", + "sync-strategy-overwrite-hint": "Hiermee maakt of actualiseert u geselecteerde entiteiten in de opslagplaats. Alle andere opslagplaatsentiteiten worden verwijderd.", + "device-credentials-conflict": "Kan het device niet laden met externe id {{entityId}}
omdat dezelfde referenties al aanwezig zijn in de database voor een ander device.
Overweeg om de instelling voor laadreferenties in het herstelformulier uit te schakelen.", + "missing-referenced-entity": "Het laden van de {{sourceEntityTypeName}} met externe id {{sourceEntityId}}
omdat het verwijst naar ontbrekende {{targetEntityTypeName}} met id {{targetEntityId}}.", + "add-entity-groups": "Entiteitsgroepen toevoegen", + "entity-groups": "Entiteitsgroepen", + "integration-routing-key-conflict": "Kan de integratie met externe id niet laden {{entityId}}
vanwege dezelfde integratiesleutel is al aanwezig in de database voor een andere integratie.
Overweeg om de instelling voor het automatisch genereren van integratiesleutels in het herstelformulier in te schakelen.", + "auto-generate-integration-key": "Integratiesleutel automatisch genereren", + "runtime-failed": "Mislukt: {{message}}", + "auto-commit-settings-read-only-hint": "De functie voor automatisch vastleggen werkt niet met de ingeschakelde niet editeerbaar optie in de instellingen van de opslagplaats." + }, + "widget": { + "widget-library": "Widgets Bibliotheek", + "widget-bundle": "Widgets Bundel", + "all-bundles": "Alle bundels", + "select-widgets-bundle": "Selecteer widgetsbundel", + "management": "Beheer van widgets", + "editor": "Widget-editor", + "widget-type-not-found": "Probleem met het laden van de configuratie van de widget.
Waarschijnlijk geassocieerd\n Widgettype is verwijderd.", + "widget-type-load-error": "Widget is niet geladen vanwege de volgende fouten:", + "remove": "Widget verwijderen", + "edit": "Widget bewerken", + "remove-widget-title": "Weet je zeker dat je de widget '{{widgetTitle}}' wilt verwijderen?", + "remove-widget-text": "Na de bevestiging worden de widget en alle gerelateerde gegevens onherstelbaar.", + "timeseries": "Timeseries", + "search-data": "Zoekgegevens", + "no-data-found": "Geen gegevens gevonden", + "latest": "Meest recente waarden", + "rpc": "Controle widget", + "alarm": "Alarm widget", + "static": "Statische widget", + "select-widget-type": "Selecteer widgettype", + "missing-widget-title-error": "De titel van de widget moet worden opgegeven!", + "widget-saved": "Widget opgeslagen", + "unable-to-save-widget-error": "Kan widget niet opslaan! Widget heeft fouten!", + "save": "Widget opslaan", + "saveAs": "Widget opslaan als", + "save-widget-type-as": "Widgettype opslaan als", + "save-widget-type-as-text": "Voer een nieuwe widgettitel in en/of selecteer de bundel met doelwidgets", + "toggle-fullscreen": "Volledig scherm in- en uitschakelen", + "run": "Widget uitvoeren", + "title": "Titel van de widget", + "title-required": "De titel van de widget is vereist.", + "type": "Type widget", + "resources": "Weg", + "resource-url": "JavaScript/CSS-URL", + "resource-is-module": "Is-module", + "remove-resource": "Bron verwijderen", + "add-resource": "Bron toevoegen", + "html": ".HTML", + "tidy": "Ordelijk", + "css": ".CSS", + "settings-schema": "Schema Instellingen", + "datakey-settings-schema": "Schema voor instellingen voor gegevenssleutels", + "latest-datakey-settings-schema": "Schema met meest recente instellingen voor gegevenssleutels", + "widget-settings": "Widget instellingen", + "description": "Omschrijving: __________", + "image-preview": "Voorbeeld van afbeelding", + "settings-form-selector": "Formulierkiezer voor instellingen", + "data-key-settings-form-selector": "Formulierkiezer voor instellingen voor gegevenssleutels", + "latest-data-key-settings-form-selector": "Formulierkiezer voor de meest recente gegevenssleutelinstellingen", + "javascript": "Javascript", + "js": ".JS", + "remove-widget-type-title": "Weet je zeker dat je het widgettype '{{widgetName}}' wilt verwijderen?", + "remove-widget-type-text": "Na de bevestiging worden het type widget en alle gerelateerde gegevens onherstelbaar.", + "remove-widget-type": "Widgettype verwijderen", + "add-widget-type": "Nieuw widgettype toevoegen", + "widget-type-load-failed-error": "Kan widgettype niet laden!", + "widget-template-load-failed-error": "Kan widgetsjabloon niet laden!", + "add": "Widget toevoegen", + "undo": "Wijzigingen in widgets ongedaan maken", + "export": "Widget exporteren", + "export-data": "Widgetgegevens exporteren", + "export-to-csv": "Exporteer gegevens naar CSV...", + "export-to-excel": "Gegevens exporteren naar XLS...", + "export-to-excel-xlsx": "Gegevens exporteren naar XLSX...", + "no-data": "Geen gegevens om weer te geven op widget", + "data-overflow": "Widget geeft {{count}} van {{total}} entiteiten weer", + "alarm-data-overflow": "Widget geeft alarmen weer voor {{allowedEntities}} (maximaal toegestane) entiteiten van {{totalEntities}} entiteiten", + "search": "Widget zoeken", + "filter": "Type widgetfilter", + "loading-widgets": "Widgets worden geladen...", + "widget-template-error": "Ongeldige HTML-sjabloon voor widgets." + }, + "widget-action": { + "header-button": "Knop koptekst widget", + "open-dashboard-state": "Navigeer naar de nieuwe dashboardstatus", + "update-dashboard-state": "Huidige dashboardstatus bijwerken", + "open-dashboard": "Navigeer naar ander dashboard", + "custom": "Aangepaste actie", + "custom-pretty": "Aangepaste actie (met HTML-sjabloon)", + "custom-pretty-error-title": "Fout in aangepast dialoogvenster", + "custom-pretty-template-error": "Ongeldige sjabloon voor een aangepast dialoogvenster.", + "custom-pretty-controller-error": "Er is een fout opgetreden bij het evalueren van de aangepaste dialoogvensterfunctie.", + "mobile-action": "Mobiele actie", + "target-dashboard-state": "Status van doeldashboard", + "target-dashboard-state-required": "De status van het doeldashboard is vereist", + "set-entity-from-widget": "Entiteit instellen vanuit widget", + "target-dashboard": "Dashboard voor doelen", + "open-right-layout": "Dashboardlay-out rechts openen (mobiele weergave)", + "state-display-type": "Optie voor weergave van de status van het dashboard", + "open-normal": "Normaal", + "open-in-separate-dialog": "Openen in apart dialoogvenster", + "open-in-popover": "Openen in popover", + "dialog-title": "Titel van het dialoogvenster", + "dialog-hide-dashboard-toolbar": "Dashboardwerkbalk verbergen in dialoogvenster", + "dialog-width": "Dialoogvensterbreedte in procenten ten opzichte van viewportbreedte", + "dialog-height": "Dialoogvensterhoogte in procenten ten opzichte van viewporthoogte", + "dialog-size-range-error": "De procentuele waarde van de dialooggrootte moet tussen 1 en 100 liggen.", + "popover-preferred-placement": "Voorkeursplaatsing van de popover", + "popover-placement-top": "Boven", + "popover-placement-topLeft": "Linksboven", + "popover-placement-topRight": "Rechtsboven", + "popover-placement-right": "Rechts", + "popover-placement-rightTop": "Rechtsboven", + "popover-placement-rightBottom": "Rechtsonder", + "popover-placement-bottom": "Bodem", + "popover-placement-bottomLeft": "Linksonder", + "popover-placement-bottomRight": "Rechtsonder", + "popover-placement-left": "Links", + "popover-placement-leftTop": "Links boven", + "popover-placement-leftBottom": "Links onder", + "popover-hide-on-click-outside": "Popover verbergen bij klikken aan de buitenkant", + "popover-hide-dashboard-toolbar": "Dashboardwerkbalk verbergen in pop-over", + "popover-width": "Popover-breedte in browsereenheden (bijv. 100px, 25vw)", + "popover-height": "Popover-hoogte in browsereenheden (bijv. 100px, 25vh)", + "popover-style": "Popover-stijl", + "open-new-browser-tab": "Openen in een nieuw browsertabblad", + "mobile": { + "action-type": "Mobiel actietype", + "action-type-required": "Mobiel actietype is vereist", + "take-picture-from-gallery": "Maak een foto uit de galerij", + "take-photo": "Maak een foto", + "map-direction": "Routebeschrijving openen", + "map-location": "Locatie op de kaart openen", + "scan-qr-code": "QR-code scannen", + "make-phone-call": "Telefoneren", + "get-location": "Telefoonlocatie ophalen", + "take-screenshot": "Screenshot maken" + } + }, + "widgets-bundle": { + "current": "Huidige bundel", + "widgets-bundles": "Widgets Bundels", + "add": "Widgets-bundel toevoegen", + "delete": "Widgetsbundel verwijderen", + "title": "Titel", + "title-required": "Titel is vereist.", + "title-max-length": "Titel moet kleiner zijn dan 256 tekens", + "description": "Omschrijving: __________", + "image-preview": "Voorbeeld van afbeelding", + "add-widgets-bundle-text": "Nieuwe widgetsbundel toevoegen", + "no-widgets-bundles-text": "Geen widgetbundels gevonden", + "empty": "Widgets-bundel is leeg", + "details": "Details", + "widgets-bundle-details": "Details van de widgetsbundel", + "delete-widgets-bundle-title": "Weet je zeker dat je de widgets bundel '{{widgetsBundleTitle}}' wilt verwijderen?", + "delete-widgets-bundle-text": "Opgelet, na de bevestiging worden de widgetbundel en alle gerelateerde gegevens onherstelbaar.", + "delete-widgets-bundles-title": "Weet u zeker dat u { count, plural, =1 {1 widgets bundle} andere {# widgets bundles} } wilt verwijderen?", + "delete-widgets-bundles-action-title": "Verwijder { count, plural, =1 {1 widgets bundle} andere {# widgets bundles} }", + "delete-widgets-bundles-text": "Opgelet, na de bevestiging worden alle geselecteerde widgetbundels verwijderd en worden alle gerelateerde gegevens onherstelbaar.", + "no-widgets-bundles-matching": "Er zijn geen widgetbundels gevonden die overeenkomen met '{{widgetsBundle}}'.", + "widgets-bundle-required": "Widgets-bundel is vereist.", + "system": "Systeem", + "import": "Widgets importeren bundel", + "export": "Widgets exporteren bundel", + "export-failed-error": "Kan widgetbundel niet exporteren: {{error}}", + "create-new-widgets-bundle": "Nieuwe widgetsbundel maken", + "widgets-bundle-file": "Widgets bundel bestand", + "invalid-widgets-bundle-file-error": "Kan widgetbundel niet importeren: Ongeldige widgetbundel gegevensstructuur.", + "search": "Widgetbundels zoeken", + "selected-widgets-bundles": "{ count, plural, =1 {1 widgets bundle} andere {# widgets bundles} } geselecteerd", + "open-widgets-bundle": "Widgets-bundel openen", + "loading-widgets-bundles": "Widgets bundels worden geladen..." + }, + "widget-config": { + "data": "Gegevens", + "settings": "Instellingen", + "advanced": "Geavanceerd", + "title": "Titel", + "title-tooltip": "Titel Tooltip", + "general-settings": "Algemene instellingen", + "display-title": "Titel van widget weergeven", + "drop-shadow": "Schaduw verwijderen", + "enable-fullscreen": "Volledig scherm inschakelen", + "enable-data-export": "Gegevensexport inschakelen", + "background-color": "Achtergrondkleur", + "text-color": "Tekstkleur", + "padding": "Spacties", + "margin": "Marge", + "widget-style": "Widget stijl", + "widget-css": "Widget CSS", + "title-style": "Titel stijl", + "mobile-mode-settings": "Mobiele modus", + "order": "Bevelen", + "height": "Hoogte", + "mobile-hide": "Widget verbergen in mobiele modus", + "desktop-hide": "Widget verbergen in desktopmodus", + "units": "Speciaal symbool om naast waarde weer te geven", + "decimals": "Aantal cijfers na drijvende komma", + "timewindow": "Tijdvenster", + "use-dashboard-timewindow": "Dashboardtijdvenster gebruiken", + "display-timewindow": "Tijdvenster weergeven", + "legend": "Legende", + "display-legend": "Legende weergeven", + "datasources": "Gegevensbronnen", + "maximum-datasources": "Maximaal { count, plural, =1 {1 datasource is allowed.} andere {# datasources are allowed} }", + "timeseries-key-error": "Er moet ten minste één tijdreeksgegevenssleutel worden opgegeven", + "datasource-type": "Type", + "datasource-parameters": "Parameters", + "remove-datasource": "Gegevensbron verwijderen", + "add-datasource": "Gegevensbron toevoegen", + "target-device": "Doelapparaat", + "alarm-source": "Alarmbron", + "actions": "Acties", + "action": "Actie", + "add-action": "Actie toevoegen", + "search-actions": "Zoekacties", + "no-actions-text": "Geen acties gevonden", + "action-source": "Bron van actie", + "action-source-required": "Actiebron is vereist.", + "action-name": "Naam", + "action-name-required": "De naam van de actie is vereist.", + "action-name-not-unique": "Er bestaat al een andere actie met dezelfde naam.
De naam van de actie moet uniek zijn binnen dezelfde actiebron.", + "action-icon": "Pictogram", + "show-hide-action-using-function": "Actie tonen/verbergen met behulp van functie", + "action-type": "Type", + "action-type-required": "Actietype is vereist.", + "edit-action": "Actie bewerken", + "delete-action": "Actie verwijderen", + "delete-action-title": "Widgetactie verwijderen", + "delete-action-text": "Weet je zeker dat je de actie voor het verwijderen van widgets met de naam '{{actionName}}' wilt?", + "title-icon": "Titel icoon", + "display-icon": "Titelpictogram weergeven", + "icon-color": "Kleur van het pictogram", + "icon-size": "Grootte van het pictogram", + "advanced-settings": "Geavanceerde instellingen", + "data-settings": "Gegevensinstellingen", + "no-data-display-message": "Alternatief bericht 'Geen gegevens om weer te geven'", + "data-page-size": "Maximum aantal entiteiten per gegevensbron", + "settings-component-not-found": "Instellingen formuliercomponent niet gevonden voor selector '{{selector}}'" + }, + "widget-type": { + "import": "Type widget importeren", + "export": "Widgettype exporteren", + "export-failed-error": "Kan widgettype niet exporteren: {{error}}", + "create-new-widget-type": "Nieuw widgettype maken", + "widget-type-file": "Bestand van het type widget", + "invalid-widget-type-file-error": "Kan widgettype niet importeren: Ongeldige gegevensstructuur van het widgettype." + }, + "self-registration": { + "self-registration": "Zelf registratie", + "self-registration-url": "URL voor zelfregistratie", + "captcha-version": "reCAPTCHA-versie", + "captcha-action": "naam van de actie van het reCAPTCHA-logboek", + "captcha-site-key": "reCAPTCHA-sitesleutel", + "captcha-secret-key": "reCAPTCHA geheime sleutel", + "notification-email": "E-mail met melding", + "privacy-policy-text": "Tekst van het privacybeleid", + "terms-of-use-text": "Tekst van de gebruiksvoorwaarden", + "text-message-page": "Sms voor aanmeldingspagina", + "enable-mobile-self-registration": "Zelfregistratie inschakelen vanuit de mobiele applicatie", + "mobile-self-registration-title": "Instellingen voor zelfregistratie van mobiele applicaties", + "mobile-package": "Applicatie pakket", + "mobile-package-placeholder": "Vb.: my.example.app", + "mobile-package-hint": "Voor Android: uw eigen unieke applicatie-ID. Voor iOS: ID van productbundel.", + "mobile-package-required": "Aanvraagpakket is vereist", + "mobile-app-secret": "Geheim van de toepassing", + "invalid-mobile-app-secret": "Het geheim van de toepassing mag alleen alfanumerieke tekens bevatten en moet tussen de 16 en 2048 tekens lang zijn.", + "copy-mobile-app-secret": "Toepassingsgeheim kopiëren", + "mobile-app-scheme": "URL-schema van toepassing", + "mobile-app-scheme-placeholder": "Vb.: maatwerk", + "mobile-app-scheme-required": "Het URL-schema van de aanvraag is vereist", + "mobile-app-host": "Hostnaam van applicatie-URL", + "mobile-app-host-placeholder": "Bijv.: mijn.app.host", + "mobile-app-host-required": "Hostnaam van toepassings-URL is vereist", + "show-privacy-policy": "Toon Privacybeleid", + "show-terms-of-use": "Toon Gebruiksvoorwaarden", + "domain-settings": "Domein instellingen", + "general-settings": "Algemene instellingen" + }, + "solution-template": { + "solution-template": "Oplossing sjabloon", + "solution-templates": "Sjablonen voor oplossingen", + "management": "Oplossingssjablonen beheren", + "details": "Details", + "install": "Installeren", + "level": "Niveau", + "install-title": "Oplossingssjabloon succesvol geïnstalleerd", + "install-failed-title": "Installatie van oplossingssjabloon mislukt", + "instructions": "Aanwijzingen", + "goto-main-dashboard": "Ga naar het hoofddashboard", + "delete": "Verwijderen", + "delete-solution-title": "Weet u zeker dat u de oplossing '{{solutionTitle}}' wilt verwijderen?", + "delete-solution-text": "Opgelet, na de bevestiging worden de oplossing en alle gerelateerde gegevens onherstelbaar.", + "installing": "Oplossingssjabloon installeren..." + }, + "markdown": { + "edit": "Bewerken", + "preview": "Voorbeeld", + "copy-code": "Klik om te kopiëren", + "copied": "Gekopieerd!" + }, + "white-labeling": { + "white-labeling": "White labelling", + "white-labeling-general": "Algemene White Labeling", + "login-white-labeling": "Inloggen White Labeling", + "general": "Algemeen", + "login": "Inloggen", + "preview": "Voorbeeld", + "app-title": "Titel van de toepassing", + "favicon": "Website icoon", + "favicon-description": "*.ico, *.gif of *.png afbeelding met een maximale grootte {{kbSize}} KBytes.", + "favicon-size-error": "De afbeelding van de website is te groot. De maximaal toegestane afbeeldingsgrootte van de website {{kbSize}} KBytes.", + "favicon-type-error": "Ongeldige bestandsindeling voor websiteafbeeldingen. Alleen ICO-, GIF- of PNG-afbeeldingen worden geaccepteerd.", + "drop-favicon-image": "Zet een afbeelding van een websitepictogram neer of klik om een bestand te selecteren om te uploaden.", + "drop-favicon-image-or": "Sleep een afbeelding van een websitepictogram of", + "no-favicon-image": "Geen pictogram geselecteerd", + "logo": "Logo", + "logo-description": "Elke afbeelding met een maximale grootte {{kbSize}} KBytes.", + "logo-size-error": "De afbeelding van het logo is te groot. De maximaal toegestane afbeeldingsgrootte van het logo {{kbSize}} KBytes.", + "logo-type-error": "Ongeldig bestandsformaat voor logo's. Alleen afbeeldingen worden geaccepteerd.", + "drop-logo-image": "Zet een logo-afbeelding neer of klik om een bestand te selecteren om te uploaden.", + "drop-logo-image-or": "Een logo-afbeelding slepen en neerzetten of", + "no-logo-image": "Geen logo geselecteerd", + "logo-height": "Hoogte logo, px", + "primary-palette": "Primair palet", + "accent-palette": "Accent palet", + "customize-palette": "Aanpassen", + "advanced-css": "Geavanceerde CSS", + "edit-palette": "Palet bewerken", + "save-palette": "Palet opslaan", + "primary-background": "Primaire achtergrond", + "secondary-background": "Secundaire achtergrond", + "hue1": "TINT 1", + "hue2": "HUE 2", + "hue3": "HUE 3", + "page-background-color": "Achtergrondkleur van de pagina", + "dark-foreground": "Donkere voorgrond", + "domain-name": "Domeinnaam", + "base-url": "Basis-URL", + "base-url-required": "Basis-URL is vereist.", + "prohibit-different-url": "Verbieden om de hostnaam te gebruiken in de headers van de clientaanvraag", + "prohibit-different-url-hint": "Deze instelling moet zijn ingeschakeld voor productieomgevingen. Kan beveiligingsproblemen veroorzaken wanneer deze is uitgeschakeld", + "help-link-base-url": "Help-links basis-url", + "ui-help-base-url": "Help-basis-URL van de gebruikersinterface", + "enable-help-links": "Helpkoppelingen inschakelen", + "error-verification-url": "Een domeinnaam mag geen symbolen '/' en ':' bevatten. Voorbeeld: thingsboard.io", + "show-platform-name-version": "Platformnaam en -versie weergeven", + "platform-name": "Naam van het platform", + "platform-version": "Platform versie", + "version-mask": "{{name}} v.{{version}}", + "position": { + "label": "Platformnaam en versiepositie", + "under-logo": "Onder het logo", + "bottom": "Onderaan het inlogformulier" + } + }, + "widgets": { + "chart": { + "common-settings": "Algemene instellingen", + "enable-stacking-mode": "Stapelmodus inschakelen", + "line-shadow-size": "Grootte van de lijnschaduw", + "display-smooth-lines": "Vloeiende (gebogen) lijnen weergeven", + "default-bar-width": "Standaardbalkbreedte voor niet-geaggregeerde gegevens (milliseconden)", + "bar-alignment": "Staaf uitlijning", + "bar-alignment-left": "Links", + "bar-alignment-right": "Rechts", + "bar-alignment-center": "Middelpunt", + "default-font-size": "Standaard lettergrootte", + "default-font-color": "Standaard tekstkleur", + "thresholds-line-width": "Standaardregeldikte voor alle drempels", + "tooltip-settings": "Tooltip-instellingen", + "show-tooltip": "Tooltip weergeven", + "hover-individual-points": "Beweeg de muisaanwijzer over individuele punten", + "show-cumulative-values": "Cumulatieve waarden weergeven in stapelmodus", + "hide-zero-false-values": "Nul/onwaar-waarden verbergen in knopinfo", + "tooltip-value-format-function": "Functie voor het opmaken van knoppuntwaarden", + "grid-settings": "Raster instellingen", + "show-vertical-lines": "Verticale lijnen weergeven", + "show-horizontal-lines": "Horizontale lijnen weergeven", + "grid-outline-border-width": "Rasteromtrek/randbreedte (px)", + "primary-color": "Primaire kleur", + "background-color": "Achtergrondkleur", + "ticks-color": "De kleur van de teken", + "xaxis-settings": "Instellingen van de X-as", + "axis-title": "Titel van de as", + "xaxis-tick-labels-settings": "Instellingen voor labels voor tikken op de X-as", + "show-tick-labels": "Labels voor asvinkjes weergeven", + "yaxis-settings": "Y-as instellingen", + "min-scale-value": "Minimumwaarde op de weegschaal", + "max-scale-value": "Maximale waarde op de weegschaal", + "yaxis-tick-labels-settings": "Instellingen voor labels voor Y-as tikken", + "tick-step-size": "Stapgrootte tussen teken", + "number-of-decimals": "Het aantal decimalen dat moet worden weergegeven", + "ticks-formatter-function": "Teken formatter-functie", + "comparison-settings": "Vergelijkingsinstellingen", + "enable-comparison": "Vergelijking inschakelen", + "time-for-comparison": "Vergelijkingsperiode", + "time-for-comparison-previous-interval": "Vorig interval (standaard)", + "time-for-comparison-days": "Dag geleden", + "time-for-comparison-weeks": "Week geleden", + "time-for-comparison-months": "Maand geleden", + "time-for-comparison-years": "Jaar geleden", + "time-for-comparison-custom-interval": "Aangepast interval", + "custom-interval-value": "Aangepaste intervalwaarde (ms)", + "comparison-x-axis-settings": "Vergelijking X-asinstellingen", + "axis-position": "Positie van de as", + "axis-position-top": "Top (standaard)", + "axis-position-bottom": "Bodem", + "custom-legend-settings": "Aangepaste legende-instellingen", + "enable-custom-legend": "Aangepaste legende inschakelen (hiermee kunt u attribuut-/tijdreekswaarden gebruiken in sleutellabels)", + "key-name": "Naam van de sleutel", + "key-name-required": "Sleutelnaam is vereist", + "key-type": "Type sleutel", + "key-type-attribute": "Attribuut", + "key-type-timeseries": "Timeseries", + "label-keys-list": "Toetsenlijst voor labels", + "no-label-keys": "Geen sleutels geconfigureerd", + "add-label-key": "Nieuwe sleutel toevoegen", + "line-width": "Lijndikte", + "color": "Kleur", + "data-is-hidden-by-default": "Gegevens zijn standaard verborgen", + "disable-data-hiding": "Schakel het verbergen van gegevens uit", + "remove-from-legend": "Datakey verwijderen uit legende", + "exclude-from-stacking": "Uitsluiten van stapelen (beschikbaar in de modus \"Stapelen\")", + "line-settings": "Lijn instellingen", + "show-line": "Toon lijn", + "fill-line": "Lijn vullen", + "fill-line-opacity": "Dekking vullen", + "points-settings": "Punten instellingen", + "show-points": "Toon punten", + "points-line-width": "Lijndikte van punten", + "points-radius": "Straal van punten", + "point-shape": "Puntvorm", + "point-shape-circle": "Cirkel", + "point-shape-cross": "Kruis", + "point-shape-diamond": "Diamant", + "point-shape-square": "Vierkant", + "point-shape-triangle": "Driehoek", + "point-shape-custom": "Aangepaste functie", + "point-shape-draw-function": "Tekenfunctie voor puntvorm", + "show-separate-axis": "Afzonderlijke as weergeven", + "axis-position-left": "Links a-x positie", + "axis-position-right": "Rechts a-x positie", + "thresholds": "Drempels", + "no-thresholds": "Geen drempels geconfigureerd", + "add-threshold": "Nieuwe drempel toevoegen", + "show-values-for-comparison": "Historische waarden weergeven ter vergelijking", + "comparison-values-label": "Label met historische waarden", + "threshold-settings": "Drempelwaarde instellingen", + "use-as-threshold": "Sleutelwaarde gebruiken als drempelwaarde", + "threshold-line-width": "Lijnbreedte drempel", + "threshold-color": "Drempel kleur", + "common-pie-settings": "Algemene taartinstellingen", + "radius": "Radius", + "inner-radius": "Binnen radius", + "tilt": "Kantelen", + "stroke-settings": "Lijn instellingen", + "width-pixels": "Breedte (pixels)", + "show-labels": "Labels weergeven", + "animation-settings": "Animatie-instellingen", + "animated-pie": "Pie animatie inschakelen (experimenteel)", + "border-settings": "Randinstellingen", + "border-width": "Breedte van de edge", + "border-color": "Kleur van de edge", + "legend-settings": "Legende-instellingen", + "display-legend": "Legende weergeven", + "labels-font-color": "Letterkleur van labels" + }, + "dashboard-state": { + "dashboard-state-settings": "Instellingen voor de status van het dashboard", + "dashboard-state": "Status-id van dashboard", + "autofill-state-layout": "Standaard lay-outhoogte voor automatisch aanvullen", + "default-margin": "Standaard widgets marge", + "default-background-color": "Standaard achtergrondkleur", + "sync-parent-state-params": "Statusparameters synchroniseren met het bovenliggende dashboard" + }, + "date-range-navigator": { + "date-range-picker-settings": "Instellingen voor het kiezen van het datumbereik", + "hide-date-range-picker": "Datumbereik kiezer verbergen", + "picker-one-panel": "Datumbereik kiezer één paneel", + "picker-auto-confirm": "Datumbereik kiezer automatisch bevestigen", + "picker-show-template": "Datumbereik kiezer toon sjabloon", + "first-day-of-week": "Eerste dag van de week", + "interval-settings": "Interval-instellingen", + "hide-interval": "Interval verbergen", + "initial-interval": "Initieel interval", + "interval-hour": "Uur", + "interval-day": "Dag", + "interval-week": "Week", + "interval-two-weeks": "2 weken", + "interval-month": "Maand", + "interval-three-months": "3 maanden", + "interval-six-months": "6 maanden", + "step-settings": "Stap instellingen", + "hide-step-size": "Stapgrootte verbergen", + "initial-step-size": "Grootte van de eerste stap", + "hide-labels": "Labels verbergen", + "use-session-storage": "Sessieopslag gebruiken", + "localizationMap": { + "Sun": "Zon", + "Mon": "Ma", + "Tue": "Di", + "Wed": "Woe", + "Thu": "Do", + "Fri": "Vr", + "Sat": "Zaterdag", + "Jan": "Jan", + "Feb": "Februari", + "Mar": "Mar", + "Apr": "Apr", + "May": "Mei", + "Jun": "Jun", + "Jul": "Jul", + "Aug": "Aug", + "Sep": "Sep", + "Oct": "Oct", + "Nov": "Nov", + "Dec": "Dec", + "January": "Januari", + "February": "Februari", + "March": "Maart", + "April": "April", + "June": "Juni", + "July": "Juli", + "August": "Augustus", + "September": "September", + "October": "Oktober", + "November": "November", + "December": "December", + "Custom Date Range": "Aangepast datumbereik", + "Date Range Template": "Datumbereik sjabloon", + "Today": "Vandaag", + "Yesterday": "Gisteren", + "This Week": "Deze week", + "Last Week": "Vorige week", + "This Month": "Deze maand", + "Last Month": "Afgelopen maand", + "Year": "Jaar", + "This Year": "Dit jaar", + "Last Year": "Vorig jaar", + "Date picker": "Datumkiezer", + "Hour": "Uur", + "Day": "Dag", + "Week": "Week", + "2 weeks": "2 weken", + "Month": "Maand", + "3 months": "3 maanden", + "6 months": "6 maanden", + "Custom interval": "Aangepast interval", + "Interval": "Interval", + "Step size": "Stap grootte", + "Ok": "OK" + } + }, + "entities-hierarchy": { + "hierarchy-data-settings": "Instellingen voor hiërarchische gegevens", + "relations-query-function": "Queryfunctie voor knooppuntrelaties", + "has-children-function": "Knoop heeft een kinderfunctie", + "node-state-settings": "Instellingen voor knooppuntstatus", + "node-opened-function": "Standaard functie voor het openen van rule nodes", + "node-disabled-function": "Knooppuntuitgeschakelde functie", + "display-settings": "Weergave-instellingen", + "node-icon-function": "Knooppuntpictogram functie", + "node-text-function": "Knooppunttekstfunctie", + "sort-settings": "Sorteer instellingen", + "nodes-sort-function": "Sorteerfunctie voor rule nodes" + }, + "edge": { + "display-default-title": "Standaardtitel weergeven" + }, + "gateway": { + "general-settings": "Algemene instellingen", + "widget-title": "Titel van de widget", + "default-archive-file-name": "Standaard bestandsnaam voor archief", + "device-type-for-new-gateway": "Device type voor nieuwe gateway", + "messages-settings": "Instellingen voor Berichten", + "save-config-success-message": "Sms-bericht over succesvol opgeslagen gatewayconfiguratie", + "device-name-exists-message": "Sms-bericht wanneer het device met de ingevoerde naam al bestaat", + "gateway-title": "Gateway-formulier", + "read-only": "Alleen-lezen", + "events-title": "Titel van het formulier voor gateway events", + "events-filter": "Events filter", + "event-key-contains": "Events sleutel bevat..." + }, + "gauge": { + "default-color": "Standaard kleur", + "radial-gauge-settings": "Instellingen radiale meter", + "ticks-settings": "Vink instellingen aan", + "min-value": "Minimumwaarde", + "max-value": "Maximale waarde", + "start-ticks-angle": "Start tikt hoek", + "ticks-angle": "De hoek van teken", + "major-ticks-count": "Aantal belangrijke teken", + "major-ticks-color": "De belangrijkste kleur van teken", + "minor-ticks-count": "Kleine vinkjes tellen", + "minor-ticks-color": "Kleine vinkjes kleur", + "tick-numbers-font": "Tick nummers lettertype", + "unit-title-settings": "Instellingen voor eenheidstitels", + "show-unit-title": "Titel van eenheid weergeven", + "unit-title": "Titel van de eenheid", + "title-font": "Lettertype van titeltekst", + "units-settings": "Instellingen van de eenheden", + "units-font": "Eenheden tekstlettertype", + "value-box-settings": "Instellingen voor het vak Waarde", + "show-value-box": "Vak Waarde weergeven", + "value-int": "Cijfers tellen mee voor het gehele deel van de waarde", + "value-font": "Waarde tekstlettertype", + "value-box-rect-stroke-color": "Voordeelvak rechthoekige lijnkleur", + "value-box-rect-stroke-color-end": "Voordeelvak rechthoekige lijnkleur - eindverloop", + "value-box-background-color": "Achtergrondkleur van het waardevak", + "value-box-shadow-color": "Waarde vak schaduw kleur", + "plate-settings": "Plaat instellingen", + "show-plate-border": "Plaatrand weergeven", + "plate-color": "Kleur van de plaat", + "needle-settings": "Naald instellingen", + "needle-circle-size": "De grootte van de naaldcirkel", + "needle-color": "Kleur van de naald", + "needle-color-end": "Naaldkleur - eindverloop", + "needle-color-shadow-up": "Bovenste helft van de kleur van de naaldschaduw", + "needle-color-shadow-down": "Kleur slagschaduw naald", + "highlights-settings": "Instellingen voor markeringen", + "highlights-width": "Breedte van hoogtepunten", + "highlights": "Hoogtepunten", + "highlight-from": "Van", + "highlight-to": "Aan", + "highlight-color": "Kleur", + "no-highlights": "Geen markeringen geconfigureerd", + "add-highlight": "Markering toevoegen", + "animation-settings": "Animatie-instellingen", + "enable-animation": "Animatie inschakelen", + "animation-duration": "Duur van de animatie", + "animation-rule": "Animatie regel", + "animation-linear": "Lineair", + "animation-quad": "Quad", + "animation-quint": "Quint", + "animation-cycle": "Cyclus", + "animation-bounce": "Stuiteren", + "animation-elastic": "Elastisch", + "animation-dequad": "Splitsen", + "animation-dequint": "Afscheid nemen", + "animation-decycle": "Uitzetten", + "animation-debounce": "Debouncen", + "animation-delastic": "Delastisch", + "linear-gauge-settings": "Lineaire meetinstellingen", + "bar-stroke-width": "Lijnbreedte van de staaf", + "bar-stroke-color": "Kleur balklijn", + "bar-background-color": "Achtergrondkleur van de meterbalk", + "bar-background-color-end": "Achtergrondkleur van de balk - eindverloop", + "progress-bar-color": "Kleur voortgangsbalk", + "progress-bar-color-end": "Kleur voortgangsbalk - eindverloop", + "major-ticks-names": "Belangrijke namen van teken", + "show-stroke-ticks": "Toon teken beroerte", + "major-ticks-font": "Het belangrijkste teken lettertype", + "border-color": "Kleur van de edge", + "border-width": "Breedte van de edge", + "needle-circle-color": "De cirkelkleur van de naald", + "animation-target": "Animatie doel", + "animation-target-needle": "Naald", + "animation-target-plate": "Bord", + "common-settings": "Gemeenschappelijke meterinstellingen", + "gauge-type": "Type meter", + "gauge-type-arc": "Boog", + "gauge-type-donut": "Donut", + "gauge-type-horizontal-bar": "Rekstok", + "gauge-type-vertical-bar": "Verticale balk", + "donut-start-angle": "Hoek om mee te beginnen", + "bar-settings": "Meetbalk-instellingen", + "relative-bar-width": "Relatieve staafbreedte", + "neon-glow-brightness": "Neon glow effect helderheid, (0-100), 0 - effect uitschakelen", + "stripes-thickness": "Dikte van de strepen, 0 - geen strepen", + "rounded-line-cap": "Afgeronde lijndop weergeven", + "bar-color-settings": "Kleurinstellingen voor de balk", + "use-precise-level-color-values": "Gebruik nauwkeurige kleurniveaus", + "bar-colors": "Balkkleuren, van onder naar boven", + "color": "Kleur", + "no-bar-colors": "Geen balkkleuren geconfigureerd", + "add-bar-color": "Staafkleur toevoegen", + "from": "Van", + "to": "Aan", + "fixed-level-colors": "Staafkleuren met behulp van grenswaarden", + "gauge-title-settings": "Titelinstellingen meten", + "show-gauge-title": "Metertitel weergeven", + "gauge-title": "Titel van de meter", + "gauge-title-font": "Lettertype van de metertitel", + "unit-title-and-timestamp-settings": "Instellingen voor titel en tijdstempel van de eenheid", + "show-timestamp": "Tijdstempel van de waarde weergeven", + "timestamp-format": "Indeling tijdstempel", + "label-font": "Lettertype van label dat onder waarde wordt weergegeven", + "value-settings": "Waarde-instellingen", + "show-value": "Waardetekst weergeven", + "min-max-settings": "Instellingen voor minimale/maximale labels", + "show-min-max": "Min- en max-waarden weergeven", + "min-max-font": "Lettertype van minimum- en maximumlabels", + "show-ticks": "Toon teken", + "tick-width": "Tikbreedte", + "tick-color": "Vink kleur aan", + "tick-values": "Vink waarden aan", + "no-tick-values": "Geen vinkjes geconfigureerd", + "add-tick-value": "Tick waarde toevoegen" + }, + "gpio": { + "pin": "Speld", + "label": "Etiket", + "row": "Rijen", + "column": "Kolom", + "color": "Kleur", + "panel-settings": "Paneel instellingen", + "background-color": "Achtergrondkleur", + "gpio-switches": "GPIO-schakelaars", + "no-gpio-switches": "Geen GPIO-schakelaars geconfigureerd", + "add-gpio-switch": "GPIO-schakelaar toevoegen", + "gpio-status-request": "GPIO-statusaanvraag", + "method-name": "Naam van de methode", + "method-body": "Methode lichaam", + "gpio-status-change-request": "Aanvraag voor wijziging van GPIO-status", + "parse-gpio-status-function": "GPIO-statusfunctie parseren", + "gpio-leds": "GPIO-leds", + "no-gpio-leds": "Geen GPIO-leds geconfigureerd", + "add-gpio-led": "GPIO-led toevoegen" + }, + "html-card": { + "html": ".HTML", + "css": ".CSS" + }, + "input-widgets": { + "attribute-not-allowed": "Attribuutparameter kan niet worden gebruikt in deze widget", + "blocked-location": "Geolocatie is geblokkeerd in uw browser", + "claim-device": "Device claimen", + "claim-failed": "Kan het device niet claimen!", + "claim-not-found": "Device niet gevonden!", + "claim-successful": "Device is succesvol geclaimd!", + "date": "Datum", + "device-name": "Naam van het device", + "device-name-required": "Device naam is vereist", + "discard-changes": "Wijzigingen negeren", + "entity-attribute-required": "Entiteitskenmerk is vereist", + "entity-coordinate-required": "Beide velden, lengte- en breedtegraad, zijn verplicht", + "entity-timeseries-required": "Timeseries van entiteiten zijn vereist", + "get-location": "Huidige locatie ophalen", + "invalid-date": "Ongeldige datum", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", + "min-value-error": "Minimale waarde is {{value}}", + "max-value-error": "Maximale waarde is {{value}}", + "not-allowed-entity": "Geselecteerde entiteit kan geen gedeelde attributen hebben", + "no-attribute-selected": "Er is geen attribuut geselecteerd", + "no-datakey-selected": "Er is geen datakey geselecteerd", + "no-coordinate-specified": "Datakey voor lengte- en breedtegraad is niet opgegeven", + "no-entity-selected": "Geen entiteit geselecteerd", + "no-image": "Geen afbeelding", + "no-support-geolocation": "Uw browser ondersteunt geen geolocatie", + "no-support-web-camera": "Uw browser ondersteunt geen camera's", + "enable-https-use-widget": "Schakel HTTPS in om deze widget te gebruiken", + "no-found-your-camera": "Kan je camera niet vinden", + "no-permission-camera": "Toestemming is geweigerd door de gebruiker / Deze site heeft geen toestemming om de camera te gebruiken", + "no-timeseries-selected": "Geen tijdreeks geselecteerd", + "secret-key": "Geheime sleutel", + "secret-key-required": "Geheime sleutel is vereist", + "switch-attribute-value": "Entiteitskenmerkwaarde wijzigen", + "switch-camera": "Wissel van camera", + "switch-timeseries-value": "Tijdreekswaarde van entiteit wijzigen", + "take-photo": "Maak een foto", + "time": "Tijd", + "timeseries-not-allowed": "Timeseries parameter kan niet worden gebruikt in deze widget", + "update-failed": "Update mislukt", + "update-successful": "Update geslaagd", + "update-attribute": "Attribuut bijwerken", + "update-timeseries": "Timeseries bijwerken", + "value": "Waarde", + "general-settings": "Algemene instellingen", + "widget-title": "Titel van de widget", + "claim-button-label": "Knoplabel claimen", + "show-secret-key-field": "Toon 'Geheime sleutel' invoerveld", + "labels-settings": "Labels instellingen", + "show-labels": "Labels weergeven", + "device-name-label": "Label voor invoerveld device naam", + "secret-key-label": "Label voor het invoerveld van de geheime sleutel", + "messages-settings": "Instellingen voor berichten", + "claim-device-success-message": "Sms-bericht van succesvolle claiming van device", + "claim-device-not-found-message": "Sms wanneer device niet gevonden", + "claim-device-failed-message": "Sms-bericht van geclaimd device", + "claim-device-name-required-message": "Foutbericht 'Device naam vereist'", + "claim-device-secret-key-required-message": "Foutmelding 'Geheime sleutel vereist'", + "relations-settings": "Instellingen voor relaties", + "relate-device-to-state-entity": "Device relateren aan de entiteit met de huidige status", + "relate-direction": "Richting relateren", + "relate-direction-from": "Van", + "relate-direction-to": "Aan", + "relation-type": "Relatie type", + "show-label": "Etiket weergeven", + "label": "Etiket", + "required": "Vereist", + "required-error-message": "Foutmelding 'Vereist'", + "show-result-message": "Resultaatbericht weergeven", + "integer-field-settings": "Instellingen voor velden met gehele getallen", + "min-value": "Min waarde", + "max-value": "Maximale waarde", + "double-field-settings": "Dubbele veldinstellingen", + "text-field-settings": "Instellingen voor tekstvelden", + "min-length": "Min lengte", + "max-length": "Maximale lengte", + "checkbox-settings": "Selectievakjes instellingen", + "true-label": "Gecontroleerd etiket", + "false-label": "Onaangevinkt etiket", + "image-input-settings": "Instellingen voor beeldinvoer", + "display-preview": "Voorbeeld weergeven", + "display-clear-button": "Knop Wissen weergeven", + "display-apply-button": "Knop Toepassen weergeven", + "display-discard-button": "Knop voor weggooien weergeven", + "datetime-field-settings": "Instellingen voor datum-/tijdvelden", + "display-time-input": "Tijdinvoer weergeven", + "latitude-key-name": "Naam van breedtegraad-sleutel", + "longitude-key-name": "Lengtegraad sleutelnaam", + "show-get-location-button": "Toon knop 'Huidige locatie ophalen'", + "use-high-accuracy": "Gebruik hoge nauwkeurigheid", + "location-fields-settings": "Instellingen voor locatievelden", + "latitude-label": "Label voor breedtegraad", + "longitude-label": "Label voor lengtegraad", + "input-fields-alignment": "Uitlijning van invoervelden", + "input-fields-alignment-column": "Kolom (standaard)", + "input-fields-alignment-row": "Rijen", + "latitude-field-required": "Breedtegraadveld vereist", + "longitude-field-required": "Lengtegraadveld vereist", + "attribute-settings": "Attribuut instellingen", + "widget-mode": "Widget-modus", + "widget-mode-update-attribute": "Attribuut bijwerken", + "widget-mode-update-timeseries": "Timeseries bijwerken", + "attribute-scope": "Bereik van attribuut", + "attribute-scope-server": "Server-attribuut", + "attribute-scope-shared": "Gedeeld attribuut", + "value-required": "Vereiste waarde", + "image-settings": "Beeldinstellingen", + "image-format": "Beeldformaat", + "image-format-jpeg": ".JPEG", + "image-format-png": ".PNG", + "image-format-webp": ".WEBP", + "image-quality": "Beeldkwaliteit die compressie met verlies gebruikt, zoals jpeg en webp", + "max-image-width": "Maximale afbeeldingsbreedte", + "max-image-height": "Maximale afbeeldingshoogte", + "action-buttons": "Actieknoppen", + "show-action-buttons": "Actieknoppen weergeven", + "update-all-values": "Werk alle waarden bij, niet alleen gewijzigd", + "save-button-label": "Label met de knop 'OPSLAAN'", + "reset-button-label": "Knoplabel 'ONGEDAAN MAKEN'", + "group-settings": "Groepsinstellingen", + "show-group-title": "Titel weergeven voor groep velden, gerelateerd aan verschillende entiteiten", + "group-title": "Titel van de groep", + "fields-alignment": "Uitlijning van velden", + "fields-alignment-row": "Rij (standaard)", + "fields-alignment-column": "Kolom", + "fields-in-row": "Aantal velden in de rij", + "option-value": "Waarde (schrijf 'null' voor de optie leeg maken)", + "option-label": "Etiket", + "hide-input-field": "Invoerveld verbergen", + "datakey-type": "Type datakey", + "datakey-type-server": "Serverkenmerk (standaard)", + "datakey-type-shared": "Gedeeld attribuut", + "datakey-type-timeseries": "Timeseries", + "datakey-value-type": "Waardetype Datakey", + "datakey-value-type-string": "Snaar", + "datakey-value-type-double": "Dubbel", + "datakey-value-type-integer": "Geheel getal", + "datakey-value-type-json": "JSON", + "datakey-value-type-boolean-checkbox": "Booleaans (selectievakje)", + "datakey-value-type-boolean-switch": "Booleaans (schakelaar)", + "datakey-value-type-date-time": "Datum en tijd", + "datakey-value-type-date": "Datum", + "datakey-value-type-time": "Tijd", + "datakey-value-type-select": "Selecteren", + "value-is-required": "Waarde is vereist", + "ability-to-edit-attribute": "Mogelijkheid om attribuut te bewerken", + "ability-to-edit-attribute-editable": "Bewerkbaar (standaard)", + "ability-to-edit-attribute-disabled": "Invalide", + "ability-to-edit-attribute-readonly": "Alleen-lezen", + "disable-on-datakey-name": "Uitschakelen bij valse waarde van een andere datakey (geef de naam van de datakey op)", + "slide-toggle-settings": "Instellingen voor schuifschakelaar", + "slide-toggle-label-position": "Schuif de positie van het label omschakelen", + "slide-toggle-label-position-after": "Na", + "slide-toggle-label-position-before": "Voor", + "select-options": "Opties selecteren", + "no-select-options": "Geen geselecteerde opties geconfigureerd", + "add-select-option": "Selectieoptie toevoegen", + "numeric-field-settings": "Instellingen voor numerieke velden", + "step-interval": "Stapinterval tussen waarden", + "error-messages": "Foutberichten", + "min-value-error-message": "Foutmelding 'Min. waarde'", + "max-value-error-message": "Foutmelding 'Maximale waarde'", + "invalid-date-error-message": "Foutmelding 'Ongeldige datum'", + "invalid-JSON-error-message": "Foutbericht 'Ongeldige JSON'", + "icon-settings": "Icoon instellingen", + "dialog-editor-settings": "Instellingen van de dialoogeditor", + "use-custom-icon": "Aangepast pictogram gebruiken", + "input-cell-icon": "Pictogram om weer te geven vóór invoercel", + "value-conversion-settings": "Instellingen voor waardeconversie", + "get-value-settings": "Waarde-instellingen ophalen", + "use-get-value-function": "De getValue-functie gebruiken", + "get-value-function": "getValue, functie", + "set-value-settings": "Waarde-instellingen instellen", + "use-set-value-function": "Gebruik de setValue-functie", + "set-value-function": "setValue-functie", + "json-invalid": "JSON-waarde heeft een ongeldige indeling", + "title": "Titel", + "cancel-button-label": "Label met de knop 'Annuleren'" + }, + "invalid-qr-code-text": "Ongeldige invoertekst voor QR-code. Invoer moet een tekenreekstype hebben", + "qr-code": { + "use-qr-code-text-function": "Gebruik de tekstfunctie van de QR-code", + "qr-code-text-pattern": "Tekstpatroon QR-code (bijv. '${entityName} | ${keyName} - wat tekst.')", + "qr-code-text-pattern-hint": "Het tekstpatroon van de QR-code gebruikt de waarde van de eerst gevonden sleutel in de entiteiten in de entiteitsalias.", + "qr-code-text-pattern-required": "Tekstpatroon QR-code is vereist.", + "qr-code-text-function": "Tekstfunctie QR-code" + }, + "label-widget": { + "label-pattern": "Patroon", + "label-pattern-hint": "Tip: bijv. 'Text <span style=\"color: #000;\">${keyName<span style=\"color: #000;\">} eenheden.' of <span style=\"color: #000;\">${#<key index><span style=\"color: #000;\">} eenheden'", + "label-pattern-required": "Patroon is vereist", + "label-position": "Positie (percentage ten opzichte van achtergrond)", + "x-pos": "X", + "y-pos": "Y", + "background-color": "Achtergrondkleur", + "font-settings": "Lettertype-instellingen", + "background-image": "Achtergrondafbeelding", + "labels": "Etiketten", + "no-labels": "Geen labels geconfigureerd", + "add-label": "Label toevoegen" + }, + "navigation": { + "title": "Titel", + "navigation-path": "Navigatie pad", + "filter-type": "Filter type", + "filter-type-all": "Alle artikelen", + "filter-type-include": "Items opnemen", + "filter-type-exclude": "Items uitsluiten", + "items": "Items", + "enter-urls-to-filter": "Voer url's in om te filteren..." + }, + "persistent-table": { + "rpc-id": "RPC-ID", + "message-type": "Soort bericht", + "method": "Methode", + "params": "Params", + "created-time": "Gecreëerde tijd", + "expiration-time": "Vervaltijd", + "retries": "Pogingen", + "status": "Status", + "filter": "Filter", + "refresh": "Opfrissen", + "add": "RPC-aanvraag toevoegen", + "details": "Details", + "delete": "Verwijderen", + "delete-request-title": "Persistent RPC-verzoek verwijderen", + "delete-request-text": "Weet je zeker dat je een verzoek wilt verwijderen?", + "details-title": "Details RPC-ID:", + "additional-info": "Aanvullende informatie", + "response": "Antwoord", + "any-status": "Elke status", + "rpc-status-list": "RPC-statuslijst", + "no-request-prompt": "Geen verzoek om weer te geven", + "send-request": "Aanvraag versturen", + "add-title": "Permanente RPC-aanvraag maken", + "method-error": "Methode is vereist.", + "timeout-error": "De minimale time-outwaarde is 5000 (5 seconden).", + "white-space-error": "Witruimte is niet toegestaan.", + "rpc-status": { + "QUEUED": "WACHTRIJ", + "SENT": "VERZONDEN", + "DELIVERED": "GELEVERD", + "SUCCESSFUL": "SUCCESVOL", + "TIMEOUT": "TIMEOUT", + "EXPIRED": "VERVALLEN", + "FAILED": "MISLUKT" + }, + "rpc-search-status-all": "ALLE", + "message-types": { + "false": "Twee richtingen", + "true": "One-way" + }, + "general-settings": "Algemene instellingen", + "enable-filter": "Filter inschakelen", + "enable-sticky-header": "Koptekst weergeven tijdens scrollen", + "enable-sticky-action": "Kolom Acties weergeven tijdens het scrollen", + "display-request-details": "Aanvraaggegevens weergeven", + "allow-send-request": "Sta het verzenden van RPC-verzoek toe", + "allow-delete-request": "Verwijderingsverzoek toestaan", + "columns-settings": "Instellingen voor kolommen", + "display-columns": "Weer te geven kolommen", + "column": "Kolom", + "no-columns-found": "Geen kolommen gevonden", + "no-columns-matching": "'{{column}}' niet gevonden." + }, + "rpc": { + "value-settings": "Waarde-instellingen", + "initial-value": "Beginwaarde", + "retrieve-value-settings": "Aan/uit-waarde-instellingen ophalen", + "retrieve-value-method": "Waarde ophalen met behulp van methode", + "retrieve-value-method-none": "Niet ophalen", + "retrieve-value-method-rpc": "RPC aanroepen om waardemethode op te halen", + "retrieve-value-method-attribute": "Abonneren op attribuut", + "retrieve-value-method-timeseries": "Schrijf je in voor tijdseries", + "attribute-value-key": "Attribuut sleutel", + "timeseries-value-key": "Tijdreeks-toets", + "get-value-method": "RPC get value methode", + "parse-value-function": "Functie voor het parseren van waarden", + "update-value-settings": "Waarde-instellingen bijwerken", + "set-value-method": "RPC-methode voor ingestelde waarden", + "convert-value-function": "Functie Waarde converteren", + "rpc-settings": "RPC-instellingen", + "request-timeout": "Time-out voor RPC-aanvraag (ms)", + "persistent-rpc-settings": "Persistente RPC-instellingen", + "request-persistent": "RPC-aanvraag persistent", + "persistent-polling-interval": "Pollinginterval (ms) om persistente RPC-opdrachtrespons te krijgen", + "common-settings": "Algemene instellingen", + "switch-title": "Titel wijzigen", + "show-on-off-labels": "Aan/uit-labels weergeven", + "slide-toggle-label": "Schuif toggle label", + "label-position": "Positie van het etiket", + "label-position-before": "Voor", + "label-position-after": "Na", + "slider-color": "Kleur schuifregelaar", + "slider-color-primary": "Primair", + "slider-color-accent": "Accent", + "slider-color-warn": "Waarschuwen", + "button-style": "Knop stijl", + "button-raised": "Verhoogde knop", + "button-primary": "Primaire kleur", + "button-background-color": "Achtergrondkleur knop", + "button-text-color": "Tekstkleur knop", + "widget-title": "Titel van de widget", + "button-label": "Knop label", + "device-attribute-scope": "Bereik van device attributen", + "server-attribute": "Server-attribuut", + "shared-attribute": "Gedeeld attribuut", + "device-attribute-parameters": "Parameters voor device attributen", + "is-one-way-command": "Is eenrichtingscommando", + "rpc-method": "RPC-methode", + "rpc-method-params": "Params van de RPC-methode", + "show-rpc-error": "Fout bij het uitvoeren van RPC-opdrachten weergeven", + "led-title": "LED-titel", + "led-color": "LED-kleur", + "check-status-settings": "Statusinstellingen controleren", + "perform-rpc-status-check": "Voer de statuscontrole van het RPC-device uit", + "retrieve-led-status-value-method": "Leidsstatuswaarde ophalen met behulp van methode", + "led-status-value-attribute": "Apparaatkenmerk met led-statuswaarde", + "led-status-value-timeseries": "Tijdreeks van het device met led-statuswaarde", + "check-status-method": "RPC-methode voor het controleren van de apparaatstatus", + "parse-led-status-value-function": "Led-statuswaardefunctie parseren", + "knob-title": "Titel van de knop", + "min-value": "Minimumwaarde", + "max-value": "Maximale waarde" + }, + "maps": { + "select-entity": "Entiteit selecteren", + "select-entity-hint": "Tip: klik na selectie op de kaart om de positie in te stellen", + "tooltips": { + "placeMarker": "Klik om de entiteit '{{entityName}}' te plaatsen", + "firstVertex": "Polygoon voor '{{entityName}}': klik om eerste punt te plaatsen", + "firstVertex-cut": "Klik om het eerste punt te plaatsen", + "continueLine": "Polygoon voor '{{entityName}}': klik om verder te tekenen", + "continueLine-cut": "Klik om door te gaan met tekenen", + "finishLine": "Klik op een bestaande markering om te voltooien", + "finishPoly": "Polygoon voor '{{entityName}}': klik op de eerste markering om te voltooien en op te slaan", + "finishPoly-cut": "Klik op de eerste markering om te voltooien en op te slaan", + "finishRect": "Polygoon voor '{{entityName}}': klik om te voltooien en op te slaan", + "startCircle": "Cirkel voor '{{entityName}}': klik om het midden van de cirkel te plaatsen", + "finishCircle": "Cirkel voor '{{entityName}}': klik om cirkel te voltooien", + "placeCircleMarker": "Klik om de cirkelmarkering te plaatsen" + }, + "actions": { + "finish": "Fins", + "cancel": "Annuleren", + "removeLastVertex": "Laatste punt verwijderen" + }, + "buttonTitles": { + "drawMarkerButton": "Entiteit plaatsen", + "drawPolyButton": "Polygoon maken", + "drawLineButton": "Polylijn maken", + "drawCircleButton": "Cirkel maken", + "drawRectButton": "Rechthoek maken", + "editButton": "Modus bewerken", + "dragButton": "Drag-drop-modus", + "cutButton": "Snijd polygoongebied", + "deleteButton": "Verwijderen", + "drawCircleMarkerButton": "Cirkelmarkering maken", + "rotateButton": "Polygoon roteren" + }, + "map-provider-settings": "Instellingen van kaartprovider", + "map-provider": "Kaart provider", + "map-provider-google": "Google kaarten", + "map-provider-openstreet": "OpenStreet-kaarten", + "map-provider-here": "HERE kaarten", + "map-provider-image": "Afbeelding kaart", + "map-provider-tencent": "Tencent kaarten", + "openstreet-provider": "OpenStreet-kaartprovider", + "openstreet-provider-mapnik": "OpenStreetMap.Mapnik (standaard)", + "openstreet-provider-hot": "OpenStreetMap.HOT", + "openstreet-provider-esri-street": "Esri.WorldStreetMap", + "openstreet-provider-esri-topo": "Esri.WorldTopoMap", + "openstreet-provider-esri-imagery": "Esri.WorldImagery", + "openstreet-provider-cartodb-positron": "CartoDB.Positron", + "openstreet-provider-cartodb-dark-matter": "CartoDB.DarkMatter", + "use-custom-provider": "Aangepaste provider gebruiken", + "custom-provider-tile-url": "Aangepaste tegel-URL van provider", + "google-maps-api-key": "API-sleutel voor Google Maps", + "default-map-type": "Standaard kaarttype", + "google-map-type-roadmap": "Stappenplan", + "google-map-type-satelite": "Satelliet", + "google-map-type-hybrid": "Hybride", + "google-map-type-terrain": "Terrein", + "map-layer": "Kaart laag", + "here-map-normal-day": "HERE.normalDay (standaard)", + "here-map-normal-night": "HIER.normalNight", + "here-map-hybrid-day": "HIER.hybridDay", + "here-map-terrain-day": "HIER.terreinDag", + "credentials": "Geloofsbrief", + "here-app-id": "HERE-app-id", + "here-app-code": "HERE-app-code", + "here-api-key": "HERE API-sleutel", + "here-use-new-version-api-3": "API-versie 3 gebruiken", + "tencent-maps-api-key": "API-sleutel voor Tencent Maps", + "tencent-map-type-roadmap": "Stappenplan", + "tencent-map-type-satelite": "Satelliet", + "tencent-map-type-hybrid": "Hybride", + "image-map-background": "De achtergrond van de afbeeldingskaart", + "image-map-background-from-entity-attribute": "Neem de achtergrond van de afbeeldingstoewijzing van het entiteitskenmerk", + "image-url-source-entity-alias": "Alias van de bronentiteit van de afbeeldings-URL", + "image-url-source-entity-attribute": "Entiteitskenmerk bronentiteit afbeeldings-URL", + "common-map-settings": "Algemene kaartinstellingen", + "x-pos-key-name": "Naam van de X-positiesleutel", + "y-pos-key-name": "Y-positie sleutelnaam", + "latitude-key-name": "Naam van breedtegraad-sleutel", + "longitude-key-name": "Lengtegraad sleutelnaam", + "default-map-zoom-level": "Standaard zoomniveau voor kaart (0 - 20)", + "default-map-center-position": "Standaard middenpositie op de kaart (0,0)", + "disable-scroll-zooming": "Scrollen uitschakelen", + "disable-double-click-zooming": "Dubbelklikken uitschakelen", + "disable-zoom-control-buttons": "Zoombedieningsknoppen uitschakelen", + "fit-map-bounds": "Pas kaartgrenzen aan om alle markeringen te bedekken", + "use-default-map-center-position": "Gebruik de standaardpositie in het midden van de kaart", + "entities-limit": "Limiet van te laden entiteiten", + "markers-settings": "Instellingen voor markeringen", + "marker-offset-x": "Markering X verschuiving ten opzichte van positie vermenigvuldigd met markeringsbreedte", + "marker-offset-y": "Markering Y-verschuiving ten opzichte van positie vermenigvuldigd met markeringshoogte", + "position-function": "Positieconversiefunctie, moet x,y-coördinaten retourneren als dubbel van 0 tot 1 elk", + "draggable-marker": "Versleepbare markering", + "label": "Etiket", + "show-label": "Etiket weergeven", + "use-label-function": "Labelfunctie gebruiken", + "label-pattern": "Label (patroonvoorbeelden: '${entityName}', '${entityName}: (Tekst ${keyName} eenheden.)' )", + "label-function": "Label functie", + "tooltip": "Tooltip", + "show-tooltip": "Tooltip weergeven", + "show-tooltip-action": "Actie voor het weergeven van de knopinfo", + "show-tooltip-action-click": "Knopinfo weergeven bij klikken (Standaard)", + "show-tooltip-action-hover": "Knopinfo weergeven bij aanwijzen", + "auto-close-tooltips": "Tooltips voor automatisch sluiten", + "use-tooltip-function": "Tooltip-functie gebruiken", + "tooltip-pattern": "Tooltip (bijv. 'Tekst ${keyName} eenheden.' of Linktekst')", + "tooltip-function": "Tooltip-functie", + "tooltip-offset-x": "Tooltip X-verschuiving ten opzichte van markeringsanker vermenigvuldigd met markeringsbreedte", + "tooltip-offset-y": "Tooltip Y-verschuiving ten opzichte van markeringsanker vermenigvuldigd met markeringshoogte", + "color": "Kleur", + "use-color-function": "Kleurfunctie gebruiken", + "color-function": "Kleur functie", + "marker-image": "Afbeelding markeren", + "use-marker-image-function": "Gebruik de functie voor markeringsafbeeldingen", + "custom-marker-image": "Aangepaste markeringsafbeelding", + "custom-marker-image-size": "Afbeeldingsgrootte aangepaste markering (px)", + "marker-image-function": "Marker image functie", + "marker-images": "Marker afbeeldingen", + "polygon-settings": "Polygoon-instellingen", + "show-polygon": "Polygoon weergeven", + "polygon-key-name": "Naam van de Polygoonsleutel", + "enable-polygon-edit": "Polygoonbewerking inschakelen", + "polygon-label": "Polygoon etiket", + "show-polygon-label": "Polygoonlabel weergeven", + "use-polygon-label-function": "Polygoonlabelfunctie gebruiken", + "polygon-label-pattern": "Polygoonlabel (patroonvoorbeelden: '${entityName}', '${entityName}: (Tekst ${keyName} eenheden.)' )", + "polygon-label-function": "Polygoon label functie", + "polygon-tooltip": "Tooltip voor polygoon", + "show-polygon-tooltip": "Polygoon knopinfo weergeven", + "auto-close-polygon-tooltips": "Tooltips voor polygoon automatisch sluiten", + "use-polygon-tooltip-function": "Polygoon-tooltipfunctie gebruiken", + "polygon-tooltip-pattern": "Tooltip (bijv. 'Tekst ${keyName} eenheden.' of Linktekst')", + "polygon-tooltip-function": "Polygoon tooltip functie", + "polygon-color": "Polygoon kleur", + "polygon-opacity": "Dekking van polygoon", + "use-polygon-color-function": "Polygoonkleurfunctie gebruiken", + "polygon-color-function": "Polygoon kleur functie", + "polygon-stroke": "Polygoon lijn", + "stroke-color": "Kleur van de lijn", + "stroke-opacity": "Dekking van de beroerte", + "stroke-weight": "Het gewicht van de slag", + "use-polygon-stroke-color-function": "Gebruik de kleurfunctie van de polygoonlijn", + "polygon-stroke-color-function": "Kleurfunctie voor polygoonlijnen", + "circle-settings": "Cirkel-instellingen", + "show-circle": "Toon cirkel", + "circle-key-name": "Naam van de cirkelsleutel", + "enable-circle-edit": "Cirkelbewerking inschakelen", + "circle-label": "Het etiket van de cirkel", + "show-circle-label": "Cirkellabel weergeven", + "use-circle-label-function": "Gebruik de cirkellabelfunctie", + "circle-label-pattern": "Cirkellabel (patroonvoorbeelden: '${entityName}', '${entityName}: (Tekst ${keyName} eenheden.)' )", + "circle-label-function": "Cirkellabel-functie", + "circle-tooltip": "Tooltip voor cirkelen", + "show-circle-tooltip": "Knopinfo cirkel weergeven", + "auto-close-circle-tooltips": "Tooltips voor automatisch sluiten van cirkels", + "use-circle-tooltip-function": "Gebruik de cirkel-tooltip-functie", + "circle-tooltip-pattern": "Tooltip (bijv. 'Tekst ${keyName} eenheden.' of Linktekst')", + "circle-tooltip-function": "Cirkel tooltip functie", + "circle-fill-color": "Vulkleur cirkel", + "circle-fill-color-opacity": "Dekking van de vulkleur van de cirkel", + "use-circle-fill-color-function": "Gebruik de functie voor het vullen van cirkels", + "circle-fill-color-function": "Cirkelvulkleurfunctie", + "circle-stroke": "De slag van de cirkel", + "use-circle-stroke-color-function": "Gebruik de kleurfunctie van de cirkelstreek", + "circle-stroke-color-function": "Cirkellijn kleurfunctie", + "markers-clustering-settings": "Instellingen voor het clusteren van markeringen", + "use-map-markers-clustering": "Clustering van kaartmarkeringen gebruiken", + "zoom-on-cluster-click": "Inzoomen bij het klikken op een cluster", + "max-cluster-zoom": "Het maximale zoomniveau wanneer een markering deel kan uitmaken van een cluster (0 - 18)", + "max-cluster-radius-pixels": "Maximale straal die een cluster in pixels bestrijkt", + "cluster-zoom-animation": "Animatie weergeven op markeringen bij het zoomen", + "show-markers-bounds-on-cluster-mouse-over": "De grenzen van markeringen weergeven wanneer u de muisaanwijzer op een cluster plaatst", + "spiderfy-max-zoom-level": "Spiderfy op het maximale zoomniveau (om alle clustermarkeringen te zien)", + "load-optimization": "Optimalisatie van de belasting", + "cluster-chunked-loading": "Gebruik chunks om markeringen toe te voegen, zodat de pagina niet vastloopt", + "cluster-markers-lazy-load": "Gebruik lazy load voor het toevoegen van markeringen", + "editor-settings": "Editor-instellingen", + "enable-snapping": "Uitlijnen op andere punten inschakelen voor nauwkeurig tekenen", + "init-draggable-mode": "Kaart initialiseren in versleepbare modus", + "hide-all-edit-buttons": "Alle bedieningsknoppen voor bewerken verbergen", + "hide-draw-buttons": "Tekenknoppen verbergen", + "hide-edit-buttons": "Bewerkingsknoppen verbergen", + "hide-remove-button": "Verwijderknop verbergen", + "route-map-settings": "Routekaartinstellingen", + "trip-animation-settings": "Instellingen voor reisanimatie", + "normalization-step": "Normalisatie gegevensstap (ms)", + "tooltip-background-color": "Achtergrondkleur tooltip", + "tooltip-font-color": "Tekstkleur van knopinfo", + "tooltip-opacity": "Tooltip dekking (0-1)", + "auto-close-tooltip": "Tooltip voor automatisch sluiten", + "rotation-angle": "Stel extra draaihoek in voor marker (graden)", + "path-settings": "Pad-instellingen", + "path-color": "De kleur van het pad", + "use-path-color-function": "Padkleurfunctie gebruiken", + "path-color-function": "Padkleurfunctie", + "path-decorator": "Pad decorateur", + "use-path-decorator": "Gebruik paddecorateur", + "decorator-symbol": "Het symbool van de decorateur", + "decorator-symbol-arrow-head": "Pijl", + "decorator-symbol-dash": "Scheutje", + "decorator-symbol-size": "Grootte decorateur symbool (px)", + "use-path-decorator-custom-color": "Gebruik de aangepaste kleur van de paddecorateur", + "decorator-custom-color": "Decorateur aangepaste kleur", + "decorator-offset": "Decorateur offset", + "end-decorator-offset": "Einddecorateur offset", + "decorator-repeat": "Decorateur herhalen", + "points-settings": "Punten instellingen", + "show-points": "Toon punten", + "point-color": "Kleur van de punt", + "point-size": "Punt grootte (px)", + "use-point-color-function": "Puntkleurfunctie gebruiken", + "point-color-function": "Puntkleurfunctie", + "use-point-as-anchor": "Punt gebruiken als anker", + "point-as-anchor-function": "Aanwijzen als ankerfunctie", + "independent-point-tooltip": "Tooltip voor onafhankelijk punt", + "clustering-markers": "Markeringen clusteren", + "use-icon-create-function": "Gebruik de kleurfunctie van markeringen", + "marker-color-function": "Marker kleur functie" + }, + "markdown": { + "use-markdown-text-function": "Markdown/HTML-waardefunctie gebruiken", + "markdown-text-function": "Markdown/HTML-waarde functie", + "markdown-text-pattern": "Markdown/HTML-patroon (markdown of HTML met variabelen, bijvoorbeeld '${entityName} of ${keyName} - wat tekst.')", + "apply-default-markdown-style": "Standaardstijl voor afprijzing toepassen", + "markdown-css": "Afprijzing/HTML CSS" + }, + "simple-card": { + "label-position": "Positie van het etiket", + "label-position-left": "Links", + "label-position-top": "Boven" + }, + "table": { + "common-table-settings": "Algemene tabelinstellingen", + "enable-search": "Zoeken inschakelen", + "enable-sticky-header": "Koptekst altijd weergeven", + "enable-sticky-action": "Kolom Acties altijd weergeven", + "hidden-cell-button-display-mode": "Weergavemodus voor verborgen celknopacties", + "show-empty-space-hidden-action": "Lege ruimte weergeven in plaats van verborgen celknopactie", + "dont-reserve-space-hidden-action": "Reserveer geen ruimte voor verborgen actieknoppen", + "display-timestamp": "Tijdstempelkolom weergeven", + "display-milliseconds": "Tijdstempel milliseconden weergeven", + "display-pagination": "Paginering weergeven", + "default-page-size": "Standaard paginaformaat", + "use-entity-label-tab-name": "Entiteitslabel gebruiken in de naam van het tabblad", + "hide-empty-lines": "Lege regels verbergen", + "row-style": "Rij stijl", + "use-row-style-function": "Rijstijlfunctie gebruiken", + "row-style-function": "Rijstijl functie", + "cell-style": "Cel stijl", + "use-cell-style-function": "Celstijlfunctie gebruiken", + "cell-style-function": "Functie voor celstijl", + "cell-content": "Celinhoud", + "use-cell-content-function": "De functie voor celinhoud gebruiken", + "cell-content-function": "Functie voor celinhoud", + "show-latest-data-column": "Kolom met meest recente gegevens weergeven", + "latest-data-column-order": "Meest recente volgorde van gegevenskolommen", + "entities-table-title": "Titel van de tabel Entiteiten", + "enable-select-column-display": "Schakel geselecteerde kolommen in om weer te geven", + "display-entity-name": "Kolom met entiteitsnaam weergeven", + "entity-name-column-title": "Kolomtitel van entiteitsnaam", + "display-entity-label": "Kolom met entiteitslabel weergeven", + "entity-label-column-title": "Kolomtitel van entiteitslabel", + "display-entity-type": "Kolom met entiteitstype weergeven", + "default-sort-order": "Standaard sorteervolgorde", + "custom-title": "Aangepaste kopteksttitel", + "column-width": "Kolombreedte (px of %)", + "default-column-visibility": "Standaard zichtbaarheid van kolommen", + "column-visibility-visible": "Zichtbaar", + "column-visibility-hidden": "Verborgen", + "column-visibility-hidden-mobile": "Verborgen in mobiele modus", + "column-selection-to-display": "Kolomselectie in 'Weer te geven kolommen'", + "column-selection-to-display-enabled": "Ingeschakeld", + "column-selection-to-display-disabled": "Invalide", + "column-export-option": "Kolom opnemen in export", + "column-export-option-always": "Altijd", + "column-export-option-only-visible": "Alleen als de kolom zichtbaar is", + "column-export-option-never": "Nooit", + "alarms-table-title": "Titel van de tabel Alarmen", + "enable-alarms-selection": "Selectie van alarmen inschakelen", + "enable-alarms-search": "Zoeken naar alarmen inschakelen", + "enable-alarm-filter": "Alarmfilter inschakelen", + "display-alarm-details": "Alarmdetails weergeven", + "allow-alarms-ack": "Alarmbevestiging toestaan", + "allow-alarms-clear": "Laat alarmen wissen", + "display-alarm-activity": "Alarmactiviteit weergeven", + "allow-alarms-assign": "Toewijzing van alarmen toestaan", + "blob-entities-table-title": "Titel van de tabel met blobentiteiten", + "display-created-time": "Aangemaakte tijdkolom weergeven", + "display-type": "Kolom Weergavetype", + "display-customer": "Klantkolom weergeven", + "no-data-display-message": "Alternatief bericht 'Geen gegevens om weer te geven'", + "force-default-blob-entity-type": "Standaard blob-entiteitstype afdwingen", + "scheduler-events-table-title": "Titel van de tabel met scheduler events", + "force-default-event-type": "Standaardgebeurtenistype afdwingen" + }, + "scheduler": { + "enabled-scheduler-event-views": "Weergaven van scheduler events ingeschakeld", + "enabled-scheduler-event-views-both": "Beide", + "enabled-scheduler-event-views-list": "Alleen lijstweergave", + "enabled-scheduler-event-views-calendar": "Alleen agendaweergave", + "display-name": "Weergavenaam", + "type-name": "Typ naam", + "display-originator-select": "Aansteller van entiteit weergeven", + "display-message-type-select": "Berichttype weergeven selecteren", + "display-metadata-table": "Tabel met metagegevens van berichten weergeven", + "configuration-html-template": "Configuratie HTML-sjabloon", + "custom-event-types": "Aangepaste gebeurtenistypen", + "no-custom-event-types": "Geen aangepaste gebeurtenistypen geconfigureerd", + "add-custom-event-type": "Aangepast gebeurtenistype toevoegen" + }, + "value-source": { + "value-source": "Waardebron", + "predefined-value": "Vooraf gedefinieerde waarde", + "entity-attribute": "Waarde ontleend aan entiteitskenmerk", + "value": "Waarde", + "source-entity-alias": "Alias van bronentiteit", + "source-entity-attribute": "Kenmerk van bronentiteit" + }, + "widget-font": { + "font-family": "Lettertype familie", + "size": "Grootte", + "relative-font-size": "Relatieve lettergrootte (procenten)", + "font-style": "Stijl", + "font-style-normal": "Normaal", + "font-style-italic": "Cursief", + "font-style-oblique": "Schuin", + "font-weight": "Gewicht", + "font-weight-normal": "Normaal", + "font-weight-bold": "Bold", + "font-weight-bolder": "Bolder", + "font-weight-lighter": "Lichter", + "color": "Kleur", + "shadow-color": "De kleur van de schaduw" + }, + "home": { + "no-data-available": "Geen gegevens beschikbaar" + }, + "system-info": { + "cpu": "CPU", + "ram": "RAM", + "disk": "Schijf", + "cpu-warning-text": "Hoog CPU-gebruik. Optimaliseer de systeemprestaties om systeemstoringen te voorkomen.", + "cpu-critical-text": "Kritisch hoog CPU-gebruik. Optimaliseer de systeemprestaties om systeemstoringen te voorkomen.", + "ram-warning-text": "Heeft bijna geen RAM-geheugen meer. Om systeemstoringen te voorkomen, optimaliseert u de systeemprestaties of vergroot u de grootte van het RAM-geheugen.", + "ram-critical-text": "Kritisch lage RAM-reserve. Om systeemstoringen te voorkomen, optimaliseert u de systeemprestaties of vergroot u de grootte van het RAM-geheugen.", + "disk-warning-text": "Weinig schijfruimte. Om gegevensverlies te voorkomen, maakt u schijfruimte vrij of breidt u deze uit.", + "disk-critical-text": "Kritisch weinig schijfruimte. Om gegevensverlies te voorkomen, maakt u schijfruimte vrij of breidt u deze uit." + }, + "cluster-info": { + "service-id": "Service-id", + "service-type": "Soort dienst", + "no-data": "Geen gegevens" + }, + "transport-messages": { + "title": "Berichten vervoeren", + "info": "Alle berichten die afkomstig zijn van Devices" + }, + "activity": { + "title": "Activiteit" + }, + "documentation": { + "title": "Documentatie", + "add-link": "Link toevoegen", + "add-link-title": "Documentatiekoppeling toevoegen", + "name": "Naam", + "name-required": "Naam is verplicht.", + "link": "Verbinden", + "link-required": "Link is vereist.", + "columns": "Kolommen" + }, + "quick-links": { + "title": "Snelkoppelingen", + "add-link": "Link toevoegen", + "add-link-title": "Snelkoppeling toevoegen", + "quick-link": "Snelkoppeling", + "quick-link-required": "Snelle koppeling is vereist.", + "no-links-matching": "Er zijn geen links gevonden die overeenkomen met '{{name}}'.", + "columns": "Kolommen" + }, + "recent-dashboards": { + "title": "Dashboards", + "last": "Laatst bekeken", + "starred": "Speelde", + "name": "Naam", + "last-viewed": "Laatst bekeken", + "no-last-viewed-dashboards": "Nog geen laatst bekeken dashboards" + }, + "configured-features": { + "title": "Geconfigureerde functies", + "info": "Status van functies waarvoor configuratie vereist is", + "white-labeling-feature": "White labelling", + "email-feature": "E-mail", + "sms-feature": "SMS", + "slack-feature": "Los", + "oauth2-feature": "OAuth 2", + "2fa-feature": "2FA", + "feature-configured": "Functie is geconfigureerd.\nKlik om in te stellen", + "feature-not-configured": "Functie is niet geconfigureerd.\nKlik om in te stellen" + }, + "version-info": { + "title": "Versie", + "contact-us": "Neem contact met ons op", + "current-version": "Huidige versie", + "current": "Actueel", + "available-version": "Beschikbare versie", + "available": "Beschikbaar", + "upgrade": "Opwaarderen", + "version-is-up-to-date": "Versie is up-to-date" + }, + "license-info": { + "title": "Licentie info", + "view-all": "Alles weergeven", + "license-portal": "Licentie portaal", + "devices-info": "{count} / { max, plural, =0 {∞} andere {#} } Devices", + "assets-info": "{count} / { max, plural, =0 {∞} andere {#} } Assets", + "dashboards-info": "{count} / { max, plural, =0 {∞} andere {#} } Dashboards", + "integrations-info": "{count} / { max, plural, =0 {∞} andere {#} } Integraties", + "community-support": "Ondersteuning van de gemeenschap", + "white-labeling": "White labelling", + "unlimited-datapoints-and-messages": "Onbeperkt aantal datapunten en berichten", + "unlimited-api-calls": "Onbeperkt aantal API-aanroepen" + }, + "usage-info": { + "title": "Gebruik", + "entities": "Entiteiten", + "api-calls": "API-aanroepen" + }, + "functions": { + "title": "Functies", + "pe-feature-tooltip": "Alleen op ThingsBoard\nProfessionele editie", + "switch-to-pe": "Overschakelen naar PE", + "alarms": "Alarmen", + "dashboards": "Dashboards", + "entities-and-relations": "Entiteiten en relaties", + "profiles": "Profielen", + "advanced-features": "Geavanceerde functies", + "notification-center": "Meldingscentrum", + "api-usage": "API-gebruik", + "customers": "Klanten", + "customers-hierarchy": "Hiërarchie van klanten", + "roles-and-permissions": "Rollen en machtigingen", + "groups": "Groepen", + "integrations": "Integraties", + "solution-templates": "Sjablonen voor oplossingen", + "scheduler": "Scheduler", + "white-labeling": "White labelling" + }, + "devices": { + "view-docs": "Documenten bekijken", + "inactive": "Inactief", + "active": "Actief", + "total": "Totaal" + }, + "alarms": { + "critical": "Kritisch", + "assigned-to-me": "Aan mij toegewezen", + "total": "Totaal" + }, + "solution-templates": { + "title": "Sjablonen voor oplossingen", + "prototype-plan": "Prototype plan", + "startup-plan": "Opstart plan" + }, + "getting-started": { + "get-started": "Start", + "finish": "Einde", + "done-welcome-title": "Welkom", + "done-welcome-text": "Je hebt het geweldig gedaan!", + "sys-admin": { + "step1": { + "title": "Tenant en tenantbeheerder maken", + "content": "

Een tenant is een persoon of een organisatie die devices en bedrijfsmiddelen bezit of produceert. De tenant kan meerdere tenantbeheerders, gebruikers, klanten, devices en assets hebben.

De tenantbeheerder kan devices, assets, klanten en dashboards maken en beheren binnen het tenantaccount.

Volg de documentatie over hoe u dit moet doen:

", + "how-to-create-tenant": "Tenant en tenantbeheerder aanmaken" + }, + "step2": { + "title": "Functie configureren: E-mailserver", + "content": "

De configuratie van de e-mailserver is essentieel voor gebruikersactivering, wachtwoordherstel en bezorging van alarmmeldingen.

Volg de documentatie over hoe u dit moet doen:

", + "how-to-configure-mail-server": "Hoe de e-mailserver te configureren" + }, + "step3": { + "title": "Configureer functie: SMS-provider", + "content": "

Configureer sms-providers om de klanten via sms op de hoogte te stellen van de alarmen.

Volg de documentatie over hoe u dit moet doen:

", + "how-to-configure-sms-provider": "Hoe sms-provider te configureren" + }, + "step4": { + "title": "Configureer functie: White-labeling", + "content": "

Pas eenvoudig het logo en het kleurenschema van uw bedrijf of product aan zonder codering en zonder de service opnieuw te starten.

Volg de documentatie over hoe u dit moet doen:

", + "how-to-configure-white-labeling": "Whitelabeling configureren" + }, + "step5": { + "title": "Configureer functie: 2FA", + "content": "

Verbeter de beveiliging van de platformaccounts met tweefactorauthenticatie.

Volg de documentatie over hoe u dit moet doen:

", + "how-to-configure-2fa": "Hoe configureer je 2FA?" + }, + "step6": { + "title": "Functie configureren: OAuth 2", + "content": "

Vereenvoudig het inloggen voor de tenant en klantgebruikers met Single Sign-On-functionaliteit via OAuth 2.0.

Volg de documentatie over hoe u dit moet doen:

", + "how-to-configure-oauth2": "Hoe OAuth 2 te configureren" + }, + "step7": { + "title": "Functie configureren: Slack", + "content": "

Distribueer meldingen naar de tenant en klantgebruikers via Slack volgens de meldingsregels die u instelt.

Volg de documentatie over hoe u dit moet doen:

", + "how-to-configure-notifications": "Slack configureren" + } + }, + "tenant-admin": { + "step1": { + "title": "Device maken", + "content": "

Laten we uw eerste device via de gebruikersinterface op het platform aanmaken. Volg de documentatie over hoe u dit moet doen:

", + "how-to-create-device": "Hoe een device te maken" + }, + "step2": { + "title": "Device aansluiten", + "content-before": "

Om verbinding te maken met het device, moet u de device referenties ophalen. We raden u aan standaard automatisch gegenereerde referenties te gebruiken die toegangstoken zijn voor deze handleiding.

  • Ga naar de device tabel
  • Klik op de device rij om de apparaatgegevens te openen
  • Druk op de knop \"Toegangstoken kopiëren\"

Gebruik eenvoudige commando's om gegevens via HTTP te publiceren. Vergeet niet om $ACCESS_TOKEN te vervangen door het toegangstoken van uw device:

", + "ubuntu": { + "install-curl": "Installeer cURL voor Ubuntu:" + }, + "macos": { + "install-curl": "Installeer cURL voor MacOS:" + }, + "windows": { + "install-curl": "Vanaf Windows 10 b17063 is cURL standaard beschikbaar." + }, + "replace-access-token": "Vervang $ACCESS_TOKEN door het token van uw device:", + "content-after": "

U kunt ook andere protocollen gebruiken, zoals MQTT, CoAP, enz.

Volg de documentatie over hoe u dit moet doen:

", + "how-to-connect-device": "Hoe het device aan te sluiten" + }, + "step3": { + "title": "Dashboard maken", + "content": "

Maak een dashboard om gegevens van entiteiten zoals assets, devices, enz. te visualiseren.

Volg de documentatie over hoe u dit moet doen:

", + "how-to-create-dashboard": "Hoe maak je een dashboard?" + }, + "step4": { + "title": "Alarmregels configureren", + "alarm-rules": "Alarm regels", + "content": "

Laten we alarm slaan als de temperatuur 25°C bereikt. Volg de documentatie over hoe u dit moet doen:

", + "how-to-configure-alarm-rules": "Alarmregels configureren" + }, + "step5": { + "title": "Alarm maken", + "content-before": "

Om het alarm te activeren, verzendt u een nieuwe telemetriewaarde van 26°C of hoger.

", + "replace-access-token": "Vervang $ACCESS_TOKEN door het token van uw device:", + "content-after": "

Volg de documentatie over hoe u dit moet doen:

", + "how-to-create-alarm": "Hoe maak je een alarm aan?" + }, + "step6": { + "title": "Dashboard voor klanten maken en delen", + "content": "

Door dashboards voor eindgebruikers te maken, kan een klantgebruiker alleen zijn eigen devices zien en worden gegevens van een andere klant verborgen.

Volg de documentatie over hoe u dit moet doen:

", + "how-to-create-customer-and-share-dashboard": "Hoe maak je een klantdashboard en deel je het dashboard?" + } + } + } + }, + "icon": { + "icon": "Pictogram", + "select-icon": "Selecteer icoon", + "material-icons": "Materiaal iconen", + "show-all": "Toon alle iconen" + }, + "subscription": { + "entity-limit-text": "U kunt uw abonnement echter upgraden om uw limieten te verhogen.", + "upgrade-your-plan": "Abonnement upgraden", + "white-labeling-feature": "White labeling-functie", + "white-labeling-text-full": "Rebrand de webinterface van het ThingsBoard-platform in 2 minuten met uw bedrijfs- of productlogo en kleurenschema.

Verwijder \"Powered By\" in de voettekst van het dashboard.
Geen codering of herstart van de service vereist. Sta uw klanten toe om hun interface ook te whitelabelen.", + "enable-white-labeling": "Schakel nu de whitelabeling-functie in door uw abonnement te upgraden!", + "read-more": "Lees meer", + "white-labeling-video-text": "Zie onderstaande video-tutorial om te zien hoe deze functie werkt!" + }, + "subscription-error": { + "title": "Overtreding van het abonnement", + "warning-title": "Waarschuwing voor abonnement", + "upgrade-subscription-plan": "Upgrade uw abonnement", + "upgrade-subscription-plan-to-install-solution-template": "Om {{solutionTemplateName}} oplossing te installeren, moet u uw abonnement minimaal upgraden naar het {{planName}} abonnement!", + "limit-reached": { + "device-count": "Je hebt het maximale aantal devices ({{value}}) bereikt dat is toegestaan door je abonnement!", + "asset-count": "U hebt het maximale vermogen ({{value}}) bereikt dat is toegestaan door uw abonnement!" + }, + "feature-disabled": { + "white-labeling": "De White Labeling-functie is niet toegestaan door uw abonnement!" + } + }, + "phone-input": { + "phone-input-label": "Telefoonnummer", + "phone-input-required": "Telefoonnummer is verplicht", + "phone-input-validation": "Telefoonnummer is ongeldig of niet mogelijk", + "phone-input-pattern": "Ongeldig telefoonnummer. Moet in E.164-formaat zijn, bijv. {{phoneNumber}}", + "phone-input-hint": "Telefoonnummer in E.164-formaat, bijv. {{phoneNumber}}" + }, + "custom": { + "widget-action": { + "action-cell-button": "Actiecelknop", + "row-click": "Klik op rij", + "polygon-click": "Klik op polygoon", + "marker-click": "Klik op de markering", + "circle-click": "Klik op de cirkel", + "tooltip-tag-action": "Actie voor tooltip-tag", + "node-selected": "Op knooppunt geselecteerd", + "element-click": "Klik op het HTML-element op", + "pie-slice-click": "Klik op slice", + "row-double-click": "Dubbelklik op de rij" + } + }, + "paginator": { + "items-per-page": "Items per pagina:", + "first-page-label": "Eerste pagina", + "last-page-label": "Laatste pagina", + "next-page-label": "Volgende pagina", + "previous-page-label": "Vorige pagina", + "items-per-page-separator": "van" + }, + "language": { + "language": "Taal", + "locales": { + "ca_ES": "Catalaans", + "cs_CZ": "Česky", + "da_DK": "Dansk", + "de_DE": "Deutsch", + "el_GR": "Ελληνικά", + "en_US": "Engels", + "es_ES": "Español", + "fa_IR": "فارسي", + "fr_FR": "Français", + "it_IT": "Italiano", + "ja_JP": "日本語", + "ka_GE": "ქართული", + "ko_KR": "한국어", + "lv_LV": "Kanton Latviešu", + "pt_BR": "Português do Brasil", + "ro_RO": "Română", + "ru_RU": "Русский", + "sl_SI": "Slovenščina", + "tr_TR": "Türkçe", + "uk_UA": "Українська", + "zh_CN": "简体中文", + "zh_TW": "繁體中文" + } + } +} diff --git a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json deleted file mode 100644 index 5e15a3496e..0000000000 --- a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json +++ /dev/null @@ -1,1875 +0,0 @@ -{ - "access": { - "unauthorized": "Неавторизированный", - "unauthorized-access": "Несанкционированный доступ", - "unauthorized-access-text": "Вы должны войти в систему для получения доступа к этому ресурсу!", - "access-forbidden": "Доступ запрещен", - "access-forbidden-text": "У вас нет прав доступа к этому ресурсу!
Для получения доступа попробуйте войти под другим пользователем.", - "refresh-token-expired": "Сессия истекла", - "refresh-token-failed": "Не удалось обновить сессию" - }, - "action": { - "activate": "Активировать", - "suspend": "Приостановить", - "save": "Сохранить", - "saveAs": "Сохранить как", - "cancel": "Отмена", - "ok": "ОК", - "delete": "Удалить", - "add": "Добавить", - "yes": "Да", - "no": "Нет", - "update": "Обновить", - "remove": "Удалить", - "search": "Поиск", - "clear-search": "Очистить", - "assign": "Присвоить", - "unassign": "Отозвать", - "share": "Поделиться", - "make-private": "Закрыть для общего доступа", - "apply": "Применить", - "apply-changes": "Применить изменения", - "edit-mode": "Режим редактирования", - "enter-edit-mode": "Режим редактирования", - "decline-changes": "Отменить изменения", - "close": "Закрыть", - "back": "Назад", - "run": "Запуск", - "sign-in": "Войти", - "edit": "Редактировать", - "view": "Просмотреть", - "create": "Создать", - "drag": "Переместить", - "refresh": "Обновить", - "undo": "Откатить", - "copy": "Копировать", - "paste": "Вставить", - "copy-reference": "Копировать ссылку", - "paste-reference": "Вставить ссылку", - "import": "Импортировать", - "export": "Экспортировать", - "share-via": "Поделиться в {{provider}}", - "continue": "Продолжить", - "discard-changes": "Отменить изменения", - "done": "Завершено" - }, - "aggregation": { - "aggregation": "Агрегация", - "function": "Тип агрегации данных", - "limit": "Максимальное значение", - "group-interval": "Интервал группировки", - "min": "Мин", - "max": "Maкс", - "avg": "Среднее", - "sum": "Сумма", - "count": "Количество", - "none": "Без агрегации" - }, - "admin": { - "general": "Общие", - "general-settings": "Общие настройки", - "outgoing-mail": "Исходящая почта", - "outgoing-mail-settings": "Настройки исходящей почты", - "system-settings": "Системные настройки", - "test-mail-sent": "Пробное письмо успешно отправлено!", - "base-url": "Базовая URL", - "base-url-required": "Базовая URL обязательна.", - "mail-from": "Отправитель", - "mail-from-required": "Отправитель обязателен.", - "smtp-protocol": "SMTP протокол", - "smtp-host": "SMTP хост", - "smtp-host-required": "SMTP хост обязателен.", - "smtp-port": "SMTP порт", - "smtp-port-required": "SMTP порт обязателен.", - "smtp-port-invalid": "Недействительный SMTP порт.", - "timeout-msec": "Таймаут (мс)", - "timeout-required": "Таймаут обязателен.", - "timeout-invalid": "Недействительный таймаут.", - "enable-tls": "Включить TLS", - "tls-version" : "Версия TLS", - "send-test-mail": "Отправить пробное письмо", - "security-settings": "Настройки безопасности", - "password-policy": "Политика паролей", - "minimum-password-length": "Минимальная длина пароля", - "minimum-password-length-required": "Требуется минимальная длина пароля", - "minimum-password-length-range": "Минимальная длина пароля должна быть в диапазоне от 5 до 50", - "minimum-uppercase-letters": "Минимальное количество прописных букв", - "minimum-uppercase-letters-range": "Минимальное количество прописных букв не может быть отрицательным", - "minimum-lowercase-letters": "Минимальное количество строчных букв", - "minimum-lowercase-letters-range": "Минимальное количество строчных букв не может быть отрицательным", - "minimum-digits": "Минимальное количество цифр", - "minimum-digits-range": "Минимальное количество цифр не может быть отрицательным", - "minimum-special-characters": "Минимальное количество специальных символов", - "minimum-special-characters-range": "Минимальное количество специальных символов не может быть отрицательным", - "password-expiration-period-days": "Срок действия пароля в днях", - "password-expiration-period-days-range": "Срок действия пароля в днях не может быть отрицательным", - "password-reuse-frequency-days": "Частота повторного использования пароля в днях", - "password-reuse-frequency-days-range": "Частота повторного использования пароля в днях не может быть отрицательной", - "general-policy": "Общая политика", - "max-failed-login-attempts": "Максимальное количество неудачных попыток входа в систему, прежде чем учетная запись заблокирована", - "minimum-max-failed-login-attempts-range": "Максимальное количество неудачных попыток входа в систему не может быть отрицательным", - "user-lockout-notification-email": "В случае блокировки учетной записи пользователя отправьте уведомление на электронную почту", - "smpp-provider": { - "smpp-version": "SMPP версия", - "smpp-host": "SMPP хост", - "smpp-host-required": "SMPP хост обязателен.", - "smpp-port": "SMPP порт", - "smpp-port-required": "SMPP порт обязателен.", - "system-id": "ИД системи", - "system-id-required": "ИД системи обязателен.", - "password": "Пароль", - "password-required": "Пароль обязателен.", - "type-settings": "Настройки типов", - "source-settings": "Настройки источника", - "destination-settings": "Настройки назначения", - "additional-settings": "Дополнительные настройки", - "system-type": "Тип системы", - "bind-type": "Тип привязки", - "service-type": "Тип обслуживания", - "source-address": "Адрес источника", - "source-ton": "Тип номера источника", - "source-npi": "Идентификация плана нумерации источника", - "destination-ton": "Тип номера назничения", - "destination-npi": "Идентификация плана нумерации назначения", - "address-range": "Диапазон адресов", - "coding-scheme": "Схема кодирования" - } - }, - "alarm": { - "alarm": "Оповещение", - "alarms": "Оповещения", - "select-alarm": "Выбрать оповещение", - "no-alarms-matching": "Оповещения '{{entity}}' не найдены.", - "alarm-required": "Оповещение обязательно", - "alarm-status": "Статус оповещения", - "search-status": { - "ANY": "Все", - "ACTIVE": "Активные", - "CLEARED": "Сброшенные", - "ACK": "Подтвержденные", - "UNACK": "Неподтвержденные" - }, - "display-status": { - "ACTIVE_UNACK": "Активные неподтвержденные", - "ACTIVE_ACK": "Активные подтвержденные", - "CLEARED_UNACK": "Сброшенные неподтвержденные", - "CLEARED_ACK": "Сброшенные подтвержденные" - }, - "no-alarms-prompt": "Оповещения отсутствуют", - "created-time": "Время создания", - "type": "Тип", - "severity": "Уровень", - "originator": "Инициатор", - "originator-type": "Тип инициатора", - "details": "Подробности", - "status": "Статус", - "alarm-details": "Подробности об оповещении", - "start-time": "Время начала", - "end-time": "Время окончания", - "ack-time": "Время подтверждения", - "clear-time": "Время сброса", - "severity-critical": "Критический", - "severity-major": "Основной", - "severity-minor": "Второстепенный", - "severity-warning": "Предупреждение", - "severity-indeterminate": "Неопределенный", - "acknowledge": "Подтвердить", - "clear": "Сбросить", - "search": "Поиск оповещений", - "selected-alarms": "Выбрано { count, plural, =1 {1 оповещение} few {# оповещения} other {# оповещений} }", - "no-data": "Нет данных для отображения", - "polling-interval": "Интервал опроса оповещений (сек)", - "polling-interval-required": "Интервал опроса оповещений обязателен.", - "min-polling-interval-message": "Минимальный интервал опроса оповещений 1 секунда.", - "aknowledge-alarms-title": "Подтвердить { count, plural, =1 {1 оповещение} other {# оповещений} }", - "aknowledge-alarms-text": "Вы точно хотите подтвердить { count, plural, =1 {1 оповещение} other {# оповещений} }?", - "aknowledge-alarm-title": "Подтвердить оповещение", - "aknowledge-alarm-text": "Вы точно хотите подтвердить оповещение?", - "clear-alarms-title": "Сбросить { count, plural, =1 {1 оповещение} other {# оповещений} }", - "clear-alarms-text": "Вы точно хотите сбросить { count, plural, =1 {1 оповещение} other {# оповещений} }?", - "clear-alarm-title": "Сбросить оповещение", - "clear-alarm-text": "Вы точно хотите сбросить оповещение?", - "alarm-status-filter": "Фильтр оповещений", - "max-count-load": "Максимальное количество оповещений для загрузки (0 - неограниченно)", - "max-count-load-required": "Максимальное количество оповещений для загрузки обязателен.", - "max-count-load-error-min": "Минимальное значение 0.", - "fetch-size": "Размер пакета для загрузки", - "fetch-size-required": "Размер пакета для загрузки обязателен.", - "fetch-size-error-min": "Минимальное значение 10." - }, - "alias": { - "add": "Добавить псевдоним", - "edit": "Редактировать псевдоним", - "name": "Псевдоним", - "name-required": "Псевдоним обязателен", - "duplicate-alias": "Такой псевдоним уже существует.", - "filter-type-single-entity": "Отдельный объект", - "filter-type-entity-list": "Список объектов", - "filter-type-entity-name": "Название объекта", - "filter-type-state-entity": "Объект из состояния дашборда", - "filter-type-state-entity-description": "Объект, полученный из параметров состояния дашборда", - "filter-type-asset-type": "Тип актива", - "filter-type-asset-type-description": "Активы типа '{{assetTypes}}'", - "filter-type-asset-type-and-name-description": "Активы типа '{{assetTypes}}' и названием, начинающимся с '{{prefix}}'", - "filter-type-device-type": "Тип устройства", - "filter-type-device-type-description": "Устройства типа '{{deviceTypes}}'", - "filter-type-device-type-and-name-description": "Устройства типа '{{deviceTypes}}' и названием, начинающимся с '{{prefix}}'", - "filter-type-entity-view-type": "Тип Представления Объекта", - "filter-type-entity-view-type-description": "Представления Объекта типа '{{entityViewTypes}}'", - "filter-type-entity-view-type-and-name-description": "Представления Объекта типа '{{entityViewTypes}}' и названием, начинающимся с '{{prefix}}'", - "filter-type-relations-query": "Запрос по типу отношений", - "filter-type-relations-query-description": "{{entities}}, имеющие отношение типа {{relationType}} {{direction}} {{rootEntity}}", - "filter-type-asset-search-query": "Поисковый запрос по активам", - "filter-type-asset-search-query-description": "Активы типа {{assetTypes}}, имеющие отношение типа {{relationType}} {{direction}} {{rootEntity}}", - "filter-type-device-search-query": "Поисковый запрос по устройствам", - "filter-type-device-search-query-description": "Устройства типа {{deviceTypes}}, имеющие отношение типа {{relationType}} {{direction}} {{rootEntity}}", - "filter-type-entity-view-search-query": "Поисковый запрос по представлениям объектов", - "filter-type-entity-view-search-query-description": "Представления объектов типа {{entityViewTypes}}, имеющие отношение типа {{relationType}} {{direction}} {{rootEntity}}", - "entity-filter": "Фильтр объектов", - "resolve-multiple": "Принять как несколько объектов", - "filter-type": "Тип фильтра", - "filter-type-required": "Тип фильтра обязателен.", - "entity-filter-no-entity-matched": "Объекты, соответствующие фильтру, не найдены.", - "no-entity-filter-specified": "Не указан фильтр объектов", - "root-state-entity": "Использовать объект, полученный из дашборда, как корневой", - "last-level-relation": "Использовать только отношения последнего уровня", - "root-entity": "Корневой объект", - "state-entity-parameter-name": "Название объекта состояния", - "default-state-entity": "Объект состояния по умолчанию", - "default-entity-parameter-name": "По умолчанию", - "max-relation-level": "Максимальная глубина отношений", - "unlimited-level": "Неограниченная глубина", - "state-entity": "Объект состояния дашборда", - "all-entities": "Все объекты", - "any-relation": "не указано" - }, - "asset": { - "asset": "Актив", - "assets": "Активы", - "management": "Управление активами", - "view-assets": "Просмотреть активы", - "add": "Добавить актив", - "assign-to-customer": "Присвоить клиенту", - "assign-asset-to-customer": "Присвоить актив(ы) клиенту", - "assign-asset-to-customer-text": "Пожалуйста, выберите активы, которые нужно присвоить объекту", - "no-assets-text": "Активы не найдены", - "assign-to-customer-text": "Пожалуйста, выберите клиента, которому нужно присвоить актив(ы)", - "public": "Общедоступные", - "assignedToCustomer": "Присвоить клиенту", - "make-public": "Открыть общий доступ к активу", - "make-private": "Закрыть общий доступ к активу", - "unassign-from-customer": "Отозвать у клиента", - "delete": "Удалить актив", - "asset-public": "Актив общедоступный", - "asset-type": "Тип актива", - "asset-type-required": "Тип актива обязателен.", - "select-asset-type": "Выберите тип актива", - "enter-asset-type": "Введите тип актива", - "any-asset": "Любой актив", - "no-asset-types-matching": "Активы типа '{{entitySubtype}}' не найдены.", - "asset-type-list-empty": "Типы активов не выбраны.", - "asset-types": "Типы активов", - "name": "Название", - "name-required": "Название обязательно.", - "description": "Описание", - "type": "Тип", - "type-required": "Тип обязателен.", - "details": "Подробности", - "events": "События", - "add-asset-text": "Добавить новый актив", - "asset-details": "Подробности об активе", - "assign-assets": "Присвоить активы", - "assign-assets-text": "Присвоить { count, plural, =1 {1 актив} few {# актива} other {# активов} } клиенту", - "delete-assets": "Удалить активы", - "unassign-assets": "Отозвать активы", - "unassign-assets-action-title": "Отозвать { count, plural, =1 {1 актив} few {# актива} other {# активов} } у клиента", - "assign-new-asset": "Присвоить новый актив", - "delete-asset-title": "Вы точно хотите удалить '{{assetName}}'?", - "delete-asset-text": "Внимание, после подтверждения актив и все связанные с ним данные будут безвозвратно удалены.", - "delete-assets-title": "Вы точно хотите удалить { count, plural, =1 {1 актив} few {# актива} other {# активов} }", - "delete-assets-action-title": "Удалить { count, plural, =1 {1 актив} few {# актива} other {# активов} }", - "delete-assets-text": "Внимание, после подтверждения выбранные активы и все связанные с ними данные будут безвозвратно удалены.", - "make-public-asset-title": "Вы точно хотите открыть общий доступ к активу '{{assetName}}'?", - "make-public-asset-text": "Внимание, после подтверждения актив и все связанные с ним данные станут общедоступными.", - "make-private-asset-title": "Вы точно хотите закрыть общий доступ к активу '{{assetName}}'?", - "make-private-asset-text": "После подтверждения актив и все связанные с ним данные будут закрыты для общего доступа", - "unassign-asset-title": "Вы точно хотите отозвать актив '{{assetName}}'?", - "unassign-asset-text": "После подтверждения актив будут отозван, и клиент потеряет к нему доступ.", - "unassign-asset": "Отозвать актив", - "unassign-assets-title": "Вы точно хотите отозвать { count, plural, =1 {1 актив} few {# актива} other {# активов} }?", - "unassign-assets-text": "После подтверждения активы будут отозваны, и клиент потеряет к ним доступ.", - "copyId": "Копировать ИД актива", - "idCopiedMessage": "ИД актива скопировано в буфер обмена", - "select-asset": "Выбрать активы", - "no-assets-matching": "Активы, соответствующие '{{entity}}', не найдены.", - "asset-required": "Актив обязателен", - "name-starts-with": "Название актива, начинающееся с", - "import": "Импортировать активы", - "asset-file": "Файл с активами", - "label": "Метка" - }, - "attribute": { - "attributes": "Атрибуты", - "latest-telemetry": "Последняя телеметрия", - "attributes-scope": "Контекст атрибутов объекта", - "scope-latest-telemetry": "Последняя телеметрия", - "scope-client": "Клиентские атрибуты", - "scope-server": "Серверные атрибуты", - "scope-shared": "Общие атрибуты", - "add": "Добавить атрибут", - "key": "Ключ", - "last-update-time": "Последнее обновление", - "key-required": "Ключ атрибута обязателен.", - "value": "Значение", - "value-required": "Значение атрибута обязательно.", - "delete-attributes-title": "Вы уверенны, что хотите удалить { count, plural, =1 {1 атрибут} few {# атрибута} other {# атрибутов} }? ", - "delete-attributes-text": "Внимание, после подтверждения выбранные атрибуты будут удалены.", - "delete-attributes": "Удалить атрибуты", - "enter-attribute-value": "Введите значение атрибута", - "show-on-widget": "Показать на виджете", - "widget-mode": "Виджет-режим", - "next-widget": "Следующий виджет", - "prev-widget": "Предыдущий виджет", - "add-to-dashboard": "Добавить на дашборд", - "add-widget-to-dashboard": "Добавить виджет на дашборд", - "selected-attributes": "{ count, plural, =1 {Выбран} other {Выбраны} } { count, plural, =1 {1 атрибут} few {# атрибута} other {# атрибутов} }", - "selected-telemetry": "{ count, plural, =1 {Выбран} other {Выбраны} } { count, plural, =1 {1 параметр} few {# параметра} other {# параметров} } телеметрии" - }, - "audit-log": { - "audit": "Аудит", - "audit-logs": "Логи аудита", - "timestamp": "Время", - "entity-type": "Тип объекта", - "entity-name": "Название объекта", - "user": "Пользователь", - "type": "Тип", - "status": "Статус", - "details": "Подробности", - "type-added": "Добавленный", - "type-deleted": "Удаленный", - "type-updated": "Обновленный", - "type-attributes-updated": "Обновлены атрибуты", - "type-attributes-deleted": "Удалены атрибуты", - "type-rpc-call": "RPC вызов", - "type-credentials-updated": "Обновлены учетные данные", - "type-assigned-to-customer": "Присвоен клиенту", - "type-unassigned-from-customer": "Отозван у клиента", - "type-activated": "Активирован", - "type-suspended": "Приостановлен", - "type-credentials-read": "Чтение учетные данных", - "type-attributes-read": "Чтение атрибутов", - "type-relation-add-or-update": "Обновлены отношения", - "type-relation-delete": "Удалены отношения", - "type-relations-delete": "Удалены все отношения", - "type-alarm-ack": "Подтвержден", - "type-alarm-clear": "Сброшен", - "type-login": "Вход", - "type-logout": "Выход", - "type-lockout": "Заблокирован", - "status-success": "Успех", - "status-failure": "Сбой", - "audit-log-details": "Подробности аудит лога", - "no-audit-logs-prompt": "Логи не найдены", - "action-data": "Данные действия", - "failure-details": "Подробности сбоя", - "search": "Поиск аудит логов", - "clear-search": "Очистить поиск" - }, - "confirm-on-exit": { - "message": "У вас есть несохраненные изменения. Вы точно хотите покинуть эту страницу?", - "html-message": "У вас есть несохраненные изменения.
Вы точно хотите покинуть эту страницу?", - "title": "Несохраненные изменения" - }, - "contact": { - "country": "Страна", - "city": "Город", - "state": "Штат", - "postal-code": "Почтовый код", - "postal-code-invalid": "Допустимы только цифры", - "address": "Адрес", - "address2": "Адрес 2", - "phone": "Телефон", - "email": "Эл. адрес", - "no-address": "Адрес не указан" - }, - "common": { - "username": "Имя пользователя", - "password": "Пароль", - "enter-username": "Введите имя пользователя", - "enter-password": "Введите пароль", - "enter-search": "Введите условие поиска", - "created-time": "Время создания" - }, - "content-type": { - "json": "Json", - "text": "Текстовый", - "binary": "Бинарный (Base64)" - }, - "customer": { - "customer": "Клиент", - "customers": "Клиенты", - "management": "Управление клиентами", - "dashboard": "Дашборд клиента", - "dashboards": "Дашборды клиента", - "devices": "Устройства клиента", - "entity-views": "Представления объектов клиента", - "assets": "Активы клиента", - "public-dashboards": "Общедоступные дашборды", - "public-devices": "Общедоступные устройства", - "public-assets": "Общедоступные активы", - "public-entity-views": "Общедоступные представления объектов", - "add": "Добавить клиента", - "delete": "Удалить клиента", - "manage-customer-users": "Управление пользователями клиента", - "manage-customer-devices": "Управление устройствами клиента", - "manage-customer-dashboards": "Управление дашбордами клиента", - "manage-public-devices": "Управление общедоступными устройствами", - "manage-public-dashboards": "Управление общедоступными дашбордами", - "manage-customer-assets": "Управление активами клиента", - "manage-public-assets": "Управление общедоступными активами", - "add-customer-text": "Добавить нового клиента", - "no-customers-text": "Клиенты не найдены", - "customer-details": "Подробности о клиенте", - "delete-customer-title": "Вы точно хотите удалить клиента '{{customerTitle}}'?", - "delete-customer-text": "Внимание, после подтверждения клиент и все связанные с ним данные будут безвозвратно удалены.", - "delete-customers-title": "Вы точно хотите удалить { count, plural, =1 {1 клиент} few {# клиента} other {# клиентов} }?", - "delete-customers-action-title": "Удалить { count, plural, =1 {1 клиент} few {# клиента} other {# клиентов} }", - "delete-customers-text": "Внимание, после подтверждения выбранные клиенты и все связанные с ними данные будут безвозвратно удалены.", - "manage-users": "Управление пользователями", - "manage-assets": "Управление активами", - "manage-devices": "Управление устройствами", - "manage-dashboards": "Управление дашбордами", - "title": "Имя", - "title-required": "Название обязательно.", - "description": "Описание", - "details": "Подробности", - "events": "События", - "copyId": "Копировать ИД клиента", - "idCopiedMessage": "ИД клиента скопирован в буфер обмена", - "select-customer": "Выбрать клиента", - "no-customers-matching": "Клиенты, соответствующие '{{entity}}', не найдены.", - "customer-required": "Клиент обязателен", - "select-default-customer": "Выбрать клиента по умолчанию", - "default-customer": "Клиент по умолчанию", - "default-customer-required": "Клиент по умолчанию обязателен для отладки дашборда на уровне на уровне Владельца" - }, - "datetime": { - "date-from": "Дата с", - "time-from": "Время с", - "date-to": "Дата до", - "time-to": "Время до" - }, - "dashboard": { - "dashboard": "Дашборд", - "dashboards": "Дашборды", - "management": "Управление дашбордами", - "view-dashboards": "Просмотреть дашборды", - "add": "Добавить дашборд", - "assign-dashboard-to-customer": "Прикрепить дашборд(ы) к клиенту", - "assign-dashboard-to-customer-text": "Пожалуйста, выберите дашборды, которые нужно прикрепить к клиенту", - "assign-to-customer-text": "Пожалуйста, выберите клиента, к которому нужно прикрепить дашборд(ы)", - "assign-to-customer": "Прикрепить к клиенту", - "unassign-from-customer": "Отозвать у клиента", - "make-public": "Открыть дашборд для общего доступа", - "make-private": "Закрыть дашборд для общего доступа", - "manage-assigned-customers": "Управление назначенными клиентами", - "assigned-customers": "Назначенные клиенты", - "assign-to-customers": "Присвоить дашборд(ы) клиенту", - "assign-to-customers-text": "Пожалуйста, выбери клиентов, которым нужно присвоить дашборд(ы)", - "unassign-from-customers": "Отозвать дашборд(ы) у клиентов", - "unassign-from-customers-text": "Пожалуйста, выберите клиентов, у которых нужно отозвать дашборд(ы)", - "no-dashboards-text": "Дашборды не найдены", - "no-widgets": "Виджеты не сконфигурированы", - "add-widget": "Добавить новый виджет", - "title": "Название", - "select-widget-title": "Выберите виджет", - "copyId": "Копировать идентификатор дашборда", - "idCopiedMessage": "Идентификатор дашборда скопирован в буфер обмена", - "select-widget-subtitle": "Список доступных виджетов", - "delete": "Удалить дашборд", - "title-required": "Название обязательно.", - "description": "Описание", - "details": "Подробности", - "dashboard-details": "Подробности о дашборде", - "add-dashboard-text": "Добавить новый дашборд", - "assign-dashboards": "Прикрепить дашборды", - "assign-new-dashboard": "Прикрепить новый дашборд", - "assign-dashboards-text": "Прикрепить { count, plural, =1 {1 дашборд} few {# дашборда} other {# дашбордов} } к клиенту", - "unassign-dashboards-action-text": "Отозвать { count, plural, =1 {1 дашборд} few {# дашборда} other {# дашбордов} } у клиента", - "delete-dashboards": "Удалить дашборды", - "unassign-dashboards": "Отозвать дашборды", - "unassign-dashboards-action-title": "Отозвать { count, plural, =1 {1 дашборд} few {# дашборда} other {# дашбордов} } у клиента", - "delete-dashboard-title": "Вы точно хотите удалить дашборд '{{dashboardTitle}}'?", - "delete-dashboard-text": "Внимание, после подтверждения дашборд и все связанные с ним данные будут безвозвратно утеряны.", - "delete-dashboards-title": "Вы точно хотите удалить { count, plural, =1 {1 дашборд} few {# дашборда} other {# дашбордов} }?", - "delete-dashboards-action-title": "Удалить { count, plural, =1 {1 дашборд} few {# дашборда} other {# дашбордов} }", - "delete-dashboards-text": "Внимание, после подтверждения дашборды и все связанные с ними данные будут безвозвратно утеряны.", - "unassign-dashboard-title": "Вы точно хотите отозвать дашборд '{{dashboardTitle}}'?", - "unassign-dashboard-text": "После подтверждения дашборд не будет доступен клиенту.", - "unassign-dashboard": "Отозвать дашборд", - "unassign-dashboards-title": "Вы точно хотите отозвать { count, plural, =1 {1 дашборд} few {# дашборда} other {# дашбордов} }?", - "unassign-dashboards-text": "После подтверждения выбранные дашборды не будут доступны клиенту.", - "public-dashboard-title": "Теперь дашборд общедоступный", - "public-dashboard-text": "Теперь ваш дашборд {{dashboardTitle}} доступен всем по ссылке:", - "public-dashboard-notice": "Примечание: Для получения доступа к данным устройства нужно открыть общий доступ к этому устройству.", - "make-private-dashboard-title": "Вы точно хотите закрыть общий доступ к дашборду '{{dashboardTitle}}'?", - "make-private-dashboard-text": "После подтверждения дашборд будет закрыт для общего доступа.", - "make-private-dashboard": "Закрыть дашборд для общего доступа", - "socialshare-text": "'{{dashboardTitle}}' сделано ThingsBoard", - "socialshare-title": "'{{dashboardTitle}}' сделано ThingsBoard", - "select-dashboard": "Выберите дашборд", - "no-dashboards-matching": "Дашборд '{{entity}}' не найден.", - "dashboard-required": "Дашборд обязателен.", - "select-existing": "Выберите существующий дашборд", - "create-new": "Создать новый дашборд", - "new-dashboard-title": "Новое название дашборда", - "open-dashboard": "Открыть дашборд", - "set-background": "Установить фон", - "background-color": "Фоновый цвет", - "background-image": "Фоновая картинка", - "background-size-mode": "Размер фона", - "no-image": "Картинка не выбрана", - "drop-image": "Перетащите картинку или кликните для выбора файла.", - "settings": "Настройки", - "columns-count": "Количество колонок", - "columns-count-required": "Количество колонок обязательно.", - "min-columns-count-message": "Минимальное число колонок - 10.", - "max-columns-count-message": "Максимальное число колонок - 1000.", - "widgets-margins": "Величина отступа между виджетами", - "horizontal-margin": "Величина горизонтального отступа", - "horizontal-margin-required": "Величина горизонтального отступа обязательна.", - "min-horizontal-margin-message": "Минимальная величина горизонтального отступа - 0.", - "max-horizontal-margin-message": "Максимальная величина горизонтального отступа - 50.", - "vertical-margin": "Величина вертикального отступа", - "vertical-margin-required": "Величина вертикального отступа обязательна.", - "min-vertical-margin-message": "Минимальная величина вертикального отступа - 0.", - "max-vertical-margin-message": "Максимальная величина вертикального отступа - 50.", - "autofill-height": "Автозаполнение по высоте", - "mobile-layout": "Настройки мобильного режима", - "mobile-row-height": "Высота строки в мобильном режиме, px", - "mobile-row-height-required": "Высота строки в мобильном режиме обязательна.", - "min-mobile-row-height-message": "Минимальная высота строки в мобильном режиме составляет 5 px.", - "max-mobile-row-height-message": "Максимальная высота строки в мобильном режиме составляет 200 px.", - "display-title": "Показать название дашборда", - "toolbar-always-open": "Отображать панель инструментов", - "title-color": "Цвет названия", - "display-dashboards-selection": "Отображать выборку дашбордов", - "display-entities-selection": "Отображать выбору объектов", - "display-dashboard-timewindow": "Показать временное окно", - "display-dashboard-export": "Показать экспорт", - "import": "Импортировать дашборд", - "export": "Экспортировать дашборд", - "export-failed-error": "Не удалось экспортировать дашборд: {{error}}", - "create-new-dashboard": "Создать новый дашборд", - "dashboard-file": "Файл дашборда", - "invalid-dashboard-file-error": "Не удалось импортировать дашборд: неизвестная схема данных дашборда.", - "dashboard-import-missing-aliases-title": "Настроить псевдонимы импортированного дашборда", - "create-new-widget": "Создать новый виджет", - "import-widget": "Импортировать виджет", - "widget-file": "Виджет-файл", - "invalid-widget-file-error": "Не удалось импортировать виджет: неправильный формат данных.", - "widget-import-missing-aliases-title": "Настроить псевдонимы, которые использует импортированный виджет", - "open-toolbar": "Открыть панель инструментов дашборда", - "close-toolbar": "Закрыть панель инструментов", - "configuration-error": "Ошибка в настройках", - "alias-resolution-error-title": "Ошибка в настройках псевдонимов дашборда", - "invalid-aliases-config": "Не удалось найти устройство, соответствующее фильтру псевдонимов.
Пожалуйста, обратитесь к администратору для устранения неполадки.", - "select-devices": "Выберите устройства", - "assignedToCustomer": "Присвоенные клиенту", - "assignedToCustomers": "Присвоенные клиентам", - "public": "Публичный", - "public-link": "Публичная ссылка", - "copy-public-link": "Копировать публичную ссылку", - "public-link-copied-message": "Публичная ссылка на дашборд скопирована в буфер обмена.", - "manage-states": "Управление состоянием дашборда", - "states": "Состояния дашборда", - "search-states": "Поиск состояния дашборда", - "selected-states": "Выбрано { count, plural, =1 {1 состояние} few {# состояния} other {# состояний} } дашборда", - "edit-state": "Изменить состояние дашборда", - "delete-state": "Удалить состояние дашборда", - "add-state": "Добавить состояние дашборда", - "state": "Состояние дашборда", - "state-name": "Название", - "state-name-required": "Название состояния дашборда обязательно.", - "state-id": "ИД состояния", - "state-id-required": "ИД состояния дашборда обязателен.", - "state-id-exists": "Состояния дашборда с таким именем уже существует.", - "is-root-state": "Корневое состояние", - "delete-state-title": "Удалить состояние дашборда", - "delete-state-text": "Вы точно хотите удалить состояние дашборда '{{stateName}}'?", - "show-details": "Показать подробности", - "hide-details": "Скрыть подробности", - "select-state": "Выбрать состояние", - "state-controller": "Контроллер состояния" - }, - "datakey": { - "settings": "Настройки", - "advanced": "Дополнительно", - "label": "Метка", - "color": "Цвет", - "units": "Укажите символы, которые нужно указывать после значения", - "decimals": "Число знаков после запятой", - "data-generation-func": "Функция генерации данных", - "use-data-post-processing-func": "Использовать функцию пост-обработки данных", - "configuration": "Конфигурация ключа данных", - "timeseries": "Телеметрия", - "attributes": "Атрибуты", - "entity-field": "Поле объекта", - "alarm": "Параметры оповещения", - "timeseries-required": "Телеметрия объекта обязательна.", - "timeseries-or-attributes-required": "Телеметрия/атрибуты обязательны.", - "maximum-timeseries-or-attributes": "Максимальное количество параметров телеметрии или атрибутов равно {{count}}", - "alarm-fields-required": "Параметры оповещения обязательны.", - "function-types": "Тип функции", - "function-types-required": "Тип функции обязателен.", - "maximum-function-types": "Максимальное количество типов функции равно {{count}}", - "time-description": "время текущего значения;", - "value-description": "текущее значение;", - "prev-value-description": "результат предыдущего вызова функции;", - "time-prev-description": "время предыдущего значения;", - "prev-orig-value-description": "исходное предыдущее значение;" - }, - "datasource": { - "type": "Тип источника данных", - "name": "Название", - "add-datasource-prompt": "Пожалуйста, добавьте источник данных" - }, - "details": { - "edit-mode": "Режим редактирования", - "edit-json": "Редактировать JSON", - "toggle-edit-mode": "Режим редактирования" - }, - "device": { - "device": "Устройство", - "device-required": "Устройство обязательно.", - "devices": "Устройства", - "management": "Управление устройствами", - "view-devices": "Просмотреть устройства", - "device-alias": "Псевдоним устройства", - "aliases": "Псевдонимы устройства", - "no-alias-matching": "'{{alias}}' не найден.", - "no-aliases-found": "Псевдонимы не найдены.", - "no-key-matching": "'{{key}}' не найден.", - "no-keys-found": "Ключи не найдены.", - "create-new-alias": "Создать новый!", - "create-new-key": "Создать новый!", - "duplicate-alias-error": "Найден дублирующийся псевдоним '{{alias}}'.
В рамках дашборда псевдонимы устройств должны быть уникальными.", - "configure-alias": "Настроить '{{alias}}' псевдоним", - "no-devices-matching": "Устройство '{{entity}}' не найдено.", - "alias": "Псевдоним", - "alias-required": "Псевдоним устройства обязателен.", - "remove-alias": "Удалить псевдоним устройства", - "add-alias": "Добавить псевдоним устройства", - "name-starts-with": "Название начинается с", - "device-list": "Список устройств", - "use-device-name-filter": "Использовать фильтр", - "device-list-empty": "Устройства не выбраны.", - "device-name-filter-required": "Фильтр названия устройства обязателен.", - "device-name-filter-no-device-matched": "Устройства, названия которых начинаются с '{{device}}', не найдены.", - "add": "Добавить устройство", - "assign-to-customer": "Присвоить клиенту", - "assign-device-to-customer": "Присвоить устройство(а) клиенту", - "assign-device-to-customer-text": "Пожалуйста, выберите устройства, которые нужно присвоить клиенту", - "make-public": "Открыть общий доступ к устройству", - "make-private": "Закрыть общий доступ к устройству", - "no-devices-text": "Устройства не найдены", - "assign-to-customer-text": "Пожалуйста, выберите клиента, которому нужно присвоить устройство(а)", - "device-details": "Подробности об устройстве", - "add-device-text": "Добавить новое устройство", - "credentials": "Учетные данные", - "manage-credentials": "Управление учетными данными", - "delete": "Удалить устройство", - "assign-devices": "Присвоить устройство", - "assign-devices-text": "Присвоить { count, plural, =1 {1 устройство} few {# устройства} other {# устройств} } клиенту", - "delete-devices": "Удалить устройства", - "unassign-from-customer": "Отозвать у клиенту", - "unassign-devices": "Отозвать устройства", - "unassign-devices-action-title": "Отозвать у клиента { count, plural, =1 {1 устройство} few {# устройства} other {# устройств} }", - "assign-new-device": "Присвоить новое устройство", - "make-public-device-title": "Вы точно хотите открыть общий доступ к устройству '{{deviceName}}'?", - "make-public-device-text": "После подтверждения устройство и все связанные с ним данные будут общедоступными.", - "make-private-device-title": "Вы точно хотите закрыть общий доступ к устройству '{{deviceName}}'", - "make-private-device-text": "После подтверждения устройство и все связанные с ним данные будут закрыты для общего доступа.", - "view-credentials": "Просмотреть учетные данные", - "delete-device-title": "Вы точно хотите удалить устройство '{{deviceName}}'?", - "delete-device-text": "Внимание, после подтверждения устройство и все связанные с ним данные будут безвозвратно утеряны.", - "delete-devices-title": "Вы точно хотите удалить { count, plural, =1 {1 устройство} few {# устройства} other {# устройств} }?", - "delete-devices-action-title": "Удалить { count, plural, =1 {1 устройство} few {# устройства} other {# устройств} }", - "delete-devices-text": "Внимание, после подтверждения выбранные устройства и все связанные с ними данные будут безвозвратно утеряны..", - "unassign-device-title": "Вы точно хотите отозвать устройство '{{deviceName}}'?", - "unassign-device-text": "После подтверждения устройство будет недоступно клиенту.", - "unassign-device": "Отозвать устройство", - "unassign-devices-title": "Вы точно хотите отозвать { count, plural, =1 {1 устройство} few {# устройства} other {# устройств} }?", - "unassign-devices-text": "После подтверждения выбранные устройства будут недоступны клиенту.", - "device-credentials": "Учетные данные устройства", - "credentials-type": "Тип учетных данных", - "access-token": "Токен", - "access-token-required": "Токен обязателен.", - "access-token-invalid": "Длина токена должна быть от 1 до 32 символов.", - "secret": "Секрет", - "secret-required": "Секрет обязателен.", - "device-type": "Тип устройства", - "device-type-required": "Тип устройства обязатеен.", - "select-device-type": "Выберите тип устройства", - "enter-device-type": "Введите тип устройства", - "any-device": "Любое устройство", - "no-device-types-matching": "Тип устройства, соответствующий '{{entitySubtype}}', не найден.", - "device-type-list-empty": "Не выбран тип устройства.", - "device-types": "Типы устройств", - "name": "Название", - "name-required": "Название обязательно.", - "description": "Описание", - "events": "События", - "details": "Подробности", - "copyId": "Копировать идентификатор устройства", - "copyAccessToken": "Копировать токен", - "idCopiedMessage": "Идентификатор устройства скопирован в буфер обмена", - "accessTokenCopiedMessage": "Токен устройства скопирован в буфер обмена", - "assignedToCustomer": "Присвоен клиенту", - "unable-delete-device-alias-title": "Не удалось удалить псевдоним устройства", - "unable-delete-device-alias-text": "Не удалось удалить псевдоним '{{deviceAlias}}' устройства, т.к. он используется следующими виджетами:
{{widgetsList}}", - "is-gateway": "Гейтвей", - "public": "Общедоступный", - "device-public": "Устройство общедоступно", - "select-device": "Выбрать устройство", - "import": "Импортировать устройства", - "device-file": "Файл с устройствами" - }, - "dialog": { - "close": "Закрыть диалог" - }, - "direction": { - "column": "Колонка", - "row": "Строка" - }, - "error": { - "unable-to-connect": "Не удалось подключиться к серверу! Пожалуйста, проверьте интернет-соединение.", - "unhandled-error-code": "Код необработанной ошибки: {{errorCode}}", - "unknown-error": "Неизвестная ошибка" - }, - "entity": { - "entity": "Объект", - "entities": "Объекты", - "aliases": "Псевдонимы объекта", - "entity-alias": "Псевдоним объекта", - "unable-delete-entity-alias-title": "Не удалось удалить псевдоним объекта", - "unable-delete-entity-alias-text": "Псевдоним объекта '{{entityAlias}}' не может быть удален, так как используется следующим(и) виджетом(ами):
{{widgetsList}}", - "duplicate-alias-error": "Найден дубликат псевдонима '{{alias}}'.
В рамках одного дашборда псевдонимы объектов должны быть уникальными.", - "missing-entity-filter-error": "Отсутствует фильтр для псевдонима '{{alias}}'.", - "configure-alias": "Настроить псевдоним '{{alias}}'", - "alias": "Псевдоним", - "alias-required": "Псевдоним объекта обязателен.", - "remove-alias": "Убрать псевдоним объекта", - "add-alias": "Добавить псевдоним объекта", - "entity-list": "Список объектов", - "entity-type": "Тип объекта", - "entity-types": "Типы объекта", - "entity-type-list": "Список типов объекта", - "any-entity": "Любой объект", - "enter-entity-type": "Введите тип объекта", - "no-entities-matching": "Объекты, соответствующие '{{entity}}', не найдены.", - "no-entity-types-matching": "Типы объектов, соответствующие '{{entityType}}', не найдены.", - "name-starts-with": "Название, начинающееся с", - "use-entity-name-filter": "Использовать фильтр", - "entity-list-empty": "Не выбраны объекты.", - "entity-name-filter-required": "Фильтр по названию объекта обязателен.", - "entity-name-filter-no-entity-matched": "Объекты, чье название начинается с '{{entity}}', не найдены.", - "all-subtypes": "Все", - "select-entities": "Выберите объекты", - "no-aliases-found": "Псевдонимы не найдены.", - "no-alias-matching": "Псевдоним '{{alias}}' не найден.", - "create-new-alias": "Создать новый!", - "key": "Ключ", - "key-name": "Название ключа", - "no-keys-found": "Ключ не найден.", - "no-key-matching": "Ключ '{{key}}' не найден.", - "create-new-key": "Создать новый!", - "type": "Тип", - "type-required": "Тип объекта обязателен.", - "type-device": "Устройство", - "type-devices": "Устройства", - "list-of-devices": "{ count, plural, =1 {Одно устройство} other {Список из # устройств} }", - "device-name-starts-with": "Устройства, чьи название начинается с '{{prefix}}'", - "type-asset": "Актив", - "type-assets": "Активы", - "list-of-assets": "{ count, plural, =1 {Один актив} other {Список из # активов} }", - "asset-name-starts-with": "Активы, чьи название начинается с '{{prefix}}'", - "type-entity-view": "Представление Объекта", - "type-entity-views": "Представления Объекта", - "list-of-entity-views": "{ count, plural, =1 {Одно представление объекта} other {Список из # представлений объекта} }", - "entity-view-name-starts-with": "Представления Объекта, чьи название начинается с '{{prefix}}'", - "type-rule": "Правило", - "type-rules": "Правила", - "list-of-rules": "{ count, plural, =1 {Одно правило} other {Список из # правил} }", - "rule-name-starts-with": "Правила, чьи названия начинаются с '{{prefix}}'", - "type-plugin": "Плагин", - "type-plugins": "Плагины", - "list-of-plugins": "{ count, plural, =1 {Один плагин} other {Список из # плагинов} }", - "plugin-name-starts-with": "Плагины, чьи имена начинаются с '{{prefix}}'", - "type-tenant": "Владелец", - "type-tenants": "Владельцы", - "list-of-tenants": "{ count, plural, =1 {Один владелец} other {Список из # владельцев} }", - "tenant-name-starts-with": "Владельцы, чьи имена начинаются с '{{prefix}}'", - "type-customer": "Клиент", - "type-customers": "Клиенты", - "list-of-customers": "{ count, plural, =1 {Один клиент} other {Список из # клиентов} }", - "customer-name-starts-with": "Клиенты, чьи имена начинаются с '{{prefix}}'", - "type-user": "Пользователь", - "type-users": "Пользователи", - "list-of-users": "{ count, plural, =1 {Один пользователь} other {Список из # пользователей} }", - "user-name-starts-with": "Пользователи, чьи имена начинаются с '{{prefix}}'", - "type-dashboard": "Дашборд", - "type-dashboards": "Дашборды", - "list-of-dashboards": "{ count, plural, =1 {Один дашборд} other {Список из # дашбордов} }", - "dashboard-name-starts-with": "Дашборды, чьи названия начинаются с '{{prefix}}'", - "type-alarm": "Оповещение", - "type-alarms": "Оповещения", - "list-of-alarms": "{ count, plural, =1 {Одно оповещение} other {Список из # оповещений} }", - "alarm-name-starts-with": "Оповещения, чьи названия начинаются с '{{prefix}}'", - "type-rulechain": "Цепочка правил", - "type-rulechains": "Цепочки правил", - "list-of-rulechains": "{ count, plural, =1 {Одна цепочка правил} other {Список из # цепочек правил} }", - "rulechain-name-starts-with": "Цепочки правил, чьи названия начинаются с '{{prefix}}'", - "type-rulenode": "Правило", - "type-rulenodes": "Правила", - "list-of-rulenodes": "{ count, plural, =1 {Одно правило} other {Список из # правил} }", - "rulenode-name-starts-with": "Правила, чьи названия начинаются с '{{prefix}}'", - "type-current-customer": "Текущий клиент", - "type-current-tenant": "Текущий владелец", - "search": "Поиск объектов", - "selected-entities": "Выбран(ы) { count, plural, =1 {1 объект} few {# объекта} other {# объектов} }", - "entity-name": "Название объекта", - "entity-label": "Метка объекта", - "details": "Подробности об объекте", - "no-entities-prompt": "Объекты не найдены", - "no-data": "Нет данных для отображения", - "columns-to-display": "Отобразить следующие колонки" - }, - "entity-field": { - "created-time": "Время создания", - "name": "Название", - "type": "Тип", - "first-name": "Имя", - "last-name": "Фамилия", - "email": "Электронная почта", - "title": "Название", - "country": "Страна", - "state": "Штат/Область", - "city": "Город", - "address": "Адрес", - "address2": "Адрес 2", - "zip": "Индекс", - "phone": "Телефон", - "label": "Метка" - }, - "entity-view": { - "entity-view": "Представление Объекта", - "entity-view-required": "Представление объекта обязательно.", - "entity-views": "Представления Объектов", - "management": "Управление представлениями объектов", - "view-entity-views": "Просмотр представлений объектов", - "entity-view-alias": "Псевдоним Представления Объекта", - "aliases": "Псевдонимы Представления Объекта", - "no-alias-matching": "Псевдоним '{{alias}}' не найден.", - "no-aliases-found": "Псевдонимы не найдены.", - "no-key-matching": "Ключ '{{key}}' не найден.", - "no-keys-found": "Ключи не найдены.", - "create-new-alias": "Создать новый!", - "create-new-key": "Создать новый!", - "duplicate-alias-error": "Найден дубликат псевдонима '{{alias}}'.
В рамках одного дашборда псевдонимы представлений объектов должны быть уникальными.", - "configure-alias": "Настроить псевдоним '{{alias}}'", - "no-entity-views-matching": "Объекты, соответствующие '{{entity}}', не найдены.", - "alias": "Псевдоним", - "alias-required": "Псевдоним представления объекта обязателен.", - "remove-alias": "Убрать псевдоним представления объекта", - "add-alias": "Добавить псевдоним представления объекта", - "name-starts-with": "Представления объектов, чьи название начинается с", - "entity-view-list": "Список представлений объектов", - "use-entity-view-name-filter": "Использовать фильтр", - "entity-view-list-empty": "Не выбраны представления объектов.", - "entity-view-name-filter-required": "Для представлений объектов фильтр по названиям обязателен.", - "entity-view-name-filter-no-entity-view-matched": "Представление объектов, чьи название начинаются с '{{entityView}}', не найдены.", - "add": "Представление объекта", - "assign-to-customer": "Назначить клиенту", - "assign-entity-view-to-customer": "Назначить представление(я) объекта(ов) клиенту", - "assign-entity-view-to-customer-text": "Пожалуйста, выберите представления объектов, которые нужно назначить клиенту", - "no-entity-views-text": "Представления объектов не найдены", - "assign-to-customer-text": "Пожалуйста, выберите клиента, которому нужно назначить представления объектов", - "entity-view-details": "Подробности о представлении объекта", - "add-entity-view-text": "Добавить новое представление объекта", - "delete": "Удалить представление объекта", - "assign-entity-views": "Назначить представления объектов", - "assign-entity-views-text": "Назначить клиенту { count, plural, =1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }", - "delete-entity-views": "Удалить представления объектов", - "unassign-from-customer": "Отозвать у клиента", - "unassign-entity-views": "Отозвать представления объектов", - "unassign-entity-views-action-title": "Отозвать у клиента { count, plural, =1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }", - "assign-new-entity-view": "Назначит новое представление объекта", - "delete-entity-view-title": "Вы точно хотите удалить представление объекта '{{entityViewName}}'?", - "delete-entity-view-text": "Внимание, после подтверждения представление объекта и все связанные с ним данные будут безвозвратно удалены.", - "delete-entity-views-title": "Вы точно хотите удалить { count, plural, =1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }?", - "delete-entity-views-action-title": "Удалить { count, plural, =1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }", - "delete-entity-views-text": "Внимание, после подтверждения выбранные представления объектов и все связанные с ними данные будут безвозвратно удалены.", - "unassign-entity-view-title": "Вы точно хотите отозвать представление объекта '{{entityViewName}}'?", - "unassign-entity-view-text": "После подтверждение представление объекта будет недоступно клиенту.", - "unassign-entity-view": "Отозвать представление объекта", - "unassign-entity-views-title": "Вы точно хотите отозвать { count, plural, =1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }?", - "unassign-entity-views-text": "После подтверждение выбранные представления объектов будет недоступно клиенту.", - "entity-view-type": "Тип представления объекта", - "entity-view-type-required": "Тип представления объекта обязателен.", - "select-entity-view-type": "Выберите тип представления объекта", - "enter-entity-view-type": "Введите тип представления объекта", - "any-entity-view": "Любое представление объекта", - "no-entity-view-types-matching": "Типы представления объекта, соответствующие '{{entitySubtype}}', не найдены.", - "entity-view-type-list-empty": "Не выбраны типы представления объекта.", - "entity-view-types": "Типы представления объекта", - "name": "Название", - "name-required": "Название обязательно.", - "description": "Описание", - "events": "События", - "details": "Подробности", - "copyId": "Копировать ИД представление объекта", - "assignedToCustomer": "Назначено клиенту", - "unable-entity-view-device-alias-title": "Не удалось удалить псевдоним представления объекта", - "unable-entity-view-device-alias-text": "Не удалось удалить псевдоним устройства '{{entityViewAlias}}', т.к. он используется следующими виджетами:
{{widgetsList}}", - "select-entity-view": "Выбрать представление объекта", - "make-public": "Открыть общий доступ к представлению объекта", - "make-private": "Закрыть общий доступ к представлению объекта", - "start-date": "Дата начала", - "start-ts": "Время начала", - "end-date": "Дата окончания", - "end-ts": "Время окончания", - "date-limits": "Временной лимит", - "client-attributes": "Клиентские атрибуты", - "shared-attributes": "Общие атрибуты", - "server-attributes": "Серверные атрибуты", - "timeseries": "Телеметрия", - "client-attributes-placeholder": "Клиентские атрибуты", - "shared-attributes-placeholder": "Общие атрибуты", - "server-attributes-placeholder": "Серверные атрибуты", - "timeseries-placeholder": "Телеметрия", - "target-entity": "Целевой объект", - "attributes-propagation": "Пробросить атрибуты", - "attributes-propagation-hint": "Представление объекта автоматически копирует указанные атрибуты с Целевого Объекта каждый раз, когда вы сохраняете или обновляете это представление. В целях производительности атрибуты целевого объекта не пробрасываются в представление объекта на каждом их изменении. Вы можете включить автоматический проброс, настроив в вашей цепочке правило \"copy to view\" и соединив его с сообщениями типа \"Post attributes\" и \"Attributes Updated\".", - "timeseries-data": "Данные телеметрии", - "timeseries-data-hint": "Настроить ключи данных телеметрии целевого объекта, которые будут доступны представлению объекта. Эти данные только для чтения.", - "make-public-entity-view-title": "Вы уверенны, что хотите открыть общий доступ к представленю объекта '{{entityViewName}}'?", - "make-public-entity-view-text": "После подтверждения представление объекта и все связанные с ним данные станут публичными и доступными для других пользователей.", - "make-private-entity-view-title": "Вы уверенны, что хотите закрыть общий доступ к представлению объекта '{{entityViewName}}'?", - "make-private-entity-view-text": "После подтверждения представление объекта и все звязанные с ним данные станут приватными и не будут доступны для других пользователей." - }, - "event": { - "event-type": "Тип события", - "type-error": "Ошибка", - "type-lc-event": "Событие жизненного цикла", - "type-stats": "Статистика", - "type-debug-rule-node": "Отладка", - "type-debug-rule-chain": "Отладка", - "no-events-prompt": "События не найдены", - "error": "Ошибка", - "alarm": "Аварийное оповещение", - "event-time": "Время возникновения события", - "server": "Сервер", - "body": "Тело", - "method": "Метод", - "type": "Тип", - "message-id": "ИД сообщения", - "message-type": "Тип сообщения", - "data-type": "Тип данных", - "relation-type": "Тип отношения", - "metadata": "Метаданные", - "data": "Данные", - "event": "Событие", - "status": "Статус", - "success": "Успех", - "failed": "Неудача", - "messages-processed": "Сообщения обработаны", - "errors-occurred": "Возникли ошибки", - "all-events": "Все", - "entity-type": "Тип объекта", - "clear-request-title": "Удалить все события", - "clear-request-text": "Вы точно хотите удалить все события?" - }, - "extension": { - "extensions": "Расширение", - "selected-extensions": "Выбрано { count, plural, =1 {1 расширение} few {# расширения} other {# расширений} }", - "type": "Тип", - "key": "Ключ", - "value": "Значение", - "id": "ИД", - "extension-id": "ИД расширения", - "extension-type": "Тип расширения", - "transformer-json": "JSON *", - "unique-id-required": "Такое ИД расширения уже существует.", - "delete": "Удалить расширение", - "add": "Добавить расширение", - "edit": "Редактировать расширение", - "delete-extension-title": "Вы точно хотите удалить расширение '{{extensionId}}'?", - "delete-extension-text": "Внимание, после подтверждения расширение и все связанные с ним данные будут безвозвратно удалены.", - "delete-extensions-title": "Вы точно хотите удалить { count, plural, =1 {1 расширение} few {# расширения} other {# расширений} }?", - "delete-extensions-text": "Внимание, после подтверждения выбранные расширения будут удалены.", - "converters": "Конвертеры", - "converter-id": "ИД конвертера", - "configuration": "Конфигурация", - "converter-configurations": "Конфигурация конвертера", - "token": "Токен безопасности", - "add-converter": "Добавить конвертер", - "add-config": "Добавить конфигурацию конвертера", - "device-name-expression": "Маска названия устройства", - "device-type-expression": "Маска типа устройства", - "custom": "Пользовательский", - "to-double": "To Double", - "transformer": "Преобразователь", - "json-required": "JSON преобразователя обязателен.", - "json-parse": "Не удалось распознать JSON преобразователя.", - "attributes": "Атрибуты", - "add-attribute": "Добавить атрибут", - "add-map": "Добавить элемент сопоставления", - "timeseries": "Телеметрия", - "add-timeseries": "Добавить параметр телеметрии", - "field-required": "Параметр обязателен", - "brokers": "Брокеры", - "add-broker": "Добавить брокер", - "host": "Хост", - "port": "Порт", - "port-range": "Значение порта лежит в диапазоне от 1 до 65535.", - "ssl": "SSL", - "credentials": "Учетные данные", - "username": "Имя пользователя", - "password": "Пароль", - "retry-interval": "Интервал повтора в миллисекундах", - "anonymous": "Анонимный", - "basic": "Общий", - "pem": "PEM", - "ca-cert": "Файл CA сертификата *", - "private-key": "Файл приватного ключа *", - "cert": "Файл сертификата *", - "no-file": "Не выбран файл.", - "drop-file": "Перетяните файл или нажмите для выбора файла.", - "mapping": "Сопоставление", - "topic-filter": "Фильтр тем", - "converter-type": "Тип конвертера", - "converter-json": "JSON", - "json-name-expression": "JSON выражение для названия устройства", - "topic-name-expression": "Выражение для названия устройства в названии темы", - "json-type-expression": "JSON выражение для типа устройства", - "topic-type-expression": "Выражение для типа устройства в названии темы", - "attribute-key-expression": "Выражение для атрибута", - "attr-json-key-expression": "JSON выражение для атрибута", - "attr-topic-key-expression": "Выражение для атрибута в названии темы", - "request-id-expression": "Выражение для ИД запроса", - "request-id-json-expression": "JSON выражение для ИД запроса", - "request-id-topic-expression": "Выражение для ИД запроса в названии темы", - "response-topic-expression": "Выражение для темы ответов", - "value-expression": "Выражение для значения", - "topic": "Тема", - "timeout": "Таймаут в миллисекундах", - "converter-json-required": "JSON конвертер обязателен.", - "converter-json-parse": "Не удалось распознать JSON конвертера.", - "filter-expression": "Выражение для фильтрации", - "connect-requests": "Запросы о подключении устройства", - "add-connect-request": "Добавить запросы о подключении устройства", - "disconnect-requests": "Запросы об отсоединении устройства", - "add-disconnect-request": "Добавить запрос об отсоединении устройства", - "attribute-requests": "Запросы для атрибутов", - "add-attribute-request": "Добавить запрос для атрибутов", - "attribute-updates": "Обновление атрибутов", - "add-attribute-update": "Добавить обновление атрибутов", - "server-side-rpc": "Серверный RPC", - "add-server-side-rpc-request": "Добавить серверный RPC", - "device-name-filter": "Фильтр для названия устройства", - "attribute-filter": "Фильтр для атрибутов", - "method-filter": "Фильтр для процедур", - "request-topic-expression": "Выражение для темы запросов", - "response-timeout": "Время ожидания ответа в миллисекундах", - "topic-expression": "Выражение для названия темы", - "client-scope": "Клиентский", - "add-device": "Добавить устройство", - "opc-server": "Серверы", - "opc-add-server": "Добавить сервер", - "opc-add-server-prompt": "Пожалуйста, добавьте сервер", - "opc-application-name": "Название приложения", - "opc-application-uri": "URI приложения", - "opc-scan-period-in-seconds": "Частота сканирования в секундах", - "opc-security": "Безопасность", - "opc-identity": "Идентификация", - "opc-keystore": "Хранилище ключей", - "opc-type": "Тип", - "opc-keystore-type": "Тип", - "opc-keystore-location": "Расположение *", - "opc-keystore-password": "Пароль", - "opc-keystore-alias": "Псевдоним", - "opc-keystore-key-password": "Пароль для ключ", - "opc-device-node-pattern": "Паттерн OPC узла устройства", - "opc-device-name-pattern": "Паттерн названия устройства", - "modbus-server": "Серверы/ведомые устройства", - "modbus-add-server": "Добавить сервер/ведомое устройство", - "modbus-add-server-prompt": "Пожалуйста, добавить сервер/ведомое устройство", - "modbus-transport": "Транспорт", - "modbus-tcp-reconnect": "Переподключатсься автоматически", - "modbus-port-name": "Название последовательного порта", - "modbus-encoding": "Кодирование символов", - "modbus-parity": "Паритет", - "modbus-baudrate": "Скорость передачи", - "modbus-databits": "Биты данных", - "modbus-stopbits": "Стоп-биты", - "modbus-databits-range": "Параметр \"Биты данных\" может принимать значения 7 или 8.", - "modbus-stopbits-range": "Параметр \"Стоп-биты\" может принимать значения 1 или 2.", - "modbus-unit-id": "ИД устройства", - "modbus-unit-id-range": "ИД устройства должен быть в диапазоне от 1 до 247.", - "modbus-device-name": "Название устройства", - "modbus-poll-period": "Частота опроса (в миллисекундах)", - "modbus-attributes-poll-period": "Частота опроса для атрибутов (в миллисекундах)", - "modbus-timeseries-poll-period": "Частота опроса для телеметрии (в миллисекундах)", - "modbus-poll-period-range": "Значение параметра \"Частота опроса\" должно быть больше ноля.", - "modbus-tag": "Тег", - "modbus-function": "Modbus функция", - "modbus-register-address": "Адрес регистра", - "modbus-register-address-range": "Адрес регистра должен быть в диапазоне от 0 до 65535.", - "modbus-register-bit-index": "Номер бита", - "modbus-register-bit-index-range": "Номер бита должен быть в диапазоне от 0 до 15.", - "modbus-register-count": "Количество регистров", - "modbus-register-count-range": "Количество регистров должно быть больше ноля.", - "modbus-byte-order": "Порядок байтов", - "sync": { - "status": "Статус", - "sync": "Синхронизирован", - "not-sync": "Не синхронизирован", - "last-sync-time": "Время последней синхронизации", - "not-available": "Не доступен" - }, - "export-extensions-configuration": "Экспортировать конфигурацию расширений", - "import-extensions-configuration": "Импортировать конфигурацию расширений", - "import-extensions": "Импортировать расширения", - "import-extension": "Импортировать расширение", - "export-extension": "Экспортировать расширение", - "file": "Файл расширений", - "invalid-file-error": "Не правильный формат файла" - }, - "fullscreen": { - "expand": "Во весь экран", - "exit": "Выйти из полноэкранного режима", - "toggle": "Во весь экран", - "fullscreen": "Полноэкранный режим" - }, - "function": { - "function": "Функция" - }, - "grid": { - "delete-item-title": "Вы точно хотите удалить этот объект?", - "delete-item-text": "Внимание, после подтверждения объект и все связанные с ним данные будут безвозвратно утеряны.", - "delete-items-title": "Вы точно хотите удалить { count, plural, =1 {1 объект} few {# объекта} other {# объектов} }?", - "delete-items-action-title": "Удалить { count, plural, =1 {1 объект} few {# объекта} other {# объектов} }", - "delete-items-text": "Внимание, после подтверждения выбранные объекты и все связанные с ними данные будут безвозвратно утеряны.", - "add-item-text": "Добавить новый объект", - "no-items-text": "Объекты не найдены", - "item-details": "Подробности об объекте", - "delete-item": "Удалить объект", - "delete-items": "Удалить объекты", - "scroll-to-top": "Прокрутка к началу" - }, - "help": { - "goto-help-page": "Перейти к справке" - }, - "home": { - "home": "Главная", - "profile": "Профиль", - "logout": "Выйти из системы", - "menu": "Меню", - "avatar": "Аватар", - "open-user-menu": "Открыть меню пользователя" - }, - "import": { - "no-file": "Файл не выбран", - "drop-file": "Перетащите JSON файл или кликните для выбора файла.", - "drop-file-csv": "Перетащите CSV файл или кликните для выбора файла.", - "column-value": "Значение", - "column-title": "Название", - "column-example": "Пример значений данных", - "column-key": "Ключ атрибута/телеметрии", - "csv-delimiter": "Разделитель в CSV файле", - "csv-first-line-header": "Первая строка содержит названия колонок", - "csv-update-data": "Обновить атрибут/телеметрию", - "import-csv-number-columns-error": "Файл должен содержать как минимум две колонки", - "import-csv-invalid-format-error": "Неверный формат данных. Строка: '{{line}}'", - "column-type": { - "name": "Название", - "type": "Тип", - "label": "Метка", - "column-type": "Тип колонки", - "client-attribute": "Клиентский атрибут", - "shared-attribute": "Общий атрибут", - "server-attribute": "Серверный атрибут", - "timeseries": "Телеметрия", - "entity-field": "Entity field", - "access-token": "Токен" - }, - "stepper-text": { - "select-file": "Выберите файл", - "configuration": "Конфигурация импорта", - "column-type": "Выберите тип колонок", - "creat-entities": "Создание новых объектов" - }, - "message": { - "create-entities": "{{count}} новый(х) объект(ов) было успешно создано.", - "update-entities": "{{count}} объект(ов) успешно обновлено.", - "error-entities": "Возникла ошибка при создании {{count}} объекта(ов)." - } - }, - "item": { - "selected": "Выбранные" - }, - "js-func": { - "no-return-error": "Функция должна возвращать значение!", - "return-type-mismatch": "Функция должна возвращать значение типа '{{type}}'!" - }, - "key-val": { - "key": "Ключ", - "value": "Значение", - "remove-entry": "Удалить элемент", - "add-entry": "Добавить элемент", - "no-data": "Элементы отсутствуют" - }, - "layout": { - "layout": "Макет", - "manage": "Управление макетами", - "settings": "Настройки макета", - "color": "Цвет", - "main": "Основной", - "right": "Правый", - "select": "Выбрать макет" - }, - "legend": { - "direction": "Расположение элементов легенды", - "position": "Расположение легенды", - "show-max": "Показать максимальное значение", - "show-min": "Показать минимальное значение", - "show-avg": "Показать среднее значение", - "show-total": "Показать сумму", - "settings": "Настройки легенды", - "min": "Мин", - "max": "Макс", - "avg": "Среднее", - "total": "Сумма", - "comparison-time-ago": { - "days": "(день назад)", - "weeks": "(неделю назад)", - "months": "(месяц назад)", - "years": "(год назад)" - } - }, - "login": { - "login": "Войти", - "request-password-reset": "Запрос на сброс пароля", - "reset-password": "Сбросить пароль", - "create-password": "Создать пароль", - "passwords-mismatch-error": "Введенные пароли должны быть одинаковыми!", - "password-again": "Введите пароль еще раз", - "sign-in": "Пожалуйста, войдите в систему", - "username": "Имя пользователя (эл. адрес)", - "remember-me": "Запомнить меня", - "forgot-password": "Забыли пароль?", - "password-reset": "Пароль сброшен", - "expired-password-reset-message": "Срок действия Вашего пароля закончился! Пожалуйста, создайте новый пароль.", - "new-password": "Новый пароль", - "new-password-again": "Повторите новый пароль", - "password-link-sent-message": "Ссылка для сброса пароля была успешно отправлена!", - "email": "Эл. адрес", - "login-with": "Войти через {{name}}", - "or": "или" - }, - "position": { - "top": "Верх", - "bottom": "Низ", - "left": "Левый край", - "right": "Правый край" - }, - "profile": { - "profile": "Профиль", - "last-login-time": "Время последнего входа в систему", - "change-password": "Изменить пароль", - "current-password": "Текущий пароль", - "copy-jwt-token": "Копировать JWT токен", - "tokenCopiedMessage": "JWT токен скопирован в буфер обмена", - "tokenCopiedWarnMessage": "JWT токен недействителен! Перезагрузите страницу." - }, - "relation": { - "relations": "Отношения", - "direction": "Направления", - "search-direction": { - "FROM": "От", - "TO": "К" - }, - "direction-type": { - "FROM": "от", - "TO": "к" - }, - "from-relations": "Исходящие отношения", - "to-relations": "Входящие отношения", - "selected-relations": "Выбрано { count, plural, =1 {1 отношение} few {# отношения} other {# отношений} }", - "type": "Тип", - "to-entity-type": "К типу объекта", - "to-entity-name": "К объекта", - "from-entity-type": "От типа объекта", - "from-entity-name": "От объекта", - "to-entity": "К объекту", - "from-entity": "От объекта", - "delete": "Удалить отношение", - "relation-type": "Тип отношения", - "relation-type-required": "Тип отношения обязателен.", - "any-relation-type": "Любой тип", - "add": "Добавить отношение", - "edit": "Редактировать отношение", - "delete-to-relation-title": "Вы точно хотите удалить отношение, идущее к объекту '{{entityName}}'?", - "delete-to-relation-text": "Внимание, после подтверждения объект '{{entityName}}' будет отвязан от текущего объекта.", - "delete-to-relations-title": "Вы точно хотите удалить { count, plural, =1 {1 отношение} few {# отношения} other {# отношений} }?", - "delete-to-relations-text": "Внимание, после подтверждения выбранные объекты будут отвязаны от текущего объекта.", - "delete-from-relation-title": "Вы точно хотите удалить отношение, идущее от объекта '{{entityName}}'?", - "delete-from-relation-text": "Внимание, после подтверждения текущий объект будет отвязан от объекта '{{entityName}}'.", - "delete-from-relations-title": "Вы точно хотите удалить { count, plural, =1 {1 отношение} few {# отношения} other {# отношений} }?", - "delete-from-relations-text": "Внимание, после подтверждения выбранные объекты будут отвязаны от соответствующих объектов.", - "remove-relation-filter": "Удалить фильтр отношений", - "add-relation-filter": "Добавить фильтр отношений", - "any-relation": "Любое отношение", - "relation-filters": "Фильтры отношений", - "additional-info": "Дополнительная информация (JSON)", - "invalid-additional-info": "Не удалось распознать JSON с дополнительной информацией." - }, - "rulechain": { - "rulechain": "Цепочка правил", - "rulechains": "Цепочки правил", - "root": "Корневая", - "delete": "Удалить цепочку правил", - "name": "Названия", - "name-required": "Название необходимо.", - "description": "Описание", - "add": "Добавить цепочку правил", - "set-root": "Сделать цепочку корневой", - "set-root-rulechain-title": "Вы точно хотите сделать цепочку правил '{{ruleChainName}}' корневой?", - "set-root-rulechain-text": "После подтверждения цепочка правил станет корневой и будет обрабатывать все входящие сообщения.", - "delete-rulechain-title": "Вы точно хотите удалить цепочку правил '{{ruleChainName}}'?", - "delete-rulechain-text": "Внимание, после подтверждения цепочка правил и все связанные с ней данные будут безвозвратно удалены.", - "delete-rulechains-title": "Вы точно хотите удалить { count, plural, =1 {1 цепочку правил} few {# цепочки правил} other {# цепочек правил} }?", - "delete-rulechains-action-title": "Удалить { count, plural, =1 {1 цепочку правил} few {# цепочки правил} other {# цепочек правил} }", - "delete-rulechains-text": "Внимание, после подтверждения выбранные цепочки правил и все связанные с ними данные будут безвозвратно удалены.", - "add-rulechain-text": "Добавить новую цепочку правил", - "no-rulechains-text": "Цепочки правил не найдены", - "rulechain-details": "Подробности о цепочке правил", - "details": "Подробности", - "events": "События", - "system": "Системная", - "import": "Импортировать цепочку правил", - "export": "Экспортировать цепочку правил", - "export-failed-error": "Не удалось экспортировать цепочку правил: {{error}}", - "create-new-rulechain": "Создать новую цепочку правил", - "rulechain-file": "Файл цепочки правил", - "invalid-rulechain-file-error": "Не удалось импортировать цепочку правил: неправильный формат.", - "copyId": "Копировать ИД цепочки правил", - "idCopiedMessage": "ИД цепочки правил скопирован в буфер обмена", - "select-rulechain": "Выбрать цепочку правил", - "no-rulechains-matching": "Цепочки правил, соответствующие '{{entity}}', не найдены.", - "rulechain-required": "Цепочка правил обязательна", - "management": "Управление цепочками правил", - "debug-mode": "Режим отладки" - }, - "rulenode": { - "details": "Подробности", - "events": "События", - "search": "Поиск правил", - "open-node-library": "Открыть библиотеку правил", - "add": "Добавить правило", - "name": "Название", - "name-required": "Название обязательно.", - "type": "Тип", - "delete": "Удалить правило", - "select-all-objects": "Выделить все правила и связи", - "deselect-all-objects": "Отменить выделение правил и связей", - "delete-selected-objects": "Удалить выделенные правила и связи", - "delete-selected": "Удалить выделенные", - "select-all": "Выделить всё", - "copy-selected": "Копировать выделенное", - "deselect-all": "Отменить выделение", - "rulenode-details": "Подробности о правиле", - "debug-mode": "Режим отладки", - "configuration": "Настройки", - "link": "Связь", - "link-details": "Подробности о связи правила", - "add-link": "Добавить связь", - "link-label": "Метка связи", - "link-label-required": "Метка связи обязателен.", - "custom-link-label": "Пользовательская метка связи", - "custom-link-label-required": "Пользовательская метка связи обязателен.", - "link-labels": "Метки связи", - "link-labels-required": "Метки связи обязательны.", - "no-link-labels-found": "Метки связи не найдены", - "no-link-label-matching": "Метка '{{label}}' не найдена.", - "create-new-link-label": "Создать новую!", - "type-filter": "Фильтр", - "type-filter-details": "Фильтр входящих сообщений с заданными условиями", - "type-enrichment": "Насыщение", - "type-enrichment-details": "Добавить данные в метадату сообщения", - "type-transformation": "Преобразование", - "type-transformation-details": "Изменить содержимое сообщение и его метадату", - "type-action": "Действие", - "type-action-details": "Выполнить заданное действие", - "type-external": "Сторонние", - "type-external-details": "Взаимодействовать со сторонними системами", - "type-rule-chain": "Цепочка правил", - "type-rule-chain-details": "Перенаправить входящее сообщение в другую цепочку правил", - "type-input": "Вход", - "type-input-details": "Логический вход цепочки правил перенаправляет входящие сообщения в следующее правило", - "type-unknown": "Неизвестный", - "type-unknown-details": "Неопределенное правило", - "directive-is-not-loaded": "Указанная директива конфигурации '{{directiveName}}' не доступна.", - "ui-resources-load-error": "Не удалось загрузить UI ресурсы.", - "invalid-target-rulechain": "Не удалось определить целевую цепочку правил!", - "test-script-function": "Протестировать скрипт", - "message": "Сообщение", - "message-type": "Тип сообщения", - "select-message-type": "Выбрать тип сообщения", - "message-type-required": "Тип сообщения обязателен", - "metadata": "Метаданные", - "metadata-required": "Метаданные объекта не могут быть пустыми.", - "output": "Выход", - "test": "Протестировать", - "help": "Помощь", - "reset-debug-mode": "Сбросить режим отладки во всех правилах" - }, - "queue": { - "select_name": "Выберите имя для Queue", - "name": "Имя для Queue", - "name_required": "Поле 'Имя для Queue' обязательно к заполнению!" - }, - "tenant": { - "tenant": "Владелец", - "tenants": "Владельцы", - "management": "Управление владельцами", - "add": "Добавить владельца", - "admins": "Администраторы", - "manage-tenant-admins": "Управление администраторами владельца", - "delete": "Удалить владельца", - "add-tenant-text": "Добавить нового владельца", - "no-tenants-text": "Владельцы не найдены", - "tenant-details": "Подробности об владельце", - "delete-tenant-title": "Вы точно хотите удалить владельца '{{tenantTitle}}'?", - "delete-tenant-text": "Внимание, после подтверждения владелец и все связанные с ним данные будут безвозвратно утеряны.", - "delete-tenants-title": "Вы точно хотите удалить { count, plural, =1 {1 владельца} other {# владельцев} }?", - "delete-tenants-action-title": "Удалить { count, plural, =1 {1 владельца} other {# владельцев} }", - "delete-tenants-text": "Внимание, после подтверждения выбранные Владельцы и все связанные с ними данные будут безвозвратно утеряны.", - "title": "Имя", - "title-required": "Имя обязательно.", - "description": "Описание", - "details": "Подробности", - "events": "События", - "copyId": "Копировать ИД владельца", - "idCopiedMessage": "ИД владельца скопирован в буфер обмена", - "select-tenant": "Выбрать владельца", - "no-tenants-matching": "Владельцы, соответствующие '{{entity}}', не найдены.", - "tenant-required": "Владелец обязателен" - }, - "timeinterval": { - "seconds-interval": "{ seconds, plural, =1 {1 секунда} few {# секунды} other {# секунд} }", - "minutes-interval": "{ minutes, plural, =1 {1 минута} few {# минуты} other {# минут} }", - "hours-interval": "{ hours, plural, =1 {1 час} few {# часа} other {# часов} }", - "days-interval": "{ days, plural, =1 {1 день} few {# дня} other {# дней} }", - "days": "Дни", - "hours": "Часы", - "minutes": "Минуты", - "seconds": "Секунды", - "advanced": "Дополнительно" - }, - "timewindow": { - "days": "{ days, plural, =1 {1 день} few {# дня} other {# дней} }", - "hours": "{ hours, plural, =1 {1 час} few {# часа} other {# часов} }", - "minutes": "{ minutes, plural, =1 {1 минута} few {# минуты} other {# минут} }", - "seconds": "{ seconds, plural, =1 {1 секунда} few {# секунды} other {# секунд} }", - "realtime": "Режим реального времени", - "history": "История", - "last-prefix": "Последние", - "period": "с {{ startTime }} до {{ endTime }}", - "edit": "Изменить временное окно", - "date-range": "Диапазон дат", - "last": "Последние", - "time-period": "Период времени", - "hide": "Скрыть" - }, - "user": { - "user": "Пользователь", - "users": "Пользователи", - "customer-users": "Пользователи клиента", - "tenant-admins": "Администраторы владельца", - "sys-admin": "Системный администратор", - "tenant-admin": "Администратор владельца", - "customer": "Клиент", - "anonymous": "Аноним", - "add": "Добавить пользователя", - "delete": "Удалить пользователя", - "add-user-text": "Добавить нового пользователя", - "no-users-text": "Пользователи не найдены", - "user-details": "Подробности о пользователе", - "delete-user-title": "Вы точно хотите удалить пользователя '{{userEmail}}'?", - "delete-user-text": "Внимание, после подтверждения пользователь и все связанные с ним данные будут безвозвратно утеряны.", - "delete-users-title": "Вы точно хотите удалить { count, plural, =1 {1 пользователя} other {# пользователей} }?", - "delete-users-action-title": "Удалить { count, plural, =1 {1 пользователя} other {# пользователей} }", - "delete-users-text": "Внимание, после подтверждения выбранные пользователи и все связанные с ними данные будут безвозвратно утеряны.", - "activation-email-sent-message": "Активационное письмо успешно отправлено!", - "resend-activation": "Повторить отправку активационного письма", - "email": "Эл. адрес", - "email-required": "Эл. адрес обязателен.", - "invalid-email-format": "Неправильный формат эл. адреса'.", - "first-name": "Имя", - "last-name": "Фамилия", - "description": "Описание", - "default-dashboard": "Дашборд по умолчанию", - "always-fullscreen": "Всегда в полноэкранном режиме", - "select-user": "Выбрать пользователя", - "no-users-matching": "Пользователи, соответствующие '{{entity}}', не найдены.", - "user-required": "Необходимо указать пользователя", - "activation-method": "Метод активации", - "display-activation-link": "Отобразить ссылку для активации", - "send-activation-mail": "Отправить активационное письмо", - "activation-link": "Активационная ссылка для пользователя", - "activation-link-text": "Для активации пользователя используйте ссылку :", - "copy-activation-link": "Копировать активационную ссылку", - "activation-link-copied-message": "Ссылка для активации пользователя скопирована в буфер обмена", - "details": "Подробности", - "login-as-tenant-admin": "Войти как администратор владельца", - "login-as-customer-user": "Войти как пользователь клиента", - "disable-account": "Отключить учетную запись пользователя", - "enable-account": "Включить учетную запись пользователя", - "enable-account-message": "Учетная запись пользователя была успешно включена!", - "disable-account-message": "Учетная запись пользователя была успешно отключена!", - "copyId": "Копировать ИД пользователя", - "idCopiedMessage": "ИД пользователя скопирован в буфер обмена" - }, - "value": { - "type": "Тип значения", - "string": "Строка", - "string-value": "Строковое значение", - "integer": "Целое число", - "integer-value": "Целочисленное значение", - "invalid-integer-value": "Неправильный формат целого числа", - "double": "Число двойной точности", - "double-value": "Значение двойной точности", - "boolean": "Логический тип", - "boolean-value": "Логическое значение", - "false": "Ложь", - "true": "Правда", - "long": "Целое число" - }, - "widget": { - "widget-library": "Галерея виджетов", - "widget-bundle": "Набор виджетов", - "select-widgets-bundle": "Выберите набор виджетов", - "management": "Управление виджетами", - "editor": "Редактор виджетов", - "widget-type-not-found": "Ошибка при загрузке конфигурации виджета.
Возможно, связанный с ней\n тип виджета уже удален.", - "widget-type-load-error": "Не удалось загрузить виджет по следующим причинам:", - "remove": "Удалить виджет", - "edit": "Редактировать виджет", - "remove-widget-title": "Вы точно хотите удалить виджет '{{widgetTitle}}'?", - "remove-widget-text": "Внимание, после подтверждения виджет и все связанные с ним данные будут безвозвратно утеряны.", - "timeseries": "Телеметрия", - "search-data": "Поиск данных", - "no-data-found": "Данные не найдено", - "latest": "Последние значения", - "rpc": "Управляющий виджет", - "alarm": "Виджет оповещений", - "static": "Статический виджет", - "select-widget-type": "Выберите тип виджета", - "missing-widget-title-error": "Укажите название виджета!", - "widget-saved": "Виджет сохранен", - "unable-to-save-widget-error": "Не удалось сохранить виджет! Виджет содержит ошибки!", - "save": "Сохранить виджет", - "saveAs": "Сохранить виджет как", - "save-widget-type-as": "Сохранить тип виджета как", - "save-widget-type-as-text": "Пожалуйста, введите название виджета и/или укажите целевой набор виджетов", - "toggle-fullscreen": "Во весь экран", - "run": "Запустить виджет", - "title": "Название виджета", - "title-required": "Название виджета обязательно.", - "type": "Тип виджета", - "resources": "Ресурсы", - "resource-url": "JavaScript/CSS URL", - "remove-resource": "Удалить ресурс", - "add-resource": "Добавить ресурс", - "html": "HTML", - "tidy": "Форматировать", - "css": "CSS", - "settings-schema": "Схема конфигурации", - "datakey-settings-schema": "Схема конфигурации ключа данных", - "javascript": "Javascript", - "add-widget-type": "Добавить новый тип виджета", - "widget-template-load-failed-error": "Не удалось загрузить шаблон виджета!", - "add": "Добавить виджет", - "undo": "Откатить изменения в виджете", - "export": "Экспортировать виджет" - }, - "widget-action": { - "header-button": "Кнопка заголовка виджета", - "open-dashboard-state": "Перейти к новому состоянию дашборда", - "update-dashboard-state": "Обновить текущее состояние дашборда", - "open-dashboard": "Перейти к другому дашборду", - "custom": "Пользовательское действие", - "custom-pretty": "Пользовательское действие (с HTML шаблоном)", - "target-dashboard-state": "Целевое состояние дашборда", - "target-dashboard-state-required": "Целевое состояние дашборда обязательно", - "set-entity-from-widget": "Установить объект из виджета", - "target-dashboard": "Целевой дашборд", - "open-right-layout": "Открыть мобильный режим дашборда" - }, - "widgets-bundle": { - "current": "Текущий набор", - "widgets-bundles": "Наборы виджетов", - "add": "Добавить набор виджетов", - "delete": "Удалить набор виджетов", - "title": "Название", - "title-required": "Название обязательно.", - "add-widgets-bundle-text": "Добавить новый набор виджетов", - "no-widgets-bundles-text": "Наборы виджетов не найдены", - "empty": "Пустой набор виджетов", - "details": "Подробности", - "widgets-bundle-details": "Подробности о наборе виджетов", - "delete-widgets-bundle-title": "Вы точно хотите удалить набор виджетов '{{widgetsBundleTitle}}'?", - "delete-widgets-bundle-text": "Внимание, после подтверждения набор виджетов и все связанные с ним данные будут безвозвратно утеряны.", - "delete-widgets-bundles-title": "Вы точно хотите удалить { count, plural, =1 {1 набор виджетов} few {# набора виджетов} other {# наборов виджетов} }?", - "delete-widgets-bundles-action-title": "Удалить { count, plural, =1 {1 набор виджетов} few {# набора виджетов} other {# наборов виджетов} }", - "delete-widgets-bundles-text": "Внимание, после подтверждения выбранные наборы виджетов и все связанные с ними данные будут безвозвратно утеряны..", - "no-widgets-bundles-matching": "Набор виджетов '{{widgetsBundle}}' не найден.", - "widgets-bundle-required": "Набор виджетов обязателен.", - "system": "Системный", - "import": "Импортировать набор виджетов", - "export": "Экспортировать набор виджетов", - "export-failed-error": "Не удалось экспортировать набор виджетов: {{error}}", - "create-new-widgets-bundle": "Создать новый набор виджетов", - "widgets-bundle-file": "Файл набора виджетов", - "invalid-widgets-bundle-file-error": "Не удалось импортировать набор виджетов: неизвестная схема данных набора виджетов." - }, - "widget-config": { - "data": "Данные", - "settings": "Настройки", - "advanced": "Дополнительно", - "title": "Название", - "general-settings": "Общие настройки", - "display-title": "Показать название на виджете", - "drop-shadow": "Тень", - "enable-fullscreen": "Во весь экран", - "background-color": "Цвет фона", - "text-color": "Цвет текста", - "padding": "Отступ", - "margin": "Margin", - "widget-style": "Стиль виджета", - "title-style": "Стиль названия", - "mobile-mode-settings": "Мобильный режим", - "order": "Порядок", - "height": "Высота", - "units": "Специальный символ после значения", - "decimals": "Количество цифр после запятой", - "timewindow": "Временное окно", - "use-dashboard-timewindow": "Использовать временное окно дашборда", - "display-timewindow": "Показывать временное окно", - "legend": "Легенда", - "display-legend": "Показать легенду", - "datasources": "Источники данных", - "maximum-datasources": "Максимальной количество источников данных равно {{count}}", - "datasource-type": "Тип", - "datasource-parameters": "Параметры", - "remove-datasource": "Удалить источник данных", - "add-datasource": "Добавить источник данных", - "target-device": "Целевое устройство", - "alarm-source": "Источник оповещения", - "actions": "Действия", - "action": "Действие", - "add-action": "Добавить действие", - "search-actions": "Поиск действий", - "action-source": "Источник действий", - "action-source-required": "Источник действий обязателен.", - "action-name": "Название", - "action-name-required": "Название действия обязательно.", - "action-name-not-unique": "Действие с таким именем уже существует.
Название должно быть уникально в рамках одного источника действий.", - "action-icon": "Иконка", - "action-type": "Тип", - "action-type-required": "Тип действий обязателен.", - "edit-action": "Редактировать действие", - "delete-action": "Удалить действие", - "delete-action-title": "Удалить действие виджета", - "delete-action-text": "Вы точно хотите удалить действие виджета '{{actionName}}'?", - "title-icon": "Иконка в названии виджета", - "display-icon": "Показывать иконку в названии виджета", - "icon-color": "Цвет иконки", - "icon-size": "Размер иконки", - "advanced-settings": "Расширенные настройки", - "data-settings": "Настройки данных", - "no-data-display-message": "\"Нет данных для отображения\" альтернативный текст" - }, - "widget-type": { - "import": "Импортировать тип виджета", - "export": "Экспортировать тип виджета", - "export-failed-error": "Не удалось экспортировать тип виджета: {{error}}", - "create-new-widget-type": "Создать новый тип виджета", - "widget-type-file": "Файл типа виджета", - "invalid-widget-type-file-error": "Не удалось импортировать виджет: неизвестная схема данных типа виджета." - }, - "widgets": { - "date-range-navigator": { - "localizationMap": { - "Sun": "Вс", - "Mon": "Пн", - "Tue": "Вт", - "Wed": "Ср", - "Thu": "Чт", - "Fri": "Пт", - "Sat": "Сб", - "Jan": "Янв.", - "Feb": "Февр.", - "Mar": "Март", - "Apr": "Апр.", - "May": "Май", - "Jun": "Июнь", - "Jul": "Июль", - "Aug": "Авг.", - "Sep": "Сент.", - "Oct": "Окт.", - "Nov": "Нояб.", - "Dec": "Дек.", - "January": "Январь", - "February": "Февраль", - "March": "Март", - "April": "Апрель", - "June": "Июнь", - "July": "Июль", - "August": "Август", - "September": "Сентябрь", - "October": "Октября", - "November": "Ноябрь", - "December": "Декабрь", - "Custom Date Range": "Пользовательский диапазон дат", - "Date Range Template": "Шаблон диапазона дат", - "Today": "Сегодня", - "Yesterday": "Вчера", - "This Week": "На этой неделе", - "Last Week": "Прошлая неделя", - "This Month": "Этот месяц", - "Last Month": "Прошлый месяц", - "Year": "Год", - "This Year": "В этом году", - "Last Year": "Прошлый год", - "Date picker": "Выбор даты", - "Hour": "Час", - "Day": "День", - "Week": "Неделю", - "2 weeks": "2 Недели", - "Month": "Месяц", - "3 months": "3 Месяца", - "6 months": "6 Месяцев", - "Custom interval": "Пользовательский интервал", - "Interval": "Интервал", - "Step size": "Размер шага", - "Ok": "Ok" - } - }, - "input-widgets": { - "attribute-not-allowed": "Атрибут не может быть выбран в этом виджете", - "date": "Дата", - "blocked-location": "Геолокация заблокирована в вашем браузере", - "claim-device": "Подтвердить устройство", - "claim-failed": "Не удалось подтвердить устройство!", - "claim-not-found": "Устройство не найдено!", - "claim-successful": "Устройство успешно подтверждено!", - "discard-changes": "Отменить изменения", - "device-name": "Название устройства", - "device-name-required": "Необходимо указать название устройства", - "entity-attribute-required": "Значение атрибута обязателено", - "entity-coordinate-required": "Необходимо указать широту и долготу", - "entity-timeseries-required": "Значение телеметрии обязательно", - "get-location": "Получить текущее местоположение", - "latitude": "Широта", - "longitude": "Долгота", - "not-allowed-entity": "Выбраный объект не имеет общих атрибутов", - "no-attribute-selected": "Атрибут не выбран", - "no-datakey-selected": "Ни один datakey не выбран", - "no-entity-selected": "Объект не выбран", - "no-coordinate-specified": "Ключ для широты/долготы не указан", - "no-support-geolocation": "Ваш браузер не поддерживает геолокацию", - "no-image": "Нет изображения", - "no-support-web-camera": "Нет поддерживаемой веб-камеры", - "no-timeseries-selected": "Параметр телеметрии не выбран", - "secret-key": "Секретный ключ", - "secret-key-required": "Необходимо указать секретный ключ", - "switch-attribute-value": "Изменить значение атрибута", - "switch-camera": "Изменить камеру", - "switch-timeseries-value": "Изменить значение телеметрии", - "take-photo": "Сделать фото", - "time": "Время", - "timeseries-not-allowed": "Телеметрия не может быть выбрана в этом виджете", - "update-failed": "Не удалось обновить", - "update-successful": "Успешно обновлено", - "update-attribute": "Обновить атрибут", - "update-timeseries": "Обновить телеметрию", - "value": "Значение" - }, - "persistent-table": { - "rpc-id": "RPC ID", - "message-type": "Тип сообщения", - "method": "Метод", - "params": "Параметры", - "created-time": "Время создания", - "expiration-time": "Время жизни", - "retries": "Повторные попытки", - "status": "Статус", - "filter": "Фильтр", - "refresh": "Обновить", - "add": "Добавить RPC запрос", - "details": "Детали", - "delete": "Удалить", - "delete-request-title": "Удалить RPC запрос", - "delete-request-text": "Вы точно хотите удалить RPC запрос?", - "details-title": "Детали RPC ID: ", - "additional-info": "Дополнительная информация", - "response": "Ответ", - "any-status": "Любой статус", - "rpc-status-list": "Список RPC статусов", - "no-request-prompt": "Запросы не найдены", - "send-request": "Отправить запрос", - "add-title": "Добавить новый RPC запрос", - "method-error": "Метод обязателен.", - "white-space-error": "Пробелы не допускаются.", - "rpc-status": { - "QUEUED": "В ОЧЕРЕДИ", - "SENT": "ОТПРАВЛЕННО", - "DELIVERED": "ДОСТАВЛЕННО", - "SUCCESSFUL": "УСПЕШНО", - "TIMEOUT": "ВРЕМЯ ИСТЕКЛО", - "EXPIRED": "ПРОСРОЧЕНО", - "FAILED": "НЕУДАЧНО" - }, - "rpc-search-status-all": "ВСЕ", - "message-types": { - "false": "Двусторонний", - "true": "Односторонний" - } - } - }, - "icon": { - "icon": "Иконка", - "select-icon": "Выбрать иконку", - "material-icons": "Иконки в стиле Material", - "show-all": "Показать все иконки" - }, - "custom": { - "widget-action": { - "action-cell-button": "Кнопка действия в ячейке таблицы", - "row-click": "Действий при щелчке на строку", - "marker-click": "Действия при щелчке на маркер", - "polygon-click": "Действия при щелчке на полигон", - "tooltip-tag-action": "Действие при нажатии на ссылку в подсказке", - "node-selected": "Действий при выборе ноды", - "element-click": "Действий при щелчке на HTML элементе", - "pie-slice-click": "Действий при щелчке на секции круговой диаграммы", - "row-double-click": "Действий при двойном щелчке на строку" - } - }, - "language": { - "language": "Язык" - } -} diff --git a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json index e9be2e691a..b5b70cd8f9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -2430,7 +2430,6 @@ "search": "Kural Ara", "selected-rulechains": "{ count, plural, =1 {1 kural} other {# kural} } seçildi", "open-rulechain": "Kuralı Aç", - "assign-new-rulechain": "Yeni kural zinciri atayın", "edge-template-root": "Şablon Kökü", "assign-to-edge": "Uca Ata", "edge-rulechain": "Uç kuralı zinciri", diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index da3b6ecaee..dce1bf0a2e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -6,13 +6,20 @@ "access-forbidden": "Доступ заборонено", "access-forbidden-text": "Недостатньо прав для доступу!
Спробуйте увійти як інший користувач, якщо ви все ще хочете отримати доступ до цього ресурсу.", "refresh-token-expired": "Дані про сесію застарілі", - "refresh-token-failed": "Не вдається відновити сеанс" + "refresh-token-failed": "Не вдається відновити сеанс", + "permission-denied": "У дозволі відмовлено", + "permission-denied-text": "Ви не маєте дозволу на виконання цієї операції!" + }, + "account": { + "account": "Обліковий запис", + "notification-settings": "Налаштування сповіщень" }, "action": { "activate": "Активувати", "suspend": "Призупинити", "save": "Зберегти", "saveAs": "Зберегти як", + "move": "Перемістити", "cancel": "Скасувати", "ok": "OK", "delete": "Видалити", @@ -21,6 +28,7 @@ "no": "Ні", "update": "Оновити", "remove": "Видалити", + "select": "Вибрати", "search": "Пошук", "clear-search": "Очистити пошук", "assign": "Надати", @@ -32,6 +40,7 @@ "edit-mode": "Режим редагування", "enter-edit-mode": "Ввійти в режим редагування", "decline-changes": "Відхилити зміни", + "decline": "Відхилити", "close": "Закрити", "back": "Назад", "run": "Запустити", @@ -51,9 +60,27 @@ "share-via": "Поділитися через {{provider}}", "continue": "Продовжити", "discard-changes": "Скасувати зміни", - "move": "Перемістити", - "select": "Вибрати", - "done": "Завершено" + "download": "Завантажити", + "next": "Наступний", + "next-with-label": "Наступний: {{label}}", + "read-more": "Прочитати більше", + "hide": "Приховати", + "done": "Завершено", + "print": "Друкувати", + "restore": "Відновити", + "confirm": "Підтвердити", + "more": "Більше", + "less": "Менше", + "skip": "Пропустити", + "send": "Надіслати", + "reset": "Скидання", + "show-more": "Показати більше", + "dont-show-again": "Більше не показувати", + "see-documentation": "Див. документацію", + "clear": "Очистити", + "upload": "Надіслати", + "delete-anyway": "Все одно видалити", + "delete-selected": "Видалити вибране" }, "aggregation": { "aggregation": "Агрегація", @@ -68,6 +95,7 @@ "none": "Відсутня" }, "admin": { + "settings": "Налаштування", "general": "Загальне", "general-settings": "Загальні налаштування", "outgoing-mail": "Поштовий сервер", @@ -126,15 +154,15 @@ "minimum-max-failed-login-attempts-range": "Максимальна кількість невдалих спроб входу не може бути негативною", "user-lockout-notification-email": "У разі блокування облікового запису користувача, надішліть сповіщення на електронну пошту", "smpp-provider": { - "smpp-version": "SMPP верія", + "smpp-version": "SMPP верcія", "smpp-host": "SMPP хост", "smpp-host-required": "Хост SMPP обов'язковий.", "smpp-port": "SMPP порт", "smpp-port-required": "Порт SMPP обов'язковий.", "system-id": "Id системи", - "system-id-required": "Id системи обязателен.", + "system-id-required": "ID системи обов'язковий.", "password": "Пароль", - "password-required": "Пароль обязателен.", + "password-required": "Пароль обов'язковий.", "type-settings": "Налаштування типів", "source-settings": "Налаштування джерела", "destination-settings": "Налаштування призначення", @@ -144,12 +172,13 @@ "service-type": "Тип обслуговування", "source-address": "Адреса джерела", "source-ton": "Тип номера джерела", - "source-npi": "Идентификация плана нумерации джерела", + "source-npi": "Ідентифікація плану нумерації джерела", "destination-ton": "Тип номера призначення", "destination-npi": "Ідентифікація плану нумерації призначення", "address-range": "Діапазон адрес", "coding-scheme": "Схема кодування" - } + }, + "resources": "Ресурси" }, "alarm": { "alarm": "Сигнал тривоги", @@ -249,8 +278,8 @@ "filter-type": "Тип фільтра", "filter-type-required": "Необхідно вказати тип фільтра.", "entity-filter-no-entity-matched": "Не знайдено жодних сутностей, які відповідають вказаному фільтру.", - "no-entity-filter-specified": "Фільтр обїектів не вказано", - "root-state-entity": "Використовувати сутінсть стану як кореневу", + "no-entity-filter-specified": "Фільтр сутностей не вказано", + "root-state-entity": "Використовувати сутність стану як кореневу", "last-level-relation": "Використовувати лише відношення останнього рівня", "group-state-entity": "Використовувати групу сутностей стану як кореневу", "root-entity": "Коренева сутність", @@ -323,7 +352,7 @@ "copyId": "Копіювати Id активу", "idCopiedMessage": "Id активу був скопійований у буфер обміну", "select-asset": "Виберіть актив", - "no-assets-matching": "Не знайдено жодних активів, що відповідають'{{entity}}'.", + "no-assets-matching": "Не знайдено жодних активів, що відповідають '{{entity}}'.", "asset-required": "Необхідно задати актив", "name-starts-with": "Назва активу починається з", "selected-assets": "{ count, plural, =1 {1 актив} other {# активи} } selected", @@ -367,6 +396,9 @@ "selected-attributes": "{ count, plural, =1 {1 attribute} other {# attributes} } selected ...вибрані вибрати", "selected-telemetry": "{ count, plural, =1 {1 telemetry unit} other {# telemetry units} } selected" }, + "api-usage": { + "api-usage": "Використання API" + }, "audit-log": { "audit": "Операція", "audit-logs": "Журнал операцій", @@ -476,14 +508,14 @@ "test": "Тест", "metadata": "Метадані", "metadata-required": "Записи метаданих не можуть бути порожніми.", - "integration-metadata": "Метедані інтеграції", + "integration-metadata": "Метадані інтеграції", "integration-metadata-required": "Параметри метаданих інтеграції не можуть бути порожніми.", "output": "Вихідні дані", "import": "Імпорт перетворювача даних", "export": "Експорт перетворювача даних", "export-failed-error": "Неможливо експортувати перетворювач даних: {{помилка}}", "create-new-converter": "Створити новий перетворювач даних", - "converter-file": "Файл перетворювача даних(конвектер файл)", + "converter-file": "Файл перетворювача даних", "invalid-converter-file-error": "Неможливо імпортувати перетворювач даних: недійсна структура даних перетворювача.", "type": "Тип", "type-required": "Необхідно задати тип.", @@ -620,7 +652,7 @@ "unassign-dashboard-text": "Після підтвердження, клієнт буде позбавлений панелі приладів. Панель приладів і пов'язані з нею дані будуть недоступні клієнтові.", "unassign-dashboard": "Позбавити панелі приладів", "unassign-dashboards-title": "Ви впевнені, що хочете позбавити { count, plural, =1 {1 панелі приладів} other {# панелей приладів} }?", - "unassign-dashboards-text": "Після підтвердження, клієтн буде позбавлений усіх вибраних панелей приладів і даних, які з ними пов'язані.", + "unassign-dashboards-text": "Після підтвердження, клієнт буде позбавлений усіх вибраних панелей приладів і даних, які з ними пов'язані.", "public-dashboard-title": "Панель приладів тепер публічна", "public-dashboard-text": "Ваша панель приладів {{dashboardTitle}} тепер публічна і доступна іншим link:", "public-dashboard-notice": "Note: Не забудьте зробити спільні пристрої загальнодоступними, щоб отримати доступ до їхніх даних.", @@ -645,7 +677,7 @@ "settings": "Налаштування", "columns-count": "Кількість стовпців", "columns-count-required": "Необхідно вказати кількість стовпців.", - "min-columns-count-message": "Дозволений мінімум -10 стовпців.", + "min-columns-count-message": "Дозволений мінімум - 10 стовпців.", "max-columns-count-message": "Дозволений максимум - 1000 стовпців.", "widgets-margins": "Відступ між віджетами", "horizontal-margin": "Горизонтальний відступ", @@ -686,8 +718,8 @@ "widget-file": "Файл віджета", "invalid-widget-file-error": "Неможливо імпортувати віджет: неправильна структура даних віджета.", "widget-import-missing-aliases-title": "Налаштувати псевдоніми, що використовуються імпортованим віджетом", - "open-toolbar": "Відкрити панель інструменів ", - "close-toolbar": "Закрити панель інструменів", + "open-toolbar": "Відкрити панель інструментів", + "close-toolbar": "Закрити панель інструментів", "configuration-error": "Помилка конфігурації", "alias-resolution-error-title": "Помилка конфігурації псевдонімів панелі візуалізації", "invalid-aliases-config": "Неможливо знайти пристрої, які відповідають певному фільтру псевдонімів.
Зверніться до свого адміністратора, щоб вирішити цю проблему.", @@ -717,7 +749,7 @@ "show-details": "Показати деталі", "hide-details": "Приховати деталі", "select-state": "Виберіть цільовий стан", - "state-controller": "Контроллер стану" + "state-controller": "Контролер стану" }, "datakey": { "settings": "Налаштування", @@ -732,11 +764,11 @@ "timeseries": "Телеметрія", "attributes": "Атрибути", "entity-field": "Поле сутності", - "alarm": "Поля сигнала тривоги", + "alarm": "Поля сигналу тривоги", "timeseries-required": "Необхідно вказати Телеметрія.", "timeseries-or-attributes-required": "Необхідно вказати телеметрію/атрибути.", "maximum-timeseries-or-attributes": "Максимальні { count, plural, =1 {1 телеметрія/атрибут дозволені.} other {# телеметрія/атрибути дозволені} }", - "alarm-fields-required": "Необхідно вказати поля сигнала тривоги.", + "alarm-fields-required": "Необхідно вказати поля сигналу тривоги.", "function-types": "Типи функцій", "function-types-required": "Необхідно вказати типи функцій.", "maximum-function-types": "Maximum { count, plural, =1 {1 function type is allowed.} other {# function types are allowed} }", @@ -771,7 +803,7 @@ "no-keys-found": "Ключі не знайдено.", "create-new-alias": "Створити новий!", "create-new-key": "Створити новий!", - "duplicate-alias-error": "Псевдонім з таким іменем '{{alias}}' вже існює.
Псевдоніми пристроїв повинні бути унікальними на панелі візуалізації.", + "duplicate-alias-error": "Псевдонім з таким іменем '{{alias}}' вже існує.
Псевдоніми пристроїв повинні бути унікальними на панелі візуалізації.", "configure-alias": "Налаштувати псевдонім '{{alias}}'", "no-devices-matching": "Не знайдено жодних пристроїв, які відповідають '{{entity}}'.", "alias": "Псевдонім", @@ -861,6 +893,14 @@ "list-of-groups": "{ count, plural, =1 {Одна група пристроїв} other {Список # груп пристроїв} }", "group-name-starts-with": "Групи пристроїв, імена яких починаються з '{{prefix}}'" }, + "asset-profile": { + "asset-profile": "Профіль активу", + "asset-profiles": "Профілі активів" + }, + "device-profile": { + "device-profile": "Профіль пристрою", + "device-profiles": "Профілі пристроїв" + }, "dialog": { "close": "Закрити діалогове вікно" }, @@ -868,6 +908,11 @@ "column": "Колонка", "row": "Рядок" }, + "edge": { + "management": "Керування Edge", + "instances": "Інстанси", + "rulechain-templates": "Шаблони ланцюжків правил" + }, "error": { "unable-to-connect": "Неможливо підключитися до сервера! Перевірте підключення до Інтернету.", "unhandled-error-code": "Неопрацьований помилковий код: {{errorCode}}", @@ -879,7 +924,7 @@ "aliases": "Псевдоніми сутності", "entity-alias": "Псевдонім сутності", "unable-delete-entity-alias-title": "Неможливо видалити псевдонім сутності", - "unable-delete-entity-alias-text": "Псевдонім сутності'{{entityAlias}}' неможливо видалити, так як це використовується наступним віджетом(s):
{{widgetsList}}", + "unable-delete-entity-alias-text": "Псевдонім сутності '{{entityAlias}}' неможливо видалити, оскільки він використовується наступним віджетом(s):
{{widgetsList}}", "duplicate-alias-error": "Знайдено повторюваний псевдонім '{{alias}}'.
Псевдоніми сутностей повинні бути унікальними на панелі візуалізації.", "missing-entity-filter-error": "Відсутній фільтр для псевдоніма '{{alias}}'.", "configure-alias": "Налаштувати '{{alias}}' псевдонім", @@ -1118,9 +1163,9 @@ "view-entity-views": "Переглянути представлення сутностей", "entity-view-alias": "Псевдонім представлення сутності", "aliases": "Псевдоніми представлення сутності", - "no-alias-matching": "Псевдонім'{{alias}}' не знайдено.", + "no-alias-matching": "Псевдонім '{{alias}}' не знайдено.", "no-aliases-found": "Псевдоніми не знайдено.", - "no-key-matching": "'Ключ {{key}}' не знайдено.", + "no-key-matching": "Ключ '{{key}}' не знайдено.", "no-keys-found": "Ключі не знайдено.", "create-new-alias": "Створити новий!", "create-new-key": "Створити новий!", @@ -1135,7 +1180,7 @@ "entity-view-list": "Список представленнь сутності", "use-entity-view-name-filter": "Використати фільтр", "entity-view-list-empty": "Не вибрано жодного представлення сутності.", - "entity-view-name-filter-required": "Необхідно вказвти фільтр назв представлення сутності.", + "entity-view-name-filter-required": "Необхідно вказати фільтр назв представлення сутності.", "entity-view-name-filter-no-entity-view-matched": "Представлення сутностей, назви яких починаються з '{{entityView}}' не знайдено.", "add": "Додати представлення сутності", "assign-to-customer": "Призначити клієнту", @@ -1398,8 +1443,11 @@ "import-extension": "Імпортувати розширення", "export-extension": "Експортувати розширення", "file": "Файл розширень", - "invalid-file-error": "Не правильний формат файла" + "invalid-file-error": "Неправильний формат файлу" }, + "feature": { + "advanced-features": "Додаткові можливості" + }, "fullscreen": { "expand": "Відкрити у повноекранному режимі", "exit": "Вийти з повноекранного режиму", @@ -1410,7 +1458,7 @@ "function": "Функція" }, "grid": { - "delete-item-title": "Ви впенені, що хочете видалити цей елемент?", + "delete-item-title": "Ви впевнені, що хочете видалити цей елемент?", "delete-item-text": "Будьте обережні, після підтвердження, цей елемент і всі пов'язані з ним дані, стануть недоступними.", "delete-items-title": "Ви впенені, що хочете видалити { count, plural, =1 {1 елемент} other {# елементи} }?", "delete-items-action-title": "Видалити{ count, plural, =1 {1 елемент} other {# елементи} }", @@ -1695,6 +1743,12 @@ "login-with": "Увійти через {{name}}", "or": "або" }, + "notification": { + "notification-center": "Центр сповіщень" + }, + "ota-update": { + "ota-updates": "Оновлення OTA" + }, "position": { "top": "Угорі", "bottom": "Знизу", @@ -1710,6 +1764,12 @@ "tokenCopiedMessage": "JWT токен скопійовано в буфер обміну", "tokenCopiedWarnMessage": "JWT токен не є дійсним! Перезавантажте сторінку." }, + "profiles": { + "profiles": "Профілі" + }, + "security": { + "security": "Безпека" + }, "relation": { "relations": "Відношення", "direction": "Напрямок", @@ -1752,41 +1812,44 @@ "additional-info": "Додаткова інформація (JSON)", "invalid-additional-info": "Не вдалося розібрати JSON з додатковою інформацією ." }, + "resource": { + "resources-library": "Бібліотека ресурсів" + }, "rulechain": { - "rulechain": "Ланцюг правил", - "rulechains": "Ланцюги правил", + "rulechain": "Ланцюжок правил", + "rulechains": "Ланцюжки правил", "root": "Основний", - "delete": "Видалити ланцюг правил", + "delete": "Видалити ланцюжок правил", "name": "Ім'я", "name-required": "Необхідно вказати ім'я.", "description": "Опис", - "add": "Додати ланцюг правил", - "set-root": "Зробити ланцюг правил основним", - "set-root-rulechain-title": "Ви впевнені, що хочете зробити ланцюг правил '{{ruleChainName}}' основним?", - "set-root-rulechain-text": "Після підтвердження ланцюг правил стане основним і буде обробляти всі вхідні транспортні повідомлення.", - "delete-rulechain-title": "Ви впевнені, що хочете видалити ланцюг правил '{{ruleChainName}}'?", - "delete-rulechain-text": "Будьте обережні, після підтвердження ланцюг правил і всі пов'язані з ним дані стануть недоступними.", + "add": "Додати ланцюжок правил", + "set-root": "Зробити ланцюжок правил основним", + "set-root-rulechain-title": "Ви впевнені, що хочете зробити ланцюжок правил '{{ruleChainName}}' основним?", + "set-root-rulechain-text": "Після підтвердження ланцюжок правил стане основним і буде обробляти всі вхідні транспортні повідомлення.", + "delete-rulechain-title": "Ви впевнені, що хочете видалити ланцюжок правил '{{ruleChainName}}'?", + "delete-rulechain-text": "Будьте обережні, після підтвердження ланцюжок правил і всі пов'язані з ним дані стануть недоступними.", "delete-rulechains-title": "Ви впевнені, що хочете видалити { count, plural, =1 {1 ланцюг правил} other {# ланцюги правил} }?", "delete-rulechains-action-title": "Видалити{ count, plural, =1 {1 ланцюг правил} other {# ланцюги правил} }", - "delete-rulechains-text": "Будьте обережні, після підтвердження, вибрані ланцюги правил і всі пов'язані з ними дані стануть недоступними.", - "add-rulechain-text": "Додати новий ланцюг правил", - "no-rulechains-text": "Ланцюг правил не знайдено", - "rulechain-details": "Деталі ланцюга правил", + "delete-rulechains-text": "Будьте обережні, після підтвердження, вибрані ланцюжки правил і всі пов'язані з ними дані стануть недоступними.", + "add-rulechain-text": "Додати новий ланцюжок правил", + "no-rulechains-text": "Ланцюжок правил не знайдено", + "rulechain-details": "Деталі ланцюжка правил", "details": "Деталі", "events": "Події", "system": "Система", - "import": "Імпортувати ланцюг правил", - "export": "Експортувати ланцюг правил", - "export-failed-error": "Не вдалося експортувати ланцюг правил: {{error}}", - "create-new-rulechain": "Створити новий ланцюг правил", - "rulechain-file": "Файл ланцюга правил", - "invalid-rulechain-file-error": "Неможливо імпортувати ланцюг правил: недійсна структуру даних ланцюга правил.", - "copyId": "Копіювати Id ланцюга правил", - "idCopiedMessage": "Id ланцюга правил скопійовано в буфер обміну", - "select-rulechain": "Вибрати ланцюг правил", - "no-rulechains-matching": "Не знайдено жодних ланцюгів правил, які відповідають '{{entity}}'.", - "rulechain-required": "Необхідно вказати ланцюг правил", - "management": "Управління ланцюгами правил", + "import": "Імпортувати ланцюжок правил", + "export": "Експортувати ланцюжок правил", + "export-failed-error": "Не вдалося експортувати ланцюжок правил: {{error}}", + "create-new-rulechain": "Створити новий ланцюжок правил", + "rulechain-file": "Файл ланцюжка правил", + "invalid-rulechain-file-error": "Неможливо імпортувати ланцюжок правил: недійсна структуру даних ланцюжка правил.", + "copyId": "Копіювати Id ланцюжка правил", + "idCopiedMessage": "Id ланцюжка правил скопійовано в буфер обміну", + "select-rulechain": "Вибрати ланцюжок правил", + "no-rulechains-matching": "Не знайдено жодних ланцюжків правил, які відповідають '{{entity}}'.", + "rulechain-required": "Необхідно вказати ланцюжок правил", + "management": "Управління ланцюжками правил", "debug-mode": "Режим налагодження" }, "rulenode": { @@ -1820,7 +1883,7 @@ "link-labels": "Мітки посилання", "link-labels-required": "Необхідно вказати мітки посилання.", "no-link-labels-found": "Не знайдено жодних міток посилання", - "no-link-label-matching": "Мітка'{{label}}' не знайдена.", + "no-link-label-matching": "Мітку '{{label}}' не знайдено.", "create-new-link-label": "Створити нову!", "type-filter": "Фільтр", "type-filter-details": "Фільтрувати вхідні повідомлення з заданими умовами", @@ -1834,15 +1897,15 @@ "type-analytics-details": "Виконує аналіз потокових або збережених даних", "type-external": "Зовнішній", "type-external-details": "Взаємодіє з зовнішньою системою", - "type-rule-chain": "Ланцюг правил", - "type-rule-chain-details": "Перенаправити вхідне повідомлення на вказаний ланцюг правил", + "type-rule-chain": "Ланцюжок правил", + "type-rule-chain-details": "Перенаправити вхідне повідомлення на вказаний ланцюжок правил", "type-input": "Вхід", - "type-input-details": "Логічний вхід ланцюга правил, перенаправляє вхідні повідомлення на наступний пов'язаний вузол правил", + "type-input-details": "Логічний вхід ланцюжка правил, перенаправляє вхідні повідомлення на наступний пов'язаний вузол правил", "type-unknown": "Невідомий", "type-unknown-details": "Невизначений вузол правил", "directive-is-not-loaded": "Вказана директива конфігурації '{{directiveName}}' недоступна.", "ui-resources-load-error": "Не вдалося завантажити UI ресурси.", - "invalid-target-rulechain": "Не вдається визначити цільовий ланцюг правил!", + "invalid-target-rulechain": "Не вдається визначити цільовий ланцюжок правил!", "test-script-function": "Протестувати скрипт", "message": "Повідомлення", "message-type": "Тип повідомлення", @@ -1860,7 +1923,7 @@ "scheduler-event": "Подія планувальника", "select-scheduler-event": "Вибрати подію", "no-scheduler-events-matching": "Не знайдено жодних подій, які відповідають '{{entity}}'.", - "scheduler-event-required": "Необхвдно вказати заплановану подію", + "scheduler-event-required": "Необхідно вказати заплановану подію", "management": "Управління планувальником", "scheduler-events": "Планування подій", "add-scheduler-event": "Додати подію", @@ -1902,7 +1965,7 @@ "repeat-on-monday": "Повторити в понеділок", "repeat-on-tuesday": "Повторити у вівторок", "repeat-on-wednesday": "Повторити в середу", - "repeat-on-thursday": "Повторити в червер", + "repeat-on-thursday": "Повторити в четвер", "repeat-on-friday": "Повторити в п'ятницю", "repeat-on-saturday": "Повторити в суботу", "event-type": "Тип події", @@ -1915,7 +1978,7 @@ "week": "Тиждень", "day": "День", "agenda-week": "Порядок тижня", - "agenda-day": "Порялок дня", + "agenda-day": "Порядок дня", "list-year": "Список року", "list-month": "Список місяця", "list-week": "Список тижня", @@ -1994,7 +2057,7 @@ "download-blob-entity": "Завантажити файл", "delete-blob-entity": "Видалити файл", "delete-blob-entity-title": "Ви впевнені, що хочете видалити файл '{{blobEntityName}}'?", - "delete-blob-entity-text": "Будьте обережні, після підствердження, дані файлу стануть недоступними." + "delete-blob-entity-text": "Будьте обережні, після підтвердження, дані з файлу стануть недоступними для відновлення." }, "timezone": { "timezone": "Часовий пояс", @@ -2018,7 +2081,7 @@ "add-tenant-text": "Додати нового власника", "no-tenants-text": "Не знайдено жодного власника", "tenant-details": "Подробиці про власника", - "delete-tenant-title": "Ви впевнені, що хочете видалити власника'{{tenantTitle}}'?", + "delete-tenant-title": "Ви впевнені, що хочете видалити власника '{{tenantTitle}}'?", "delete-tenant-text": "Будьте обережні, після підтвердження власник і всі пов'язані з ним дані стануть недоступними.", "delete-tenants-title": "Ви впевнені, що хочете видалити { count, plural, =1 {1 власник} other {# власники} }?", "delete-tenants-action-title": "Видалити { count, plural, =1 {1 власник} other {# власники} }", @@ -2076,7 +2139,7 @@ "add-user-text": "Додати нового користувача", "no-users-text": "Не знайдено жодного користувача", "user-details": "Подробиці про користувача", - "delete-user-title": "Ви впевнені, що хочете видалити користувача'{{userEmail}}'?", + "delete-user-title": "Ви впевнені, що хочете видалити користувача '{{userEmail}}'?", "delete-user-text": "Будьте обережні, після підтвердження, користувач і всі пов'язані з ним дані стануть недоступними.", "delete-users-title": "Ви впевнені, що хочете видалити { count, plural, =1 {1 користувача} other {# користувачів} }?", "delete-users-action-title": "Видалити { count, plural, =1 {1 користувача} other {# користувачів} }", @@ -2128,6 +2191,9 @@ "true": "Правдиве", "long": "Довге" }, + "version-control": { + "version-control": "Керування версіями" + }, "widget": { "widget-library": "Бібліотека віджетів", "widget-bundle": "Пакет віджетів", @@ -2145,7 +2211,7 @@ "no-data-found": "Даних не знайдено", "latest": "Останні значення", "rpc": "Керуючий віджет", - "alarm": "Віджет сигнала тривоги", + "alarm": "Віджет сигналу тривоги", "static": "Статичний віджет", "select-widget-type": "Вибрати тип віджета", "missing-widget-title-error": "Необхідно вказати назву віджета!", @@ -2252,7 +2318,7 @@ "remove-datasource": "Видалити джерело даних", "add-datasource": "Додати джерело даних", "target-device": "Цільовий пристрій", - "alarm-source": "Джерело сигнала тривоги", + "alarm-source": "Джерело сигналу тривоги", "actions": "Дії", "action": "Дія", "add-action": "Додати дію", @@ -2367,7 +2433,7 @@ "no-coordinate-specified": "Ключ для широти/довготи не вказаний", "no-support-geolocation": "Ваш браузер не підтримує геолокацію", "no-image": "Немає зображення", - "no-support-web-camera": "Нет поддерживаемой веб-камеры", + "no-support-web-camera": "Немає підтримуваної веб-камери", "no-timeseries-selected": "Параметр телеметрії не вибрано", "secret-key": "Секретний ключ", "secret-key-required": "Необхідно вказати секретний ключ", @@ -2412,10 +2478,10 @@ "rpc-status": { "QUEUED": "В ЧЕРЗІ", "SENT": "ВІДПРАВЛЕНО", - "DELIVERED": "ДОСТАВЛЕННО", + "DELIVERED": "ДОСТАВЛЕНО", "SUCCESSFUL": "УСПІШНО", "TIMEOUT": "ЧАС МИНУВ", - "EXPIRED": "ПРОСРОЧЕНО", + "EXPIRED": "ПРОТЕРМІНОВАНО", "FAILED": "НЕ ВДАЛО" }, "rpc-search-status-all": "ВСІ", @@ -2426,7 +2492,7 @@ } }, "white-labeling": { - "white-labeling": "Білий маркування", + "white-labeling": "Біле маркування", "login-white-labeling": "Login White Labeling", "preview": "Попередній перегляд", "app-title": "Назва програми", @@ -2441,7 +2507,7 @@ "logo-size-error": "Зображення логотипу занадто велике. Максимально дозволений розмір зображення логотипу{{kbSize}} KBytes.", "logo-type-error": "Недійсний формат файлу логотипу. Приймаються тільки зображення.", "drop-logo-image": "Зніміть зображення логотипу або клацніть, щоб вибрати файл для завантаження.", - "no-logo-image": "Не вибрано жожного логотипу", + "no-logo-image": "Не вибрано жодного логотипу", "logo-height": "Висота логотипу, px", "primary-palette": "Основна палітра", "accent-palette": "Палітра акцент", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index 2c241a1c44..28c4d20f59 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -10,11 +10,16 @@ "permission-denied": "权限被拒绝", "permission-denied-text": "您没有执行此操作的权限!" }, + "account": { + "account": "账号", + "notification-settings": "通知设置" + }, "action": { "activate": "激活", "suspend": "暂停", "save": "保存", "saveAs": "另存为", + "move": "移动", "cancel": "取消", "ok": "确定", "delete": "删除", @@ -35,6 +40,7 @@ "edit-mode": "编辑模式", "enter-edit-mode": "进入编辑模式", "decline-changes": "撤销更改", + "decline": "撤销", "close": "关闭", "back": "后退", "run": "运行", @@ -66,7 +72,12 @@ "more": "更多", "less": "更少", "skip": "跳过", - "send": "发送" + "send": "发送", + "reset": "重置", + "show-more": "显示更多", + "dont-show-again": "不再显示", + "see-documentation": "查看文档", + "clear": "清除" }, "aggregation": { "aggregation": "聚合", @@ -94,6 +105,23 @@ "base-url-required": "基本URL必填。", "prohibit-different-url": "禁止从客户端请求头中使用主机名", "prohibit-different-url-hint": "应为生产环境启用此设置。禁用时可能会导致安全问题", + "device-connectivity": { + "device-connectivity": "设备连接", + "http-s": "HTTP(s)", + "mqtt-s": "MQTT(s)", + "coap-s": "COAP(s)", + "http": "HTTP", + "https": "HTTPs", + "mqtt": "MQTT", + "mqtts": "MQTTs", + "coap": "COAP", + "coaps": "COAPs", + "hint": "如果主机或端口字段为空,将使用默认的协议值。", + "host": "主机", + "port": "端口", + "port-pattern": "端口必须是正整数。", + "port-range": "端口应在1到65535的范围内。" + }, "mail-from": "邮件来自", "mail-from-required": "邮件发件人必填。", "smtp-protocol": "SMTP协议", @@ -173,6 +201,7 @@ "domain-name-unique": "域名和协议必须是唯一的。", "domain-name-max-length": "域名应该少于256个字符。", "error-verification-url": "域名不应包含符号 “/” 和 “:”。例:thingsboard.io", + "connection-settings": "连接设置", "oauth2": { "access-token-uri": "访问令牌URI", "access-token-uri-required": "访问令牌 URI 必填。", @@ -263,7 +292,28 @@ "platform-android": "Android", "platform-ios": "iOS", "all-platforms": "所有平台", - "allowed-platforms": "允许平台" + "smtp-provider": "SMTP提供商", + "allowed-platforms": "允许的平台", + "authentication": "身份验证", + "basic": "基本", + "provider": "提供商", + "redirect-url": "重定向URL", + "domain-name": "域名", + "redirect-url-template": "重定向URL模板", + "microsoft-tenant-id": "目录(租户)ID", + "microsoft-tenant-id-required": "需要目录(租户)ID", + "token-uri": "令牌URI", + "token-uri-required": "需要令牌URI", + "redirect-uri": "重定向URI", + "google-provider": "谷歌", + "microsoft-provider": "Office 365", + "sendgrid-provider": "Sendgrid", + "custom-provider": "自定义", + "generate-access-token": "生成访问令牌", + "update-access-token": "更新访问令牌", + "access-token-status": "访问令牌状态:", + "token-status-generated": "已生成", + "token-status-not-generated": "未生成" }, "smpp-provider": { "smpp-version": "SMPP版本", @@ -481,6 +531,7 @@ "severity-indeterminate": "不确定", "acknowledge": "应答", "clear": "清除", + "delete": "删除", "search": "查找告警", "selected-alarms": "已选择 { count, plural, =1 {1 个告警} other {# 个告警} }", "no-data": "无数据显示", @@ -491,10 +542,14 @@ "aknowledge-alarms-text": "确定要确认 { count, plural, =1 {1 个告警} other {# 个告警} }吗?", "aknowledge-alarm-title": "确认告警", "aknowledge-alarm-text": "确定要确认告警吗?", + "selected-alarms-are-acknowledged": "所选的告警已经被确认", "clear-alarms-title": "清除 { count, plural, =1 {1 个告警} other {# 个告警} }", "clear-alarms-text": "确定要清除 { count, plural, =1 {1 个告警} other {# 个告警} }?", - "clear-alarm-title": "清除警报", - "clear-alarm-text": "确定要清除警报吗?", + "clear-alarm-title": "清除告警", + "clear-alarm-text": "确定要清除告警吗?", + "delete-alarms-title": "删除 { count, plural, =1 {1 个告警} other {# 个告警} }", + "delete-alarms-text": "您确定要删除 { count, plural, =1 {1 个告警} other {# 个告警} } 吗?", + "selected-alarms-are-cleared": "所选的告警已清除", "alarm-status-filter": "告警状态筛选器", "alarm-filter-title": "告警筛选器", "assigned": "已分配", @@ -512,7 +567,11 @@ "search-propagated-alarms": "检索已传递的警报", "comments": "告警评论", "show-more": "显示更多", - "additional-info": "附加信息" + "additional-info": "附加信息", + "alarm-type": "告警类型", + "enter-alarm-type": "输入告警类型", + "no-alarm-types-matching": "找不到与 '{{entitySubtype}}' 相匹配的告警类型。", + "alarm-type-list-empty": "未选择告警类型。" }, "alarm-activity": { "add": "添加评论", @@ -666,6 +725,7 @@ "attribute": { "attributes": "属性", "latest-telemetry": "最新遥测数据", + "no-latest-telemetry": "没有最新的遥测数据", "attributes-scope": "设备属性范围", "scope-telemetry": "遥测", "scope-latest-telemetry": "最新遥测数据", @@ -679,6 +739,8 @@ "key-required": "属性键必填。", "value": "数值", "value-required": "属性值必填。", + "telemetry-key-required": "需要遥测键", + "telemetry-value-required": "需要遥测数值", "delete-attributes-title": "确定要删除 { count, plural, =1 {1 个属性} other {# 个属性} }吗?", "delete-attributes-text": "注意,确认后所有选中的属性都会被删除。", "delete-attributes": "删除属性", @@ -694,7 +756,19 @@ "no-attributes-text": "未找到属性", "no-telemetry-text": "未找到遥测数据", "copy-key": "复制键", - "copy-value": "复制值" + "add-telemetry": "添加遥测数据", + "copy-value": "复制数值", + "delete-timeseries": { + "start-time": "开始时间", + "ends-on": "结束时间", + "strategy": "策略", + "delete-strategy": "删除策略", + "all-data": "删除所有数据", + "all-data-except-latest-value": "删除除最新值外的所有数据", + "latest-value": "删除最新值", + "all-data-for-time-period": "删除指定时间段的所有数据", + "rewrite-latest-value": "重写最新值" + } }, "api-usage": { "api-features": "Api统计", @@ -713,8 +787,14 @@ "email-messages-monthly-activity": "每月产生的邮件消息", "exceptions": "异常", "executions": "执行数", + "scripts": "Scripts", + "scripts-hourly-activity": "脚本每小时活动", + "scripts-daily-activity": "脚本每日活动", + "scripts-monthly-activity": "脚本每月活动", "javascript": "JavaScript", "javascript-executions": "JavaScript 执行数", + "tbel": "TBEL", + "tbel-executions": "TBEL 执行数", "latest-error": "最新错误", "messages": "消息", "notifications": "通知", @@ -753,6 +833,18 @@ "view-details": "查看详细信息", "view-statistics": "查看统计信息" }, + "api-limit": { + "cassandra-queries": "Cassandra查询次数", + "entity-version-creation": "实体版本创建次数", + "entity-version-load": "实体版本加载次数", + "notification-requests": "通知请求次数", + "notification-requests-per-rule": "每个规则的通知请求次数", + "rest-api-requests": "REST API请求次数", + "rest-api-requests-per-customer": "每个客户的REST API请求次数", + "transport-messages": "传输消息次数", + "transport-messages-per-device": "每个设备的传输消息次数", + "ws-updates-per-session": "每个会话的WS更新次数" + }, "audit-log": { "audit": "审计", "audit-logs": "审计日志", @@ -804,7 +896,8 @@ "type-provision-success": "设备已预配置", "type-provision-failure": "设备预配置失败", "type-timeseries-updated": "遥测数据已更新", - "type-timeseries-deleted": "遥测数据已删除" + "type-timeseries-deleted": "遥测数据已删除", + "type-sms-sent": "短信发送" }, "confirm-on-exit": { "message": "有未保存的更改,确定要离开此页吗?", @@ -835,7 +928,9 @@ "created-time": "创建时间", "loading": "正在加载中...", "proceed": "继续", - "open-details-page": "打开详情页" + "open-details-page": "打开详情页", + "not-found": "未找到", + "documentation": "文档" }, "content-type": { "json": "Json", @@ -898,6 +993,13 @@ "edges": "客户边缘实例", "manage-edges": "管理边缘" }, + "date": { + "last-update-n-ago": "N分钟前的最后更新", + "last-update-n-ago-text": "最后更新{{ agoText }}", + "custom-date": "自定义日期", + "format": "格式", + "preview": "预览" + }, "datetime": { "date-from": "开始日期", "time-from": "开始时间", @@ -927,6 +1029,7 @@ "no-dashboards-text": "未找到仪表板", "no-widgets": "没有配置部件", "add-widget": "添加部件", + "add-widget-button-text": "添加部件", "title": "标题", "image": "仪表板图片", "mobile-app-settings": "移动端应用设置", @@ -973,7 +1076,7 @@ "no-dashboards-matching": "未找到匹配 '{{entity}}' 的仪表板。", "dashboard-required": "仪表板必填。", "select-existing": "选择现有仪表板", - "create-new": "创建新的仪表板", + "create-new": "创建仪表板", "new-dashboard-title": "新仪表板标题", "open-dashboard": "打开仪表板", "set-background": "设置背景", @@ -1030,11 +1133,11 @@ "import": "导入仪表板", "export": "导出仪表板", "export-failed-error": "无法导出仪表板: {{error}}", - "create-new-dashboard": "创建新的仪表板", + "create-new-dashboard": "创建仪表板", "dashboard-file": "仪表板文件", "invalid-dashboard-file-error": "无法导入仪表板: 仪表板数据结构无效。", "dashboard-import-missing-aliases-title": "配置导入仪表板使用的别名", - "create-new-widget": "创建新部件", + "create-new-widget": "创建部件", "import-widget": "导入部件", "widget-file": "部件文件", "invalid-widget-file-error": "无法导入窗口部件: 窗口部件数据结构无效。", @@ -1055,6 +1158,7 @@ "public-link-copied-message": "仪表板的公共链接已被复制到剪贴板", "manage-states": "仪表板状态管理", "states": "仪表板状态", + "states-short": "状态", "search-states": "仪表板状态检索", "selected-states": "已选择 { count, plural, =1 {1 个仪表板状态} other {# 个仪表板状态} }", "edit-state": "仪表板状态编辑", @@ -1083,11 +1187,14 @@ "unassign-dashboards-from-edge-text": "确认后,所有选定的仪表板将被取消分配,边缘将无法访问。", "assign-dashboard-to-edge": "将仪表板分配给边缘", "assign-dashboard-to-edge-text": "请选择要分配给边缘的仪表板", - "non-existent-dashboard-state-error": "找不到ID为 \"{{ stateId }}\" 的仪表板状态。" + "non-existent-dashboard-state-error": "找不到ID为 '{{ stateId }}' 的仪表板状态。", + "edit-mode": "编辑模式" }, "datakey": { "settings": "设置", + "general": "常规", "advanced": "高级", + "key": "键", "label": "标签", "color": "颜色", "units": "单位符号", @@ -1107,6 +1214,10 @@ "function-types": "函数类型", "function-type": "函数类型", "function-types-required": "函数类型必填。", + "data-keys": "数据键", + "data-key": "数据键", + "data-keys-required": "数据键是必须的。", + "data-key-required": "数据键是必须的。", "alarm-keys": "告警数据键", "alarm-key": "告警数据键", "alarm-key-functions": "告警数据键函数", @@ -1139,7 +1250,13 @@ "delta-calculation-result": "增量计算结果", "delta-calculation-result-previous-value": "上个值", "delta-calculation-result-delta-absolute": "绝对值", - "delta-calculation-result-delta-percent": "百分比" + "delta-calculation-result-delta-percent": "百分比", + "source": "来源", + "latest": "最新", + "latest-value": "最新值", + "delta": "增量", + "percent": "百分比", + "absolute": "绝对值" }, "datasource": { "type": "数据源类型", @@ -1166,8 +1283,8 @@ "no-aliases-found": "未找到别名。", "no-key-matching": "'{{key}}' 未找到。", "no-keys-found": "未找到密钥。", - "create-new-alias": "创建一个新的!", - "create-new-key": "创建一个新的!", + "create-new-alias": "创建别名", + "create-new-key": "创建键", "duplicate-alias-error": "找到重复别名 '{{alias}}'。
设备别名必须是唯一的。", "configure-alias": "配置 '{{alias}}' 别名", "no-devices-matching": "未找到与 '{{entity}}' 匹配的设备。", @@ -1273,7 +1390,7 @@ "client-id-pattern": "包含无效字符。", "user-name": "用户名", "user-name-required": "用户名必填。", - "client-id-or-user-name-necessary": "客户端ID和/或用户名是必需的", + "client-id-or-user-name-necessary": "客户端ID或用户名是必需的", "password": "密码", "secret": "密钥", "secret-required": "密钥必填", @@ -1284,6 +1401,7 @@ "any-device": "任意设备", "no-device-types-matching": "未找到匹配 '{{entitySubtype}}' 的设备类型。", "device-type-list-empty": "未选择设备类型", + "device-profile-type-list-empty": "至少应选择一个设备配置。", "device-types": "设备类型", "name": "名称", "name-required": "名称必填。", @@ -1325,7 +1443,25 @@ "device-details": "设备详细信息" }, "unassign-devices-from-edge-title": "确定要取消分配 { count, plural, =1 {1 个设备} other {# 个设备} } 吗?", - "unassign-devices-from-edge-text": "确认后,设备将被取消分配,边缘将无法访问。" + "unassign-devices-from-edge-text": "确认后,设备将被取消分配,边缘将无法访问。", + "time": "时间", + "connectivity": { + "check-connectivity": "检查连通性", + "device-created-check-connectivity": "设备已创建,检查连通性!", + "loading-check-connectivity-command": "正在加载检查连通性命令...", + "use-following-instructions": "请使用以下说明代表设备发送遥测数据", + "execute-following-command": "执行以下命令", + "install-curl-windows": "从 Windows 10 b17063 开始,cURL 已默认安装", + "install-curl-macos": "从 Mac OS X 10.2 6C115(Jaguar)开始,cURL 已默认安装", + "install-mqtt-windows": "使用说明下载、安装、设置和运行 mosquitto_pub", + "install-coap-client": "使用说明下载、安装、设置和运行 coap-client", + "install-necessary-client-tools": "安装必要的客户端工具", + "mqtts-x509-command": "使用以下文档通过 MQTT 连接带有 X509 授权的设备", + "coaps-x509-command": "使用以下文档通过基于 DTLS 的 CoAP 连接带有 X509 授权的设备", + "snmp-command": "使用以下文档通过 SNMP 连接设备。", + "sparkplug-command": "使用以下文档通过 MQTT Sparkplug 连接设备。", + "lwm2m-command": "使用以下文档通过 LWM2M 连接设备。" + } }, "asset-profile": { "asset-profile": "资产配置", @@ -1364,12 +1500,12 @@ "set-default-asset-profile-title": "确定要将 '{{assetProfileName}}' 设为默认资产配置吗?", "set-default-asset-profile-text": "确认后,资产配置将被标记为默认,并将用于未指定配置的新资产。", "no-asset-profiles-found": "未不到资产配置。", - "create-new-asset-profile": "创建一个新的!", + "create-new-asset-profile": "创建资产配置", "create-asset-profile": "创建资产配置", "import": "导入资产配置", "export": "导出资产配置", "export-failed-error": "无法导出资产配置: {{error}}", - "asset-profile-file": "资产配置文件", + "asset-profile-file": "资产配置", "invalid-asset-profile-file-error": "无法导入资产配置:无效的资产配置数据结构。" }, "device-profile": { @@ -1389,8 +1525,6 @@ "delete": "删除设备配置", "copyId": "复制设备配置 ID", "name-max-length": "名称长度必须少于256个字符", - "new-device-profile-name": "设备配置名称", - "new-device-profile-name-required": "设备配置名称必填。", "name": "名称", "name-required": "名称是必需的。", "type": "配置类型", @@ -1409,6 +1543,7 @@ "transport-type-lwm2m-hint": "LWM2M传输类型", "transport-type-snmp": "SNMP", "transport-type-snmp-hint": "指定 SNMP 传输配置", + "transport-type-http": "HTTP", "description": "说明", "default": "默认", "profile-configuration": "配置", @@ -1426,7 +1561,7 @@ "set-default-device-profile-title": "确定要将设备配置 '{{deviceProfileName}}' 设为默认值吗?", "set-default-device-profile-text": "确认后,设备配置将被标记为默认,并将用于未指定配置的新设备。", "no-device-profiles-found": "未找到设备配置。", - "create-new-device-profile": "创建一个新的!", + "create-new-device-profile": "创建设备配置", "mqtt-device-topic-filters": "MQTT 设备 Topic 筛选器", "mqtt-device-topic-filters-unique": "MQTT设备 Topic 筛选器必须唯一。", "mqtt-device-topic-filters-spark-plug": "MQTT Sparkplug B边缘网络(EoN)节点", @@ -1507,6 +1642,9 @@ "condition-duration-value-required": "持续时间值必填。", "condition-duration-time-unit-required": "时间单位必填。", "advanced-settings": "高级设置", + "alarm-rule-additional-info": "附加信息", + "edit-alarm-rule-additional-info": "编辑附加信息", + "alarm-rule-additional-info-placeholder": "请在此处提供评论和调整,以便在附加信息下的告警详情中显示", "alarm-rule-additional-info-hint": "提示: 使用 ${keyName} 来替代告警规则条件中使用的属性或遥测键的值。", "alarm-rule-mobile-dashboard": "移动端仪表板", "alarm-rule-mobile-dashboard-hint": "作为移动端告警详情仪表板使用。", @@ -1523,7 +1661,7 @@ "provision-strategy": "预配置策略", "provision-strategy-required": "预配置策略必填。", "provision-strategy-disabled": "禁用", - "provision-strategy-created-new": "允许创建新设备", + "provision-strategy-created-new": "允许创建设备", "provision-strategy-check-pre-provisioned": "检查预配置的设备", "provision-device-key": "预配置设备密钥名", "provision-device-key-required": "预配置设备密钥名必填。", @@ -1536,8 +1674,8 @@ "provision-strategy-x509": { "certificate-chain": "X509 证书链", "certificate-chain-hint": "X.509 证书策略用于通过客户端证书在双向TLS通信中提供设备的方式。", - "allow-create-new-devices": "创建新设备", - "allow-create-new-devices-hint": "如果选择创建新设备,则将客户端证书用作设备凭据。", + "allow-create-new-devices": "创建设备", + "allow-create-new-devices-hint": "如果选择创建设备,则将客户端证书用作设备凭据。", "certificate-value": "PEM 格式的证书", "certificate-value-required": "PEM 格式的证书必填。", "cn-regex-variable": "CN 正则表达式变量", @@ -1582,8 +1720,8 @@ "create-device-profile": "创建设备配置", "import": "导入设备配置", "export": "导出设备配置", - "export-failed-error": "无法导出设备配置文件: {{error}}", - "device-profile-file": "设备配置文件", + "export-failed-error": "无法导出设备配置: {{error}}", + "device-profile-file": "设备配置", "invalid-device-profile-file-error": "无法导入设备配置:无效的设备配置数据结构。", "power-saving-mode": "节能模式", "power-saving-mode-type": { @@ -1692,7 +1830,7 @@ "account-after-timeout-required": "帐户超时必填。", "account-after-timeout-pattern": "帐户超时必须是一个正整数。", "account-after-timeout-tooltip": "Bootstrap-Server帐户资源的超时值。", - "server-type": "Server type", + "server-type": "服务器类型", "add-new-server-title": "添加新的服务器配置", "add-server-config": "添加服务器配置", "add-lwm2m-server-config": "添加LwM2M服务器", @@ -1840,6 +1978,8 @@ "make-private-edge-text": "确认后,边缘及其所有数据将被设为私有,不被其他人访问。", "import": "导入边缘", "install-connect-instructions": "安装和连接说明", + "install-connect-instructions-edge-created": "边缘已创建!请检查安装和连接说明", + "loading-edge-instructions": "正在加载边缘说明...", "label": "标签", "load-entity-error": "加载数据失败,实体已经被删除。", "assign-new-edge": "分配新边缘", @@ -1891,11 +2031,15 @@ "type-rule-chain-metadata": "规则链元数据", "type-edge": "边缘", "type-user": "用户", + "type-tenant": "租户", + "type-tenant-profile": "租户配置", "type-customer": "客户", "type-relation": "关联", "type-widgets-bundle": "部件包", "type-widgets-type": "部件类型", "type-admin-settings": "管理员设置", + "type-ota-package": "OTA包", + "type-queue": "队列", "action-type-added": "增加", "action-type-deleted": "删除", "action-type-updated": "更新", @@ -1911,6 +2055,8 @@ "action-type-rpc-call": "RPC调用", "action-type-alarm-ack": "告警确认", "action-type-alarm-clear": "告警清除", + "action-type-alarm-assigned": "告警已分配", + "action-type-alarm-unassigned": "告警未分配", "action-type-assigned-to-edge": "分配给边缘", "action-type-unassigned-from-edge": "取消分配边缘", "action-type-credentials-request": "认证请求", @@ -1927,6 +2073,7 @@ "entities-count": "实体数量", "alarms-count": "告警数量", "aliases": "实体别名", + "aliases-short": "别名", "entity-alias": "实体别名", "unable-delete-entity-alias-title": "无法删除实体别名", "unable-delete-entity-alias-text": "实体别名 '{{entityAlias}}' 被以下部件使用不能删除:
{{widgetsList}}", @@ -1942,6 +2089,7 @@ "entity-types": "实体类型", "entity-type-list": "实体类型列表", "any-entity": "任意实体", + "add-entity-type": "添加实体类型", "enter-entity-type": "输入实体类型", "no-entities-matching": "未找到匹配 '{{entity}}' 的实体。", "no-entity-types-matching": "未找到匹配 '{{entityType}}' 类型的实体。", @@ -1949,18 +2097,20 @@ "help-text": "根据需要可以使用'%'进行匹配,例如:'%entity_name_contains%', '%entity_name_ends', 'entity_starts_with'。", "use-entity-name-filter": "用户筛选器", "entity-list-empty": "没有选择实体。", + "entity-type-list-required": "至少应选择一个实体类型。", "entity-name-filter-required": "实体名筛选器必填。", "entity-name-filter-no-entity-matched": "未找到以 '{{entity}}' 开头的实体", "all-subtypes": "全部", "select-entities": "选择实体", "no-aliases-found": "未找到别名", "no-alias-matching": "未找到 '{{alias}}'", - "create-new-alias": "创建一个新的!", + "create-new-alias": "创建别名", + "create-new": "创建", "key": "键名", "key-name": "键名", "no-keys-found": "未找到键名", "no-key-matching": "未找到键名 '{{key}}'", - "create-new-key": "创建一个新的!", + "create-new-key": "创建键", "type": "类型", "type-required": "实体类型必填。", "type-device": "设备", @@ -1969,6 +2119,7 @@ "device-name-starts-with": "以 '{{prefix}}' 开头的设备", "type-device-profile": "设备配置", "type-device-profiles": "设备配置", + "clear-selected-profiles": "清除已选择的配置", "list-of-device-profiles": "{ count, plural, =1 {1 个设备配置} other {# 个设备配置} }", "device-profile-name-starts-with": "名称以 '{{prefix}}' 开头的设备配置", "type-asset-profile": "资产配置", @@ -2030,6 +2181,9 @@ "type-widgets-bundle": "部件包", "type-widgets-bundles": "部件包", "list-of-widgets-bundles": "{ count, plural, =1 {1 个部件包} other {# 个部件包} }", + "type-widget": "部件", + "type-widgets": "部件", + "list-of-widgets": "{ count, plural, =1 {1 个部件} other {# 个部件} }", "search": "实体检索", "selected-entities": "已选择 { count, plural, =1 {1 个实体} other {# 个实体} }", "entity-name": "实体名", @@ -2049,9 +2203,15 @@ "type-queue": "队列", "type-notification": "通知", "type-notification-rule": "通知规则", + "type-notification-rules": "通知规则", + "list-of-notification-rules": "{ count, plural, =1 {1 个通知规则} other {# 个通知规则} }", "type-notification-target": "通知收件人", + "type-notification-targets": "通知接收者", + "list-of-notification-targets": "{ count, plural, =1 {1 个通知接收者} other {# 个通知接收者} }", "type-notification-request": "通知请求", - "type-notification-template": "通知模板" + "type-notification-template": "通知模板", + "type-notification-templates": "通知模板", + "list-of-notification-templates": "{ count, plural, =1 {1 个通知模板} other {# 个通知模板} }" }, "entity-field": { "created-time": "创建时间", @@ -2082,8 +2242,8 @@ "no-aliases-found": "未找到别名。", "no-key-matching": "'{{key}}' 未找到。", "no-keys-found": "未找到密钥。", - "create-new-alias": "创建一个新的!", - "create-new-key": "创建一个新的!", + "create-new-alias": "创建别名", + "create-new-key": "创建键", "duplicate-alias-error": "找到重复别名 '{{alias}}'。
实体视图别名必须是唯一的。", "configure-alias": "配置 '{{alias}}' 别名", "no-entity-views-matching": "未找到与 '{{entity}}' 匹配的实体视图。", @@ -2410,6 +2570,7 @@ "add-filter-prompt": "请添加筛选器", "no-filter-matching": "未找到 '{{filter}}' 。", "create-new-filter": "请新增!", + "create-new": "创建", "filter-required": "筛选器必填。", "operation": { "operation": "操作", @@ -2454,7 +2615,10 @@ "attribute": "属性", "timeseries": "Timeseries", "entity-field": "实体", - "constant": "常量" + "constant": "常量", + "client-attribute": "客户端属性", + "server-attribute": "服务端属性", + "shared-attribute": "共享属性" }, "value-type": { "value-type": "值类型", @@ -2497,6 +2661,11 @@ }, "gateway": { "add-entry": "添加配置", + "advanced": "高级", + "checking-device-activity": "检查设备活动", + "command": "Docker命令", + "command-copied-message": "Docker命令已复制到剪贴板", + "configuration": "配置", "connector-add": "添加连接器", "connector-enabled": "启用连接器", "connector-name": "连接器名称", @@ -2504,31 +2673,140 @@ "connector-type": "连接器类型", "connector-type-required": "连接器类型必填。", "connectors": "连接器配置", - "create-new-gateway": "创建新网关", + "connectors-config": "连接器配置", + "connectors-table-enabled": "已启用", + "connectors-table-name": "名称", + "connectors-table-type": "类型", + "connectors-table-status": "状态", + "connectors-table-actions": "操作", + "connectors-table-key": "键", + "connectors-table-class": "类", + "rpc-command-send": "发送", + "rpc-command-result": "结果", + "rpc-command-edit-params": "编辑参数", + "gateway-configuration": "通用配置", + "create-new-gateway": "创建网关", "create-new-gateway-text": "确定要创建名为 '{{gatewayName}}' 的新网关?", + "created-time": "创建时间", + "configuration-delete-dialog-header": "配置将被删除", + "configuration-delete-dialog-body": "只有对网关进行物理访问时,才有可能关闭远程配置。所有先前的配置都将被删除。

\n要关闭配置,请在下面输入网关名称", + "configuration-delete-dialog-input": "网关名称", + "configuration-delete-dialog-input-required": "网关名称是必需的", + "configuration-delete-dialog-confirm": "关闭", "delete": "删除配置", "download-tip": "下载配置", + "drop-file": "将文件拖放到此处或", "gateway": "网关", "gateway-exists": "同名设备已存在。", "gateway-name": "网关名称", "gateway-name-required": "网关名称必填。", "gateway-saved": "已成功保存网关配置。", + "grpc": "GRPC", + "grpc-keep-alive-timeout": "保持连接超时(毫秒)", + "grpc-keep-alive-timeout-required": "需要保持连接超时", + "grpc-keep-alive-timeout-min": "保持连接超时不能小于1", + "grpc-keep-alive-timeout-pattern": "保持连接超时无效", + "grpc-keep-alive": "保持连接(毫秒)", + "grpc-keep-alive-required": "需要保持连接", + "grpc-keep-alive-min": "保持连接不能小于1", + "grpc-keep-alive-pattern": "保持连接无效", + "grpc-min-time-between-pings": "最小Ping间隔(毫秒)", + "grpc-min-time-between-pings-required": "需要最小Ping间隔", + "grpc-min-time-between-pings-min": "最小Ping间隔不能小于1", + "grpc-min-time-between-pings-pattern": "最小Ping间隔无效", + "grpc-min-ping-interval-without-data": "无数据时的最小Ping间隔(毫秒)", + "grpc-min-ping-interval-without-data-required": "需要无数据时的最小Ping间隔", + "grpc-min-ping-interval-without-data-min": "无数据时的最小Ping间隔不能小于1", + "grpc-min-ping-interval-without-data-pattern": "无数据时的最小Ping间隔无效", + "grpc-max-pings-without-data": "无数据时的最大Ping数", + "grpc-max-pings-without-data-required": "需要无数据时的最大Ping数", + "grpc-max-pings-without-data-min": "无数据时的最大Ping数不能小于1", + "grpc-max-pings-without-data-pattern": "无数据时的最大Ping数无效", + "inactivity-check-period-seconds": "不活跃检查期(秒)", + "inactivity-check-period-seconds-required": "需要不活跃检查期", + "inactivity-check-period-seconds-min": "不活跃检查期不能小于1", + "inactivity-check-period-seconds-pattern": "不活跃检查期无效", + "inactivity-timeout-seconds": "不活跃超时(秒)", + "inactivity-timeout-seconds-required": "需要不活跃超时", + "inactivity-timeout-seconds-min": "不活跃超时不能小于1", + "inactivity-timeout-seconds-pattern": "不活跃超时无效", "json-parse": "无效的JSON。", "json-required": "字段不能为空。", + "logs": { + "logs": "日志", + "days": "天", + "hours": "小时", + "minutes": "分钟", + "seconds": "秒", + "date-format": "日期格式", + "date-format-required": "需要日期格式", + "log-format": "日志格式", + "log-type": "日志类型", + "log-format-required": "需要日志格式", + "remote": "远程日志记录", + "remote-logs": "远程日志", + "local": "本地日志记录", + "level": "日志级别", + "file-path": "文件路径", + "file-path-required": "需要文件路径", + "saving-period": "日志保存期限", + "saving-period-min": "日志保存期限不能小于1", + "saving-period-required": "需要日志保存期限", + "backup-count": "备份数量", + "backup-count-min": "备份数量不能小于1", + "backup-count-required": "需要备份数量" + }, + "min-pack-send-delay": "最小包发送延迟(毫秒)", + "min-pack-send-delay-required": "最小包发送延迟是必需的", + "min-pack-send-delay-min": "最小包发送延迟不能小于0", "no-connectors": "无连接器", "no-data": "没有配置", "no-gateway-found": "未找到网关。", "no-gateway-matching": "未找到 '{{item}}' 。", "path-logs": "日志文件的路径", "path-logs-required": "路径是必需的。", + "permit-without-calls": "保持连接许可,无需响应", "remote": "远程配置", "remote-logging-level": "日志记录级别", "remove-entry": "删除配置", + "remote-shell": "远程Shell", + "remote-configuration": "远程配置", + "other": "其他", "save-tip": "保存配置", "security-type": "安全类型", "security-types": { "access-token": "访问令牌", - "tls": "TLS" + "username-password": "用户名和密码", + "tls": "TLS", + "tls-access-token": "TLS + 访问令牌", + "tls-private-key": "TLS + 私钥" + }, + "server-port": "服务器端口", + "statistics": { + "statistic": "统计信息", + "statistics": "统计信息", + "statistic-commands-empty": "无可用统计信息", + "commands": "命令", + "send-period": "统计信息发送周期(秒)", + "send-period-required": "统计信息发送周期是必需的", + "send-period-min": "统计信息发送周期不能小于60", + "send-period-pattern": "统计信息发送周期无效", + "check-connectors-configuration": "检查连接器配置(秒)", + "check-connectors-configuration-required": "检查连接器配置是必需的", + "check-connectors-configuration-min": "检查连接器配置不能小于1", + "check-connectors-configuration-pattern": "检查连接器配置无效", + "add": "添加命令", + "timeout": "超时时间", + "timeout-ms": "超时时间(毫秒)", + "timeout-required": "超时时间是必需的", + "timeout-min": "超时时间不能小于1", + "timeout-pattern": "超时时间无效", + "attribute-name": "属性名称", + "attribute-name-required": "属性名称是必需的", + "command": "命令", + "command-required": "命令是必需的", + "command-pattern": "命令无效", + "remove": "删除命令" }, "storage": "存储", "storage-max-file-records": "文件中的最大记录数", @@ -2540,6 +2818,16 @@ "storage-max-records-min": "最小记录数为1。", "storage-max-records-pattern": "数字无效。", "storage-max-records-required": "最大记录项必填。", + "storage-read-record-count": "存储中的读取记录数", + "storage-read-record-count-min": "最小记录数为1。", + "storage-read-record-count-pattern": "数字不合法。", + "storage-read-record-count-required": "需要读取记录数。", + "storage-max-read-record-count": "存储中的最大读取记录数", + "storage-max-read-record-count-min": "最小记录数为1。", + "storage-max-read-record-count-pattern": "数字不合法。", + "storage-max-read-record-count-required": "最大读取记录数必需。", + "storage-data-folder-path": "数据文件夹路径", + "storage-data-folder-path-required": "需要数据文件夹路径。", "storage-pack-size": "最大事件包大小", "storage-pack-size-min": "最小值为1。", "storage-pack-size-pattern": "数字无效。", @@ -2549,9 +2837,11 @@ "storage-type": "存储类型", "storage-types": { "file-storage": "文件存储", - "memory-storage": "内存存储" + "memory-storage": "内存存储", + "sqlite": "SQLITE" }, "thingsboard": "ThingsBoard", + "general": "常规", "thingsboard-host": "ThingsBoard主机", "thingsboard-host-required": "主机必填。", "thingsboard-port": "ThingsBoard端口", @@ -2564,10 +2854,61 @@ "title-connectors-json": "连接器 {{typeName}} 配置", "tls-path-ca-certificate": "网关上CA证书的路径", "tls-path-client-certificate": "网关上客户端证书的路径", + "messages-ttl-check-in-hours": "消息TTL检查小时数", + "messages-ttl-check-in-hours-required": "需要提供消息TTL检查小时数。", + "messages-ttl-check-in-hours-min": "最小值为1。", + "messages-ttl-check-in-hours-pattern": "数字无效。", + "messages-ttl-in-days": "消息TTL天数", + "messages-ttl-in-days-required": "需要提供消息TTL天数。", + "messages-ttl-in-days-min": "最小值为1。", + "messages-ttl-in-days-pattern": "数字无效。", + "mqtt-qos": "QoS", + "mqtt-qos-required": "需要提供QoS", + "mqtt-qos-range": "QoS值的范围是从0到1", "tls-path-private-key": "网关上私钥的路径", "toggle-fullscreen": "切换全屏", "transformer-json-config": "配置JSON*", - "update-config": "添加/更新配置JSON" + "update-config": "添加/更新配置JSON", + "hints": { + "remote-configuration": "启用对网关的远程配置和管理", + "remote-shell": "通过远程Shell小部件启用对网关操作系统的远程控制", + "host": "ThingsBoard 主机名或IP地址", + "port": "ThingsBoard MQTT服务端口", + "token": "ThingsBoard 网关访问令牌", + "client-id": "ThingsBoard 网关MQTT客户端ID", + "username": "ThingsBoard 网关MQTT用户名", + "password": "ThingsBoard 网关MQTT密码", + "ca-cert": "CA证书文件的路径", + "date-form": "日志消息中的日期格式", + "data-folder": "包含数据的文件夹的路径(相对或绝对路径)", + "log-format": "日志消息格式", + "remote-log": "启用对网关的远程日志记录和日志读取", + "backup-count": "如果备份计数大于0,则在执行轮换时,最多保留备份计数个文件-最旧的文件将被删除", + "storage": "提供将数据发送到平台之前保存传入数据的配置", + "max-file-count": "将创建的文件的最大数量", + "max-read-count": "从存储中获取的消息计数并发送到ThingsBoard", + "max-records": "一个文件中存储的最大记录数", + "read-record-count": "从存储中获取的消息计数并发送到ThingsBoard", + "max-records-count": "在将数据发送到ThingsBoard之前,存储中的最大数据计数", + "ttl-check-hour": "网关多久检查一次数据是否过时", + "ttl-messages-day": "存储将保存数据的最大天数", + "commands": "用于收集附加统计信息的命令", + "attribute": "统计遥测键", + "timeout": "命令执行的超时时间", + "command": "命令执行的结果,将用作遥测的值", + "check-device-activity": "启用监视每个连接设备的活动", + "inactivity-timeout": "在此时间后,网关将断开设备的连接", + "inactivity-period": "设备活动检查的周期", + "minimal-pack-delay": "发送消息包之间的延迟(减小此设置会导致增加CPU使用率)", + "qos": "MQTT消息传递的服务质量(0-至多一次,1-至少一次)", + "server-port": "GRPC服务器侦听传入连接的网络端口", + "grpc-keep-alive-timeout": "在考虑连接死亡之前,服务器等待keepalive ping响应的最长时间", + "grpc-keep-alive": "没有活动RPC调用时两个连续keepalive ping消息之间的持续时间", + "grpc-min-time-between-pings": "服务器在发送keepalive ping消息之间应等待的最小时间量", + "grpc-max-pings-without-data": "在没有接收到任何数据之前,服务器可以发送的keepalive ping消息的最大数量,然后将连接视为死亡", + "grpc-min-ping-interval-without-data": "在没有发送或接收数据时,服务器在发送keepalive ping消息之间应等待的最小时间量", + "permit-without-calls": "允许服务器在没有活动RPC调用时保持GRPC连接活动" + } }, "grid": { "delete-item-title": "确定要删除此项吗?", @@ -2599,8 +2940,10 @@ "browse-files": "浏览文件" }, "image-input": { - "drop-image-or": "拖放一张图片或者", "drop-images-or": "拖放一张或多张图片", + "drag-and-drop": "拖放", + "or": "或", + "browse": "浏览", "no-images": "未选择任何图片", "images": "图片" }, @@ -2651,6 +2994,12 @@ "lwm2m-server-secret-key": "LwM2M server证书密钥", "lwm2m-server-public-key-id": "LwM2M server公钥" }, + "snmp": { + "host": "SNMP 主机地址", + "port": "SNMP 端口", + "version": "SNMP 版本 (例如:v1, v2c, 或 v3)", + "community-string": "SNMP 团体字符串" + }, "isgateway": "是否网关", "activity-time-from-gateway-device": "来自网关设备的活动时间", "description": "说明", @@ -2661,7 +3010,7 @@ "select-file": "选择一个文件", "configuration": "导入配置", "column-type": "选择列类型", - "creat-entities": "创建新实体" + "creat-entities": "创建实体" }, "message": { "create-entities": "{{count}} 个新实体已成功创建。", @@ -2687,6 +3036,7 @@ }, "layout": { "layout": "布局", + "layouts": "布局", "manage": "布局管理", "settings": "布局设置", "color": "颜色", @@ -2712,6 +3062,12 @@ "legend": { "direction": "图例方向", "position": "图例位置", + "show-values": "显示数值", + "min-option": "最小值", + "max-option": "最大值", + "average-option": "平均值", + "total-option": "总数", + "latest-option": "最新值", "sort-legend": "在图例中排序数据键", "show-max": "显示最大值", "show-min": "显示最小值", @@ -2731,7 +3087,9 @@ "weeks": "(一周前)", "months": "(一个月前)", "years": "(一年前)" - } + }, + "label": "标签", + "value": "值" }, "login": { "login": "登录", @@ -2746,7 +3104,7 @@ "remember-me": "记住我", "forgot-password": "忘记密码?", "password-reset": "密码重置", - "expired-password-reset-message": "您的凭据已过期!请创建新密码。", + "expired-password-reset-message": "您的凭据已过期!请创建密码。", "new-password": "新密码", "new-password-again": "再次输入新密码", "password-link-sent-message": "密码重置链接已成功发送!", @@ -2793,6 +3151,7 @@ "api-feature-hint": "如果字段为空,则触发器将应用于所有API功能", "api-usage-trigger-settings": "API使用触发设置", "new-platform-version-trigger-settings": "新平台版本触发设置", + "rate-limits-trigger-settings": "超出速率限制触发设置", "at-least-one-should-be-selected": "至少需要选择一个", "basic-settings": "基本设置", "button-text": "按钮文本", @@ -2833,6 +3192,8 @@ "email-preview": "Email 通知预览", "slack": "Slack", "slack-preview": "Slack 通知预览", + "microsoft-teams" : "Microsoft Teams", + "microsoft-teams-preview" : "Microsoft Teams 通知预览", "sms": "SMS", "sms-preview": "SMS 通知预览", "web": "Web", @@ -2870,6 +3231,7 @@ "dashboard": "打开仪表板", "link": "打开URL链接" }, + "loading-notifications": "加载通知中…", "management": "通知管理", "mark-all-as-read": "全部标记为已读", "mark-as-read": "标记为已读", @@ -2910,20 +3272,20 @@ "only-rule-chain-lifecycle-failures": "仅在规则链生命周期故障时通知", "only-rule-node-lifecycle-failures": "仅在规则节点生命周期故障时通知", "platform-users": "平台用户", + "rate-limits": "速率限制", + "rate-limits-hint": "如果该字段为空,则触发器将应用于所有速率限制", "recipient": "收件人", "recipient-group": "收件人组", "recipient-type": { "affected-tenant-administrators": "受影响的租户管理员", "affected-user": "受影响的用户", - "affected-user-hint": "受影响用户的提示", "all-users": "所有用户", "customer-users": "客户用户", "system-administrators": "系统管理员", "tenant-administrators": "租户管理员", "user-filters": "用户筛选器", "user-list": "用户列表", - "users-entity-owner": "实体所有者的用户", - "users-entity-owner-hint": "实体所有者用户的提示" + "users-entity-owner": "实体所有者的用户" }, "recipients": "收件人", "notification-recipients": "通知 / 收件人", @@ -2943,6 +3305,8 @@ "rule-engine-filter": "规则引擎筛选器", "rule-name": "规则名称", "rule-name-required": "名称必填。", + "rule-disable": "禁用通知规则", + "rule-enable": "启用通知规则", "rule-node-filter": "规则节点筛选器", "rules": "规则", "notification-rules": "通知 / 规则", @@ -2986,13 +3350,15 @@ "general": "通用", "rule-engine-lifecycle-event": "规则引擎生命周期事件", "rule-node": "规则节点", - "new-platform-version": "新的平台版本" + "new-platform-version": "新的平台版本", + "rate-limits": "超过速率限制" }, "templates": "模板", "notification-templates": "通知 / 模板", - "tenant-profiles-list-rule-hint": "如果字段为空,触发器将应用于所有租户配置文件", + "tenant-profiles-list-rule-hint": "如果字段为空,触发器将应用于所有租户配置", "tenants-list-rule-hint": "如果字段为空,触发器将应用于所有租户", "threshold": "阈值", + "theme-color": "主题颜色", "time": "时间", "track-rule-node-events": "追踪规则节点事件", "trigger": { @@ -3005,6 +3371,7 @@ "entity-action": "实体操作", "rule-engine-lifecycle-event": "规则引擎生命周期事件", "new-platform-version": "新的平台版本", + "rate-limits": "超过速率限制", "trigger": "触发器", "trigger-required": "触发器必填。" }, @@ -3013,7 +3380,21 @@ "updated": "已更新", "use-template": "使用模板", "view-all": "查看全部", - "warning": "警告" + "warning": "警告", + "webhook-url": "Webhook URL", + "webhook-url-required": "需要填写Webhook URL", + "channel-name": "频道名称", + "channel-name-required": "需要填写频道名称", + "settings": { + "notification-settings": "通知设置", + "reset-all": "重置全部设置", + "reset-all-title": "您确定要将表单重置吗?", + "reset-all-text": "确认后,设置表单将重置为默认值并保存。", + "type": "类型", + "enable-all": "全部启用", + "disable-all": "全部禁用", + "delivery-not-configured": "未配置传递方式" + } }, "ota-update": { "add": "添加包", @@ -3028,7 +3409,7 @@ "checksum-copied-message": "包校验和已复制到剪贴板", "change-firmware": "固件的更改可能会导致 { count, plural, =1 {1 个设备} other {# 个设备} } 的更新。", "change-software": "软件的更改可能会导致 { count, plural, =1 {1 个设备} other {# 个设备} } 的更新。", - "chose-compatible-device-profile": "上传的包仅适用于具有所选配置文件的设备。", + "chose-compatible-device-profile": "上传的包仅适用于具有所选配置的设备。", "chose-firmware-distributed-device": "选择将分发到设备的固件", "chose-software-distributed-device": "选择将分发到设备的软件", "content-type": "内容类型", @@ -3103,6 +3484,13 @@ "security": { "security": "安全", "general-settings": "通用安全设置", + "access-token": "访问令牌", + "access-token-required": "需要访问令牌", + "clientId": "客户端ID", + "clientId-required": "需要客户端ID", + "username": "用户名", + "username-required": "需要用户名", + "ca-cert": "CA证书", "2fa": { "2fa": "双因素认证", "2fa-description": "双因素认证可保护您的帐户免受未经授权的访问。在登录时必须输入安全验证码。", @@ -3175,6 +3563,7 @@ "relation": { "relations": "关联", "direction": "方向", + "clear-relation-type": "清除关系类型", "search-direction": { "FROM": "从", "TO": "到" @@ -3209,6 +3598,7 @@ "delete-from-relations-title": "确定删除 { count, plural, =1 {1 个关联} other {# 个关联} } 吗?", "delete-from-relations-text": "确定删除所有选择的关联关系后,当前实体将与对应的实体取消关联", "remove-relation-filter": "移除关联筛选器", + "remove-filter": "移除过滤器", "add-relation-filter": "添加关联筛选器", "any-relation": "任意关联", "relation-filters": "关联筛选器", @@ -3218,6 +3608,7 @@ }, "resource": { "add": "添加资源", + "all-types": "全部", "copyId": "复制资源ID", "delete": "删除资源", "delete-resource-text": "请注意:确认后,资源将不可恢复。", @@ -3243,7 +3634,13 @@ "system": "系统", "title": "标题", "title-required": "标题是必填项。", - "title-max-length": "标题长度应该少于256个字符。" + "title-max-length": "标题长度应该少于256个字符。", + "type": { + "jks": "JKS", + "js-module": "JS 模块", + "lwm2m-model": "LWM2M 模型", + "pkcs-12": "PKCS #12" + } }, "rulechain": { "rulechain": "规则链", @@ -3273,7 +3670,7 @@ "import": "导入规则链", "export": "导出规则链", "export-failed-error": "无法导出规则链:{{error}}", - "create-new-rulechain": "创建新的规则链", + "create-new-rulechain": "创建规则链", "rulechain-file": "规则链文件", "invalid-rulechain-file-error": "不能导入规则链:无效的规则链数据格式。", "copyId": "复制规则链ID", @@ -3286,7 +3683,6 @@ "search": "查找规则链", "selected-rulechains": "已选择 { count, plural, =1 {1 个规则链} other {# 个规则链} }", "open-rulechain": "打开规则链", - "assign-new-rulechain": "分配新的规则链", "edge-template-root": "模版根链", "assign-to-edge": "分配给边缘", "edge-rulechain": "边缘规则链", @@ -3314,11 +3710,13 @@ "events": "事件", "search": "查找节点", "open-node-library": "打开节点库", + "close-node-library": "关闭节点库", "add": "添加规则节点", "name": "名称", "name-required": "名称必填。", "name-max-length": "名称长度应该少于256个字符。", "type": "类型", + "rule-node-description": "规则节点描述", "delete": "删除规则节点", "select-all-objects": "选择所有节点和连接", "deselect-all-objects": "取消选择所有节点和连接", @@ -3343,7 +3741,7 @@ "link-labels-required": "链接标签必填。", "no-link-labels-found": "未找到链接标签", "no-link-label-matching": "未找到匹配 '{{label}}' 的链接标签。", - "create-new-link-label": "创建一个新的!", + "create-new-link-label": "创建链接标签", "type-filter": "筛选器", "type-filter-details": "使用配置条件筛选传入消息", "type-enrichment": "属性集", @@ -3377,7 +3775,8 @@ "output": "输出", "test": "测试", "help": "帮助", - "reset-debug-mode": "重置所有节点中的调试模式" + "reset-debug-mode": "重置所有节点中的调试模式", + "test-with-this-message": "使用此消息进行{{test}}测试" }, "timezone": { "timezone": "时区", @@ -3451,6 +3850,8 @@ "description": "说明", "description-hint": "此文本将显示在队列说明中,而不是所选策略中", "alt-description": "提交策略:{{submitStrategy}},处理策略:{{processingStrategy}}", + "custom-properties": "自定义属性", + "custom-properties-hint": "自定义队列(主题)创建属性,例如 'retention.ms:604800000;retention.bytes:1048576000'", "strategies": { "sequential-by-originator-label": "按发起者顺序处理", "sequential-by-originator-hint": "在确认设备A的前一条消息之前,不会提交设备A的新消息", @@ -3551,12 +3952,12 @@ "set-default-tenant-profile-title": "确定要将租户配置 '{{tenantProfileName}}' 设为默认值吗?", "set-default-tenant-profile-text": "确认后,此租户配置将被标记为默认配置,并将用于未指定配置的新租户。", "no-tenant-profiles-found": "未找到租户配置。", - "create-new-tenant-profile": "创建一个新的!", - "create-tenant-profile": "创建新的租户配置", + "create-new-tenant-profile": "创建租户配置", + "create-tenant-profile": "创建租户配置", "import": "导入租户配置", "export": "导出租户配置", "export-failed-error": "无法导出租户配置: {{error}}", - "tenant-profile-file": "租户配置文件", + "tenant-profile-file": "租户配置", "invalid-tenant-profile-file-error": "无法导入租户配置:无效的租户配置数据结构。", "advanced-settings": "高级设置", "entities": "实体", @@ -3612,6 +4013,9 @@ "max-j-s-executions": "最大 JavaScript 执行数", "max-j-s-executions-required": "最大 JavaScript 执行数必填。", "max-j-s-executions-range": "最大 JavaScript 执行数不能为负数", + "max-tbel-executions": "最大 TBEL 执行数", + "max-tbel-executions-required": "需要指定最大 TBEL 执行数。", + "max-tbel-executions-range": "最大 TBEL 执行数不能为负数。", "max-d-p-storage-days": "最大存储点天", "max-d-p-storage-days-required": "最大存储点天必填。", "max-d-p-storage-days-range": "最大存储点天不能为负数", @@ -3624,12 +4028,19 @@ "rpc-ttl-days": "RPC TTL天数", "rpc-ttl-days-required": "RPC TTL天数必填。", "rpc-ttl-days-days-range": "RPC TTL天数不能为负数", + "queue-stats-ttl-days": "队列统计信息TTL天数", + "queue-stats-ttl-days-required": "需要指定队列统计信息TTL天数", + "queue-stats-ttl-days-range": "队列统计信息TTL天数不能为负数", + "rule-engine-exceptions-ttl-days": "规则引擎异常TTL天数", + "rule-engine-exceptions-ttl-days-required": "需要指定规则引擎异常TTL天数", + "rule-engine-exceptions-ttl-days-range": "规则引擎异常TTL天数不能为负数", "max-rule-node-executions-per-message": "每条消息的最大规则节点执行数", "max-rule-node-executions-per-message-required": "每个消息的最大规则节点执行数必填。", "max-rule-node-executions-per-message-range": "每条消息的最大规则节点执行数不能为负", "max-emails": "最大电子邮件发送数", "max-emails-required": "最大电子邮件发送数必填。", "max-emails-range": "最大电子邮件发送数不能为负", + "sms-enabled": "启用短信", "max-sms": "最大短信发送数", "max-sms-required": "最大短信发送数必填。", "max-sms-range": "最大短信发送数不能为负", @@ -3740,16 +4151,24 @@ "days": "天" }, "timewindow": { + "timewindow": "时间窗口", "years": "{ years, plural, =1 {1 年 } other {# 年 } }", - "months": "{ months, plural, =1 { 月 } other {# 月 } }", - "weeks": "{ weeks, plural, =1 { 周 } other {# 周 } }", + "years-short": "{{ years }}y", + "months": "{ months, plural, =1 {1 月 } other {# 月 } }", + "months-short": "{{ months }}M", + "weeks": "{ weeks, plural, =1 {1 周 } other {# 周 } }", + "weeks-short": "{{ weeks }}w", "days": "{ days, plural, =1 {1 天 } other {# 天 } }", + "days-short": "{{ days }}d", "hours": "{ hours, plural, =0 {- 小时 } =1 {1 小时 } other {# 小时 } }", "hr": "{{ hr }} 时", + "hr-short": "{{ hr }}h", "minutes": "{ minutes, plural, =0 {- 分 } =1 {1 分 } other {# 分 } }", "min": "{{ min }} 分", + "min-short": "{{ min }}m", "seconds": "{ seconds, plural, =0 {- 秒 } =1 {1 秒 } other {# 秒 } }", "sec": "{{ sec }} 秒", + "sec-short": "{{ sec }}s", "short": { "days": "{ days, plural, =1 {1 天 } other {# 天 } }", "hours": "{ hours, plural, =1 {1 小时 } other {# 小时 } }", @@ -3768,7 +4187,426 @@ "hide": "隐藏", "interval": "区间", "just-now": "刚刚", - "ago": "之前" + "just-now-lower": "刚刚", + "ago": "之前", + "style": "时间窗口样式", + "icon": "图标", + "icon-position": "图标位置", + "icon-position-left": "左侧", + "icon-position-right": "右侧", + "font": "字体", + "color": "颜色", + "displayTypePrefix": "显示实时/历史前缀", + "preview": "预览" + }, + "tooltip": { + "value": "值", + "date": "日期", + "background-color": "背景颜色", + "background-blur": "背景模糊" + }, + "unit": { + "millimeter": "mm", + "centimeter": "cm", + "angstrom": "Å", + "nanometer": "nm", + "micrometer": "μm", + "meter": "m", + "kilometer": "km", + "inch": "in", + "foot": "ft", + "yard": "yd", + "mile": "mi", + "nautical-mile": "nmi", + "astronomical-unit": "au", + "reciprocal-metre": "m⁻¹", + "meter-per-meter": "m/m", + "steradian": "sr", + "thou": "th", + "barleycorn": "bc", + "hand": "hd", + "chain": "ch", + "furlong": "fur", + "league": "lea", + "fathom": "fath", + "cable": "cb", + "link": "li", + "rod": "rd", + "nanogram": "ng", + "microgram": "μg", + "milligram": "mg", + "gram": "g", + "kilogram": "kg", + "tonne": "t", + "ounce": "oz", + "pound": "lb", + "stone": "st", + "hundredweight-count": "cwt", + "short-tons": "s.t.", + "dalton": "Da", + "grain": "gr", + "drachm": "dr", + "quarter": "qr", + "slug": "slug", + "carat": "ct", + "cubic-millimeter": "mm³", + "cubic-centimeter": "cm³", + "cubic-meter": "m³", + "cubic-kilometer": "km³", + "microliter": "μL", + "milliliter": "mL", + "liter": "L", + "hectoliter": "hL", + "cubic-inch": "in³", + "cubic-foot": "ft³", + "cubic-yard": "yd³", + "fluid-ounce": "fl oz", + "pint": "pt", + "quart": "qt", + "gallon": "gal", + "oil-barrels": "bbl", + "cubic-meter-per-kilogram": "m³/kg", + "gill": "gi", + "hogshead": "hhd", + "teaspoon": "tsp", + "tablespoon": "tbsp", + "cup": "cup", + "celsius": "°C", + "kelvin": "K", + "rankine": "°R", + "fahrenheit": "°F", + "percent": "%", + "meter-per-second": "m/s", + "kilometer-per-hour": "km/h", + "foot-per-second": "ft/s", + "mile-per-hour": "mph", + "knot": "kn", + "millimeters-per-minute": "mm/min", + "kilometer-per-hour-squared": "km/h²", + "foot-per-second-squared": "ft/s²", + "pascal": "Pa", + "kilopascal": "kPa", + "megapascal": "MPa", + "gigapascal": "GPa", + "millibar": "mbar", + "bar": "bar", + "kilobar": "kbar", + "newton": "N", + "newton-meter": "N·m", + "foot-pounds": "ft·lbf", + "inch-pounds": "in·lbf", + "newton-per-meter": "N/m", + "atmospheres": "atm", + "pounds-per-square-inch": "psi", + "torr": "Torr", + "inches-of-mercury": "inHg", + "pascal-per-square-meter": "Pa/m²", + "pound-per-square-inch": "psi", + "newton-per-square-meter": "N/m²", + "kilogram-force-per-square-meter": "kgf/m²", + "pascal-per-square-centimeter": "Pa/cm²", + "ton-force-per-square-inch": "tonf/in²", + "kilonewton-per-square-meter": "kN/m²", + "newton-per-square-millimeter": "N/mm²", + "microjoule": "μJ", + "millijoule": "mJ", + "joule": "J", + "kilojoule": "kJ", + "megajoule": "MJ", + "gigajoule": "GJ", + "watt-hour": "Wh", + "kilowatt-hour": "kWh", + "electron-volts": "eV", + "joules-per-coulomb": "J/C", + "british-thermal-unit": "BTU", + "foot-pound": "ft·lb", + "calorie": "cal", + "small-calorie": "cal", + "kilocalorie": "kcal", + "joule-per-kelvin": "J/K", + "joule-per-kilogram-kelvin": "J/(kg·K)", + "joule-per-kilogram": "J/kg", + "watt-per-meter-kelvin": "W/(m·K)", + "joule-per-cubic-meter": "J/m³", + "therm": "thm", + "electric-dipole-moment": "Debye", + "magnetic-dipole-moment": "Am²", + "debye": "D", + "coulomb-per-square-meter-per-volt": "C/(m²·V)", + "milliwatt": "mW", + "microwatt": "μW", + "watt": "W", + "kilowatt": "kW", + "megawatt": "MW", + "gigawatt": "GW", + "metric-horsepower": "PS", + "milliwatt-per-square-centimeter": "mW/cm²", + "watt-per-square-centimeter": "W/cm²", + "kilowatt-per-square-centimeter": "kW/cm²", + "milliwatt-per-square-meter": "mW/m²", + "watt-per-square-meter": "W/m²", + "kilowatt-per-square-meter": "kW/m²", + "watt-per-square-inch": "W/in²", + "kilowatt-per-square-inch": "kW/in²", + "horsepower": "hp", + "btu-per-hour": "BTU/h", + "coulomb": "C", + "millicoulomb": "mC", + "microcoulomb": "μC", + "picocoulomb": "pC", + "coulomb-per-meter": "C/m", + "coulomb-per-cubic-meter": "C/m³", + "coulomb-per-square-meter": "C/m²", + "square-millimeter": "mm²", + "square-centimeter": "cm²", + "square-meter": "m²", + "hectare": "ha", + "square-kilometer": "km²", + "square-inch": "in²", + "square-foot": "ft²", + "square-yard": "yd²", + "acre": "ac", + "square-mile": "mi²", + "are": "a", + "barn": "b", + "circular-inch": "c in²", + "milliampere-hour": "mAh", + "ampere-hours": "Ah", + "kiloampere-hours": "kAh", + "nanoampere": "nA", + "picoampere": "pA", + "microampere": "μA", + "milliampere": "mA", + "ampere": "A", + "kiloamperes": "kA", + "microampere-per-square-centimeter": "μA/cm²", + "ampere-per-square-meter": "A/m²", + "ampere-per-meter": "A/m", + "oersted": "Oe", + "bohr-magneton": "μB", + "ampere-meter-squared": "A·m²", + "ampere-meter": "A·m", + "nanovolt": "nV", + "picovolt": "pV", + "millivolts": "mV", + "microvolts": "μV", + "volt": "V", + "kilovolts": "kV", + "dbmV": "dBmV", + "dbm": "dBm", + "volt-meter": "V·m", + "kilovolt-meter": "kV·m", + "megavolt-meter": "MV·m", + "microvolt-meter": "μV·m", + "millivolt-meter": "mV·m", + "nanovolt-meter": "nV·m", + "ohm": "Ω", + "microohm": "μΩ", + "milliohm": "mΩ", + "kilohm": "kΩ", + "megohm": "MΩ", + "gigohm": "GΩ", + "hertz": "Hz", + "kilohertz": "kHz", + "megahertz": "MHz", + "gigahertz": "GHz", + "rpm": "rpm", + "candela-per-square-meter": "cd/m²", + "candela": "cd", + "lumen": "lm", + "lux": "lx", + "foot-candle": "fc", + "lumen-per-square-meter": "lm/m²", + "lux-second": "lx·s", + "lumen-second": "lm·s", + "lumens-per-watt": "lm/W", + "absorbance": "AU", + "mole": "mol", + "nanomole": "nmol", + "micromole": "μmol", + "millimole": "mmol", + "kilomole": "kmol", + "mole-per-cubic-meter": "mol/m³", + "rssi": "RSSI", + "ppm": "ppm", + "ppb": "ppb", + "micrograms-per-cubic-meter": "μg/m³", + "aqi": "AQI", + "gram-per-cubic-meter": "g/m³", + "gram-per-kilogram": "g/kg", + "millimeters-per-second": "mm/s", + "neper": "Np", + "bel": "B", + "decibel": "dB", + "meters-per-second-squared": "m/s²", + "becquerel": "Bq", + "curie": "Ci", + "gray": "Gy", + "sievert": "Sv", + "roentgen": "R", + "cps": "cps", + "rad": "rad", + "rem": "rem", + "dps": "dps", + "rutherford": "Rd", + "coulombs-per-kilogram": "C/kg", + "becquerels-per-cubic-meter": "Bq/m³", + "curies-per-liter": "Ci/L", + "becquerels-per-second": "Bq/s", + "curies-per-second": "Ci/s", + "gy-per-second": "Gy/s", + "watt-per-steradian": "W/sr", + "watt-per-square-metre-steradian": "W/(m²·sr)", + "ph-level": "pH", + "turbidity": "NTU", + "mg-per-liter": "mg/L", + "microsiemens-per-centimeter": "μS/cm", + "millisiemens-per-meter": "mS/m", + "siemens-per-meter": "S/m", + "kilogram-per-cubic-meter": "kg/m³", + "gram-per-cubic-centimeter": "g/cm³", + "kilogram-per-square-meter": "kg/m²", + "milligram-per-milliliter": "mg/mL", + "pound-per-cubic-foot": "lb/ft³", + "ounces-per-cubic-inch": "oz/in³", + "tons-per-cubic-yard": "ton/yd³", + "particle-density": "PD", + "kilometers-per-liter": "km/L", + "miles-per-gallon": "mpg", + "liters-per-100-km": "L/100 km", + "gallons-per-mile": "gal/mi", + "liters-per-hour": "L/h", + "gallons-per-hour": "gal/h", + "beats-per-minute": "bpm", + "millimeters-of-mercury": "mmHg", + "milligrams-per-deciliter": "mg/dL", + "g-force": "G", + "kilonewton": "kN", + "kilogram-force": "kgf", + "pound-force": "lbf", + "kilopound-force": "kip", + "dyne": "dyn", + "poundal": "pdl", + "kip": "kip", + "gal": "Gal", + "gravity": "g₀", + "hectopascal": "hPa", + "atmosphere": "atm", + "millibars": "mbar", + "inch-of-mercury": "inHg", + "richter-scale": "Richter", + "second": "s", + "minute": "min", + "hour": "hr", + "day": "day", + "week": "wk", + "month": "mo", + "year": "yr", + "cubic-foot-per-minute": "ft³/min", + "cubic-meters-per-hour": "m³/h", + "cubic-meters-per-second": "m³/s", + "liter-per-second": "L/s", + "liter-per-minute": "L/min", + "gallons-per-minute": "GPM", + "cubic-foot-per-second": "ft³/s", + "milliliters-per-minute": "mL/min", + "bit": "bit", + "byte": "B", + "kilobyte": "KB", + "megabyte": "MB", + "gigabyte": "GB", + "terabyte": "TB", + "petabyte": "PB", + "exabyte": "EB", + "zettabyte": "ZB", + "yottabyte": "YB", + "bit-per-second": "bps", + "kilobit-per-second": "Kbps", + "megabit-per-second": "Mbps", + "gigabit-per-second": "Gbps", + "terabit-per-second": "Tbps", + "byte-per-second": "B/s", + "kilobyte-per-second": "KB/s", + "megabyte-per-second": "MB/s", + "gigabyte-per-second": "GB/s", + "degree": "°", + "radian": "rad", + "gradian": "grad", + "mil": "mil", + "revolution": "rev", + "siemens": "S", + "millisiemens": "mS", + "microsiemens": "μS", + "kilosiemens": "kS", + "megasiemens": "MS", + "gigasiemens": "GS", + "farad": "F", + "millifarad": "mF", + "microfarad": "μF", + "nanofarad": "nF", + "picofarad": "pF", + "kilofarad": "kF", + "megafarad": "MF", + "gigafarad": "GF", + "terfarad": "TF", + "farad-per-meter": "F/m", + "tesla": "T", + "gauss": "G", + "kilogauss": "kG", + "millitesla": "mT", + "microtesla": "μT", + "nanotesla": "nT", + "kilotesla": "kT", + "megatesla": "MT", + "millitesla-square-meters": "mT·m²", + "gamma": "γ", + "lambda": "λ", + "square-meter-per-second": "m²/s", + "square-centimeter-per-second": "cm²/s", + "stoke": "St", + "centistokes": "cSt", + "square-foot-per-second": "ft²/s", + "square-inch-per-second": "in²/s", + "pascal-second": "Pa·s", + "centipoise": "cP", + "poise": "P", + "reynolds": "Re", + "pound-per-foot-hour": "lb/(ft·hr)", + "newton-second-per-square-meter": "N·s/m²", + "dyne-second-per-square-centimeter": "dyn·s/cm²", + "kilogram-per-meter-second": "kg/(m·s)", + "tesla-square-meters": "T·m²", + "maxwell": "Mx", + "tesla-per-meter": "T/m", + "gauss-per-centimeter": "G/cm", + "weber": "Wb", + "microweber": "μWb", + "milliweber": "mWb", + "gauss-square-centimeter": "G·cm²", + "kilogauss-square-centimeter": "kG·cm²", + "henry": "H", + "millihenry": "mH", + "microhenry": "μH", + "nanohenry": "nH", + "henry-per-meter": "H/m", + "tesla-meter-per-ampere": "T·m/A", + "gauss-per-oersted": "G/Oe", + "kilogram-per-mole": "kg/mol", + "gram-per-mole": "g/mol", + "milligram-per-mole": "mg/mol", + "joule-per-mole": "J/mol", + "joule-per-mole-kelvin": "J/mol-K", + "millivolts-per-meter": "mV/m", + "volts-per-meter": "V/m", + "kilovolts-per-meter": "kV/m", + "radian-per-second": "rad/s", + "radian-per-second-squared": "rad/s^2", + "revolutions-per-minute-per-second": "rpm/s", + "revolutions-per-minute-per-second-squared": "rpm/s^2", + "deg-per-second": "deg/s", + "degrees-brix": "°Bx", + "katal": "kat", + "katal-per-cubic-metre": "kat/m^3" }, "user": { "user": "用户", @@ -3918,11 +4756,20 @@ "widget-bundle": "部件包", "all-bundles": "所有包", "select-widgets-bundle": "选择部件包", + "widgets": "部件", + "all-widgets": "所有部件", + "widget": "部件", + "select-widget": "选择部件", + "no-widgets-matching": "未找到与 '{{entity}}' 匹配的部件。", + "no-widgets": "暂无部件", + "no-widgets-text": "未找到部件", "management": "管理部件", "editor": "部件编辑器", + "confirm-to-exit-editor-html": "You have unsaved widget settings.
Are you sure you want to leave this page?", "widget-type-not-found": "加载部件配置出错。
可能关联的部件已经删除了。", "widget-type-load-error": "由于以下错误未加载部件:", "remove": "删除部件", + "delete": "Delete widget", "edit": "编辑部件", "remove-widget-title": "确定要删除 '{{widgetTitle}}'部件吗?", "remove-widget-text": "确认后,控件和所有相关数据将变得不可恢复。", @@ -3933,18 +4780,27 @@ "rpc": "控件部件", "alarm": "告警部件", "static": "静态部件", + "timeseries-short": "时序", + "latest-short": "最新", + "rpc-short": "控制", + "alarm-short": "告警", + "static-short": "静态", "select-widget-type": "选择窗口部件类型", "missing-widget-title-error": "部件标题必须指定!", "widget-saved": "部件已保存", "unable-to-save-widget-error": "无法保存部件!控件有错误!", "save": "保存部件", "saveAs": "部件另存为", - "save-widget-type-as": "部件类型另存为", - "save-widget-type-as-text": "请输入新的部件标题或选择目标部件包", + "move": "移动部件", + "save-widget-as": "另存部件为", + "save-widget-as-text": "请输入部件标题", "toggle-fullscreen": "切换全屏", "run": "运行部件", + "widget-title": "部件标题", "title": "部件标题", "title-required": "部件标题必填。", + "title-max-length": "标题应少于256个字符", + "system": "系统", "type": "部件类型", "resources": "资源", "resource-url": "JavaScript/CSS URL", @@ -3959,17 +4815,37 @@ "latest-datakey-settings-schema": "最新值数据键设置模式", "widget-settings": "部件设置", "description": "描述", + "tags": "标签", "image-preview": "图片预览", "settings-form-selector": "设置表单选择器", "data-key-settings-form-selector": "数据键设置表单选择器", "latest-data-key-settings-form-selector": "最新值数据键设置表单选择器", - "javascript": "Javascript", + "all": "全部", + "actual": "实际", + "deprecated": "已弃用", + "has-basic-mode": "具有基本模式", + "basic-mode-form-selector": "基本模式表单选择器", + "basic-mode": "基本", + "advanced-mode": "高级", + "javascript": "JavaScript", "js": "JS", - "add-widget-type": "添加部件类型", + "delete-widget-title": "确定要删除部件'{{widgetName}}'吗?", + "delete-widget-text": "确认后,部件及其所有相关数据将无法恢复。", + "delete-widgets-title": "确定要删除 { count, plural, =1 {1个部件} other {#个部件} }吗?", + "delete-widgets-text": "请谨慎操作,确认后,所有选定的部件将被删除,并且所有相关数据将无法恢复。", + "delete-widget": "删除部件", "widget-template-load-failed-error": "无法加载部件模板!", - "add": "添加部件", - "undo": "撤消部件更改", - "export": "导出部件", + "details": "详情", + "widget-details": "部件详情", + "add": "添加", + "add-existing-widget": "添加现有部件", + "add-new-widget": "添加新部件", + "search-widgets": "搜索部件", + "selected-widgets": "已选择 { count, plural, =1 {1 个部件} other {# 个部件} }", + "undo": "撤销", + "export": "导出", + "export-widgets": "导出部件", + "import": "导入部件", "no-data": "部件上没有要显示的数据", "data-overflow": "部件显示 {{count}} 条实体中的 {{total}} 条", "alarm-data-overflow": "部件显示了 {{allowedEntities}}(最大允许)实体中的告警,总共有 {{totalEntities}} 个实体", @@ -4038,6 +4914,7 @@ "widgets-bundle": { "current": "当前组", "widgets-bundles": "部件包", + "widgets-bundle-widgets": "包部件", "add": "添加部件包", "delete": "删除部件包", "title": "标题", @@ -4045,6 +4922,7 @@ "title-max-length": "标题长度应该少于256个字符。", "description": "描述", "image-preview": "图片预览", + "order": "其他", "add-widgets-bundle-text": "添加部件包", "no-widgets-bundles-text": "未找到部件包", "empty": "部件包是空的", @@ -4060,8 +4938,9 @@ "system": "系统", "import": "导入部件包", "export": "导出部件包", + "export-widgets-bundle-widgets-prompt": "在导出的数据中包含包部件(否则只会导出引用的部件完全限定名)", "export-failed-error": "无法导出部件包: {{error}}", - "create-new-widgets-bundle": "创建新的部件包", + "create-new-widgets-bundle": "创建部件包", "widgets-bundle-file": "部件包文件", "invalid-widgets-bundle-file-error": "无法导入部件包:无效的部件包数据结构。", "search": "查找部件包", @@ -4073,14 +4952,19 @@ "data": "数据", "settings": "设置", "advanced": "高级", + "appearance": "外观", + "widget-card": "部件卡片", + "mobile": "移动设备", "title": "标题", - "title-tooltip": "标题 Tooltip", - "general-settings": "基本设置", + "title-tooltip": "标题文字提示", + "general-settings": "常规设置", "display-title": "显示标题", - "drop-shadow": "阴影", + "card-title": "卡片标题", + "drop-shadow": "投影", "enable-fullscreen": "启用全屏", "background-color": "背景颜色", "text-color": "文字颜色", + "border-radius": "边框半径", "padding": "填充", "margin": "边缘", "widget-style": "部件样式", @@ -4092,13 +4976,21 @@ "mobile-hide": "在移动端隐藏部件", "desktop-hide": "在桌面端隐藏部件", "units": "特殊符号展示值", - "decimals": "浮点数后的位数", + "units-by-default": "默认单位", + "decimals": "小数位数", + "decimals-by-default": "默认小数位数", + "default-data-key-parameter-hint": "该参数适用于所有部件值,除非被数据键配置覆盖", + "units-short": "单位", + "decimals-short": "小数", + "decimals-suffix": "位小数", "timewindow": "时间窗口", - "use-dashboard-timewindow": "使用仪表板的时间窗口", + "use-dashboard-timewindow": "使用仪表板时间窗口", + "use-widget-timewindow": "使用部件时间窗口", "display-timewindow": "显示时间窗口", "legend": "图例", "display-legend": "显示图例", "datasources": "数据源", + "datasource": "数据源", "maximum-datasources": "最大允许 { count, plural, =1 {1 个数据源。} other {# 个数据源。} }", "timeseries-key-error": "需要至少指定一个 timeseries 数据键", "datasource-type": "类型", @@ -4127,26 +5019,84 @@ "delete-action-text": "确定要删除部件动作'{{actionName}}' 吗?", "title-icon": "标题图标", "display-icon": "显示标题图标", + "card-icon": "卡片图标", + "icon": "图标", "icon-color": "图标颜色", "icon-size": "图标大小", "advanced-settings": "高级设置", "data-settings": "数据设置", + "limits": "限制", "no-data-display-message": "\"没有数据可显示\" 替代信息", "data-page-size": "每个数据源的最大实体数", - "settings-component-not-found": "未找到设置的表单组件选择器 '{{selector}}'" + "settings-component-not-found": "未找到设置的表单组件选择器 '{{selector}}'", + "preview": "预览", + "set": "设置", + "set-message": "设置消息", + "advanced-title-style": "高级标题样式", + "card-style": "卡片样式", + "text": "文本", + "background": "背景", + "advanced-widget-style": "高级部件样式", + "card-buttons": "卡片按钮", + "show-card-buttons": "显示卡片按钮", + "card-border-radius": "卡片边框半径", + "card-appearance": "卡片外观", + "color": "颜色", + "tooltip": "文字提示", + "units-required": "单位是必须的。" }, "widget-type": { "import": "导入部件类型", "export": "导出部件类型", "export-failed-error": "无法导出部件类型: {{error}}", - "create-new-widget-type": "创建新的部件类型", - "widget-type-file": "部件类型文件", - "invalid-widget-type-file-error": "无法导入部件类型:无效的部件类型数据结构。" + "widget-file": "部件文件", + "invalid-widget-file-error": "无法导入部件:无效的部件数据结构。" }, "widgets": { + "background": { + "background": "背景", + "background-settings": "背景设置", + "background-type-image": "上传图像", + "background-type-image-url": "图像URL", + "background-type-color": "纯色背景", + "image-url": "图像URL", + "overlay": "覆盖", + "enable-overlay": "启用覆盖", + "blur": "模糊", + "preview": "预览" + }, + "battery-level": { + "layout": "布局", + "layout-vertical-solid": "垂直. 实心", + "layout-horizontal-solid": "水平. 实心", + "layout-vertical-divided": "垂直. 分割", + "layout-horizontal-divided": "水平. 分割", + "icon": "图标", + "value": "数值", + "auto-scale": "自动缩放", + "battery-level-color": "电池电量颜色", + "battery-shape-color": "电池形状颜色", + "battery-level-card-style": "电池电量卡片样式", + "sections-count": "分段数量" + }, + "signal-strength": { + "value": "数值", + "last-update": "上次更新", + "no-signal": "无信号", + "layout": "布局", + "layout-wifi": "Wi-Fi", + "layout-cellular-bar": "蜂窝网络条状", + "icon": "图标", + "date": "日期", + "active-bars-color": "活动信号条颜色", + "inactive-bars-color": "非活动信号条颜色", + "signal-strength-card-style": "信号强度卡片样式" + }, "chart": { "common-settings": "通用设置", "enable-stacking-mode": "启用堆叠模式", + "selection": "时间范围选择", + "enable-selection-mode": "启用选择模式", "line-shadow-size": "线条阴影大小", "display-smooth-lines": "显示平滑(曲线)线条", "default-bar-width": "非聚合数据的默认条形图宽度(毫秒)", @@ -4154,15 +5104,17 @@ "bar-alignment-left": "左", "bar-alignment-right": "右", "bar-alignment-center": "居中", + "default-font": "默认字体", "default-font-size": "默认字体大小", "default-font-color": "默认字体颜色", "thresholds-line-width": "所有阈值的默认线条宽度", - "tooltip-settings": "Tooltip 设置", - "show-tooltip": "显示 Tooltip", + "tooltip-settings": "文字提示设置", + "tooltip": "文字提示", + "show-tooltip": "显示文字提示", "hover-individual-points": "悬停在单个数据点上", "show-cumulative-values": "在堆叠模式下显示累积值", - "hide-zero-false-values": "Tooltip 隐藏 zero/false", - "tooltip-value-format-function": "Tooltip 值的格式化函数", + "hide-zero-false-values": "文字提示隐藏 zero/false", + "tooltip-value-format-function": "文字提示值的格式化函数", "grid-settings": "网格线设置", "show-vertical-lines": "显示垂直线条", "show-horizontal-lines": "显示水平线条", @@ -4235,6 +5187,7 @@ "add-threshold": "添加新阈值", "show-values-for-comparison": "显示历史比较值", "comparison-values-label": "历史值标签", + "comparison-line-color": "对比线颜色", "threshold-settings": "阈值设置", "use-as-threshold": "使用键值作为阈值", "threshold-line-width": "阈值线宽", @@ -4243,6 +5196,7 @@ "radius": "半径", "inner-radius": "内部半径", "tilt": "倾斜", + "common-pie-settings-range-error": "值应在0到1的范围内", "stroke-settings": "描边设置", "width-pixels": "宽度 (px)", "show-labels": "显示标签", @@ -4253,7 +5207,35 @@ "border-color": "边框颜色", "legend-settings": "图例设置", "display-legend": "显示图例", - "labels-font-color": "标签字体颜色" + "labels-font-color": "标签字体颜色", + "series": "系列", + "add-series": "添加系列", + "series-settings": "系列设置", + "remove-series": "删除系列", + "no-series": "未配置系列", + "no-series-error": "至少应指定一个系列", + "chart-appearance": "图表外观", + "vertical-grid-lines": "垂直网格线", + "horizontal-grid-lines": "水平网格线", + "chart-background": "图表背景", + "grid-lines-color": "网格线颜色", + "border": "边框", + "axis": "轴", + "vertical-axis": "垂直轴", + "ticks": "刻度", + "horizontal-axis": "水平轴" + }, + "color": { + "color-settings": "颜色设置", + "color-type-constant": "固定值", + "color-type-range": "数值范围", + "color-type-function": "函数", + "color": "颜色", + "value-range": "数值范围", + "from": "从", + "to": "至", + "color-function": "颜色函数", + "copy-color-settings-from": "从其他处复制颜色设置" }, "dashboard-state": { "dashboard-state-settings": "仪表板状态设置", @@ -4341,6 +5323,19 @@ "Ok": "Ok" } }, + "doughnut": { + "total": "总计", + "layout": "布局", + "layout-default": "默认", + "layout-with-total": "带总计", + "auto-scale": "自动缩放", + "clockwise-layout": "顺时针布局", + "sort-series": "按标签对系列排序", + "central-total-value": "中央总计值", + "tooltip-value-type-absolute": "绝对值", + "tooltip-value-type-percentage": "百分比", + "doughnut-card-style": "圆环样式" + }, "entities-hierarchy": { "hierarchy-data-settings": "层次数据设置", "relations-query-function": "关系查询函数", @@ -4369,7 +5364,12 @@ "read-only": "只读", "events-title": "事件标题", "events-filter": "事件过滤", - "event-key-contains": "包含事件key..." + "event-key-contains": "事件键包含...", + "show-connector": "显示连接器", + "connector-state-param-key": "连接器状态参数键", + "status": "状态", + "message": "消息", + "created-time": "创建时间" }, "gauge": { "default-color": "默认颜色", @@ -4377,10 +5377,14 @@ "ticks-settings": "刻度设置", "min-value": "最小值", "max-value": "最大值", + "min-value-short": "最小值", + "max-value-short": "最大值", "start-ticks-angle": "刻度起始角度", "ticks-angle": "刻度角度", + "major-ticks": "主刻度", "major-ticks-count": "主刻度数量", "major-ticks-color": "主刻度颜色", + "minor-ticks": "次刻度", "minor-ticks-count": "次刻度数量", "minor-ticks-color": "次刻度颜色", "tick-numbers-font": "刻度数字字体", @@ -4392,8 +5396,15 @@ "units-font": "单位文字字体", "value-box-settings": "数值框设置", "show-value-box": "显示数值框", - "value-int": "整数部分的位数", - "value-font": "数值文字字体", + "value-box": "数值框", + "value-int": "整数部位数", + "value-text": "数值文本", + "value-text-shadow": "数值文本阴影", + "value-font": "数值字体", + "rect-stroke-color-start": "矩形边框颜色 - 渐变起始", + "rect-stroke-color-end": "矩形边框颜色 - 渐变结束", + "background-color": "背景颜色", + "shadow-color": "阴影颜色", "value-box-rect-stroke-color": "数值框矩形描边颜色", "value-box-rect-stroke-color-end": "数值框矩形描边颜色 - 渐变结束", "value-box-background-color": "数值框背景颜色", @@ -4404,6 +5415,7 @@ "needle-settings": "指针设置", "needle-circle-size": "指针圆圈尺寸", "needle-color": "指针颜色", + "needle-color-start": "指针颜色 - 渐变起始", "needle-color-end": "指针颜色 - 渐变结束", "needle-color-shadow-up": "指针上半部分阴影颜色", "needle-color-shadow-down": "指针下半部分阴影颜色", @@ -4417,6 +5429,7 @@ "add-highlight": "添加高亮", "animation-settings": "动画设置", "enable-animation": "启用动画", + "animation-duration-rule": "动画持续时间和规则", "animation-duration": "动画持续时间", "animation-rule": "动画规则", "animation-linear": "线性", @@ -4431,17 +5444,21 @@ "animation-debounce": "弹跳反向", "animation-delastic": "弹性反向", "linear-gauge-settings": "线性量规设置", + "bar-stroke": "柱状图描边", "bar-stroke-width": "条形图描边宽度", "bar-stroke-color": "条形图描边颜色", "bar-background-color": "量规条形图背景颜色", "bar-background-color-end": "条形图背景颜色 - 渐变结束", "progress-bar-color": "进度条颜色", + "progress-bar": "进度条", + "progress-bar-color-start": "进度条颜色 - 渐变起始", "progress-bar-color-end": "进度条颜色 - 渐变结束", "major-ticks-names": "主刻度名称", "show-stroke-ticks": "显示刻度描边", "major-ticks-font": "主刻度字体", "border-color": "边框颜色", "border-width": "边框宽度", + "needle-circle": "指针圆圈", "needle-circle-color": "指针圆圈颜色", "animation-target": "动画目标", "animation-target-needle": "指针", @@ -4485,7 +5502,18 @@ "tick-color": "刻度颜色", "tick-values": "刻度值", "no-tick-values": "未配置刻度值", - "add-tick-value": "添加刻度值" + "add-tick-value": "添加刻度值", + "gauge-appearance": "仪表盘外观", + "units-title": "单位标题", + "value": "数值", + "ticks": "刻度", + "arrow-and-scale-color": "箭头和刻度默认颜色", + "scale-settings": "刻度设置", + "scale": "刻度尺寸", + "scale-color": "刻度颜色", + "compass-appearance": "指南针外观", + "labels": "标签", + "label-style": "标签样式" }, "gpio": { "pin": "引脚", @@ -4602,6 +5630,9 @@ "input-fields-alignment": "输入字段对齐方式", "input-fields-alignment-column": "列(默认)", "input-fields-alignment-row": "行", + "layout": "布局", + "row-gap": "行之间的间距(像素)", + "column-gap": "列之间的间距(像素)", "latitude-field-required": "纬度字段必填", "longitude-field-required": "经度字段必填", "attribute-settings": "属性设置", @@ -4650,12 +5681,19 @@ "datakey-value-type-date": "日期", "datakey-value-type-time": "时间", "datakey-value-type-select": "选择", + "datakey-value-type-color": "颜色", "value-is-required": "值必填", "ability-to-edit-attribute": "编辑属性的能力", "ability-to-edit-attribute-editable": "可编辑(默认)", "ability-to-edit-attribute-disabled": "禁用", "ability-to-edit-attribute-readonly": "只读", "disable-on-datakey-name": "在另一个数据键的值为假时禁用(指定数据键名称)", + "field-appearance": "字段外观", + "appearance-fill": "填充", + "appearance-outline": "轮廓", + "subscript-sizing": "下标大小", + "subscript-sizing-fixed": "固定", + "subscript-sizing-dynamic": "动态", "slide-toggle-settings": "滑动开关设置", "slide-toggle-label-position": "滑动开关标签位置", "slide-toggle-label-position-after": "之后", @@ -4771,6 +5809,14 @@ "no-columns-found": "找不到列", "no-columns-matching": "未找到 '{{column}}'。" }, + "range-chart": { + "chart": "图表", + "data-zoom": "数据缩放", + "range-colors": "范围颜色", + "out-of-range-color": "超出范围颜色", + "fill-area": "填充区域", + "range-chart-card-style": "范围图表卡片样式" + }, "rpc": { "value-settings": "值设置", "initial-value": "初始值", @@ -4930,17 +5976,17 @@ "use-label-function": "使用标签函数", "label-pattern": "标签 (模式示例:'${entityName}', '${entityName}: (Text ${keyName} units.)' )", "label-function": "标签函数", - "tooltip": "Tooltip", - "show-tooltip": "显示 Tooltip", - "show-tooltip-action": "显示 Tooltip 的操作", - "show-tooltip-action-click": "点击时显示 Tooltip(默认)", - "show-tooltip-action-hover": "悬停时显示 Tooltip", - "auto-close-tooltips": "自动关闭 Tooltip", - "use-tooltip-function": "使用 Tooltip 函数", - "tooltip-pattern": "Tooltip (例如 'Text ${keyName} units.' or Link text')", - "tooltip-function": "Tooltip 函数", - "tooltip-offset-x": "Tooltip X偏移相对于标记锚点乘以标记宽度", - "tooltip-offset-y": "Tooltip Y偏移相对于标记锚点乘以标记高度", + "tooltip": "文字提示", + "show-tooltip": "显示文字提示", + "show-tooltip-action": "显示文字提示的操作", + "show-tooltip-action-click": "点击时显示文字提示(默认)", + "show-tooltip-action-hover": "悬停时显示文字提示", + "auto-close-tooltips": "自动关闭文字提示", + "use-tooltip-function": "使用文字提示函数", + "tooltip-pattern": "文字提示 (例如 'Text ${keyName} units.' or Link text')", + "tooltip-function": "文字提示函数", + "tooltip-offset-x": "文字提示X偏移相对于标记锚点乘以标记宽度", + "tooltip-offset-y": "文字提示Y偏移相对于标记锚点乘以标记高度", "color": "颜色", "use-color-function": "使用颜色函数", "color-function": "颜色函数", @@ -4959,12 +6005,12 @@ "use-polygon-label-function": "使用多边形标签函数", "polygon-label-pattern": "多边形标签 (模板示例:'${entityName}', '${entityName}: (Text ${keyName} units.)' )", "polygon-label-function": "多边形标签函数", - "polygon-tooltip": "多边形 Tooltip", - "show-polygon-tooltip": "显示多边形 Tooltip", - "auto-close-polygon-tooltips": "自动关闭多边形 Tooltip", - "use-polygon-tooltip-function": "使用多边形 Tooltip 函数", - "polygon-tooltip-pattern": "Tooltip (例如 'Text ${keyName} units.' or Link text')", - "polygon-tooltip-function": "多边形 Tooltip 函数", + "polygon-tooltip": "多边形文字提示", + "show-polygon-tooltip": "显示多边形文字提示", + "auto-close-polygon-tooltips": "自动关闭多边形文字提示", + "use-polygon-tooltip-function": "使用多边形文字提示函数", + "polygon-tooltip-pattern": "文字提示(例如 'Text ${keyName} units.' or Link text')", + "polygon-tooltip-function": "多边形文字提示函数", "polygon-color": "多边形颜色", "polygon-opacity": "多边形不透明度", "use-polygon-color-function": "使用多边形颜色函数", @@ -4984,12 +6030,12 @@ "use-circle-label-function": "使用圆标签函数", "circle-label-pattern": "圆标签 (模板示例:'${entityName}', '${entityName}: (Text ${keyName} units.)' )", "circle-label-function": "圆标签函数", - "circle-tooltip": "圆 Tooltip", - "show-circle-tooltip": "显示圆 Tooltip", - "auto-close-circle-tooltips": "自动关闭圆 Tooltip", - "use-circle-tooltip-function": "使用圆 Tooltip 函数", - "circle-tooltip-pattern": "Tooltip (例如 'Text ${keyName} units.' or Link text')", - "circle-tooltip-function": "圆 Tooltip 函数", + "circle-tooltip": "圆文字提示", + "show-circle-tooltip": "显示圆文字提示", + "auto-close-circle-tooltips": "自动关闭圆文字提示", + "use-circle-tooltip-function": "使用圆文字提示函数", + "circle-tooltip-pattern": "文字提示 (例如 'Text ${keyName} units.' or Link text')", + "circle-tooltip-function": "圆文字提示函数", "circle-fill-color": "圆填充颜色", "circle-fill-color-opacity": "圆填充颜色不透明度", "use-circle-fill-color-function": "使用圆填充颜色函数", @@ -5018,10 +6064,10 @@ "route-map-settings": "路线地图设置", "trip-animation-settings": "行程动画设置", "normalization-step": "数据标准化步骤(毫秒)", - "tooltip-background-color": "Tooltip 背景颜色", - "tooltip-font-color": "Tooltip 字体颜色", - "tooltip-opacity": "Tooltip 不透明度(0-1)", - "auto-close-tooltip": "自动关闭 Tooltip", + "tooltip-background-color": "文字提示背景颜色", + "tooltip-font-color": "文字提示字体颜色", + "tooltip-opacity": "文字提示不透明度(0-1)", + "auto-close-tooltip": "自动关闭文字提示", "rotation-angle": "设置标记的附加旋转角度(度)", "path-settings": "路径设置", "path-color": "路径颜色", @@ -5046,7 +6092,7 @@ "point-color-function": "点颜色函数", "use-point-as-anchor": "使用点作为锚点", "point-as-anchor-function": "点作为锚点函数", - "independent-point-tooltip": "独立点 Tooltip", + "independent-point-tooltip": "独立点文字提示", "clustering-markers": "标记聚类", "use-icon-create-function": "使用标记颜色函数", "marker-color-function": "标记颜色函数" @@ -5059,10 +6105,137 @@ "markdown-css": "Markdown/HTML CSS" }, "simple-card": { + "label": "标签", "label-position": "标签位置", "label-position-left": "左侧", "label-position-top": "顶部" }, + "value-card": { + "layout": "布局", + "layout-square": "正方形", + "layout-vertical": "垂直", + "layout-centered": "居中", + "layout-simplified": "简化", + "layout-horizontal": "水平", + "layout-horizontal-reversed": "水平翻转", + "label": "标签", + "icon": "图标", + "value": "数值", + "date": "日期", + "value-card-style": "数值卡片样式", + "auto-scale": "自动缩放" + }, + "liquid-level-card": { + "layout-simple": "简单", + "layout-percentage": "百分比", + "layout-absolute": "绝对值", + "layout": "布局", + "background-overlay": "值背景叠加", + "total-volume": "总体积", + "tank": "储罐", + "shape": "形状", + "datasource-units": "数据源单位", + "widget-units": "小部件单位", + "decimals": "小数位数", + "liquid": "液体", + "liquid-color": "液体颜色", + "value": "值", + "value-font": "值字体", + "level": "水位", + "last-update": "最后更新", + "shape-by-attribute": "按属性名称设置储罐形状", + "tooltip-background": "背景颜色", + "background-blur": "背景模糊", + "tank-color": "储罐颜色", + "static": "静态", + "see-examples": "查看示例", + "attribute": "属性", + "shape-type": "类型", + "v-oval": "垂直椭圆", + "v-cylinder": "垂直圆柱体", + "v-capsule": "垂直胶囊", + "rectangle": "矩形", + "h-oval": "水平椭圆", + "h-ellipse": "水平椭圆", + "h-dish-ends": "水平盘形", + "h-cylinder": "水平圆柱体", + "h-capsule": "水平胶囊", + "h-elliptical_2_1": "水平2:1椭圆", + "icon": "卡片图标", + "title": "卡片标题", + "units": "单位", + "color-and-font": "颜色和字体", + "shape-attribute-name": "属性名称", + "total-volume-required": "需要总体积。", + "attribute-name-required": "需要属性名称。", + "attribute-key-not-set": "未设置属性 '{{attributeName}}' 键", + "attribute-key-invalid": "属性 '{{attributeName}}' 键无效" + }, + "aggregated-value-card": { + "subtitle": "副标题", + "chart": "图表", + "values": "数值", + "value-appearance": "数值外观", + "position": "位置", + "position-center": "中心", + "position-right-top": "右上", + "position-right-bottom": "右下", + "position-left-top": "左上", + "position-left-bottom": "左下", + "font": "字体", + "color": "颜色", + "arrow": "箭头", + "display-up-down-arrow": "显示向上/向下箭头", + "add-value": "添加数值", + "remove-value": "移除数值", + "no-values": "未配置数值", + "aggregation": "聚合", + "aggregated-value-card-style": "聚合数值卡片样式", + "auto-scale": "自动缩放" + }, + "value-chart-card": { + "layout": "布局", + "layout-left": "左侧", + "layout-right": "右侧", + "auto-scale": "自动缩放", + "icon": "图标", + "value": "数值", + "chart": "图表", + "value-chart-card-style": "数值图表卡片样式" + }, + "progress-bar": { + "layout": "布局", + "layout-default": "默认", + "layout-simplified": "简化", + "auto-scale": "自动缩放", + "icon": "图标", + "value": "数值", + "range": "范围", + "min": "最小", + "max": "最大", + "range-ticks": "范围刻度", + "bar": "进度条", + "bar-color": "进度条颜色", + "bar-background": "进度条背景", + "progress-bar-card-style": "进度条卡片样式" + }, + "alarm-count": { + "alarm-count-card-style": "告警计数卡片样式" + }, + "entity-count": { + "entity-count-card-style": "实体计数卡片样式" + }, + "count": { + "layout": "布局", + "layout-column": "列", + "layout-row": "行", + "label": "标签", + "icon": "图标", + "icon-background": "图标背景", + "value": "数值", + "chevron": "箭头", + "auto-scale": "自动缩放" + }, "table": { "common-table-settings": "常规表格设置", "enable-search": "启用搜索", @@ -5072,7 +6245,6 @@ "show-empty-space-hidden-action": "显示空白区域而不是隐藏单元格按钮操作", "dont-reserve-space-hidden-action": "不为隐藏的操作按钮预留空间", "display-timestamp": "显示时间戳列", - "display-milliseconds": "显示时间戳毫秒", "display-pagination": "显示分页", "default-page-size": "默认页面大小", "use-entity-label-tab-name": "在选项卡名称中使用实体标签", @@ -5113,7 +6285,43 @@ "allow-alarms-ack": "允许确认告警", "allow-alarms-clear": "允许清除告警", "display-alarm-activity": "显示告警活动", - "allow-alarms-assign": "允许分配告警" + "allow-alarms-assign": "允许分配告警", + "columns": "列", + "column-settings": "列设置", + "remove-column": "移除列", + "add-column": "添加列", + "no-columns": "没有配置列", + "columns-to-display": "显示的列", + "table-header": "表头", + "header-buttons": "表头按钮", + "table-buttons": "表格按钮", + "pagination": "分页", + "rows": "行", + "timeseries-column-error": "至少应指定一个时间序列列", + "alarm-column-error": "至少应指定一个告警列", + "table-tabs": "表格标签", + "show-cell-actions-menu-mobile": "在移动模式下显示单元格操作下拉菜单" + }, + "wind-speed-direction": { + "layout": "布局", + "layout-default": "默认", + "layout-advanced": "高级", + "layout-simplified": "简化", + "values": "数值", + "wind-direction": "风向", + "center-value": "中心数值", + "icon": "图标", + "arrow": "箭头", + "ticks": "刻度", + "labels-type": "标签类型", + "directional-names": "方向名称", + "degrees": "度数", + "major-ticks": "主要刻度", + "minor-ticks": "次要刻度", + "wind-speed-direction-card-style": "风速和风向卡片样式", + "ticks-color": "刻度颜色", + "ticks-labels-type": "刻度标签类型", + "arrow-color": "箭头颜色" }, "value-source": { "value-source": "值来源", @@ -5124,6 +6332,7 @@ "source-entity-attribute": "源实体属性" }, "widget-font": { + "font-settings": "字体设置", "font-family": "字体", "size": "大小", "relative-font-size": "相对字体大小(百分比)", @@ -5137,7 +6346,10 @@ "font-weight-bolder": "更粗", "font-weight-lighter": "更轻", "color": "颜色", - "shadow-color": "阴影颜色" + "shadow-color": "阴影颜色", + "preview": "预览", + "line-height": "行高", + "auto": "自动" }, "home": { "no-data-available": "暂无数据" @@ -5225,7 +6437,7 @@ "alarms": "告警", "dashboards": "仪表盘", "entities-and-relations": "实体和关系", - "profiles": "配置文件", + "profiles": "配置", "advanced-features": "高级功能", "notification-center": "通知中心", "api-usage": "API 使用情况", @@ -5240,21 +6452,111 @@ }, "devices": { "view-docs": "查看文档", - "inactive": "活动", - "active": "未活动", + "inactive": "非活动", + "active": "活动", "total": "总数" }, "alarms": { "critical": "严重", "assigned-to-me": "分配给我", "total": "总数" + }, + "getting-started": { + "get-started": "开始", + "finish": "完成", + "done-welcome-title": "欢迎加入我们", + "done-welcome-text": "你做得很好!", + "sys-admin": { + "step1": { + "title": "创建租户和租户管理员", + "content": "

租户是拥有或生产设备和资产的个人或组织,租户可以有多个租户管理员用户、客户、设备和资产。

租户管理员可以在租户账户中创建和管理设备、资产、客户和仪表板。

请按照文档进行操作:

", + "how-to-create-tenant": "如何创建租户和租户管理员" + }, + "step2": { + "title": "配置功能:邮件服务", + "content": "

邮件服务配置对于用户激活、密码恢复和报警通知的发送非常重要。

请按照文档进行操作:

", + "how-to-configure-mail-server": "如何配置邮件服务器" + }, + "step3": { + "title": "配置功能:短信提供商", + "content": "

配置短信提供商,通过短信向客户发送报警通知。

请按照文档进行操作:

", + "how-to-configure-sms-provider": "如何配置短信提供商" + }, + "step4": { + "title": "配置功能:双因素认证", + "content": "

通过双因素认证提高平台账户的安全性。

请按照文档进行操作:

", + "how-to-configure-2fa": "如何配置双因素认证" + }, + "step5": { + "title": "配置功能:OAuth 2", + "content": "

通过OAuth 2.0的单点登录功能,简化租户和客户用户的登录过程。

请按照文档进行操作:

", + "how-to-configure-oauth2": "如何配置OAuth 2" + }, + "step6": { + "title": "配置功能:Slack", + "content": "

根据设置的通知规则,通过Slack向租户和客户用户发送通知。

请按照文档进行操作:

", + "how-to-configure-notifications": "如何配置Slack" + } + }, + "tenant-admin": { + "step1": { + "title": "创建设备", + "content": "

让我们通过用户界面将您的第一个设备添加到平台。请按照文档进行操作:

", + "how-to-create-device": "如何创建设备" + }, + "step2": { + "title": "连接设备", + "content-before": "

要连接设备,您需要获取设备凭据。我们建议在本指南中使用默认生成的凭据,即访问令牌。

  • 转到设备表
  • 点击设备行以打开设备详情
  • 点击“复制访问令牌”按钮

使用简单的命令通过HTTP发布数据。不要忘记将$ACCESS_TOKEN替换为您的设备访问令牌:

", + "ubuntu": { + "install-curl": "Ubuntu上安装cURL:" + }, + "macos": { + "install-curl": "MacOS上安装cURL:" + }, + "windows": { + "install-curl": "从Windows 10 b17063开始,cURL已默认可用。" + }, + "replace-access-token": "将$ACCESS_TOKEN替换为您的设备令牌:", + "content-after": "

您还可以使用其他协议,如MQTT、CoAP等。

请按照文档进行操作:

", + "how-to-connect-device": "如何连接设备" + }, + "step3": { + "title": "创建仪表板", + "content": "

创建一个仪表板,用于可视化来自实体(如资产、设备等)的数据。

请按照文档进行操作:

", + "how-to-create-dashboard": "如何创建仪表板" + }, + "step4": { + "title": "配置报警规则", + "alarm-rules": "报警规则", + "content": "

当温度达到25°C时,让我们触发一个报警。请按照文档进行操作:

", + "how-to-configure-alarm-rules": "如何配置报警规则" + }, + "step5": { + "title": "创建报警", + "content-before": "

要触发报警,请发送一个新的温度值,该值为26℃或更高。

", + "replace-access-token": "将$ACCESS_TOKEN替换为您的设备令牌:", + "content-after": "

请按照文档进行操作:

", + "how-to-create-alarm": "如何创建报警" + }, + "step6": { + "title": "创建客户并分配仪表板", + "content": "

通过创建终端用户仪表板,客户用户只能查看自己的设备,另一个客户的数据将被隐藏。

请按照文档进行操作:

", + "how-to-create-customer-and-assign-dashboard": "如何创建客户并分配仪表板" + } + } } }, + "color": { + "color": "颜色" + }, "icon": { "icon": "图标", + "icons": "图标", "select-icon": "选择图标", "material-icons": "素材图标", - "show-all": "显示所有图标" + "show-all": "显示所有图标", + "search-icon": "搜索图标", + "no-icons-found": "没有找到符合'{{iconSearch}}'的图标" }, "phone-input": { "phone-input-label": "手机号码", @@ -5267,14 +6569,15 @@ "widget-action": { "action-cell-button": "动作单元格按钮", "row-click": "点击行时", - "polygon-click": "单击多边形", + "polygon-click": "点击多边形时", "marker-click": "点击标记时", - "circle-click": "在圆圈上单击", - "tooltip-tag-action": "Tooltip tag action", + "circle-click": "点击圆圈时", + "tooltip-tag-action": "文字提示标签操作", "node-selected": "选择节点时", "element-click": "点击HTML元素时", "pie-slice-click": "点击切片时", - "row-double-click": "双击行时" + "row-double-click": "双击行时", + "card-click": "点击卡片时" } }, "paginator": { diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json index 85c9181ae8..5b415558a1 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json @@ -4476,7 +4476,6 @@ "show-empty-space-hidden-action": "顯示空格而不是隱藏儲存格的按鈕動作", "dont-reserve-space-hidden-action": "不要為隱藏的動作按鈕保留空格", "display-timestamp": "顯示時間戳列", - "display-milliseconds": "顯示毫秒時間戳", "display-pagination": "顯示分頁", "default-page-size": "預設頁面尺寸", "use-entity-label-tab-name": "在分頁標籤名稱中使用實體標籤", diff --git a/ui-ngx/src/assets/metadata/units.json b/ui-ngx/src/assets/metadata/units.json index 3237d6620b..53744cecb7 100644 --- a/ui-ngx/src/assets/metadata/units.json +++ b/ui-ngx/src/assets/metadata/units.json @@ -1316,6 +1316,11 @@ "symbol": "mg/mL", "tags": ["concentration","mass per volume","mg/mL"] }, +{ + "name": "unit.milligram-per-cubic-meter", + "symbol": "mg/m³", + "tags": ["concentration","mass per volume","mg/m³"] +}, { "name": "unit.pound-per-cubic-foot", "symbol": "lb/ft³", diff --git a/ui-ngx/src/environments/environment.prod.ts b/ui-ngx/src/environments/environment.prod.ts index f724eb6687..b76f84da34 100644 --- a/ui-ngx/src/environments/environment.prod.ts +++ b/ui-ngx/src/environments/environment.prod.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/environments/environment.ts b/ui-ngx/src/environments/environment.ts index 82d426e95c..adee999496 100644 --- a/ui-ngx/src/environments/environment.ts +++ b/ui-ngx/src/environments/environment.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2023 The Thingsboard Authors +/// Copyright © 2016-2024 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 3b29fe986c..ee6d4167cd 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - @import './scss/constants'; @mixin form-row-column() { @@ -523,6 +522,7 @@ right: 0; background: $tb-primary-color; opacity: 0.04; + pointer-events: none; } } diff --git a/ui-ngx/src/index.html b/ui-ngx/src/index.html index 5b0ca17cd6..7b0a5987c3 100644 --- a/ui-ngx/src/index.html +++ b/ui-ngx/src/index.html @@ -1,6 +1,6 @@ - 4.1.0 + 4.2.0 2.7.2 1.5.2 5.9.3 diff --git a/transport/lwm2m/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java b/transport/lwm2m/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java new file mode 100644 index 0000000000..89a2f07179 --- /dev/null +++ b/transport/lwm2m/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java @@ -0,0 +1,296 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2016 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + * Michał Wadowski (Orange) - Add Observe-Composite feature. + */ +package org.eclipse.leshan.server.observation; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.observation.CompositeObservation; +import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.observation.SingleObservation; +import org.eclipse.leshan.core.peer.LwM2mPeer; +import org.eclipse.leshan.core.response.ObserveCompositeResponse; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpoint; +import org.eclipse.leshan.server.endpoint.LwM2mServerEndpointsProvider; +import org.eclipse.leshan.server.profile.ClientProfile; +import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.registration.RegistrationStore; +import org.eclipse.leshan.server.registration.RegistrationUpdate; +import org.eclipse.leshan.server.registration.UpdatedRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Implementation of the {@link ObservationService} accessing the persisted observation via the provided + * {@link RegistrationStore}. + * + * When a new observation is added or changed or canceled, the registered listeners are notified. + */ +@Slf4j +public class ObservationServiceImpl implements ObservationService, LwM2mNotificationReceiver { + + private final Logger LOG = LoggerFactory.getLogger(ObservationServiceImpl.class); + + private final RegistrationStore registrationStore; + private final LwM2mServerEndpointsProvider endpointProvider; + private final boolean updateRegistrationOnNotification; + + private final List listeners = new CopyOnWriteArrayList<>();; + + /** + * Creates an instance of {@link ObservationServiceImpl} + */ + public ObservationServiceImpl(RegistrationStore store, LwM2mServerEndpointsProvider endpointProvider) { + this(store, endpointProvider, false); + } + + /** + * Creates an instance of {@link ObservationServiceImpl} + * + * @param updateRegistrationOnNotification will activate registration update on observe notification. + * + * @since 1.1 + */ + public ObservationServiceImpl(RegistrationStore store, LwM2mServerEndpointsProvider endpointProvider, + boolean updateRegistrationOnNotification) { + this.registrationStore = store; + this.updateRegistrationOnNotification = updateRegistrationOnNotification; + this.endpointProvider = endpointProvider; + } + + @Override + public int cancelObservations(Registration registration) { + // check registration id + String registrationId = registration.getId(); + if (registrationId == null) + return 0; + + Collection observations = registrationStore.removeObservations(registrationId); + if (observations == null) + return 0; + + for (Observation observation : observations) { + cancel(observation); + } + + return observations.size(); + } + + @Override + public int cancelObservations(Registration registration, String nodePath) { + if (registration == null || registration.getId() == null || nodePath == null || nodePath.isEmpty()) + return 0; + + Set observations = getObservationsForCancel(registration.getId(), nodePath); + for (Observation observation : observations) { + cancelObservation(observation); + } + return observations.size(); + } + + @Override + public int cancelCompositeObservations(Registration registration, String[] nodePaths) { + if (registration == null || registration.getId() == null || nodePaths == null || nodePaths.length == 0) + return 0; + + Set observations = getCompositeObservationsForCancel(registration.getId(), nodePaths); + for (Observation observation : observations) { + cancelObservation(observation); + } + return observations.size(); + } + + @Override + public void cancelObservation(Observation observation) { + if (observation == null) + return; + + registrationStore.removeObservation(observation.getRegistrationId(), observation.getId()); + cancel(observation); + } + + private void cancel(Observation observation) { + List endpoints = endpointProvider.getEndpoints(); + for (LwM2mServerEndpoint lwM2mEndpoint : endpoints) { + lwM2mEndpoint.cancelObservation(observation); + } + + for (ObservationListener listener : listeners) { + listener.cancelled(observation); + } + } + + @Override + public Set getObservations(Registration registration) { + return getObservations(registration.getId()); + } + + private Set getObservations(String registrationId) { + if (registrationId == null) + return Collections.emptySet(); + + return new HashSet<>(registrationStore.getObservations(registrationId)); + } + + private Set getCompositeObservationsForCancel(String registrationId, String[] nodePaths) { + if (registrationId == null || nodePaths == null) + return Collections.emptySet(); + + // array of String to array of LWM2M path + List lwPaths = new ArrayList<>(nodePaths.length); + for (int i = 0; i < nodePaths.length; i++) { + lwPaths.add(new LwM2mPath(nodePaths[i])); + } + + // search composite-observation + Set result = new HashSet<>(); + for (Observation obs : getObservations(registrationId)) { + if (obs instanceof CompositeObservation) { + if (lwPaths.equals(((CompositeObservation) obs).getPaths())) { + result.add(obs); + } + } + } + return result; + } + + private Set getObservationsForCancel(String registrationId, String nodePath) { + if (registrationId == null || nodePath == null) + return Collections.emptySet(); + + Set result = new HashSet<>(); + LwM2mPath lwPath = new LwM2mPath(nodePath); + for (Observation obs : getObservations(registrationId)) { + if (obs instanceof SingleObservation) { + LwM2mPath lwPathObs = ((SingleObservation) obs).getPath(); + if (lwPath.equals(lwPathObs) || lwPathObs.startWith(lwPath)) { // nodePath = "3", lwPathObs = "3/0/9": cancel for tne all lwPathObs + result.add(obs); + } else if (!lwPath.equals(lwPathObs) && lwPath.startWith(lwPathObs)) { // nodePath = "3/0/9", lwPathObs = "3": error... + String errorMsg = String.format( + "Unexpected error : There is registration with id [%s] existing observation [%s] includes input observation [%s]!", + registrationId, lwPathObs, lwPath); + throw new IllegalStateException(errorMsg); + } + } + } + + return result; + } + + @Override + public void addListener(ObservationListener listener) { + listeners.add(listener); + } + + @Override + public void removeListener(ObservationListener listener) { + listeners.remove(listener); + } + + private Registration updateRegistrationOnRegistration(Observation observation, LwM2mPeer sender, + ClientProfile profile) { + if (updateRegistrationOnNotification) { + RegistrationUpdate regUpdate = new RegistrationUpdate(observation.getRegistrationId(), sender, null, null, + null, null, null, null, null, null, null, null); + UpdatedRegistration updatedRegistration = registrationStore.updateRegistration(regUpdate); + if (updatedRegistration == null || updatedRegistration.getUpdatedRegistration() == null) { + String errorMsg = String.format( + "Unexpected error: There is no registration with id %s for this observation %s", + observation.getRegistrationId(), observation); + LOG.error(errorMsg); + throw new IllegalStateException(errorMsg); + } + return updatedRegistration.getUpdatedRegistration(); + } + return profile.getRegistration(); + } + + // ********** NotificationListener interface **********// + @Override + public void onNotification(SingleObservation observation, LwM2mPeer sender, ClientProfile profile, + ObserveResponse response) { + try { + Registration updatedRegistration = updateRegistrationOnRegistration(observation, sender, profile); + for (ObservationListener listener : listeners) { + listener.onResponse(observation, updatedRegistration, response); + } + } catch (Exception e) { + for (ObservationListener listener : listeners) { + listener.onError(observation, profile.getRegistration(), e); + } + } + } + + @Override + public void onNotification(CompositeObservation observation, LwM2mPeer sender, ClientProfile profile, + ObserveCompositeResponse response) { + try { + Registration updatedRegistration = updateRegistrationOnRegistration(observation, sender, profile); + for (ObservationListener listener : listeners) { + listener.onResponse(observation, updatedRegistration, response); + } + } catch (Exception e) { + for (ObservationListener listener : listeners) { + listener.onError(observation, profile.getRegistration(), e); + } + } + } + + @Override + public void onError(Observation observation, LwM2mPeer sender, ClientProfile profile, Exception error) { + for (ObservationListener listener : listeners) { + listener.onError(observation, profile.getRegistration(), error); + } + } + + @Override + public void newObservation(Observation observation, Registration registration) { + for (ObservationListener listener : listeners) { + listener.newObservation(observation, registration); + } + } + + @Override + public void cancelled(Observation observation) { + for (ObservationListener listener : listeners) { + listener.cancelled(observation); + } + + } +} From cdc6daa7a906b1d2843a97e923750c6a87778a64 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 22 Jan 2024 10:57:05 +0200 Subject: [PATCH 122/209] lwm2m: fix buf license excludes --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index eac7903dd7..062a69746c 100755 --- a/pom.xml +++ b/pom.xml @@ -829,6 +829,7 @@ src/main/scripts/control/** src/main/scripts/windows/** src/main/resources/public/static/rulenode/** + src/main/java/org/eclipse/leshan/server/observation//** **/*.proto.js docker/haproxy/** docker/tb-node/** From e27ef320e748d4be15354dc8c1e65763cfe36204 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 22 Jan 2024 12:31:01 +0200 Subject: [PATCH 123/209] updated env variable name and descriptions --- application/src/main/resources/thingsboard.yml | 6 +++--- .../server/cache/TBRedisStandaloneConfiguration.java | 2 +- .../java/org/thingsboard/server/msa/ContainerTestSuite.java | 2 +- transport/coap/src/main/resources/tb-coap-transport.yml | 6 +++--- transport/http/src/main/resources/tb-http-transport.yml | 6 +++--- transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml | 6 +++--- transport/mqtt/src/main/resources/tb-mqtt-transport.yml | 6 +++--- transport/snmp/src/main/resources/tb-snmp-transport.yml | 6 +++--- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index e2f1a80d42..18565f463e 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -642,7 +642,7 @@ redis: # ssl config ssl: # Enable/disable secure connection - enabled: "${REDIS_SSL_ENABLED:false}" + enabled: "${TB_REDIS_SSL_ENABLED:false}" # Server SSL credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -651,9 +651,9 @@ redis: pem: # Path redis server (CA) certificate cert_file: "${TB_REDIS_SSL_PEM_CERT:}" - # Path to user certificate file (optional) + # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" - # Path to user private key file (optional) + # Path to user private key file. This is optional for the client and only needed if ‘ssl.pem.user_cert_file’ is configured. user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" # Keystore server credentials keystore: diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java index f0f250bbaa..d4235e3662 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java @@ -89,4 +89,4 @@ public class TBRedisStandaloneConfiguration extends TBRedisCacheConfiguration { } return jedisClientConfigurationBuilder.build(); } -} \ No newline at end of file +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java index 6df75fdac4..bbaeabe907 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java @@ -108,7 +108,7 @@ public class ContainerTestSuite { if (IS_REDIS_SSL) { addToFile(targetDir, "cache-redis.env", - Map.of("REDIS_SSL_ENABLED", "true", + Map.of("TB_REDIS_SSL_ENABLED", "true", "TB_REDIS_SSL_PEM_CERT", "/redis/certs/redisCA.crt")); } diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 87e3cf870d..43a84b3d5a 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -96,7 +96,7 @@ redis: password: "${REDIS_PASSWORD:}" ssl: # Enable/disable secure connection - enabled: "${REDIS_SSL_ENABLED:false}" + enabled: "${TB_REDIS_SSL_ENABLED:false}" # Server SSL credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -105,9 +105,9 @@ redis: pem: # Path redis server (CA) certificate cert_file: "${TB_REDIS_SSL_PEM_CERT:}" - # Path to user certificate file + # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" - # Path to user private key file + # Path to user private key file. This is optional for the client and only needed if ‘ssl.pem.user_cert_file’ is configured. user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" # Keystore server credentials keystore: diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 3f8af8bb91..e226319c20 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -129,7 +129,7 @@ redis: password: "${REDIS_PASSWORD:}" ssl: # Enable/disable secure connection - enabled: "${REDIS_SSL_ENABLED:false}" + enabled: "${TB_REDIS_SSL_ENABLED:false}" # Server SSL credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -138,9 +138,9 @@ redis: pem: # Path redis server (CA) certificate cert_file: "${TB_REDIS_SSL_PEM_CERT:}" - # Path to user certificate file + # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" - # Path to user private key file + # Path to user private key file. This is optional for the client and only needed if ‘ssl.pem.user_cert_file’ is configured. user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" # Keystore server credentials keystore: diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 345f2eab5e..93a553eea5 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -96,7 +96,7 @@ redis: password: "${REDIS_PASSWORD:}" ssl: # Enable/disable secure connection - enabled: "${REDIS_SSL_ENABLED:false}" + enabled: "${TB_REDIS_SSL_ENABLED:false}" # Server SSL credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -105,9 +105,9 @@ redis: pem: # Path redis server (CA) certificate cert_file: "${TB_REDIS_SSL_PEM_CERT:}" - # Path to user certificate file + # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" - # Path to user private key file + # Path to user private key file. This is optional for the client and only needed if ‘ssl.pem.user_cert_file’ is configured. user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" # Keystore server credentials keystore: diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index f04c06dc81..e2c65a4a63 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -97,7 +97,7 @@ redis: password: "${REDIS_PASSWORD:}" ssl: # Enable/disable secure connection - enabled: "${REDIS_SSL_ENABLED:false}" + enabled: "${TB_REDIS_SSL_ENABLED:false}" # Server SSL credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -106,9 +106,9 @@ redis: pem: # Path redis server (CA) certificate cert_file: "${TB_REDIS_SSL_PEM_CERT:}" - # Path to user certificate file + # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" - # Path to user private key file + # Path to user private key file. This is optional for the client and only needed if ‘ssl.pem.user_cert_file’ is configured. user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" # Keystore server credentials keystore: diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index a4e65a67c3..db20b596f0 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -96,7 +96,7 @@ redis: password: "${REDIS_PASSWORD:}" ssl: # Enable/disable secure connection - enabled: "${REDIS_SSL_ENABLED:false}" + enabled: "${TB_REDIS_SSL_ENABLED:false}" # Server SSL credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -105,9 +105,9 @@ redis: pem: # Path redis server (CA) certificate cert_file: "${TB_REDIS_SSL_PEM_CERT:}" - # Path to user certificate file + # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" - # Path to user private key file + # Path to user private key file. This is optional for the client and only needed if ‘ssl.pem.user_cert_file’ is configured. user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" # Keystore server credentials keystore: From dfe23510799e4b639a8c46a5a8536af48a0fbc03 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 24 Jan 2024 13:11:32 +0200 Subject: [PATCH 124/209] yml parameter description update --- application/src/main/resources/thingsboard.yml | 2 +- transport/coap/src/main/resources/tb-coap-transport.yml | 2 +- transport/http/src/main/resources/tb-http-transport.yml | 2 +- transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml | 2 +- transport/mqtt/src/main/resources/tb-mqtt-transport.yml | 2 +- transport/snmp/src/main/resources/tb-snmp-transport.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 18565f463e..38cfdd64a0 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -645,7 +645,7 @@ redis: enabled: "${TB_REDIS_SSL_ENABLED:false}" # Server SSL credentials credentials: - # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + # Server credentials type (pem - pem certificate file; keystore - java keystore) type: "${TB_REDIS_SSL_CREDENTIALS_TYPE:pem}" # PEM server credentials pem: diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 43a84b3d5a..78ca89851d 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -99,7 +99,7 @@ redis: enabled: "${TB_REDIS_SSL_ENABLED:false}" # Server SSL credentials credentials: - # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + # Server credentials type (pem - pem certificate file; keystore - java keystore) type: "${TB_REDIS_SSL_CREDENTIALS_TYPE:pem}" # PEM server credentials pem: diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index e226319c20..04a9c9417f 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -132,7 +132,7 @@ redis: enabled: "${TB_REDIS_SSL_ENABLED:false}" # Server SSL credentials credentials: - # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + # Server credentials type (pem - pem certificate file; keystore - java keystore) type: "${TB_REDIS_SSL_CREDENTIALS_TYPE:pem}" # PEM server credentials pem: diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 93a553eea5..c079f690ed 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -99,7 +99,7 @@ redis: enabled: "${TB_REDIS_SSL_ENABLED:false}" # Server SSL credentials credentials: - # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + # Server credentials type (pem - pem certificate file; keystore - java keystore) type: "${TB_REDIS_SSL_CREDENTIALS_TYPE:pem}" # PEM server credentials pem: diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index e2c65a4a63..430b5bfed5 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -100,7 +100,7 @@ redis: enabled: "${TB_REDIS_SSL_ENABLED:false}" # Server SSL credentials credentials: - # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + # Server credentials type (pem - pem certificate file; keystore - java keystore) type: "${TB_REDIS_SSL_CREDENTIALS_TYPE:pem}" # PEM server credentials pem: diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index db20b596f0..68b69bb03a 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -99,7 +99,7 @@ redis: enabled: "${TB_REDIS_SSL_ENABLED:false}" # Server SSL credentials credentials: - # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) + # Server credentials type (pem - pem certificate file; keystore - java keystore) type: "${TB_REDIS_SSL_CREDENTIALS_TYPE:pem}" # PEM server credentials pem: From d0530e63615f3efe5e42eccd1883666d19547f34 Mon Sep 17 00:00:00 2001 From: Ivan Raznatovskyi Date: Wed, 24 Jan 2024 15:52:46 +0200 Subject: [PATCH 125/209] Updated default value for params field and fixed patching value --- .../lib/gateway/gateway-service-rpc-connector.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts index 391fa190fe..69b785d133 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts @@ -279,7 +279,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue default: formGroup = this.fb.group({ command: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - params: ['{}', [jsonRequired]], + params: [{}, [jsonRequired]], }) } return formGroup; @@ -359,7 +359,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValue }).afterClosed().subscribe( (res) => { if (res) { - this.commandForm.get('params').setValue(JSON.stringify(res)); + this.commandForm.get('params').setValue(res); } } ); From 27b5fe52a40202efe191c2847b428e786b6f563b Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 26 Jan 2024 15:44:08 +0100 Subject: [PATCH 126/209] fixed stack overflow error --- .../service/sync/vc/DefaultEntitiesVersionControlService.java | 2 +- .../service/telemetry/DefaultTelemetrySubscriptionService.java | 1 - .../server/dao/attributes/CachedAttributesService.java | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java index 7c7f6e7709..18969e4e0b 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java @@ -283,7 +283,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont return cachePut(ctx.getRequestId(), result); } catch (LoadEntityException e) { return cachePut(ctx.getRequestId(), onError(e.getExternalId(), e.getCause())); - } catch (Exception e) { + } catch (Throwable e) { log.info("[{}] Failed to process request [{}] due to: ", ctx.getTenantId(), request, e); return cachePut(ctx.getRequestId(), VersionLoadResult.error(EntityLoadError.runtimeError(e))); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 2431b344f2..b19fde2983 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -237,7 +237,6 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer } @Override - public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List attributes, FutureCallback callback) { saveAndNotify(tenantId, entityId, scope, attributes, true, callback); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java index 6760ea8ac7..2757bbecbd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java @@ -256,7 +256,7 @@ public class CachedAttributesService implements AttributesService { @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { - return save(tenantId, entityId, scope, attributes); + return save(tenantId, entityId, AttributeScope.valueOf(scope), attributes); } @Override From 7650387db4fd0098346976b9de33dff03d70ce9f Mon Sep 17 00:00:00 2001 From: Ivan Raznatovskyi Date: Mon, 29 Jan 2024 11:16:26 +0200 Subject: [PATCH 127/209] Added initial connector value --- .../widget/lib/gateway/gateway-connectors.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index 85b3fa9acd..b7ac9d65b5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -382,6 +382,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie if ($event) { $event.stopPropagation(); } + this.initialConnector = attribute.value; const title = `Delete connector ${attribute.key}?`; const content = `All connector data will be deleted.`; this.dialogService.confirm(title, content, 'Cancel', 'Delete').subscribe(result => { From 41adbe1003f422979b5fa8485d19ab487b27b55c Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 30 Jan 2024 16:37:14 +0200 Subject: [PATCH 128/209] coap: final version --- ...AbstractCoapAttributesIntegrationTest.java | 5 +- .../CoapAttributesUpdatesIntegrationTest.java | 5 ++ ...pAttributesUpdatesJsonIntegrationTest.java | 4 +- ...AttributesUpdatesProtoIntegrationTest.java | 5 +- .../client/CoapClientIntegrationTest.java | 3 ++ ...tractCoapServerSideRpcIntegrationTest.java | 18 +++++-- ...apServerSideRpcDefaultIntegrationTest.java | 3 ++ .../CoapServerSideRpcJsonIntegrationTest.java | 3 ++ ...CoapServerSideRpcProtoIntegrationTest.java | 3 ++ .../transport/coap/CoapTransportResource.java | 40 +++----------- .../coap/callback/CoapResponseCallback.java | 54 +++++++++++++++++++ ...ack.java => CoapResponseCodeCallback.java} | 4 +- .../coap/client/DefaultCoapClientContext.java | 29 +++++++--- pom.xml | 2 +- 14 files changed, 127 insertions(+), 51 deletions(-) create mode 100644 common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapResponseCallback.java rename common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/{CoapOkCallback.java => CoapResponseCodeCallback.java} (88%) diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java index 081e0ce1b2..e8766ea039 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java @@ -25,6 +25,7 @@ import org.awaitility.Awaitility; import org.eclipse.californium.core.CoapObserveRelation; import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.CoAP.ResponseCode; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DynamicProtoUtils; @@ -238,7 +239,7 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap String awaitAlias = "await Json Test Subscribe To AttributesUpdates (client.getObserveRelation)"; await(awaitAlias) .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && + .until(() -> ResponseCode.VALID.equals(callbackCoap.getResponseCode()) && callbackCoap.getObserve() != null && 0 == callbackCoap.getObserve().intValue()); if (emptyCurrentStateNotification) { @@ -285,7 +286,7 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap CoapObserveRelation observeRelation = client.getObserveRelation(callbackCoap); await(awaitAlias) .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && + .until(() -> ResponseCode.VALID.equals(callbackCoap.getResponseCode()) && callbackCoap.getObserve() != null && 0 == callbackCoap.getObserve().intValue()); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java index 9ac2d5fc11..a7de7eb571 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.server.resources.Resource; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.coapserver.DefaultCoapServerService; @@ -59,11 +60,15 @@ public class CoapAttributesUpdatesIntegrationTest extends AbstractCoapAttributes processAfterTest(); } + + + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { processJsonTestSubscribeToAttributesUpdates(false); } + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception { processJsonTestSubscribeToAttributesUpdates(true); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java index 3fa625796c..4755ba134d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.coap.attributes.updates; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -44,11 +45,12 @@ public class CoapAttributesUpdatesJsonIntegrationTest extends AbstractCoapAttrib processAfterTest(); } + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { processJsonTestSubscribeToAttributesUpdates(false); } - + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception { processJsonTestSubscribeToAttributesUpdates(true); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java index 1ddeabb56d..1c9589bba9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.coap.attributes.updates; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -43,12 +44,12 @@ public class CoapAttributesUpdatesProtoIntegrationTest extends AbstractCoapAttri public void afterTest() throws Exception { processAfterTest(); } - + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { processProtoTestSubscribeToAttributesUpdates(false); } - + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception { processProtoTestSubscribeToAttributesUpdates(true); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java index 5be03683f7..e7a7485ef5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java @@ -26,6 +26,7 @@ import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.DeviceId; @@ -82,6 +83,7 @@ public class CoapClientIntegrationTest extends AbstractCoapIntegrationTest { processAfterTest(); } + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testConfirmableRequests() throws Exception { boolean confirmable = true; @@ -90,6 +92,7 @@ public class CoapClientIntegrationTest extends AbstractCoapIntegrationTest { processTestRequestAttributesValuesFromTheServer(confirmable); } + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testNonConfirmableRequests() throws Exception { boolean confirmable = false; diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java index 4a21495a96..384bfe4772 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java @@ -82,15 +82,27 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC .until(() -> CoAP.ResponseCode.VALID.equals(callbackCoap.getResponseCode()) && callbackCoap.getObserve() != null && 0 == callbackCoap.getObserve()); validateCurrentStateNotification(callbackCoap); - int expectedObserveAfterRpcProcessed = callbackCoap.getObserve() + 1; - String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; + + int expectedObserveAfterRpcProcessed1 = callbackCoap.getObserve() + 1; + String setGpioRequest = "{\"method\":\"setGpio1\",\"params\":{\"pin\": \"21\",\"value\": 1}}"; String deviceId = savedDevice.getId().getId().toString(); String result = doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); awaitAlias = "await One Way Rpc setGpio(method, params, value)"; await(awaitAlias) .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && - callbackCoap.getObserve() != null && expectedObserveAfterRpcProcessed == callbackCoap.getObserve()); + callbackCoap.getObserve() != null && expectedObserveAfterRpcProcessed1 == callbackCoap.getObserve()); + validateOneWayStateChangedNotification(callbackCoap, result); + + int expectedObserveAfterRpcProcessed2 = callbackCoap.getObserve() + 1; + setGpioRequest = "{\"method\":\"setGpio2\",\"params\":{\"pin\": \"22\",\"value\": 2}}"; + deviceId = savedDevice.getId().getId().toString(); + result = doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); + awaitAlias = "await One Way Rpc setGpio(method, params, value)"; + await(awaitAlias) + .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && + callbackCoap.getObserve() != null && expectedObserveAfterRpcProcessed2 == callbackCoap.getObserve()); validateOneWayStateChangedNotification(callbackCoap, result); observeRelation.proactiveCancel(); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java index df1159b128..8f9d3379ef 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java @@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.service.security.AccessValidator; @@ -82,11 +83,13 @@ public class CoapServerSideRpcDefaultIntegrationTest extends AbstractCoapServerS Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); } + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testServerCoapOneWayRpc() throws Exception { processOneWayRpcTest(false); } + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testServerCoapTwoWayRpc() throws Exception { processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}", false); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java index 5c94e91b72..34d678b5b5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.coap.rpc; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -43,11 +44,13 @@ public class CoapServerSideRpcJsonIntegrationTest extends AbstractCoapServerSide processAfterTest(); } + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testServerCoapOneWayRpc() throws Exception { processOneWayRpcTest(false); } + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testServerCoapTwoWayRpc() throws Exception { processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}", false); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java index 2f7d46a390..138f87e964 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.coap.rpc; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -44,11 +45,13 @@ public class CoapServerSideRpcProtoIntegrationTest extends AbstractCoapServerSid processAfterTest(); } + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testServerCoapOneWayRpc() throws Exception { processOneWayRpcTest(true); } + @Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215 @Test public void testServerCoapTwoWayRpc() throws Exception { processTwoWayRpcTest("{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}", true); diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 2528c0992e..ebc52fc034 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -19,7 +19,6 @@ import com.google.gson.JsonParseException; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.network.Exchange; import org.eclipse.californium.core.observe.ObserveRelation; import org.eclipse.californium.core.server.resources.CoapExchange; @@ -27,6 +26,8 @@ import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.californium.core.server.resources.ResourceObserver; import org.thingsboard.server.coapserver.CoapServerService; import org.thingsboard.server.coapserver.TbCoapDtlsSessionInfo; +import org.thingsboard.server.common.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; @@ -35,13 +36,11 @@ import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.adaptor.AdaptorException; -import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.coap.callback.CoapDeviceAuthCallback; import org.thingsboard.server.transport.coap.callback.CoapNoOpCallback; -import org.thingsboard.server.transport.coap.callback.CoapOkCallback; +import org.thingsboard.server.transport.coap.callback.CoapResponseCodeCallback; import org.thingsboard.server.transport.coap.callback.GetAttributesSyncSessionCallback; import org.thingsboard.server.transport.coap.callback.ToServerRpcSyncSessionCallback; import org.thingsboard.server.transport.coap.client.CoapClientContext; @@ -54,7 +53,6 @@ import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import static org.eclipse.californium.elements.DtlsEndpointContext.KEY_SESSION_ID; @@ -84,30 +82,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource { ctx.getScheduler().scheduleAtFixedRate(clients::reportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); } - /* - * Overwritten method from CoapResource to be able to manage our own observe notification counters. - */ - @Override - public void checkObserveRelation(Exchange exchange, Response response) { - String token = getTokenFromRequest(exchange.getRequest()); - final ObserveRelation relation = exchange.getRelation(); - if (relation == null || relation.isCanceled()) { - return; // because request did not try to establish a relation - } - if (response.getCode().isSuccess()) { - if (!relation.isEstablished()) { - relation.setEstablished(); - addObserveRelation(relation); - } - AtomicInteger state = clients.getNotificationCounterByToken(token); - if (state != null) { - response.getOptions().setObserve(state.getAndIncrement()); - } else { - response.getOptions().removeObserve(); - } - } // ObserveLayer takes care of the else case - } - @Override protected void processHandleGet(CoapExchange exchange) { Optional featureType = getFeatureType(exchange.advanced().getRequest()); @@ -278,7 +252,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { UUID sessionId = toSessionId(sessionInfo); transportService.process(sessionInfo, clientState.getAdaptor().convertToPostAttributes(sessionId, request, clientState.getConfiguration().getAttributesMsgDescriptor()), - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); + new CoapResponseCodeCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); } private void handlePostTelemetryRequest(TbCoapClientState clientState, CoapExchange exchange, Request request) throws AdaptorException { @@ -286,7 +260,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { UUID sessionId = toSessionId(sessionInfo); transportService.process(sessionInfo, clientState.getAdaptor().convertToPostTelemetry(sessionId, request, clientState.getConfiguration().getTelemetryMsgDescriptor()), - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); + new CoapResponseCodeCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); } private void handleClaimRequest(TbCoapClientState clientState, CoapExchange exchange, Request request) throws AdaptorException { @@ -294,7 +268,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { UUID sessionId = toSessionId(sessionInfo); transportService.process(sessionInfo, clientState.getAdaptor().convertToClaimDevice(sessionId, request, sessionInfo), - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); + new CoapResponseCodeCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); } private void handleAttributeSubscribeRequest(TbCoapClientState clientState, CoapExchange exchange, Request request) { @@ -320,7 +294,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { UUID sessionId = toSessionId(session); transportService.process(session, clientState.getAdaptor().convertToDeviceRpcResponse(sessionId, request, clientState.getConfiguration().getRpcResponseMsgDescriptor()), - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); + new CoapResponseCodeCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); } private void handleRpcSubscribeRequest(TbCoapClientState clientState, CoapExchange exchange, Request request) { diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapResponseCallback.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapResponseCallback.java new file mode 100644 index 0000000000..c0baf62bc1 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapResponseCallback.java @@ -0,0 +1,54 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.callback; + +import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.core.server.resources.CoapExchange; +import org.thingsboard.server.common.transport.TransportServiceCallback; + +public class CoapResponseCallback implements TransportServiceCallback { + + protected final CoapExchange exchange; + protected final Response onSuccessResponse; + protected final Response onFailureResponse; + + public CoapResponseCallback(CoapExchange exchange, Response onSuccessResponse, Response onFailureResponse) { + this.exchange = exchange; + this.onSuccessResponse = onSuccessResponse; + this.onFailureResponse = onFailureResponse; + } + + /** + * @param msg + */ + @Override + public void onSuccess(Void msg) { + this.onSuccessResponse.setConfirmable(isConRequest()); + exchange.respond(this.onSuccessResponse); + } + + /** + * @param e + */ + @Override + public void onError(Throwable e) { + exchange.respond(onFailureResponse); + } + + protected boolean isConRequest() { + return exchange.advanced().getRequest().isConfirmable(); + } +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapOkCallback.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapResponseCodeCallback.java similarity index 88% rename from common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapOkCallback.java rename to common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapResponseCodeCallback.java index a45821d9d6..21293395ea 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapOkCallback.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/callback/CoapResponseCodeCallback.java @@ -20,13 +20,13 @@ import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.server.resources.CoapExchange; import org.thingsboard.server.common.transport.TransportServiceCallback; -public class CoapOkCallback implements TransportServiceCallback { +public class CoapResponseCodeCallback implements TransportServiceCallback { protected final CoapExchange exchange; protected final CoAP.ResponseCode onSuccessResponse; protected final CoAP.ResponseCode onFailureResponse; - public CoapOkCallback(CoapExchange exchange, CoAP.ResponseCode onSuccessResponse, CoAP.ResponseCode onFailureResponse) { + public CoapResponseCodeCallback(CoapExchange exchange, CoAP.ResponseCode onSuccessResponse, CoAP.ResponseCode onFailureResponse) { this.exchange = exchange; this.onSuccessResponse = onSuccessResponse; this.onFailureResponse = onFailureResponse; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java index 735ec1d524..2140e1966c 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.coap.client; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.CoAP.ResponseCode; import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.observe.ObserveRelation; import org.eclipse.californium.core.server.resources.CoapExchange; @@ -26,6 +27,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.server.coapserver.CoapServerContext; +import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -45,7 +47,6 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.msg.session.FeatureType; -import org.thingsboard.server.transport.coap.CoapSessionMsgType; import org.thingsboard.server.common.transport.DeviceDeletedEvent; import org.thingsboard.server.common.transport.DeviceProfileUpdatedEvent; import org.thingsboard.server.common.transport.DeviceUpdatedEvent; @@ -53,18 +54,19 @@ import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.transport.coap.CoapSessionMsgType; import org.thingsboard.server.transport.coap.CoapTransportContext; import org.thingsboard.server.transport.coap.TbCoapMessageObserver; import org.thingsboard.server.transport.coap.TransportConfigurationContainer; import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; import org.thingsboard.server.transport.coap.callback.AbstractSyncSessionCallback; import org.thingsboard.server.transport.coap.callback.CoapNoOpCallback; -import org.thingsboard.server.transport.coap.callback.CoapOkCallback; +import org.thingsboard.server.transport.coap.callback.CoapResponseCallback; +import org.thingsboard.server.transport.coap.callback.CoapResponseCodeCallback; import java.util.Optional; import java.util.UUID; @@ -329,9 +331,14 @@ public class DefaultCoapClientContext implements CoapClientContext { TransportProtos.GetAttributeRequestMsg.newBuilder().setOnlyShared(true).build(), new CoapNoOpCallback(exchange)); } else { + Response response = new Response(CoAP.ResponseCode.VALID); + if (state.getRpc() == null) { + state.setRpc(new TbCoapObservationState(exchange, token)); + } + response.getOptions().setObserve(state.getRpc().getObserveCounter().getAndIncrement()); transportService.process(state.getSession(), TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), - new CoapOkCallback(exchange, CoAP.ResponseCode.VALID, CoAP.ResponseCode.INTERNAL_SERVER_ERROR) + new CoapResponseCallback(exchange, response, new Response(CoAP.ResponseCode.INTERNAL_SERVER_ERROR)) ); } } @@ -478,7 +485,13 @@ public class DefaultCoapClientContext implements CoapClientContext { TbCoapObservationState attrs = state.getAttrs(); if (attrs != null) { try { - Response response = state.getAdaptor().convertToPublish(msg); + Response resp = state.getAdaptor().convertToPublish(msg); + Response response = new Response(ResponseCode.VALID); + response.setPayload(resp.getPayload()); + if (state.getRpc() == null) { + state.setRpc(new TbCoapObservationState(attrs.getExchange(), attrs.getToken())); + } + response.getOptions().setObserve(state.getRpc().getObserveCounter().getAndIncrement()); respond(attrs.getExchange(), response, state.getContentFormat()); } catch (AdaptorException e) { log.trace("Failed to reply due to error", e); @@ -509,6 +522,7 @@ public class DefaultCoapClientContext implements CoapClientContext { boolean conRequest = AbstractSyncSessionCallback.isConRequest(state.getAttrs()); int requestId = getNextMsgId(); Response response = state.getAdaptor().convertToPublish(msg); + response.getOptions().setObserve(state.getRpc().getObserveCounter().getAndIncrement()); response.setConfirmable(conRequest); response.setMID(requestId); if (conRequest) { @@ -573,6 +587,7 @@ public class DefaultCoapClientContext implements CoapClientContext { int requestId = getNextMsgId(); try { Response response = state.getAdaptor().convertToPublish(msg, state.getConfiguration().getRpcRequestDynamicMessageBuilder()); + response.getOptions().setObserve(state.getRpc().getObserveCounter().getAndIncrement()); response.setConfirmable(conRequest); response.setMID(requestId); if (conRequest) { @@ -808,7 +823,7 @@ public class DefaultCoapClientContext implements CoapClientContext { state.setRpc(null); transportService.process(state.getSession(), TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), - new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); + new CoapResponseCodeCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); if (state.getAttrs() == null) { closeAndCleanup(state); } @@ -822,7 +837,7 @@ public class DefaultCoapClientContext implements CoapClientContext { state.setAttrs(null); transportService.process(state.getSession(), TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), - new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); + new CoapResponseCodeCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); if (state.getRpc() == null) { closeAndCleanup(state); } diff --git a/pom.xml b/pom.xml index 062a69746c..9079dc08e9 100755 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ 1.3.4 4.2.1 2.2.6 - 3.9.1 + 3.10.0 2.0.0-M14 2.9.0 2.3.30 From 4eda0d668c36cf763538a11958fe198ac023a92f Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 30 Jan 2024 18:49:21 +0200 Subject: [PATCH 129/209] coap: msa tests --- .../server/msa/TestCoapClient.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java index 67638ebefb..8fa6c44f4a 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java @@ -22,6 +22,11 @@ import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.californium.elements.config.Configuration.ModuleDefinitionsProvider; +import org.eclipse.californium.elements.config.IntegerDefinition; +import org.eclipse.californium.elements.config.StringDefinition; +import org.eclipse.californium.elements.config.TcpConfig; import org.eclipse.californium.elements.exception.ConnectorException; import org.thingsboard.server.common.msg.session.FeatureType; @@ -32,6 +37,26 @@ public class TestCoapClient { private static final String COAP_BASE_URL = "coap://localhost:5683/api/v1/"; private static final long CLIENT_REQUEST_TIMEOUT = 60000L; + + private static final String MODULE2 = "TEST2."; + private static final IntegerDefinition INT2 = new IntegerDefinition(MODULE2 + "INT2", "TEST", null, 1); + private static final StringDefinition STRING = new StringDefinition(MODULE2 + "STRING", "TEST"); + + private static final ModuleDefinitionsProvider DEFAULTS = new ModuleDefinitionsProvider() { + + @Override + public String getModule() { + return MODULE2; + } + + @Override + public void applyDefinitions(Configuration config) { + TcpConfig.register(); + config.set(INT2, 100); + config.set(STRING, "Hallo"); + } + }; + private final CoapClient client; public TestCoapClient(){ @@ -43,7 +68,8 @@ public class TestCoapClient { } public TestCoapClient(String featureTokenUrl) { - this.client = createClient(featureTokenUrl); + Configuration.addDefaultModule(DEFAULTS); + this.client = new CoapClient(featureTokenUrl); } public void connectToCoap(String accessToken) { From b3e49d4cda8de527078c46fd034c103d5e285031 Mon Sep 17 00:00:00 2001 From: Ivan Raznatovskyi Date: Wed, 31 Jan 2024 10:04:30 +0200 Subject: [PATCH 130/209] Removed ng-deep --- .../gateway/gateway-connectors.component.scss | 5 ----- ...teway-service-rpc-connector.component.html | 2 +- ...teway-service-rpc-connector.component.scss | 20 +------------------ .../gateway-service-rpc.component.html | 10 ++++++---- .../gateway-service-rpc.component.scss | 7 ------- 5 files changed, 8 insertions(+), 36 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss index 036523e269..76676b4c5e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss @@ -97,8 +97,3 @@ background-color: rgb(25, 128, 56); } } - -:host ::ng-deep tb-json-object-edit > div { - flex-grow: 1; -} - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html index d0d8e32acd..2d992187f6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html @@ -279,7 +279,7 @@ {{ 'gateway.rpc.oids' | translate }}*
- +
- {{ 'gateway.rpc-command-result' | translate }} -
schedule - {{ resultTime | date: 'yyyy/MM/dd HH:mm:ss' }}
+ {{ 'gateway.rpc-command-result' | translate }} +
+ schedule + {{ resultTime | date: 'yyyy/MM/dd HH:mm:ss' }} +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss index d3dd9114c4..f74097bbba 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss @@ -81,10 +81,3 @@ border-radius: 4px; } } - -:host ::ng-deep { - .tb-json-content { - height: 100%; - } -} - From 719ec92653a01c25851094db38b7a128ca272a59 Mon Sep 17 00:00:00 2001 From: Ivan Raznatovskyi Date: Wed, 31 Jan 2024 11:11:47 +0200 Subject: [PATCH 131/209] Fixed methods writing --- ...eway-service-rpc-connector-templates.component.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts index 6e212dc079..4392b35f2d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts @@ -23,6 +23,7 @@ import { EntityType } from '@shared/models/entity-type.models'; import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; import { AttributeService } from '@core/http/attribute.service'; import { WidgetContext } from '@home/models/widget-component.models'; +import { isLiteralObject } from '@app/core/utils'; @Component({ selector: 'tb-gateway-service-rpc-connector-templates', @@ -46,6 +47,9 @@ export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit { @Input() rpcTemplates: Array; + public readonly originalOrder = (): number => 0; + public readonly isObject = (value: any) => isLiteralObject(value); + constructor(private attributeService: AttributeService) { } @@ -75,12 +79,4 @@ export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit { }]).subscribe(() => { }) } - - public originalOrder = (a, b): number => { - return 0; - } - - public isObject(value: any) { - return value !== null && typeof value === 'object' && !Array.isArray(value); - } } From 537a8be9828b2a6edc915f77d37d3b3e6941497e Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 31 Jan 2024 13:32:47 +0200 Subject: [PATCH 132/209] coap: comments1 --- .../transport/coap/client/DefaultCoapClientContext.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java index 2140e1966c..08e4c513df 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java @@ -488,10 +488,7 @@ public class DefaultCoapClientContext implements CoapClientContext { Response resp = state.getAdaptor().convertToPublish(msg); Response response = new Response(ResponseCode.VALID); response.setPayload(resp.getPayload()); - if (state.getRpc() == null) { - state.setRpc(new TbCoapObservationState(attrs.getExchange(), attrs.getToken())); - } - response.getOptions().setObserve(state.getRpc().getObserveCounter().getAndIncrement()); + response.getOptions().setObserve(attrs.getObserveCounter().getAndIncrement()); respond(attrs.getExchange(), response, state.getContentFormat()); } catch (AdaptorException e) { log.trace("Failed to reply due to error", e); @@ -522,7 +519,7 @@ public class DefaultCoapClientContext implements CoapClientContext { boolean conRequest = AbstractSyncSessionCallback.isConRequest(state.getAttrs()); int requestId = getNextMsgId(); Response response = state.getAdaptor().convertToPublish(msg); - response.getOptions().setObserve(state.getRpc().getObserveCounter().getAndIncrement()); + response.getOptions().setObserve(attrs.getObserveCounter().getAndIncrement()); response.setConfirmable(conRequest); response.setMID(requestId); if (conRequest) { From 7748c56bc3e5d39a9e5d1f5b194d5ce57441d81f Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 1 Feb 2024 15:47:26 +0200 Subject: [PATCH 133/209] coap: comments1 --- .../server/msa/AbstractCoapClientTest.java | 81 ++++++++++ .../server/msa/TestCoapClient.java | 148 ------------------ .../server/msa/TestCoapClientCallback.java | 68 -------- .../msa/connectivity/CoapClientTest.java | 28 +--- 4 files changed, 85 insertions(+), 240 deletions(-) create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractCoapClientTest.java delete mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java delete mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClientCallback.java diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractCoapClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractCoapClientTest.java new file mode 100644 index 0000000000..4ed5426bc4 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractCoapClientTest.java @@ -0,0 +1,81 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.msa; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.core.config.CoapConfig; +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.californium.elements.config.Configuration.ModuleDefinitionsProvider; +import org.eclipse.californium.elements.config.IntegerDefinition; +import org.eclipse.californium.elements.config.TcpConfig; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.msg.session.FeatureType; + +public abstract class AbstractCoapClientTest extends AbstractContainerTest{ + + private static final String COAP_BASE_URL = "coap://localhost:5683/api/v1/"; + private static final long CLIENT_REQUEST_TIMEOUT = 60000L; + + + private static final String COAP_CLIENT_TEST = "COAP_CLIENT_TEST."; + private static final IntegerDefinition COAP_PORT_DEF = CoapConfig.COAP_PORT; + + private static final ModuleDefinitionsProvider MODULE_DEFINITIONS_PROVIDER = new ModuleDefinitionsProvider() { + + @Override + public String getModule() { + return COAP_CLIENT_TEST; + } + + @Override + public void applyDefinitions(Configuration config) { + TcpConfig.register(); + config.set(COAP_PORT_DEF, 5683); + } + }; + + protected CoapClient client; + + protected byte[] createCoapClientAndPublish(String deviceName) throws Exception { + String provisionRequestMsg = createTestProvisionMessage(deviceName); + Configuration.addDefaultModule(MODULE_DEFINITIONS_PROVIDER); + String featureTokenUrl = COAP_BASE_URL + FeatureType.PROVISION.name().toLowerCase(); + client = new CoapClient(featureTokenUrl); + return client.setTimeout(CLIENT_REQUEST_TIMEOUT) + .post(provisionRequestMsg.getBytes(), MediaTypeRegistry.APPLICATION_JSON) + .getPayload(); + } + + protected void disconnect() { + if (client != null) { + client.shutdown(); + } + } + + private String createTestProvisionMessage(String deviceName) { + ObjectNode provisionRequest = JacksonUtil.newObjectNode(); + provisionRequest.put("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY); + provisionRequest.put("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET); + if (deviceName != null) { + provisionRequest.put("deviceName", deviceName); + } + return provisionRequest.toString(); + } +} + + diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java deleted file mode 100644 index 8fa6c44f4a..0000000000 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.msa; - -import org.eclipse.californium.core.CoapClient; -import org.eclipse.californium.core.CoapHandler; -import org.eclipse.californium.core.CoapObserveRelation; -import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.MediaTypeRegistry; -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.elements.config.Configuration; -import org.eclipse.californium.elements.config.Configuration.ModuleDefinitionsProvider; -import org.eclipse.californium.elements.config.IntegerDefinition; -import org.eclipse.californium.elements.config.StringDefinition; -import org.eclipse.californium.elements.config.TcpConfig; -import org.eclipse.californium.elements.exception.ConnectorException; -import org.thingsboard.server.common.msg.session.FeatureType; - -import java.io.IOException; - -public class TestCoapClient { - - private static final String COAP_BASE_URL = "coap://localhost:5683/api/v1/"; - private static final long CLIENT_REQUEST_TIMEOUT = 60000L; - - - private static final String MODULE2 = "TEST2."; - private static final IntegerDefinition INT2 = new IntegerDefinition(MODULE2 + "INT2", "TEST", null, 1); - private static final StringDefinition STRING = new StringDefinition(MODULE2 + "STRING", "TEST"); - - private static final ModuleDefinitionsProvider DEFAULTS = new ModuleDefinitionsProvider() { - - @Override - public String getModule() { - return MODULE2; - } - - @Override - public void applyDefinitions(Configuration config) { - TcpConfig.register(); - config.set(INT2, 100); - config.set(STRING, "Hallo"); - } - }; - - private final CoapClient client; - - public TestCoapClient(){ - this.client = createClient(); - } - - public TestCoapClient(String accessToken, FeatureType featureType) { - this.client = createClient(getFeatureTokenUrl(accessToken, featureType)); - } - - public TestCoapClient(String featureTokenUrl) { - Configuration.addDefaultModule(DEFAULTS); - this.client = new CoapClient(featureTokenUrl); - } - - public void connectToCoap(String accessToken) { - setURI(accessToken, null); - } - - public void connectToCoap(String accessToken, FeatureType featureType) { - setURI(accessToken, featureType); - } - - public void disconnect() { - if (client != null) { - client.shutdown(); - } - } - - public CoapResponse postMethod(String requestBody) throws ConnectorException, IOException { - return this.postMethod(requestBody.getBytes()); - } - - public CoapResponse postMethod(byte[] requestBodyBytes) throws ConnectorException, IOException { - return client.setTimeout(CLIENT_REQUEST_TIMEOUT).post(requestBodyBytes, MediaTypeRegistry.APPLICATION_JSON); - } - - public void postMethod(CoapHandler handler, String payload, int format) { - client.post(handler, payload, format); - } - - public void postMethod(CoapHandler handler, byte[] payload, int format) { - client.post(handler, payload, format); - } - - public CoapResponse getMethod() throws ConnectorException, IOException { - return client.setTimeout(CLIENT_REQUEST_TIMEOUT).get(); - } - - public CoapObserveRelation getObserveRelation(TestCoapClientCallback callback){ - Request request = Request.newGet().setObserve(); - request.setType(CoAP.Type.CON); - return client.observe(request, callback); - } - - public void setURI(String featureTokenUrl) { - if (client == null) { - throw new RuntimeException("Failed to connect! CoapClient is not initialized!"); - } - client.setURI(featureTokenUrl); - } - - public void setURI(String accessToken, FeatureType featureType) { - if (featureType == null){ - featureType = FeatureType.ATTRIBUTES; - } - setURI(getFeatureTokenUrl(accessToken, featureType)); - } - - private CoapClient createClient() { - return new CoapClient(); - } - - private CoapClient createClient(String featureTokenUrl) { - return new CoapClient(featureTokenUrl); - } - - public static String getFeatureTokenUrl(FeatureType featureType) { - return COAP_BASE_URL + featureType.name().toLowerCase(); - } - - public static String getFeatureTokenUrl(String token, FeatureType featureType) { - return COAP_BASE_URL + token + "/" + featureType.name().toLowerCase(); - } - - public static String getFeatureTokenUrl(String token, FeatureType featureType, int requestId) { - return COAP_BASE_URL + token + "/" + featureType.name().toLowerCase() + "/" + requestId; - } -} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClientCallback.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClientCallback.java deleted file mode 100644 index 49c5a06476..0000000000 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClientCallback.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.msa; - -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapHandler; -import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.CoAP; - -import java.util.concurrent.CountDownLatch; - -@Slf4j -@Data -public class TestCoapClientCallback implements CoapHandler { - - protected final CountDownLatch latch; - protected Integer observe; - protected byte[] payloadBytes; - protected CoAP.ResponseCode responseCode; - - public TestCoapClientCallback() { - this.latch = new CountDownLatch(1); - } - - public TestCoapClientCallback(int subscribeCount) { - this.latch = new CountDownLatch(subscribeCount); - } - - public Integer getObserve() { - return observe; - } - - public byte[] getPayloadBytes() { - return payloadBytes; - } - - public CoAP.ResponseCode getResponseCode() { - return responseCode; - } - - @Override - public void onLoad(CoapResponse response) { - observe = response.getOptions().getObserve(); - payloadBytes = response.getPayload(); - responseCode = response.getCode(); - latch.countDown(); - } - - @Override - public void onError() { - log.warn("Command Response Ack Error, No connect"); - } - -} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/CoapClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/CoapClientTest.java index 4dc7d91b2a..a65aff1bc1 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/CoapClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/CoapClientTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.msa.connectivity; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.gson.JsonObject; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -26,18 +25,14 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.msg.session.FeatureType; -import org.thingsboard.server.msa.AbstractContainerTest; +import org.thingsboard.server.msa.AbstractCoapClientTest; import org.thingsboard.server.msa.DisableUIListeners; -import org.thingsboard.server.msa.TestCoapClient; import static org.assertj.core.api.Assertions.assertThat; import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultDevicePrototype; @DisableUIListeners -public class CoapClientTest extends AbstractContainerTest { - private TestCoapClient client; - +public class CoapClientTest extends AbstractCoapClientTest{ private Device device; @BeforeMethod public void setUp() throws Exception { @@ -48,6 +43,7 @@ public class CoapClientTest extends AbstractContainerTest { @AfterMethod public void tearDown() { testRestClient.deleteDeviceIfExists(device.getId()); + disconnect(); } @Test @@ -101,21 +97,5 @@ public class CoapClientTest extends AbstractContainerTest { assertThat(response.get("status").asText()).isEqualTo("NOT_FOUND"); } - - private byte[] createCoapClientAndPublish(String deviceName) throws Exception { - String provisionRequestMsg = createTestProvisionMessage(deviceName); - client = new TestCoapClient(TestCoapClient.getFeatureTokenUrl(FeatureType.PROVISION)); - return client.postMethod(provisionRequestMsg.getBytes()).getPayload(); - } - - private String createTestProvisionMessage(String deviceName) { - ObjectNode provisionRequest = JacksonUtil.newObjectNode(); - provisionRequest.put("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY); - provisionRequest.put("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET); - if (deviceName != null) { - provisionRequest.put("deviceName", deviceName); - } - return provisionRequest.toString(); - } - } + From a89802e801a73245daa38b4f831dd863b6197794 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 2 Feb 2024 17:23:11 +0200 Subject: [PATCH 134/209] Response code fix --- .../attributes/AbstractCoapAttributesIntegrationTest.java | 4 ++-- .../transport/coap/client/DefaultCoapClientContext.java | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java index e8766ea039..92554b3067 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java @@ -239,7 +239,7 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap String awaitAlias = "await Json Test Subscribe To AttributesUpdates (client.getObserveRelation)"; await(awaitAlias) .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .until(() -> ResponseCode.VALID.equals(callbackCoap.getResponseCode()) && + .until(() -> ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && callbackCoap.getObserve() != null && 0 == callbackCoap.getObserve().intValue()); if (emptyCurrentStateNotification) { @@ -286,7 +286,7 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap CoapObserveRelation observeRelation = client.getObserveRelation(callbackCoap); await(awaitAlias) .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .until(() -> ResponseCode.VALID.equals(callbackCoap.getResponseCode()) && + .until(() -> ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && callbackCoap.getObserve() != null && 0 == callbackCoap.getObserve().intValue()); diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java index 08e4c513df..7177542c3d 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java @@ -485,9 +485,7 @@ public class DefaultCoapClientContext implements CoapClientContext { TbCoapObservationState attrs = state.getAttrs(); if (attrs != null) { try { - Response resp = state.getAdaptor().convertToPublish(msg); - Response response = new Response(ResponseCode.VALID); - response.setPayload(resp.getPayload()); + Response response = state.getAdaptor().convertToPublish(msg); response.getOptions().setObserve(attrs.getObserveCounter().getAndIncrement()); respond(attrs.getExchange(), response, state.getContentFormat()); } catch (AdaptorException e) { From 6a755ab0b7dc54e51ee67315572e095705ce1be0 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 6 Feb 2024 15:10:09 +0200 Subject: [PATCH 135/209] deleted java keystore type of creds support --- .../src/main/resources/thingsboard.yml | 32 ++------- .../cache/RedisKeystoreCredentialsConfig.java | 36 ---------- .../cache/RedisPemCredentialsConfig.java | 28 -------- ...guration.java => RedisSslCredentials.java} | 9 ++- .../cache/TBRedisCacheConfiguration.java | 69 +++++-------------- .../org/thingsboard/common/util/SslUtil.java | 1 + msa/black-box-tests/README.md | 4 ++ .../src/main/resources/tb-coap-transport.yml | 32 ++------- .../src/main/resources/tb-http-transport.yml | 32 ++------- .../src/main/resources/tb-lwm2m-transport.yml | 32 ++------- .../src/main/resources/tb-mqtt-transport.yml | 32 ++------- .../src/main/resources/tb-snmp-transport.yml | 32 ++------- 12 files changed, 70 insertions(+), 269 deletions(-) delete mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/RedisKeystoreCredentialsConfig.java delete mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/RedisPemCredentialsConfig.java rename common/cache/src/main/java/org/thingsboard/server/cache/{RedisSslCredentialsConfiguration.java => RedisSslCredentials.java} (84%) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 38cfdd64a0..050d46a700 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -643,32 +643,14 @@ redis: ssl: # Enable/disable secure connection enabled: "${TB_REDIS_SSL_ENABLED:false}" - # Server SSL credentials + # Server SSL credentials (only PEM format is supported) credentials: - # Server credentials type (pem - pem certificate file; keystore - java keystore) - type: "${TB_REDIS_SSL_CREDENTIALS_TYPE:pem}" - # PEM server credentials - pem: - # Path redis server (CA) certificate - cert_file: "${TB_REDIS_SSL_PEM_CERT:}" - # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client - user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" - # Path to user private key file. This is optional for the client and only needed if ‘ssl.pem.user_cert_file’ is configured. - user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" - # Keystore server credentials - keystore: - # Type of the trust store (JKS or PKCS12) - truststore_type: "${TB_REDIS_SSL_KEY_STORE_TYPE:JKS}" - # The location of the trust store file - truststore_location: "${TB_REDIS_SSL_TRUSTSTORE_LOCATION:}" - # The password of trust store file if specified - truststore_password: "${TB_REDIS_SSL_TRUSTSTORE_PASSWORD:}" - # Type of the key store (JKS or PKCS12) - keystore_type: "${TB_REDIS_SSL_KEY_STORE_TYPE:JKS}" - # The location of the key store file. This is optional for the client and can be used for two-way authentication for the client - keystore_location: "${TB_REDIS_SSL_KEYSTORE_LOCATION:}" - # The store password for the key store file. This is optional for the client and only needed if ‘ssl.keystore.location’ is configured. Key store password is not supported for PEM format - keystore_password: "${TB_REDIS_SSL_KEYSTORE_PASSWORD:}" + # Path redis server (CA) certificate + cert_file: "${TB_REDIS_SSL_PEM_CERT:}" + # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client + user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" + # Path to user private key file. This is optional for the client and only needed if ‘user_cert_file’ is configured. + user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" # pool config pool_config: # Maximum number of connections that can be allocated by the connection pool diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/RedisKeystoreCredentialsConfig.java b/common/cache/src/main/java/org/thingsboard/server/cache/RedisKeystoreCredentialsConfig.java deleted file mode 100644 index 1d2a08e6d4..0000000000 --- a/common/cache/src/main/java/org/thingsboard/server/cache/RedisKeystoreCredentialsConfig.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.cache; - -import lombok.Data; - -@Data -public class RedisKeystoreCredentialsConfig { - - private String type; - - private String truststoreType; - - private String truststoreLocation; - - private String truststorePassword; - - private String keystoreType; - - private String keystoreLocation; - - private String keystorePassword; -} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/RedisPemCredentialsConfig.java b/common/cache/src/main/java/org/thingsboard/server/cache/RedisPemCredentialsConfig.java deleted file mode 100644 index 50e2c71966..0000000000 --- a/common/cache/src/main/java/org/thingsboard/server/cache/RedisPemCredentialsConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.cache; - -import lombok.Data; - -@Data -public class RedisPemCredentialsConfig { - - private String certFile; - - private String userCertFile; - - private String userKeyFile; -} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/RedisSslCredentialsConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/RedisSslCredentials.java similarity index 84% rename from common/cache/src/main/java/org/thingsboard/server/cache/RedisSslCredentialsConfiguration.java rename to common/cache/src/main/java/org/thingsboard/server/cache/RedisSslCredentials.java index 6bd46baf1b..aeac975d15 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/RedisSslCredentialsConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/RedisSslCredentials.java @@ -22,12 +22,11 @@ import org.springframework.context.annotation.Configuration; @Configuration @ConfigurationProperties(prefix = "redis.ssl.credentials") @Data -public class RedisSslCredentialsConfiguration { +public class RedisSslCredentials { - private String type; + private String certFile; - private RedisKeystoreCredentialsConfig keystore; - - private RedisPemCredentialsConfig pem; + private String userCertFile; + private String userKeyFile; } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java index d8805044f3..c3d3655883 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java @@ -42,11 +42,14 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; -import java.io.FileInputStream; +import java.io.IOException; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.CertPath; import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.time.Duration; @@ -100,13 +103,16 @@ public abstract class TBRedisCacheConfiguration { @Value("${redis.pool_config.blockWhenExhausted:true}") private boolean blockWhenExhausted; + @Value("${redis.ssl.enabled:false}") + private boolean sslEnabled; + @Bean public RedisConnectionFactory redisConnectionFactory() { return loadFactory(); } @Autowired - private RedisSslCredentialsConfiguration redisSslCredentials; + private RedisSslCredentials redisSslCredentials; protected abstract JedisConnectionFactory loadFactory(); @@ -176,57 +182,35 @@ public abstract class TBRedisCacheConfiguration { sslContext.init(keyManagerFactory == null ? null : keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); return sslContext.getSocketFactory(); } catch (Exception e) { - throw new RuntimeException(e); + throw new RuntimeException("Creating TLS factory failed!", e); } } private TrustManagerFactory createAndInitTrustManagerFactory() throws Exception { - String type = redisSslCredentials.getType(); - if ("pem".equals(type)) { - RedisPemCredentialsConfig pemCredentials = redisSslCredentials.getPem(); - List caCerts = SslUtil.readCertFileByPath(pemCredentials.getCertFile()); - + List caCerts = SslUtil.readCertFileByPath(redisSslCredentials.getCertFile()); KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); caKeyStore.load(null, null); for (X509Certificate caCert : caCerts) { caKeyStore.setCertificateEntry("redis-caCert-cert-" + caCert.getSubjectX500Principal().getName(), caCert); } - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509"); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(caKeyStore); return trustManagerFactory; - } else if ("keystore".equals(type)) { - RedisKeystoreCredentialsConfig keystore = redisSslCredentials.getKeystore(); - KeyStore trustStore = KeyStore.getInstance(keystore.getKeystoreType()); - trustStore.load(new FileInputStream(keystore.getTruststoreLocation()), keystore.getTruststorePassword().toCharArray()); - - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509"); - trustManagerFactory.init(trustStore); - return trustManagerFactory; - } else { - throw new RuntimeException(type + ": Invalid SSL credentials configuration. None of the PEM or KEYSTORE configurations can be used!"); - } } private KeyManagerFactory createAndInitKeyManagerFactory() throws Exception { - String type = redisSslCredentials.getType(); - if ("pem".equals(type)) { - RedisPemCredentialsConfig pemCredentials = redisSslCredentials.getPem(); - return getKeyManagerFactory(pemCredentials); - } else if ("keystore".equals(type)) { - RedisKeystoreCredentialsConfig keystore = redisSslCredentials.getKeystore(); - return getKeyManagerFactory(keystore); - } else { - throw new RuntimeException(type + ": Invalid SSL credentials configuration. None of the PEM or KEYSTORE configurations can be used!"); - } + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(loadKeyStore(), null); + return kmf; } - private KeyManagerFactory getKeyManagerFactory(RedisPemCredentialsConfig pemCredentials) throws Exception { - if (pemCredentials.getUserCertFile().isBlank() || pemCredentials.getUserKeyFile().isBlank()) { + private KeyStore loadKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + if (redisSslCredentials.getUserCertFile().isBlank() || redisSslCredentials.getUserKeyFile().isBlank()) { return null; } - List certificates = SslUtil.readCertFileByPath(pemCredentials.getCertFile()); - PrivateKey privateKey = SslUtil.readPrivateKeyByFilePath(pemCredentials.getUserKeyFile(), null); + List certificates = SslUtil.readCertFileByPath(redisSslCredentials.getCertFile()); + PrivateKey privateKey = SslUtil.readPrivateKeyByFilePath(redisSslCredentials.getUserKeyFile(), null); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); @@ -242,21 +226,6 @@ public abstract class TBRedisCacheConfiguration { Certificate[] x509Certificates = path.toArray(new Certificate[0]); keyStore.setKeyEntry("redis-private-key", privateKey, null, x509Certificates); } - - KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); - kmf.init(keyStore, null); - return kmf; - } - - private KeyManagerFactory getKeyManagerFactory(RedisKeystoreCredentialsConfig keystore) throws Exception { - if (keystore.getKeystoreLocation().isBlank() || keystore.getKeystoreLocation().isBlank()) { - return null; - } - KeyStore keyStore = KeyStore.getInstance(keystore.getKeystoreType()); - keyStore.load(new FileInputStream(keystore.getKeystoreLocation()), keystore.getKeystorePassword().toCharArray()); - - KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); - kmf.init(keyStore, keystore.getKeystorePassword().toCharArray()); - return kmf; + return keyStore; } } diff --git a/common/util/src/main/java/org/thingsboard/common/util/SslUtil.java b/common/util/src/main/java/org/thingsboard/common/util/SslUtil.java index e520e946ec..a62870ea9b 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/SslUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/SslUtil.java @@ -129,4 +129,5 @@ public class SslUtil { } return privateKey; } + } diff --git a/msa/black-box-tests/README.md b/msa/black-box-tests/README.md index 340f0d0eb8..31277e2a55 100644 --- a/msa/black-box-tests/README.md +++ b/msa/black-box-tests/README.md @@ -22,6 +22,10 @@ As result, in REPOSITORY column, next images should be present: mvn clean install -DblackBoxTests.skip=false +- Run the black box tests (without ui tests) in the [msa/black-box-tests](../black-box-tests) directory with Redis standalone with TLS: + + mvn clean install -DblackBoxTests.skip=false -DblackBoxTests.redisSsl=true + - Run the black box tests in the [msa/black-box-tests](../black-box-tests) directory with Redis cluster: mvn clean install -DblackBoxTests.skip=false -DblackBoxTests.redisCluster=true diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 78ca89851d..3934acfb87 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -97,32 +97,14 @@ redis: ssl: # Enable/disable secure connection enabled: "${TB_REDIS_SSL_ENABLED:false}" - # Server SSL credentials + # Server SSL credentials (only PEM format is supported) credentials: - # Server credentials type (pem - pem certificate file; keystore - java keystore) - type: "${TB_REDIS_SSL_CREDENTIALS_TYPE:pem}" - # PEM server credentials - pem: - # Path redis server (CA) certificate - cert_file: "${TB_REDIS_SSL_PEM_CERT:}" - # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client - user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" - # Path to user private key file. This is optional for the client and only needed if ‘ssl.pem.user_cert_file’ is configured. - user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" - # Keystore server credentials - keystore: - # Type of the trust store (JKS or PKCS12) - truststore_type: "${TB_REDIS_SSL_KEY_STORE_TYPE:JKS}" - # The location of the trust store file - truststore_location: "${TB_REDIS_SSL_TRUSTSTORE_LOCATION:}" - # The password of trust store file if specified - truststore_password: "${TB_REDIS_SSL_TRUSTSTORE_PASSWORD:}" - # Type of the key store (JKS or PKCS12) - keystore_type: "${TB_REDIS_SSL_KEY_STORE_TYPE:JKS}" - # The location of the key store file. This is optional for the client and can be used for two-way authentication for the client - keystore_location: "${TB_REDIS_SSL_KEYSTORE_LOCATION:}" - # The store password for the key store file. This is optional for the client and only needed if ‘ssl.keystore.location’ is configured. Key store password is not supported for PEM format - keystore_password: "${TB_REDIS_SSL_KEYSTORE_PASSWORD:}" + # Path redis server (CA) certificate + cert_file: "${TB_REDIS_SSL_PEM_CERT:}" + # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client + user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" + # Path to user private key file. This is optional for the client and only needed if ‘user_cert_file’ is configured. + user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" # pool config pool_config: # Maximum number of connections that can be allocated by the connection pool diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 04a9c9417f..556c004bf8 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -130,32 +130,14 @@ redis: ssl: # Enable/disable secure connection enabled: "${TB_REDIS_SSL_ENABLED:false}" - # Server SSL credentials + # Server SSL credentials (only PEM format is supported) credentials: - # Server credentials type (pem - pem certificate file; keystore - java keystore) - type: "${TB_REDIS_SSL_CREDENTIALS_TYPE:pem}" - # PEM server credentials - pem: - # Path redis server (CA) certificate - cert_file: "${TB_REDIS_SSL_PEM_CERT:}" - # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client - user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" - # Path to user private key file. This is optional for the client and only needed if ‘ssl.pem.user_cert_file’ is configured. - user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" - # Keystore server credentials - keystore: - # Type of the trust store (JKS or PKCS12) - truststore_type: "${TB_REDIS_SSL_KEY_STORE_TYPE:JKS}" - # The location of the trust store file - truststore_location: "${TB_REDIS_SSL_TRUSTSTORE_LOCATION:}" - # The password of trust store file if specified - truststore_password: "${TB_REDIS_SSL_TRUSTSTORE_PASSWORD:}" - # Type of the key store (JKS or PKCS12) - keystore_type: "${TB_REDIS_SSL_KEY_STORE_TYPE:JKS}" - # The location of the key store file. This is optional for the client and can be used for two-way authentication for the client - keystore_location: "${TB_REDIS_SSL_KEYSTORE_LOCATION:}" - # The store password for the key store file. This is optional for the client and only needed if ‘ssl.keystore.location’ is configured. Key store password is not supported for PEM format - keystore_password: "${TB_REDIS_SSL_KEYSTORE_PASSWORD:}" + # Path redis server (CA) certificate + cert_file: "${TB_REDIS_SSL_PEM_CERT:}" + # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client + user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" + # Path to user private key file. This is optional for the client and only needed if ‘user_cert_file’ is configured. + user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" # pool config pool_config: # Maximum number of connections that can be allocated by the connection pool diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index c079f690ed..6d7257d7f9 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -97,32 +97,14 @@ redis: ssl: # Enable/disable secure connection enabled: "${TB_REDIS_SSL_ENABLED:false}" - # Server SSL credentials + # Server SSL credentials (only PEM format is supported) credentials: - # Server credentials type (pem - pem certificate file; keystore - java keystore) - type: "${TB_REDIS_SSL_CREDENTIALS_TYPE:pem}" - # PEM server credentials - pem: - # Path redis server (CA) certificate - cert_file: "${TB_REDIS_SSL_PEM_CERT:}" - # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client - user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" - # Path to user private key file. This is optional for the client and only needed if ‘ssl.pem.user_cert_file’ is configured. - user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" - # Keystore server credentials - keystore: - # Type of the trust store (JKS or PKCS12) - truststore_type: "${TB_REDIS_SSL_KEY_STORE_TYPE:JKS}" - # The location of the trust store file - truststore_location: "${TB_REDIS_SSL_TRUSTSTORE_LOCATION:}" - # The password of trust store file if specified - truststore_password: "${TB_REDIS_SSL_TRUSTSTORE_PASSWORD:}" - # Type of the key store (JKS or PKCS12) - keystore_type: "${TB_REDIS_SSL_KEY_STORE_TYPE:JKS}" - # The location of the key store file. This is optional for the client and can be used for two-way authentication for the client - keystore_location: "${TB_REDIS_SSL_KEYSTORE_LOCATION:}" - # The store password for the key store file. This is optional for the client and only needed if ‘ssl.keystore.location’ is configured. Key store password is not supported for PEM format - keystore_password: "${TB_REDIS_SSL_KEYSTORE_PASSWORD:}" + # Path redis server (CA) certificate + cert_file: "${TB_REDIS_SSL_PEM_CERT:}" + # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client + user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" + # Path to user private key file. This is optional for the client and only needed if ‘user_cert_file’ is configured. + user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" # pool config pool_config: # Maximum number of connections that can be allocated by the connection pool diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index 430b5bfed5..41d96ff9d7 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -98,32 +98,14 @@ redis: ssl: # Enable/disable secure connection enabled: "${TB_REDIS_SSL_ENABLED:false}" - # Server SSL credentials + # Server SSL credentials (only PEM format is supported) credentials: - # Server credentials type (pem - pem certificate file; keystore - java keystore) - type: "${TB_REDIS_SSL_CREDENTIALS_TYPE:pem}" - # PEM server credentials - pem: - # Path redis server (CA) certificate - cert_file: "${TB_REDIS_SSL_PEM_CERT:}" - # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client - user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" - # Path to user private key file. This is optional for the client and only needed if ‘ssl.pem.user_cert_file’ is configured. - user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" - # Keystore server credentials - keystore: - # Type of the trust store (JKS or PKCS12) - truststore_type: "${TB_REDIS_SSL_KEY_STORE_TYPE:JKS}" - # The location of the trust store file - truststore_location: "${TB_REDIS_SSL_TRUSTSTORE_LOCATION:}" - # The password of trust store file if specified - truststore_password: "${TB_REDIS_SSL_TRUSTSTORE_PASSWORD:}" - # Type of the key store (JKS or PKCS12) - keystore_type: "${TB_REDIS_SSL_KEY_STORE_TYPE:JKS}" - # The location of the key store file. This is optional for the client and can be used for two-way authentication for the client - keystore_location: "${TB_REDIS_SSL_KEYSTORE_LOCATION:}" - # The store password for the key store file. This is optional for the client and only needed if ‘ssl.keystore.location’ is configured. Key store password is not supported for PEM format - keystore_password: "${TB_REDIS_SSL_KEYSTORE_PASSWORD:}" + # Path redis server (CA) certificate + cert_file: "${TB_REDIS_SSL_PEM_CERT:}" + # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client + user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" + # Path to user private key file. This is optional for the client and only needed if ‘user_cert_file’ is configured. + user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" # pool config pool_config: # Maximum number of connections that can be allocated by the connection pool diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index 68b69bb03a..971237efe0 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -97,32 +97,14 @@ redis: ssl: # Enable/disable secure connection enabled: "${TB_REDIS_SSL_ENABLED:false}" - # Server SSL credentials + # Server SSL credentials (only PEM format is supported) credentials: - # Server credentials type (pem - pem certificate file; keystore - java keystore) - type: "${TB_REDIS_SSL_CREDENTIALS_TYPE:pem}" - # PEM server credentials - pem: - # Path redis server (CA) certificate - cert_file: "${TB_REDIS_SSL_PEM_CERT:}" - # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client - user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" - # Path to user private key file. This is optional for the client and only needed if ‘ssl.pem.user_cert_file’ is configured. - user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" - # Keystore server credentials - keystore: - # Type of the trust store (JKS or PKCS12) - truststore_type: "${TB_REDIS_SSL_KEY_STORE_TYPE:JKS}" - # The location of the trust store file - truststore_location: "${TB_REDIS_SSL_TRUSTSTORE_LOCATION:}" - # The password of trust store file if specified - truststore_password: "${TB_REDIS_SSL_TRUSTSTORE_PASSWORD:}" - # Type of the key store (JKS or PKCS12) - keystore_type: "${TB_REDIS_SSL_KEY_STORE_TYPE:JKS}" - # The location of the key store file. This is optional for the client and can be used for two-way authentication for the client - keystore_location: "${TB_REDIS_SSL_KEYSTORE_LOCATION:}" - # The store password for the key store file. This is optional for the client and only needed if ‘ssl.keystore.location’ is configured. Key store password is not supported for PEM format - keystore_password: "${TB_REDIS_SSL_KEYSTORE_PASSWORD:}" + # Path redis server (CA) certificate + cert_file: "${TB_REDIS_SSL_PEM_CERT:}" + # Path to user certificate file. This is optional for the client and can be used for two-way authentication for the client + user_cert_file: "${TB_REDIS_SSL_PEM_KEY:}" + # Path to user private key file. This is optional for the client and only needed if ‘user_cert_file’ is configured. + user_key_file: "${TB_REDIS_SSL_PEM_KEY_PASSWORD:}" # pool config pool_config: # Maximum number of connections that can be allocated by the connection pool From 9fa36c7a2d5109b4cdbe9adcad7daec3426a2f72 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 9 Feb 2024 16:18:30 +0200 Subject: [PATCH 136/209] lwm2m: delete from Root Application:"org.eclipse.leshan.server.observation.ObservationServiceImpl --- .../observation/ObservationServiceImpl.java | 296 ------------------ ...cLwm2MIntegrationObserveCompositeTest.java | 8 +- .../sql/RpcLwm2mIntegrationObserveTest.java | 20 +- .../DefaultLwM2mDownlinkMsgHandler.java | 31 +- 4 files changed, 39 insertions(+), 316 deletions(-) delete mode 100644 application/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java diff --git a/application/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java b/application/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java deleted file mode 100644 index 89a2f07179..0000000000 --- a/application/src/main/java/org/eclipse/leshan/server/observation/ObservationServiceImpl.java +++ /dev/null @@ -1,296 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Copyright (c) 2016 Sierra Wireless and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v2.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v20.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.html. - * - * Contributors: - * Sierra Wireless - initial API and implementation - * Michał Wadowski (Orange) - Add Observe-Composite feature. - */ -package org.eclipse.leshan.server.observation; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.observation.CompositeObservation; -import org.eclipse.leshan.core.observation.Observation; -import org.eclipse.leshan.core.observation.SingleObservation; -import org.eclipse.leshan.core.peer.LwM2mPeer; -import org.eclipse.leshan.core.response.ObserveCompositeResponse; -import org.eclipse.leshan.core.response.ObserveResponse; -import org.eclipse.leshan.server.endpoint.LwM2mServerEndpoint; -import org.eclipse.leshan.server.endpoint.LwM2mServerEndpointsProvider; -import org.eclipse.leshan.server.profile.ClientProfile; -import org.eclipse.leshan.server.registration.Registration; -import org.eclipse.leshan.server.registration.RegistrationStore; -import org.eclipse.leshan.server.registration.RegistrationUpdate; -import org.eclipse.leshan.server.registration.UpdatedRegistration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Implementation of the {@link ObservationService} accessing the persisted observation via the provided - * {@link RegistrationStore}. - * - * When a new observation is added or changed or canceled, the registered listeners are notified. - */ -@Slf4j -public class ObservationServiceImpl implements ObservationService, LwM2mNotificationReceiver { - - private final Logger LOG = LoggerFactory.getLogger(ObservationServiceImpl.class); - - private final RegistrationStore registrationStore; - private final LwM2mServerEndpointsProvider endpointProvider; - private final boolean updateRegistrationOnNotification; - - private final List listeners = new CopyOnWriteArrayList<>();; - - /** - * Creates an instance of {@link ObservationServiceImpl} - */ - public ObservationServiceImpl(RegistrationStore store, LwM2mServerEndpointsProvider endpointProvider) { - this(store, endpointProvider, false); - } - - /** - * Creates an instance of {@link ObservationServiceImpl} - * - * @param updateRegistrationOnNotification will activate registration update on observe notification. - * - * @since 1.1 - */ - public ObservationServiceImpl(RegistrationStore store, LwM2mServerEndpointsProvider endpointProvider, - boolean updateRegistrationOnNotification) { - this.registrationStore = store; - this.updateRegistrationOnNotification = updateRegistrationOnNotification; - this.endpointProvider = endpointProvider; - } - - @Override - public int cancelObservations(Registration registration) { - // check registration id - String registrationId = registration.getId(); - if (registrationId == null) - return 0; - - Collection observations = registrationStore.removeObservations(registrationId); - if (observations == null) - return 0; - - for (Observation observation : observations) { - cancel(observation); - } - - return observations.size(); - } - - @Override - public int cancelObservations(Registration registration, String nodePath) { - if (registration == null || registration.getId() == null || nodePath == null || nodePath.isEmpty()) - return 0; - - Set observations = getObservationsForCancel(registration.getId(), nodePath); - for (Observation observation : observations) { - cancelObservation(observation); - } - return observations.size(); - } - - @Override - public int cancelCompositeObservations(Registration registration, String[] nodePaths) { - if (registration == null || registration.getId() == null || nodePaths == null || nodePaths.length == 0) - return 0; - - Set observations = getCompositeObservationsForCancel(registration.getId(), nodePaths); - for (Observation observation : observations) { - cancelObservation(observation); - } - return observations.size(); - } - - @Override - public void cancelObservation(Observation observation) { - if (observation == null) - return; - - registrationStore.removeObservation(observation.getRegistrationId(), observation.getId()); - cancel(observation); - } - - private void cancel(Observation observation) { - List endpoints = endpointProvider.getEndpoints(); - for (LwM2mServerEndpoint lwM2mEndpoint : endpoints) { - lwM2mEndpoint.cancelObservation(observation); - } - - for (ObservationListener listener : listeners) { - listener.cancelled(observation); - } - } - - @Override - public Set getObservations(Registration registration) { - return getObservations(registration.getId()); - } - - private Set getObservations(String registrationId) { - if (registrationId == null) - return Collections.emptySet(); - - return new HashSet<>(registrationStore.getObservations(registrationId)); - } - - private Set getCompositeObservationsForCancel(String registrationId, String[] nodePaths) { - if (registrationId == null || nodePaths == null) - return Collections.emptySet(); - - // array of String to array of LWM2M path - List lwPaths = new ArrayList<>(nodePaths.length); - for (int i = 0; i < nodePaths.length; i++) { - lwPaths.add(new LwM2mPath(nodePaths[i])); - } - - // search composite-observation - Set result = new HashSet<>(); - for (Observation obs : getObservations(registrationId)) { - if (obs instanceof CompositeObservation) { - if (lwPaths.equals(((CompositeObservation) obs).getPaths())) { - result.add(obs); - } - } - } - return result; - } - - private Set getObservationsForCancel(String registrationId, String nodePath) { - if (registrationId == null || nodePath == null) - return Collections.emptySet(); - - Set result = new HashSet<>(); - LwM2mPath lwPath = new LwM2mPath(nodePath); - for (Observation obs : getObservations(registrationId)) { - if (obs instanceof SingleObservation) { - LwM2mPath lwPathObs = ((SingleObservation) obs).getPath(); - if (lwPath.equals(lwPathObs) || lwPathObs.startWith(lwPath)) { // nodePath = "3", lwPathObs = "3/0/9": cancel for tne all lwPathObs - result.add(obs); - } else if (!lwPath.equals(lwPathObs) && lwPath.startWith(lwPathObs)) { // nodePath = "3/0/9", lwPathObs = "3": error... - String errorMsg = String.format( - "Unexpected error : There is registration with id [%s] existing observation [%s] includes input observation [%s]!", - registrationId, lwPathObs, lwPath); - throw new IllegalStateException(errorMsg); - } - } - } - - return result; - } - - @Override - public void addListener(ObservationListener listener) { - listeners.add(listener); - } - - @Override - public void removeListener(ObservationListener listener) { - listeners.remove(listener); - } - - private Registration updateRegistrationOnRegistration(Observation observation, LwM2mPeer sender, - ClientProfile profile) { - if (updateRegistrationOnNotification) { - RegistrationUpdate regUpdate = new RegistrationUpdate(observation.getRegistrationId(), sender, null, null, - null, null, null, null, null, null, null, null); - UpdatedRegistration updatedRegistration = registrationStore.updateRegistration(regUpdate); - if (updatedRegistration == null || updatedRegistration.getUpdatedRegistration() == null) { - String errorMsg = String.format( - "Unexpected error: There is no registration with id %s for this observation %s", - observation.getRegistrationId(), observation); - LOG.error(errorMsg); - throw new IllegalStateException(errorMsg); - } - return updatedRegistration.getUpdatedRegistration(); - } - return profile.getRegistration(); - } - - // ********** NotificationListener interface **********// - @Override - public void onNotification(SingleObservation observation, LwM2mPeer sender, ClientProfile profile, - ObserveResponse response) { - try { - Registration updatedRegistration = updateRegistrationOnRegistration(observation, sender, profile); - for (ObservationListener listener : listeners) { - listener.onResponse(observation, updatedRegistration, response); - } - } catch (Exception e) { - for (ObservationListener listener : listeners) { - listener.onError(observation, profile.getRegistration(), e); - } - } - } - - @Override - public void onNotification(CompositeObservation observation, LwM2mPeer sender, ClientProfile profile, - ObserveCompositeResponse response) { - try { - Registration updatedRegistration = updateRegistrationOnRegistration(observation, sender, profile); - for (ObservationListener listener : listeners) { - listener.onResponse(observation, updatedRegistration, response); - } - } catch (Exception e) { - for (ObservationListener listener : listeners) { - listener.onError(observation, profile.getRegistration(), e); - } - } - } - - @Override - public void onError(Observation observation, LwM2mPeer sender, ClientProfile profile, Exception error) { - for (ObservationListener listener : listeners) { - listener.onError(observation, profile.getRegistration(), error); - } - } - - @Override - public void newObservation(Observation observation, Registration registration) { - for (ObservationListener listener : listeners) { - listener.newObservation(observation, registration); - } - } - - @Override - public void cancelled(Observation observation) { - for (ObservationListener listener : listeners) { - listener.cancelled(observation); - } - - } -} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java index 86cd8186c7..94ee1eb48e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java @@ -355,8 +355,8 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt } /** - * ObserveComposite {"ids":["/3/0/9", "/3/0/14", "/5/0/3", "/19/1/0/0"]} - Ok - * ObserveCompositeCancel {"ids":["/3", "/19/1/0/0"]} - Ok + * ObserveComposite {"ids":["/3/0/9", "/5/0/5", "/5/0/3", "/5/0/7", "/19/1/0/0"]} - Ok + * ObserveCompositeCancel {"ids":["/5", "/19/1/0/0"]} - Ok * last Observation * @throws Exception */ @@ -380,7 +380,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("4", rpcActualResult.get("value").asText()); + assertEquals("4", rpcActualResult.get("value").asText()); // CNT = 4 ("/5/0/5", "/5/0/3", "/5/0/7", "/19/1/0/0"9) String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); @@ -411,7 +411,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expectedValue = "for observation path " + fromVersionedIdToObjectId(idVer_3_0_9) + ", that includes this observation path " + fromVersionedIdToObjectId(objectIdVer_3); + String expectedValue = "for observation path [" + fromVersionedIdToObjectId(objectIdVer_3) + "], that includes this observation path [" + fromVersionedIdToObjectId(idVer_3_0_9); assertTrue(rpcActualResult.get("error").asText().contains(expectedValue)); // ObserveCompositeCancel diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java index bf1ba41270..5a849e70e4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java @@ -333,9 +333,9 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO // cancel observe "/3_1.2/0/9" String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; String actualResult = sendRpcObserve("ObserveCancel", expectedId_3_0_9); - String expectedValue = "existing observation [" + fromVersionedIdToObjectId(objectIdVer_3) + "] includes input observation [" + fromVersionedIdToObjectId(expectedId_3_0_9); + String expectedValue = "for observation path [" + fromVersionedIdToObjectId(objectIdVer_3) + "], that includes this observation path [" + fromVersionedIdToObjectId(expectedId_3_0_9); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertTrue(rpcActualResult.get("error").asText().contains(expectedValue)); // cancel observe "/3_1.2" @@ -346,29 +346,33 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO } /** + * Observe {"id":"/3/0/0"} * Observe {"id":"/3/0/9"} - * ObserveCancel {"id":"/3"} + * ObserveCancel {"id":"/3"} - Ok, cnt = 2 */ @Test public void testObserveResource_ObserveCancelObject_Result_CONTENT_Count_1() throws Exception { sendCancelObserveAllWithAwait(deviceId); - + String expectedId_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; + sendRpcObserve("Observe", expectedId_3_0_0); String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; sendRpcObserve("Observe", expectedId_3_0_9); String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String actualValuesReadAll = rpcActualResult.get("value").asText(); - assertEquals(1, actualValuesReadAll.split(",").length); - String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_9) + "\""; - assertTrue(actualValuesReadAll.contains(expected)); + assertEquals(2, actualValuesReadAll.split(",").length); + String expected_3_0_0 = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_0) + "\""; + String expected_3_0_9 = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_9) + "\""; + assertTrue(actualValuesReadAll.contains(expected_3_0_0)); + assertTrue(actualValuesReadAll.contains(expected_3_0_9)); // cancel observe "/3_1.2" String expectedId_3 = objectIdVer_3; String actualResult = sendRpcObserve("ObserveCancel", expectedId_3); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("1", rpcActualResult.get("value").asText()); + assertEquals("2", rpcActualResult.get("value").asText()); } private String sendRpcObserve(String method, String params) throws Exception { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index d8cd2b44ba..af127dba88 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -259,7 +259,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im for (Observation obs : observations) { LwM2mPath lwPathObs = ((SingleObservation) obs).getPath(); for (LwM2mPath nodePath : listPath) { - String validNodePath = validatePathCompositeCancel(nodePath, lwPathObs, client); + String validNodePath = validatePathObserveCancelAny(nodePath, lwPathObs, client); if (validNodePath != null) lwPaths.add(validNodePath); } }; @@ -273,13 +273,13 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } } - private String validatePathCompositeCancel(LwM2mPath nodePath, LwM2mPath lwPathObs, LwM2mClient client) throws ThingsboardException { + private String validatePathObserveCancelAny(LwM2mPath nodePath, LwM2mPath lwPathObs, LwM2mClient client) throws ThingsboardException { if (nodePath.equals(lwPathObs) || lwPathObs.startWith(nodePath)) { // nodePath = "3", lwPathObs = "3/0/9": cancel for tne all lwPathObs - return nodePath.toString(); + return lwPathObs.toString(); } else if (!nodePath.equals(lwPathObs) && nodePath.startWith(lwPathObs)) { String errorMsg = String.format( - "Unexpected error: There is registration with Endpoint %s for observation path %s, that includes this observation path %s", - client.getRegistration().getEndpoint(), nodePath, lwPathObs); + "Unexpected error: There is registration with Endpoint %s for observation path [%s], that includes this observation path [%s]", + client.getRegistration().getEndpoint(), lwPathObs, nodePath); throw new ThingsboardException(errorMsg, ThingsboardErrorCode.BAD_REQUEST_PARAMS); } return null; @@ -335,9 +335,24 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback callback) { - validateVersionedId(client, request); - int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), request.getObjectId()); - callback.onSuccess(request, observeCancelCnt); + try{ + validateVersionedId(client, request); + Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); + int observeCancelCnt = 0; + Set lwPaths = new HashSet<>(); + for (Observation obs : observations) { + LwM2mPath lwPathObs = ((SingleObservation) obs).getPath(); + LwM2mPath nodePath = new LwM2mPath(request.getObjectId()); + String validNodePath = validatePathObserveCancelAny(nodePath, lwPathObs, client); + if (validNodePath != null) lwPaths.add(validNodePath); + }; + for (String nodePath : lwPaths) { + observeCancelCnt += context.getServer().getObservationService().cancelObservations(client.getRegistration(), nodePath); + } + callback.onSuccess(request, observeCancelCnt); + } catch (ThingsboardException e){ + callback.onValidationError(request.toString(), e.getMessage()); + } } @Override From c2268595eb3e55fb42ee7af4e6ae93670f513a87 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 12 Feb 2024 13:31:38 +0200 Subject: [PATCH 137/209] lwm2m_coap: DTLS Cid Length --- .../src/main/resources/thingsboard.yml | 20 +++ .../lwm2m/AbstractLwM2MIntegrationTest.java | 7 +- .../transport/lwm2m/Lwm2mTestHelper.java | 6 +- .../lwm2m/client/DtlsSessionLogger.java | 81 +++++++++++ .../lwm2m/client/LwM2MTestClient.java | 38 +++-- .../ota/sql/OtaLwM2MIntegrationTest.java | 6 +- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 2 +- .../AbstractSecurityLwM2MIntegrationTest.java | 4 +- ...ityLwM2MIntegrationDtlsCidLength0Test.java | 38 +++++ ...ityLwM2MIntegrationDtlsCidLength3Test.java | 37 +++++ ...LwM2MIntegrationDtlsCidLengthNullTest.java | 38 +++++ ...rityLwM2MIntegrationDtlsCidLengthTest.java | 133 ++++++++++++++++++ ...oSecLwM2MIntegrationDtlsCidLengthTest.java | 48 +++++++ .../PskLwm2mIntegrationDtlsCidLengthTest.java | 46 ++++++ ...oSecLwM2MIntegrationDtlsCidLengthTest.java | 48 +++++++ .../PskLwm2mIntegrationDtlsCidLengthTest.java | 47 +++++++ ...oSecLwM2MIntegrationDtlsCidLengthTest.java | 48 +++++++ .../PskLwm2mIntegrationDtlsCidLengthTest.java | 47 +++++++ .../server/coapserver/TbCoapDtlsSettings.java | 13 ++ .../LwM2MTransportBootstrapService.java | 7 +- .../config/LwM2MTransportServerConfig.java | 4 + .../server/DefaultLwM2mTransportService.java | 18 +-- .../lwm2m/utils/LwM2MTransportUtil.java | 14 ++ 23 files changed, 707 insertions(+), 43 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/DtlsSessionLogger.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLength0Test.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLength3Test.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthNullTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/NoSecLwM2MIntegrationDtlsCidLengthTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/PskLwm2mIntegrationDtlsCidLengthTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/NoSecLwM2MIntegrationDtlsCidLengthTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/PskLwm2mIntegrationDtlsCidLengthTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/NoSecLwM2MIntegrationDtlsCidLengthTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/PskLwm2mIntegrationDtlsCidLengthTest.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 0ddc86c5b7..0d401c5974 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1019,6 +1019,16 @@ transport: # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" # Server DTLS credentials + # CoAP DTLS connection ID length. RFC 9146, Connection Identifier for DTLS 1.2 + # Default: off + # Control usage of DTLS connection ID length (CID). + # - 'off' to deactivate it. + # - 'on' to activate Connection ID support (same as CID 0 or more 0). + # - A positive value defines generated CID size in bytes. + # - A value of 0 means we accept using CID but will not generate one for foreign peer (enables support but not for incoming traffic). + # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used + # - A value that are > 4: MultiNodeConnectionIdGenerator is used + connection_id_length: "${COAP_DTLS_CONNECTION_ID_LENGTH:}" credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) type: "${COAP_DTLS_CREDENTIALS_TYPE:PEM}" @@ -1056,6 +1066,16 @@ transport: dtls: # RFC7925_RETRANSMISSION_TIMEOUT_IN_MILLISECONDS = 9000 retransmission_timeout: "${LWM2M_DTLS_RETRANSMISSION_TIMEOUT_MS:9000}" + # CoAP DTLS connection ID length for LWM2M. RFC 9146, Connection Identifier for DTLS 1.2 + # Default: off + # Control usage of DTLS connection ID length (CID). + # - 'off' to deactivate it. + # - 'on' to activate Connection ID support (same as CID 0 or more 0). + # - A positive value defines generated CID size in bytes. + # - A value of 0 means we accept using CID but will not generate one for foreign peer (enables support but not for incoming traffic). + # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used + # - A value that are > 4: MultiNodeConnectionIdGenerator is used + connection_id_length: "${LWM2M_DTLS_CONNECTION_ID_LENGTH:}" server: # LwM2M Server ID id: "${LWM2M_SERVER_ID:123}" diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 78f999080d..3d21a191a9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -236,7 +236,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte getWsClient().waitForReply(); getWsClient().registerWaitForUpdate(); - createNewClient(security, null, coapConfig, false, endpoint); + createNewClient(security, null, coapConfig, false, endpoint, null); deviceId = device.getId().getId().toString(); awaitObserveReadAll(0, deviceId); String msg = getWsClient().waitForUpdate(); @@ -304,14 +304,15 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte this.resources = resources; } - public void createNewClient(Security security, Security securityBs, Configuration coapConfig, boolean isRpc, String endpoint) throws Exception { + public void createNewClient(Security security, Security securityBs, Configuration coapConfig, boolean isRpc, + String endpoint, Integer clientDtlsCidLength) throws Exception { this.clientDestroy(); lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint); try (ServerSocket socket = new ServerSocket(0)) { int clientPort = socket.getLocalPort(); lwM2MTestClient.init(security, securityBs, coapConfig, clientPort, isRpc, - this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, isWriteAttribute); + this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, isWriteAttribute, clientDtlsCidLength); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java index 32e1444de6..b2ed3cc297 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java @@ -53,7 +53,7 @@ public class Lwm2mTestHelper { public enum LwM2MClientState { - ON_INIT(1, "onInit"), + ON_INIT(0, "onInit"), ON_BOOTSTRAP_STARTED(1, "onBootstrapStarted"), ON_BOOTSTRAP_SUCCESS(2, "onBootstrapSuccess"), ON_BOOTSTRAP_FAILURE(3, "onBootstrapFailure"), @@ -70,7 +70,9 @@ public class Lwm2mTestHelper { ON_DEREGISTRATION_SUCCESS(13, "onDeregistrationSuccess"), ON_DEREGISTRATION_FAILURE(14, "onDeregistrationFailure"), ON_DEREGISTRATION_TIMEOUT(15, "onDeregistrationTimeout"), - ON_EXPECTED_ERROR(16, "onUnexpectedError"); + ON_EXPECTED_ERROR(16, "onUnexpectedError"), + ON_READ_CONNECTION_ID (17, "onReadConnection"), + ON_WRITE_CONNECTION_ID (18, "onWriteConnection"); public int code; public String type; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/DtlsSessionLogger.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/DtlsSessionLogger.java new file mode 100644 index 0000000000..d798dbc0d4 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/DtlsSessionLogger.java @@ -0,0 +1,81 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.client; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.scandium.dtls.ClientHandshaker; +import org.eclipse.californium.scandium.dtls.DTLSContext; +import org.eclipse.californium.scandium.dtls.HandshakeException; +import org.eclipse.californium.scandium.dtls.Handshaker; +import org.eclipse.californium.scandium.dtls.SessionAdapter; +import org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState; + +import java.util.Map; +import java.util.Set; + +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_READ_CONNECTION_ID; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_WRITE_CONNECTION_ID; + +@Slf4j +public class DtlsSessionLogger extends SessionAdapter { + + private final Set clientStates; + private final Map clientDtlsCid; + + public DtlsSessionLogger(Set clientStates, Map clientDtlsCid) { + this.clientStates = clientStates; + this.clientDtlsCid = clientDtlsCid; + } + + @Override + public void handshakeStarted(Handshaker handshaker) throws HandshakeException { + if (handshaker instanceof ClientHandshaker) { + log.info("DTLS Full Handshake initiated by client : STARTED ..."); + } + } + + @Override + public void contextEstablished(Handshaker handshaker, DTLSContext establishedContext) throws HandshakeException { + if (handshaker instanceof ClientHandshaker) { + log.warn("DTLS initiated by client: SUCCEED, WriteConnectionId: [{}], ReadConnectionId: [{}]", establishedContext.getWriteConnectionId(), establishedContext.getReadConnectionId()); + clientStates.add(ON_WRITE_CONNECTION_ID); + clientStates.add(ON_READ_CONNECTION_ID); + Integer lenWrite = establishedContext.getWriteConnectionId() == null ? null : establishedContext.getWriteConnectionId().getBytes().length; + Integer lenRead = establishedContext.getReadConnectionId() == null ? null : establishedContext.getReadConnectionId().getBytes().length; + clientDtlsCid.put(ON_WRITE_CONNECTION_ID, lenWrite); + clientDtlsCid.put(ON_READ_CONNECTION_ID, lenRead); + } + } + + @Override + public void handshakeFailed(Handshaker handshaker, Throwable error) { + // get cause + String cause; + if (error != null) { + if (error.getMessage() != null) { + cause = error.getMessage(); + } else { + cause = error.getClass().getName(); + } + } else { + cause = "unknown cause"; + } + + if (handshaker instanceof ClientHandshaker) { + log.info("DTLS Full Handshake initiated by client : FAILED ({})", cause); + } + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 339c28067f..c3c1f5ff33 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -18,7 +18,6 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.config.Configuration; -import org.eclipse.californium.scandium.config.DtlsConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.leshan.client.LeshanClient; import org.eclipse.leshan.client.LeshanClientBuilder; @@ -62,11 +61,15 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.eclipse.leshan.core.LwM2mId.DEVICE; import static org.eclipse.leshan.core.LwM2mId.FIRMWARE; @@ -103,6 +106,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INST import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.setDtlsConnectorConfigCidLength; @Slf4j @@ -119,12 +123,13 @@ public class LwM2MTestClient { private LwM2MLocationParams locationParams; private LwM2mTemperatureSensor lwM2MTemperatureSensor; private Set clientStates; + private Map clientDtlsCid; private LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; private LwM2mClientContext clientContext; public void init(Security security, Security securityBs,Configuration coapConfig, int port, boolean isRpc, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, - LwM2mClientContext clientContext, boolean isWriteAttribute) throws InvalidDDFFileException, IOException { + LwM2mClientContext clientContext, boolean isWriteAttribute, Integer cIdLength) throws InvalidDDFFileException, IOException { Assert.assertNull("client already initialized", leshanClient); this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler; this.clientContext = clientContext; @@ -188,6 +193,10 @@ public class LwM2MTestClient { protected DtlsConnectorConfig.Builder createRootDtlsConnectorConfigBuilder( Configuration configuration) { DtlsConnectorConfig.Builder builder = super.createRootDtlsConnectorConfigBuilder(configuration); + + // Add DTLS Session lifecycle logger + builder.setSessionListener(new DtlsSessionLogger(clientStates, clientDtlsCid)); + return builder; }; }; @@ -213,20 +222,18 @@ public class LwM2MTestClient { // Set some DTLS stuff // These configuration values are always overwritten by CLI therefore set them to transient. - clientCoapConfig.setTransient(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); - clientCoapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); + clientCoapConfig.setTransient(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); + clientCoapConfig.setTransient(DTLS_CONNECTION_ID_LENGTH); boolean supportDeprecatedCiphers = false; - clientCoapConfig.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !supportDeprecatedCiphers); - /** - * "Control usage of DTLS connection ID.", // - * "- 'on' to activate Connection ID support (same as -cid 0)", // - * "- 'off' to deactivate it", // - * "- Positive value define the size in byte of CID generated.", // - * "- 0 value means we accept to use CID but will not generated one for foreign peer.", // - * "Default: off" - */ - Integer cid = null; - clientCoapConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, cid); + clientCoapConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, !supportDeprecatedCiphers); + + if (cIdLength!= null) { + setDtlsConnectorConfigCidLength(clientCoapConfig, cIdLength); + } + + if (cIdLength!= null) { + setDtlsConnectorConfigCidLength(clientCoapConfig, cIdLength); + } // Set Californium Configuration endpointsBuilder.setConfiguration(clientCoapConfig); @@ -279,6 +286,7 @@ public class LwM2MTestClient { builder.setSharedExecutor(executor); clientStates = new HashSet<>(); + clientDtlsCid = new HashMap<>(); clientStates.add(ON_INIT); leshanClient = builder.build(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java index 7e812f9846..17518e2ba3 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java @@ -59,7 +59,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); - createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, null); awaitObserveReadAll(0, device.getId().getId().toString()); device.setFirmwareId(createFirmware().getId()); @@ -84,7 +84,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5); - createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5, null); awaitObserveReadAll(9, device.getId().getId().toString()); device.setFirmwareId(createFirmware().getId()); @@ -114,7 +114,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9); - createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9, null); awaitObserveReadAll(9, device.getId().getId().toString()); device.setSoftwareId(createSoftware().getId()); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index 1c83bed8eb..3c5d536dcd 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -91,7 +91,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg private void initRpc () throws Exception { String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet(); - createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, true, endpoint); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, true, endpoint, null); expectedObjects = ConcurrentHashMap.newKeySet(); expectedObjectIdVers = ConcurrentHashMap.newKeySet(); expectedInstances = ConcurrentHashMap.newKeySet(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java index f164cb7fe7..b360918a9f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -196,7 +196,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M boolean isStartLw) throws Exception { createDeviceProfile(transportConfiguration); final Device device = createDevice(deviceCredentials, endpoint); - createNewClient(security, securityBs, coapConfig, true, endpoint); + createNewClient(security, securityBs, coapConfig, true, endpoint, null); lwM2MTestClient.start(isStartLw); if (isAwaitObserveReadAll) { awaitObserveReadAll(0, device.getId().getId().toString()); @@ -244,7 +244,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M createDeviceProfile(transportConfiguration); final Device device = createDevice(deviceCredentials, endpoint); String deviceIdStr = device.getId().getId().toString(); - createNewClient(security, securityBs, coapConfig, true, endpoint); + createNewClient(security, securityBs, coapConfig, true, endpoint, null); lwM2MTestClient.start(true); awaitObserveReadAll(0, deviceIdStr); await(awaitAlias) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLength0Test.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLength0Test.java new file mode 100644 index 0000000000..126dc39197 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLength0Test.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.cid; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.server.dao.service.DaoSqlTest; + + +@TestPropertySource(properties = { + "transport.lwm2m.dtls.connection_id_length=0" +}) + +@DaoSqlTest +@Slf4j +public abstract class AbstractSecurityLwM2MIntegrationDtlsCidLength0Test extends AbstractSecurityLwM2MIntegrationDtlsCidLengthTest { + + + protected void testNoSecDtlsCidLength(Integer dtlsCidLength) throws Exception { + testNoSecDtlsCidLength(dtlsCidLength, 0); + } + protected void testPskDtlsCidLength(Integer dtlsCidLength) throws Exception { + testPskDtlsCidLength(dtlsCidLength, 0); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLength3Test.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLength3Test.java new file mode 100644 index 0000000000..d3f6605e11 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLength3Test.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.cid; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.server.dao.service.DaoSqlTest; + + +@TestPropertySource(properties = { + "transport.lwm2m.dtls.connection_id_length=3" +}) + +@DaoSqlTest +@Slf4j +public abstract class AbstractSecurityLwM2MIntegrationDtlsCidLength3Test extends AbstractSecurityLwM2MIntegrationDtlsCidLengthTest { + + protected void testNoSecDtlsCidLength(Integer dtlsCidLength) throws Exception { + testNoSecDtlsCidLength(dtlsCidLength, 3); + } + protected void testPskDtlsCidLength(Integer dtlsCidLength) throws Exception { + testPskDtlsCidLength(dtlsCidLength, 3); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthNullTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthNullTest.java new file mode 100644 index 0000000000..1a63159352 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthNullTest.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.cid; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.server.dao.service.DaoSqlTest; + + +@TestPropertySource(properties = { + "transport.lwm2m.dtls.connection_id_length=" +}) + +@DaoSqlTest +@Slf4j +public abstract class AbstractSecurityLwM2MIntegrationDtlsCidLengthNullTest extends AbstractSecurityLwM2MIntegrationDtlsCidLengthTest { + + + protected void testNoSecDtlsCidLength(Integer dtlsCidLength) throws Exception { + testNoSecDtlsCidLength(dtlsCidLength, null); + } + protected void testPskDtlsCidLength(Integer dtlsCidLength) throws Exception { + testPskDtlsCidLength(dtlsCidLength, null); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java new file mode 100644 index 0000000000..c743308fcd --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java @@ -0,0 +1,133 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.cid; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpoint; +import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointsProvider; +import org.eclipse.leshan.client.object.Security; +import org.eclipse.leshan.core.util.Hex; +import org.junit.Assert; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.device.credentials.lwm2m.AbstractLwM2MClientSecurityCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static org.awaitility.Awaitility.await; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH; +import static org.eclipse.leshan.client.object.Security.psk; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_READ_CONNECTION_ID; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_WRITE_CONNECTION_ID; + +@DaoSqlTest +@Slf4j +public abstract class AbstractSecurityLwM2MIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2MIntegrationTest { + + protected AbstractLwM2MClientSecurityCredential clientCredentials; + protected Security security; + protected Lwm2mDeviceProfileTransportConfiguration transportConfiguration; + protected LwM2MDeviceCredentials deviceCredentials; + protected String clientEndpoint; + protected LwM2MSecurityMode lwM2MSecurityMode; + protected String awaitAlias; + protected final Random randomSuffix = new Random(); + + protected final Set expectedStatusesRegistrationLwm2mDtlsCidSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS, ON_READ_CONNECTION_ID, ON_WRITE_CONNECTION_ID)); + + + protected void testNoSecDtlsCidLength(Integer dtlsCidLength, Integer serverDtlsCidLength) throws Exception { + initDeviceCredentialsNoSek(); + basicTestConnectionDtlsCidLength(dtlsCidLength, serverDtlsCidLength); + } + protected void testPskDtlsCidLength(Integer dtlsCidLength, Integer serverDtlsCidLength) throws Exception { + initDeviceCredentialsPsk(); + basicTestConnectionDtlsCidLength(dtlsCidLength, serverDtlsCidLength); + } + + protected void initDeviceCredentialsNoSek() { + clientEndpoint = CLIENT_ENDPOINT_NO_SEC + "_" + randomSuffix.nextInt(100); + deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); + } + + protected void initDeviceCredentialsPsk() { + int suf = randomSuffix.nextInt(10); + clientEndpoint = CLIENT_ENDPOINT_PSK + "_" + suf; + String identity = CLIENT_PSK_IDENTITY + "_" + suf; + clientCredentials = new PSKClientCredential(); + clientCredentials.setEndpoint(clientEndpoint); + ((PSKClientCredential)clientCredentials).setIdentity(identity); + clientCredentials.setKey(CLIENT_PSK_KEY); + security = psk(SECURE_URI, + shortServerId, + identity.getBytes(StandardCharsets.UTF_8), + Hex.decodeHex(CLIENT_PSK_KEY.toCharArray())); + deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); + } + + protected void basicTestConnectionDtlsCidLength(Integer clientDtlsCidLength, + Integer serverDtlsCidLength) throws Exception { + createDeviceProfile(transportConfiguration); + final Device device = createDevice(deviceCredentials, clientEndpoint); + device.getId().getId().toString(); + createNewClient(security, null, COAP_CONFIG, true, clientEndpoint, clientDtlsCidLength); + lwM2MTestClient.start(true); + await(awaitAlias) + .atMost(40, TimeUnit.SECONDS) + .until(() -> lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS)); + Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatusesRegistrationLwm2mSuccess)); + + Configuration clientCoapConfig = ((CaliforniumClientEndpoint)((CaliforniumClientEndpointsProvider)lwM2MTestClient + .getLeshanClient().getEndpointsProvider().toArray()[0]).getEndpoints().toArray()[0]).getCoapEndpoint().getConfig(); + Assert.assertEquals(clientDtlsCidLength, clientCoapConfig.get(DTLS_CONNECTION_ID_LENGTH)); + + if (security.equals(SECURITY_NO_SEC)) { + Assert.assertTrue(lwM2MTestClient.getClientDtlsCid().isEmpty()); + } else { + Assert.assertEquals(2L, lwM2MTestClient.getClientDtlsCid().size()); + Assert.assertTrue(lwM2MTestClient.getClientDtlsCid().keySet().contains(ON_READ_CONNECTION_ID)); + Assert.assertTrue(lwM2MTestClient.getClientDtlsCid().keySet().contains(ON_WRITE_CONNECTION_ID)); + if (serverDtlsCidLength == null) { + Assert.assertNull(lwM2MTestClient.getClientDtlsCid().get(ON_WRITE_CONNECTION_ID)); + Assert.assertNull(lwM2MTestClient.getClientDtlsCid().get(ON_READ_CONNECTION_ID)); + } else { + Assert.assertEquals(clientDtlsCidLength, lwM2MTestClient.getClientDtlsCid().get(ON_READ_CONNECTION_ID)); + if (clientDtlsCidLength == null) { + Assert.assertNull(lwM2MTestClient.getClientDtlsCid().get(ON_READ_CONNECTION_ID)); + } else { + Assert.assertEquals(Integer.valueOf(serverDtlsCidLength), lwM2MTestClient.getClientDtlsCid().get(ON_WRITE_CONNECTION_ID)); + } + } + } + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/NoSecLwM2MIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/NoSecLwM2MIntegrationDtlsCidLengthTest.java new file mode 100644 index 0000000000..f43ae3af0c --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/NoSecLwM2MIntegrationDtlsCidLengthTest.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.cid.serverDtlsCidLength_0; + +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.security.cid.AbstractSecurityLwM2MIntegrationDtlsCidLength0Test; + +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.NO_SEC; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; + +public class NoSecLwM2MIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2MIntegrationDtlsCidLength0Test { + + @Before + public void setUpNoSecDtlsCidLength() { + security = SECURITY_NO_SEC; + transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(NO_SEC, NONE)); + awaitAlias = "await on client state (NoSec_Lwm2m) DtlsCidLength = 0"; + } + + @Test + public void testWithNoSecConnectLwm2mSuccessClientDtlsCidLength_Null() throws Exception { + testNoSecDtlsCidLength(null); + } + + @Test + public void testWithNoSecConnectLwm2mSuccessClientDtlsCidLength_0() throws Exception { + testNoSecDtlsCidLength(0); + } + + @Test + public void testWithNoSecConnectLwm2mSuccessClientDtlsCidLength_2() throws Exception { + testNoSecDtlsCidLength(2); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/PskLwm2mIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/PskLwm2mIntegrationDtlsCidLengthTest.java new file mode 100644 index 0000000000..38010111fa --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/PskLwm2mIntegrationDtlsCidLengthTest.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.cid.serverDtlsCidLength_0; + +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.security.cid.AbstractSecurityLwM2MIntegrationDtlsCidLength0Test; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; + +public class PskLwm2mIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2MIntegrationDtlsCidLength0Test { + + @Before + public void createProfileRpc() { + transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(lwM2MSecurityMode, NONE)); + awaitAlias = "await on client state (Psk_Lwm2m) DtlsCidLength = 0"; + } + + @Test + public void testWithPskConnectLwm2mSuccessClientDtlsCidLength_Null() throws Exception { + testPskDtlsCidLength(null); + } + + @Test + public void testWithPskConnectLwm2mSuccessClientDtlsCidLength_0() throws Exception { + testPskDtlsCidLength(0); + } + + @Test + public void testWithPskConnectLwm2mSuccessClientDtlsCidLength_2() throws Exception { + testPskDtlsCidLength(2); + } +} + diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/NoSecLwM2MIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/NoSecLwM2MIntegrationDtlsCidLengthTest.java new file mode 100644 index 0000000000..377ccf420a --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/NoSecLwM2MIntegrationDtlsCidLengthTest.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.cid.serverDtlsCidLength_3; + +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.security.cid.AbstractSecurityLwM2MIntegrationDtlsCidLength3Test; + +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.NO_SEC; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; + +public class NoSecLwM2MIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2MIntegrationDtlsCidLength3Test { + + @Before + public void setUpNoSecDtlsCidLength() { + security = SECURITY_NO_SEC; + transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(NO_SEC, NONE)); + awaitAlias = "await on client state (NoSec_Lwm2m) DtlsCidLength = 3"; + } + + @Test + public void testWithNoSecConnectLwm2mSuccessClientDtlsCidLength_Null() throws Exception { + testNoSecDtlsCidLength(null); + } + + @Test + public void testWithNoSecConnectLwm2mSuccessClientDtlsCidLength_0() throws Exception { + testNoSecDtlsCidLength(0); + } + + @Test + public void testWithNoSecConnectLwm2mSuccessClientDtlsCidLength_2() throws Exception { + testNoSecDtlsCidLength(2); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/PskLwm2mIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/PskLwm2mIntegrationDtlsCidLengthTest.java new file mode 100644 index 0000000000..afe436cdbc --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/PskLwm2mIntegrationDtlsCidLengthTest.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.cid.serverDtlsCidLength_3; + +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.security.cid.AbstractSecurityLwM2MIntegrationDtlsCidLength3Test; + +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; + +public class PskLwm2mIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2MIntegrationDtlsCidLength3Test { + + @Before + public void createProfileRpc() { + transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(lwM2MSecurityMode, NONE)); + awaitAlias = "await on client state (Psk_Lwm2m) DtlsCidLength = 3"; + } + + @Test + public void testWithPskConnectLwm2mSuccessClientDtlsCidLength_Null() throws Exception { + testPskDtlsCidLength(null); + } + + @Test + public void testWithPskConnectLwm2mSuccessClientDtlsCidLength_0() throws Exception { + testPskDtlsCidLength(0); + } + + @Test + public void testWithPskConnectLwm2mSuccessClientDtlsCidLength_2() throws Exception { + testPskDtlsCidLength(2); + } +} + diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/NoSecLwM2MIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/NoSecLwM2MIntegrationDtlsCidLengthTest.java new file mode 100644 index 0000000000..d5531f6354 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/NoSecLwM2MIntegrationDtlsCidLengthTest.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.cid.serverDtlsCidLength_null; + +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.security.cid.AbstractSecurityLwM2MIntegrationDtlsCidLengthNullTest; + +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.NO_SEC; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; + +public class NoSecLwM2MIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2MIntegrationDtlsCidLengthNullTest { + + @Before + public void setUpNoSecDtlsCidLength() { + security = SECURITY_NO_SEC; + transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(NO_SEC, NONE)); + awaitAlias = "await on client state (NoSec_Lwm2m) DtlsCidLength = Null"; + } + + @Test + public void testWithNoSecConnectLwm2mSuccessClientDtlsCidLength_Null() throws Exception { + testNoSecDtlsCidLength(null); + } + + @Test + public void testWithNoSecConnectLwm2mSuccessClientDtlsCidLength_0() throws Exception { + testNoSecDtlsCidLength(0); + } + + @Test + public void testWithNoSecConnectLwm2mSuccessClientDtlsCidLength_2() throws Exception { + testNoSecDtlsCidLength(2); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/PskLwm2mIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/PskLwm2mIntegrationDtlsCidLengthTest.java new file mode 100644 index 0000000000..5c712f78d6 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/PskLwm2mIntegrationDtlsCidLengthTest.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.cid.serverDtlsCidLength_null; + +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.security.cid.AbstractSecurityLwM2MIntegrationDtlsCidLengthNullTest; + +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; + +public class PskLwm2mIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2MIntegrationDtlsCidLengthNullTest { + + @Before + public void createProfileRpc() { + transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(lwM2MSecurityMode, NONE)); + awaitAlias = "await on client state (Psk_Lwm2m) DtlsCidLength = Null"; + } + + @Test + public void testWithPskConnectLwm2mSuccessClientDtlsCidLength_Null() throws Exception { + testPskDtlsCidLength(null); + } + + @Test + public void testWithPskConnectLwm2mSuccessClientDtlsCidLength_0() throws Exception { + testPskDtlsCidLength(0); + } + + @Test + public void testWithPskConnectLwm2mSuccessClientDtlsCidLength_2() throws Exception { + testPskDtlsCidLength(2); + } +} + diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java index e9296f70c5..f27131b7ab 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java @@ -41,6 +41,8 @@ import java.util.Collections; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.eclipse.californium.elements.config.CertificateAuthenticationMode.WANTED; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_NODE_ID; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_ROLE; import static org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole.SERVER_ONLY; @@ -59,6 +61,9 @@ public class TbCoapDtlsSettings { @Value("${transport.coap.dtls.retransmission_timeout:9000}") private int dtlsRetransmissionTimeout; + @Value("${transport.coap.dtls.connection_id_length}") + private Integer cIdLength; + @Bean @ConfigurationProperties(prefix = "transport.coap.dtls.credentials") public SslCredentialsConfig coapDtlsCredentials() { @@ -93,6 +98,14 @@ public class TbCoapDtlsSettings { configBuilder.set(DTLS_CLIENT_AUTHENTICATION_MODE, WANTED); configBuilder.set(DTLS_RETRANSMISSION_TIMEOUT, dtlsRetransmissionTimeout, MILLISECONDS); configBuilder.set(DTLS_ROLE, SERVER_ONLY); + configBuilder.set(DTLS_CONNECTION_ID_LENGTH, cIdLength); + if (cIdLength != null) { + if (cIdLength > 4) { + configBuilder.set(DTLS_CONNECTION_ID_NODE_ID, 0); + } else { + configBuilder.set(DTLS_CONNECTION_ID_NODE_ID, null); + } + } configBuilder.setAdvancedCertificateVerifier( new TbCoapDtlsCertificateVerifier( transportService, diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index d6839ae394..c8e8baa30a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -49,6 +49,7 @@ import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMIS import static org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService.PSK_CIPHER_SUITES; import static org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService.RPK_OR_X509_CIPHER_SUITES; import static org.thingsboard.server.transport.lwm2m.server.LwM2MNetworkConfig.getCoapConfig; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.setDtlsConnectorConfigCidLength; @Slf4j @Component @@ -113,15 +114,15 @@ public class LwM2MTransportBootstrapService { serverCoapConfig.setTransient(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY); serverCoapConfig.set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, serverConfig.isRecommendedCiphers()); serverCoapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); - serverCoapConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, 6); serverCoapConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups()); serverCoapConfig.setTransient(DTLS_RETRANSMISSION_TIMEOUT); serverCoapConfig.set(DTLS_RETRANSMISSION_TIMEOUT, serverConfig.getDtlsRetransmissionTimeout(), MILLISECONDS); - + if (serverConfig.getDtlsCidLength() != null) { + setDtlsConnectorConfigCidLength( serverCoapConfig, serverConfig.getDtlsCidLength()); + } /* Create DTLS Config */ - this.setServerWithCredentials(builder); // Set Californium Configuration diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java index 4b9ea2e4ac..03c4730313 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java @@ -42,6 +42,10 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { @Value("${transport.lwm2m.dtls.retransmission_timeout:9000}") private int dtlsRetransmissionTimeout; + @Getter + @Value("${transport.lwm2m.dtls.connection_id_length:}") + private Integer dtlsCidLength; + @Getter @Value("${transport.lwm2m.timeout:}") private Long timeout; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 27bf6251a9..4bf9859b16 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -58,6 +58,7 @@ import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_W import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CCM_8; import static org.thingsboard.server.transport.lwm2m.server.LwM2MNetworkConfig.getCoapConfig; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_UPDATE_COAP_RESOURCE; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.setDtlsConnectorConfigCidLength; @Slf4j @Component @@ -165,21 +166,10 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { serverCoapConfig.set(DTLS_RETRANSMISSION_TIMEOUT, config.getDtlsRetransmissionTimeout(), MILLISECONDS); serverCoapConfig.set(DTLS_ROLE, SERVER_ONLY); serverCoapConfig.setTransient(DtlsConfig.DTLS_CONNECTION_ID_LENGTH); - /** - * "Control usage of DTLS connection ID.", - * If DTLS_CONNECTION_ID_LENGTH enables the use of a connection id, this node id could be used - * to configure the generation of connection ids specific for node in a multi-node deployment (cluster). - * The value is used as first byte in generated connection ids. - * DTLS connection ID length. disabled, - * 0 enables support without active use of CID.", defaultValue == null, minimumValue == 0); - * "- 'on' to activate Connection ID support ", - * " (same as -cid 6)", - * "- 'off' to deactivate it", - * "- Positive value define the size in byte of CID generated.", - * "- 0 value means we accept to use CID but will not generated one for foreign peer.", - */ - serverCoapConfig.set(DtlsConfig.DTLS_CONNECTION_ID_LENGTH, 6); + if (config.getDtlsCidLength() != null) { + setDtlsConnectorConfigCidLength( serverCoapConfig, config.getDtlsCidLength()); + } /* Create DTLS Config */ this.setServerWithCredentials(builder); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java index b74a668b6a..db8398cd57 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.utils; import com.google.gson.JsonElement; import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.elements.config.Configuration; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; @@ -55,6 +56,8 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_NODE_ID; import static org.eclipse.leshan.core.model.ResourceModel.Type.BOOLEAN; import static org.eclipse.leshan.core.model.ResourceModel.Type.FLOAT; import static org.eclipse.leshan.core.model.ResourceModel.Type.INTEGER; @@ -425,4 +428,15 @@ public class LwM2MTransportUtil { } return newValueStr.equals(oldValueStr); } + + public static void setDtlsConnectorConfigCidLength(Configuration serverCoapConfig, Integer cIdLength) { + serverCoapConfig.setTransient(DTLS_CONNECTION_ID_LENGTH); + serverCoapConfig.setTransient(DTLS_CONNECTION_ID_NODE_ID); + serverCoapConfig.set(DTLS_CONNECTION_ID_LENGTH, cIdLength); + if ( cIdLength > 4) { + serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, 0); + } else { + serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, null); + } + } } From a71f723e5e99e882a775984dfee6b35359804366 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 12 Feb 2024 15:49:59 +0200 Subject: [PATCH 138/209] lwm2m_coap: DTLS Cid Length - thongsboard3 --- application/src/main/resources/thingsboard.yml | 2 +- .../coap/src/main/resources/tb-coap-transport.yml | 11 +++++++++++ .../lwm2m/src/main/resources/tb-lwm2m-transport.yml | 10 ++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 0dc52a58f9..64af95857a 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1018,7 +1018,7 @@ transport: bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" - # Server DTLS credentials + # Server DTLS credentials # CoAP DTLS connection ID length. RFC 9146, Connection Identifier for DTLS 1.2 # Default: off # Control usage of DTLS connection ID length (CID). diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 7a3bd01fef..08ff4111c7 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -155,6 +155,17 @@ transport: # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" # Server DTLS credentials + # Server DTLS credentials + # CoAP DTLS connection ID length. RFC 9146, Connection Identifier for DTLS 1.2 + # Default: off + # Control usage of DTLS connection ID length (CID). + # - 'off' to deactivate it. + # - 'on' to activate Connection ID support (same as CID 0 or more 0). + # - A positive value defines generated CID size in bytes. + # - A value of 0 means we accept using CID but will not generate one for foreign peer (enables support but not for incoming traffic). + # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used + # - A value that are > 4: MultiNodeConnectionIdGenerator is used + connection_id_length: "${COAP_DTLS_CONNECTION_ID_LENGTH:}" credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) type: "${COAP_DTLS_CREDENTIALS_TYPE:PEM}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 4f0d4fce4c..01008d8d13 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -166,6 +166,16 @@ transport: dtls: # RFC7925_RETRANSMISSION_TIMEOUT_IN_MILLISECONDS = 9000 retransmission_timeout: "${LWM2M_DTLS_RETRANSMISSION_TIMEOUT_MS:9000}" + # CoAP DTLS connection ID length for LWM2M. RFC 9146, Connection Identifier for DTLS 1.2 + # Default: off + # Control usage of DTLS connection ID length (CID). + # - 'off' to deactivate it. + # - 'on' to activate Connection ID support (same as CID 0 or more 0). + # - A positive value defines generated CID size in bytes. + # - A value of 0 means we accept using CID but will not generate one for foreign peer (enables support but not for incoming traffic). + # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used + # - A value that are > 4: MultiNodeConnectionIdGenerator is used + connection_id_length: "${LWM2M_DTLS_CONNECTION_ID_LENGTH:}" server: # LwM2M Server ID id: "${LWM2M_SERVER_ID:123}" From 4b42139ae845a343c7aaddeccffa235801fca367 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 12 Feb 2024 16:13:40 +0200 Subject: [PATCH 139/209] lwm2m_coap: DTLS Cid Length - yml format --- .../src/main/resources/thingsboard.yml | 38 +++++++++---------- .../src/main/resources/tb-coap-transport.yml | 18 ++++----- .../src/main/resources/tb-lwm2m-transport.yml | 18 ++++----- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 64af95857a..a066cd7109 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1018,16 +1018,16 @@ transport: bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" - # Server DTLS credentials - # CoAP DTLS connection ID length. RFC 9146, Connection Identifier for DTLS 1.2 - # Default: off - # Control usage of DTLS connection ID length (CID). - # - 'off' to deactivate it. - # - 'on' to activate Connection ID support (same as CID 0 or more 0). - # - A positive value defines generated CID size in bytes. - # - A value of 0 means we accept using CID but will not generate one for foreign peer (enables support but not for incoming traffic). - # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used - # - A value that are > 4: MultiNodeConnectionIdGenerator is used + # Server DTLS credentials + # CoAP DTLS connection ID length. RFC 9146, Connection Identifier for DTLS 1.2 + # Default: off + # Control usage of DTLS connection ID length (CID). + # - 'off' to deactivate it. + # - 'on' to activate Connection ID support (same as CID 0 or more 0). + # - A positive value defines generated CID size in bytes. + # - A value of 0 means we accept using CID but will not generate one for foreign peer (enables support but not for incoming traffic). + # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used + # - A value that are > 4: MultiNodeConnectionIdGenerator is used connection_id_length: "${COAP_DTLS_CONNECTION_ID_LENGTH:}" credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -1066,15 +1066,15 @@ transport: dtls: # RFC7925_RETRANSMISSION_TIMEOUT_IN_MILLISECONDS = 9000 retransmission_timeout: "${LWM2M_DTLS_RETRANSMISSION_TIMEOUT_MS:9000}" - # CoAP DTLS connection ID length for LWM2M. RFC 9146, Connection Identifier for DTLS 1.2 - # Default: off - # Control usage of DTLS connection ID length (CID). - # - 'off' to deactivate it. - # - 'on' to activate Connection ID support (same as CID 0 or more 0). - # - A positive value defines generated CID size in bytes. - # - A value of 0 means we accept using CID but will not generate one for foreign peer (enables support but not for incoming traffic). - # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used - # - A value that are > 4: MultiNodeConnectionIdGenerator is used + # CoAP DTLS connection ID length for LWM2M. RFC 9146, Connection Identifier for DTLS 1.2 + # Default: off + # Control usage of DTLS connection ID length (CID). + # - 'off' to deactivate it. + # - 'on' to activate Connection ID support (same as CID 0 or more 0). + # - A positive value defines generated CID size in bytes. + # - A value of 0 means we accept using CID but will not generate one for foreign peer (enables support but not for incoming traffic). + # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used + # - A value that are > 4: MultiNodeConnectionIdGenerator is used connection_id_length: "${LWM2M_DTLS_CONNECTION_ID_LENGTH:}" server: # LwM2M Server ID diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 08ff4111c7..86ed3a269b 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -155,15 +155,15 @@ transport: # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" # Server DTLS credentials - # Server DTLS credentials - # CoAP DTLS connection ID length. RFC 9146, Connection Identifier for DTLS 1.2 - # Default: off - # Control usage of DTLS connection ID length (CID). - # - 'off' to deactivate it. - # - 'on' to activate Connection ID support (same as CID 0 or more 0). - # - A positive value defines generated CID size in bytes. - # - A value of 0 means we accept using CID but will not generate one for foreign peer (enables support but not for incoming traffic). - # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used + # Server DTLS credentials + # CoAP DTLS connection ID length. RFC 9146, Connection Identifier for DTLS 1.2 + # Default: off + # Control usage of DTLS connection ID length (CID). + # - 'off' to deactivate it. + # - 'on' to activate Connection ID support (same as CID 0 or more 0). + # - A positive value defines generated CID size in bytes. + # - A value of 0 means we accept using CID but will not generate one for foreign peer (enables support but not for incoming traffic). + # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used # - A value that are > 4: MultiNodeConnectionIdGenerator is used connection_id_length: "${COAP_DTLS_CONNECTION_ID_LENGTH:}" credentials: diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 01008d8d13..1070f17746 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -166,15 +166,15 @@ transport: dtls: # RFC7925_RETRANSMISSION_TIMEOUT_IN_MILLISECONDS = 9000 retransmission_timeout: "${LWM2M_DTLS_RETRANSMISSION_TIMEOUT_MS:9000}" - # CoAP DTLS connection ID length for LWM2M. RFC 9146, Connection Identifier for DTLS 1.2 - # Default: off - # Control usage of DTLS connection ID length (CID). - # - 'off' to deactivate it. - # - 'on' to activate Connection ID support (same as CID 0 or more 0). - # - A positive value defines generated CID size in bytes. - # - A value of 0 means we accept using CID but will not generate one for foreign peer (enables support but not for incoming traffic). - # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used - # - A value that are > 4: MultiNodeConnectionIdGenerator is used + # CoAP DTLS connection ID length for LWM2M. RFC 9146, Connection Identifier for DTLS 1.2 + # Default: off + # Control usage of DTLS connection ID length (CID). + # - 'off' to deactivate it. + # - 'on' to activate Connection ID support (same as CID 0 or more 0). + # - A positive value defines generated CID size in bytes. + # - A value of 0 means we accept using CID but will not generate one for foreign peer (enables support but not for incoming traffic). + # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used + # - A value that are > 4: MultiNodeConnectionIdGenerator is used connection_id_length: "${LWM2M_DTLS_CONNECTION_ID_LENGTH:}" server: # LwM2M Server ID From cb674048e7ddef34442513fadeab76f056a15281 Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 13 Feb 2024 14:27:18 +0200 Subject: [PATCH 140/209] lwm2m: client queue Mode --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 18 +++++++++++++++--- .../lwm2m/client/LwM2MTestClient.java | 7 +++---- .../lwm2m/ota/sql/OtaLwM2MIntegrationTest.java | 6 +++--- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 2 +- .../AbstractSecurityLwM2MIntegrationTest.java | 4 ++-- .../sql/NoSecLwM2MIntegrationTest.java | 8 +++++++- .../server/client/LwM2mClientContextImpl.java | 12 ++++++++---- 7 files changed, 39 insertions(+), 18 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 3d21a191a9..7b989a1997 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -219,7 +219,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public void basicTestConnectionObserveTelemetry(Security security, LwM2MDeviceCredentials deviceCredentials, Configuration coapConfig, - String endpoint) throws Exception { + String endpoint, + boolean queueMode) throws Exception { Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE)); createDeviceProfile(transportConfiguration); Device device = createDevice(deviceCredentials, endpoint); @@ -236,7 +237,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte getWsClient().waitForReply(); getWsClient().registerWaitForUpdate(); - createNewClient(security, null, coapConfig, false, endpoint, null); + createNewClient(security, null, coapConfig, false, endpoint, null, queueMode); deviceId = device.getId().getId().toString(); awaitObserveReadAll(0, deviceId); String msg = getWsClient().waitForUpdate(); @@ -304,15 +305,26 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte this.resources = resources; } + public void createNewClient(Security security, Security securityBs, Configuration coapConfig, boolean isRpc, + String endpoint) throws Exception { + this.createNewClient(security, securityBs, coapConfig, isRpc, endpoint, null, false); + } + public void createNewClient(Security security, Security securityBs, Configuration coapConfig, boolean isRpc, String endpoint, Integer clientDtlsCidLength) throws Exception { + this.createNewClient(security, securityBs, coapConfig, isRpc, endpoint, clientDtlsCidLength, false); + } + + public void createNewClient(Security security, Security securityBs, Configuration coapConfig, boolean isRpc, + String endpoint, Integer clientDtlsCidLength, boolean queueMode) throws Exception { this.clientDestroy(); lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint); try (ServerSocket socket = new ServerSocket(0)) { int clientPort = socket.getLocalPort(); lwM2MTestClient.init(security, securityBs, coapConfig, clientPort, isRpc, - this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, isWriteAttribute, clientDtlsCidLength); + this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, isWriteAttribute + , clientDtlsCidLength, queueMode); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index c3c1f5ff33..4363bca41c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -129,7 +129,7 @@ public class LwM2MTestClient { public void init(Security security, Security securityBs,Configuration coapConfig, int port, boolean isRpc, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, - LwM2mClientContext clientContext, boolean isWriteAttribute, Integer cIdLength) throws InvalidDDFFileException, IOException { + LwM2mClientContext clientContext, boolean isWriteAttribute, Integer cIdLength, boolean queueMode) throws InvalidDDFFileException, IOException { Assert.assertNull("client already initialized", leshanClient); this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler; this.clientContext = clientContext; @@ -263,11 +263,10 @@ public class LwM2MTestClient { boolean reconnectOnUpdate = false; engineFactory.setReconnectOnUpdate(reconnectOnUpdate); engineFactory.setResumeOnConnect(true); - // new + /** - * Client use queue mode (not fully implemented). + * Client use queue mode. */ - boolean queueMode = false; engineFactory.setQueueMode(queueMode); // Create client diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java index 17518e2ba3..7e812f9846 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java @@ -59,7 +59,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); - createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, null); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); awaitObserveReadAll(0, device.getId().getId().toString()); device.setFirmwareId(createFirmware().getId()); @@ -84,7 +84,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5); - createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5, null); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5); awaitObserveReadAll(9, device.getId().getId().toString()); device.setFirmwareId(createFirmware().getId()); @@ -114,7 +114,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9); - createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9, null); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9); awaitObserveReadAll(9, device.getId().getId().toString()); device.setSoftwareId(createSoftware().getId()); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index 3c5d536dcd..1c83bed8eb 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -91,7 +91,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg private void initRpc () throws Exception { String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet(); - createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, true, endpoint, null); + createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, true, endpoint); expectedObjects = ConcurrentHashMap.newKeySet(); expectedObjectIdVers = ConcurrentHashMap.newKeySet(); expectedInstances = ConcurrentHashMap.newKeySet(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java index b360918a9f..f164cb7fe7 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -196,7 +196,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M boolean isStartLw) throws Exception { createDeviceProfile(transportConfiguration); final Device device = createDevice(deviceCredentials, endpoint); - createNewClient(security, securityBs, coapConfig, true, endpoint, null); + createNewClient(security, securityBs, coapConfig, true, endpoint); lwM2MTestClient.start(isStartLw); if (isAwaitObserveReadAll) { awaitObserveReadAll(0, device.getId().getId().toString()); @@ -244,7 +244,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M createDeviceProfile(transportConfiguration); final Device device = createDevice(deviceCredentials, endpoint); String deviceIdStr = device.getId().getId().toString(); - createNewClient(security, securityBs, coapConfig, true, endpoint, null); + createNewClient(security, securityBs, coapConfig, true, endpoint); lwM2MTestClient.start(true); awaitObserveReadAll(0, deviceIdStr); await(awaitAlias) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java index 7389f5db27..7a34ec4283 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java @@ -32,7 +32,13 @@ public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationT public void testWithNoSecConnectLwm2mSuccessAndObserveTelemetry() throws Exception { String clientEndpoint = CLIENT_ENDPOINT_NO_SEC; LwM2MDeviceCredentials clientCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); - super.basicTestConnectionObserveTelemetry(SECURITY_NO_SEC, clientCredentials, COAP_CONFIG, clientEndpoint); + super.basicTestConnectionObserveTelemetry(SECURITY_NO_SEC, clientCredentials, COAP_CONFIG, clientEndpoint, false); + } + @Test + public void testWithNoSecQueueModeConnectLwm2mSuccessAndObserveTelemetry() throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_NO_SEC + "_QueueMode"; + LwM2MDeviceCredentials clientCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); + super.basicTestConnectionObserveTelemetry(SECURITY_NO_SEC, clientCredentials, COAP_CONFIG, clientEndpoint, true); } // Bootstrap + Lwm2m diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index 03449aaaef..fa3063befb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -461,11 +461,15 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { PowerMode powerMode = client.getPowerMode(); OtherConfiguration profileSettings = null; if (powerMode == null) { - var clientProfile = getProfile(client.getProfileId()); - profileSettings = clientProfile.getClientLwM2mSettings(); - powerMode = profileSettings.getPowerMode(); - if (powerMode == null) { + if (client.getProfileId() == null) { powerMode = PowerMode.DRX; + } else { + var clientProfile = getProfile(client.getProfileId()); + profileSettings = clientProfile.getClientLwM2mSettings(); + powerMode = profileSettings.getPowerMode(); + if (powerMode == null) { + powerMode = PowerMode.DRX; + } } } if (PowerMode.DRX.equals(powerMode)) { From 9cdcad8775562ada44d887ca9b3190bb33e9584d Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 13 Feb 2024 15:13:26 +0200 Subject: [PATCH 141/209] lwm2m/coap: client queue Mode --- .../coap/client/DefaultCoapClientContext.java | 16 ++++++------- .../server/client/LwM2mClientContextImpl.java | 24 ++++++------------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java index 7177542c3d..8dafc18a88 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java @@ -18,7 +18,6 @@ package org.thingsboard.server.transport.coap.client; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.CoAP.ResponseCode; import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.observe.ObserveRelation; import org.eclipse.californium.core.server.resources.CoapExchange; @@ -222,7 +221,7 @@ public class DefaultCoapClientContext implements CoapClientContext { private void onUplink(TbCoapClientState client, boolean notifyOtherServers, long uplinkTs) { PowerMode powerMode = client.getPowerMode(); PowerSavingConfiguration profileSettings = null; - if (powerMode == null) { + if (powerMode == null && client.getProfileId() != null) { var clientProfile = getProfile(client.getProfileId()); if (clientProfile.isPresent()) { profileSettings = clientProfile.get().getClientSettings(); @@ -736,7 +735,7 @@ public class DefaultCoapClientContext implements CoapClientContext { private boolean isDownlinkAllowed(TbCoapClientState client) { PowerMode powerMode = client.getPowerMode(); PowerSavingConfiguration profileSettings = null; - if (powerMode == null) { + if (powerMode == null && client.getProfileId() != null) { var clientProfile = getProfile(client.getProfileId()); if (clientProfile.isPresent()) { profileSettings = clientProfile.get().getClientSettings(); @@ -785,11 +784,12 @@ public class DefaultCoapClientContext implements CoapClientContext { private PowerMode getPowerMode(TbCoapClientState client) { PowerMode powerMode = client.getPowerMode(); if (powerMode == null) { - Optional deviceProfile = getProfile(client.getProfileId()); - if (deviceProfile.isPresent()) { - powerMode = deviceProfile.get().getClientSettings().getPowerMode(); - } else { - powerMode = PowerMode.PSM; + powerMode = PowerMode.PSM; + if (client.getProfileId() != null) { + Optional deviceProfile = getProfile(client.getProfileId()); + if (deviceProfile.isPresent()) { + powerMode = deviceProfile.get().getClientSettings().getPowerMode(); + } } } return powerMode; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index fa3063befb..4f35489250 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -411,15 +411,12 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { public boolean isDownlinkAllowed(LwM2mClient client) { PowerMode powerMode = client.getPowerMode(); OtherConfiguration profileSettings = null; - if (powerMode == null) { + if (powerMode == null && client.getProfileId() != null) { var clientProfile = getProfile(client.getProfileId()); profileSettings = clientProfile.getClientLwM2mSettings(); powerMode = profileSettings.getPowerMode(); - if (powerMode == null) { - powerMode = PowerMode.DRX; - } } - if (PowerMode.DRX.equals(powerMode) || otaUpdateService.isOtaDownloading(client)) { + if (powerMode == null || PowerMode.DRX.equals(powerMode) || otaUpdateService.isOtaDownloading(client)) { return true; } client.lock(); @@ -460,19 +457,12 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { public void onUplink(LwM2mClient client) { PowerMode powerMode = client.getPowerMode(); OtherConfiguration profileSettings = null; - if (powerMode == null) { - if (client.getProfileId() == null) { - powerMode = PowerMode.DRX; - } else { - var clientProfile = getProfile(client.getProfileId()); - profileSettings = clientProfile.getClientLwM2mSettings(); - powerMode = profileSettings.getPowerMode(); - if (powerMode == null) { - powerMode = PowerMode.DRX; - } - } + if (powerMode == null && client.getProfileId() != null) { + var clientProfile = getProfile(client.getProfileId()); + profileSettings = clientProfile.getClientLwM2mSettings(); + powerMode = profileSettings.getPowerMode(); } - if (PowerMode.DRX.equals(powerMode)) { + if (powerMode == null || PowerMode.DRX.equals(powerMode)) { client.updateLastUplinkTime(); return; } From 85f441c7c040ccc52b5da0f34350d57aaf95d589 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 14 Feb 2024 14:49:30 +0200 Subject: [PATCH 142/209] lwm2m: if client after registration change port: update registration - ok --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 5 +- .../AbstractSecurityLwM2MIntegrationTest.java | 33 +++++++ ...rityLwM2MIntegrationDtlsCidLengthTest.java | 48 --------- ...oSecLwM2MIntegrationDtlsCidLengthTest.java | 1 - .../PskLwm2mIntegrationDtlsCidLengthTest.java | 4 +- ...oSecLwM2MIntegrationDtlsCidLengthTest.java | 1 - .../PskLwm2mIntegrationDtlsCidLengthTest.java | 3 +- ...oSecLwM2MIntegrationDtlsCidLengthTest.java | 1 - .../PskLwm2mIntegrationDtlsCidLengthTest.java | 3 +- .../AbstractLwM2MIntegrationDiffPortTest.java | 99 +++++++++++++++++++ .../LwM2MIntegrationDiffPortTest.java | 45 +++++++++ .../store/TbInMemoryRegistrationStore.java | 13 ++- 12 files changed, 193 insertions(+), 63 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/LwM2MIntegrationDiffPortTest.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 7b989a1997..1eedd74608 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -97,6 +97,8 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClient import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_STARTED; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; @@ -172,9 +174,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte " \"pagingTransmissionWindow\": null,\n" + " \"clientOnlyObserveAfterConnect\": 1\n" + " }"; - - protected final Set expectedStatusesBsSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS)); protected final Set expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); + protected final Set expectedStatusesRegistrationLwm2mSuccessUpdate = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS, ON_UPDATE_STARTED, ON_UPDATE_SUCCESS)); protected final Set expectedStatusesRegistrationBsSuccess = new HashSet<>(Arrays.asList(ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); protected DeviceProfile deviceProfile; protected ScheduledExecutorService executor; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java index f164cb7fe7..f66d4ef4b8 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -26,6 +26,7 @@ import org.junit.Assert; import org.springframework.test.web.servlet.MvcResult; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.device.credentials.lwm2m.AbstractLwM2MClientSecurityCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; @@ -50,6 +51,7 @@ import org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootst import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.PrivateKey; @@ -59,13 +61,16 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Random; import java.util.Set; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; import static org.eclipse.leshan.client.object.Security.noSecBootstrap; +import static org.eclipse.leshan.client.object.Security.psk; import static org.junit.Assert.assertEquals; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; @@ -118,6 +123,13 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials; + protected AbstractLwM2MClientSecurityCredential clientCredentials; + protected Security security; + protected Lwm2mDeviceProfileTransportConfiguration transportConfiguration; + protected LwM2MDeviceCredentials deviceCredentials; + protected String clientEndpoint; + protected final Random randomSuffix = new Random(); + public AbstractSecurityLwM2MIntegrationTest() { // create client credentials @@ -381,6 +393,27 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M return bootstrapCredentials; } + + protected void initDeviceCredentialsNoSek() { + clientEndpoint = CLIENT_ENDPOINT_NO_SEC + "_" + randomSuffix.nextInt(100); + security = SECURITY_NO_SEC; + deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); + } + protected void initDeviceCredentialsPsk() { + int suf = randomSuffix.nextInt(10); + clientEndpoint = CLIENT_ENDPOINT_PSK + "_" + suf; + String identity = CLIENT_PSK_IDENTITY + "_" + suf; + clientCredentials = new PSKClientCredential(); + clientCredentials.setEndpoint(clientEndpoint); + ((PSKClientCredential)clientCredentials).setIdentity(identity); + clientCredentials.setKey(CLIENT_PSK_KEY); + security = psk(SECURE_URI, + shortServerId, + identity.getBytes(StandardCharsets.UTF_8), + Hex.decodeHex(CLIENT_PSK_KEY.toCharArray())); + deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); + } + private LwM2MBootstrapClientCredentials getBootstrapClientCredentialsRpk(X509Certificate certificate, PrivateKey privateKey, boolean privateKeyIsBad) { LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials(); RPKBootstrapClientCredential serverCredentials = new RPKBootstrapClientCredential(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java index c743308fcd..f44a2b1874 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java @@ -19,34 +19,16 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.config.Configuration; import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpoint; import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointsProvider; -import org.eclipse.leshan.client.object.Security; -import org.eclipse.leshan.core.util.Hex; import org.junit.Assert; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.device.credentials.lwm2m.AbstractLwM2MClientSecurityCredential; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; -import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; -import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Random; -import java.util.Set; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH; -import static org.eclipse.leshan.client.object.Security.psk; -import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_READ_CONNECTION_ID; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_WRITE_CONNECTION_ID; @@ -54,17 +36,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClient @Slf4j public abstract class AbstractSecurityLwM2MIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2MIntegrationTest { - protected AbstractLwM2MClientSecurityCredential clientCredentials; - protected Security security; - protected Lwm2mDeviceProfileTransportConfiguration transportConfiguration; - protected LwM2MDeviceCredentials deviceCredentials; - protected String clientEndpoint; - protected LwM2MSecurityMode lwM2MSecurityMode; protected String awaitAlias; - protected final Random randomSuffix = new Random(); - - protected final Set expectedStatusesRegistrationLwm2mDtlsCidSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS, ON_READ_CONNECTION_ID, ON_WRITE_CONNECTION_ID)); - protected void testNoSecDtlsCidLength(Integer dtlsCidLength, Integer serverDtlsCidLength) throws Exception { initDeviceCredentialsNoSek(); @@ -75,26 +47,6 @@ public abstract class AbstractSecurityLwM2MIntegrationDtlsCidLengthTest extends basicTestConnectionDtlsCidLength(dtlsCidLength, serverDtlsCidLength); } - protected void initDeviceCredentialsNoSek() { - clientEndpoint = CLIENT_ENDPOINT_NO_SEC + "_" + randomSuffix.nextInt(100); - deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); - } - - protected void initDeviceCredentialsPsk() { - int suf = randomSuffix.nextInt(10); - clientEndpoint = CLIENT_ENDPOINT_PSK + "_" + suf; - String identity = CLIENT_PSK_IDENTITY + "_" + suf; - clientCredentials = new PSKClientCredential(); - clientCredentials.setEndpoint(clientEndpoint); - ((PSKClientCredential)clientCredentials).setIdentity(identity); - clientCredentials.setKey(CLIENT_PSK_KEY); - security = psk(SECURE_URI, - shortServerId, - identity.getBytes(StandardCharsets.UTF_8), - Hex.decodeHex(CLIENT_PSK_KEY.toCharArray())); - deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); - } - protected void basicTestConnectionDtlsCidLength(Integer clientDtlsCidLength, Integer serverDtlsCidLength) throws Exception { createDeviceProfile(transportConfiguration); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/NoSecLwM2MIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/NoSecLwM2MIntegrationDtlsCidLengthTest.java index f43ae3af0c..8ce9053e5e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/NoSecLwM2MIntegrationDtlsCidLengthTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/NoSecLwM2MIntegrationDtlsCidLengthTest.java @@ -26,7 +26,6 @@ public class NoSecLwM2MIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2 @Before public void setUpNoSecDtlsCidLength() { - security = SECURITY_NO_SEC; transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(NO_SEC, NONE)); awaitAlias = "await on client state (NoSec_Lwm2m) DtlsCidLength = 0"; } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/PskLwm2mIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/PskLwm2mIntegrationDtlsCidLengthTest.java index 38010111fa..1d73025edc 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/PskLwm2mIntegrationDtlsCidLengthTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_0/PskLwm2mIntegrationDtlsCidLengthTest.java @@ -18,13 +18,15 @@ package org.thingsboard.server.transport.lwm2m.security.cid.serverDtlsCidLength_ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.transport.lwm2m.security.cid.AbstractSecurityLwM2MIntegrationDtlsCidLength0Test; + +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; public class PskLwm2mIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2MIntegrationDtlsCidLength0Test { @Before public void createProfileRpc() { - transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(lwM2MSecurityMode, NONE)); + transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); awaitAlias = "await on client state (Psk_Lwm2m) DtlsCidLength = 0"; } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/NoSecLwM2MIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/NoSecLwM2MIntegrationDtlsCidLengthTest.java index 377ccf420a..932632a359 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/NoSecLwM2MIntegrationDtlsCidLengthTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/NoSecLwM2MIntegrationDtlsCidLengthTest.java @@ -26,7 +26,6 @@ public class NoSecLwM2MIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2 @Before public void setUpNoSecDtlsCidLength() { - security = SECURITY_NO_SEC; transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(NO_SEC, NONE)); awaitAlias = "await on client state (NoSec_Lwm2m) DtlsCidLength = 3"; } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/PskLwm2mIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/PskLwm2mIntegrationDtlsCidLengthTest.java index afe436cdbc..6049b57f77 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/PskLwm2mIntegrationDtlsCidLengthTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_3/PskLwm2mIntegrationDtlsCidLengthTest.java @@ -19,13 +19,14 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.transport.lwm2m.security.cid.AbstractSecurityLwM2MIntegrationDtlsCidLength3Test; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; public class PskLwm2mIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2MIntegrationDtlsCidLength3Test { @Before public void createProfileRpc() { - transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(lwM2MSecurityMode, NONE)); + transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); awaitAlias = "await on client state (Psk_Lwm2m) DtlsCidLength = 3"; } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/NoSecLwM2MIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/NoSecLwM2MIntegrationDtlsCidLengthTest.java index d5531f6354..d8c7c67b2b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/NoSecLwM2MIntegrationDtlsCidLengthTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/NoSecLwM2MIntegrationDtlsCidLengthTest.java @@ -26,7 +26,6 @@ public class NoSecLwM2MIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2 @Before public void setUpNoSecDtlsCidLength() { - security = SECURITY_NO_SEC; transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(NO_SEC, NONE)); awaitAlias = "await on client state (NoSec_Lwm2m) DtlsCidLength = Null"; } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/PskLwm2mIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/PskLwm2mIntegrationDtlsCidLengthTest.java index 5c712f78d6..6fefaf7622 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/PskLwm2mIntegrationDtlsCidLengthTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/serverDtlsCidLength_null/PskLwm2mIntegrationDtlsCidLengthTest.java @@ -19,13 +19,14 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.transport.lwm2m.security.cid.AbstractSecurityLwM2MIntegrationDtlsCidLengthNullTest; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; public class PskLwm2mIntegrationDtlsCidLengthTest extends AbstractSecurityLwM2MIntegrationDtlsCidLengthNullTest { @Before public void createProfileRpc() { - transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(lwM2MSecurityMode, NONE)); + transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); awaitAlias = "await on client state (Psk_Lwm2m) DtlsCidLength = Null"; } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java new file mode 100644 index 0000000000..89ff598bc2 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java @@ -0,0 +1,99 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.diffPort; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.peer.IpPeer; +import org.eclipse.leshan.core.peer.LwM2mPeer; +import org.eclipse.leshan.core.peer.SocketIdentity; +import org.eclipse.leshan.server.registration.RegistrationStore; +import org.eclipse.leshan.server.registration.RegistrationUpdate; +import org.junit.Assert; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; + +import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; + +@DaoSqlTest +@Slf4j +public abstract class AbstractLwM2MIntegrationDiffPortTest extends AbstractSecurityLwM2MIntegrationTest { + + @SpyBean + private RegistrationStore registrationStoreTest; + + protected void basicTestConnectionDifferentPort(Lwm2mDeviceProfileTransportConfiguration transportConfiguration, + String awaitAlias) throws Exception { + + createDeviceProfile(transportConfiguration); + createDevice(deviceCredentials, clientEndpoint); + createNewClient(security, null, COAP_CONFIG, true, clientEndpoint); + lwM2MTestClient.start(true); + await(awaitAlias) + .atMost(40, TimeUnit.SECONDS) + .until(() -> lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_SUCCESS) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED)); + Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatusesRegistrationLwm2mSuccess)); + + await(awaitAlias) + .atMost(40, TimeUnit.SECONDS) + .until(() -> { + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] arguments = invocation.getArguments(); + if (arguments.length > 0 && arguments[0] instanceof RegistrationUpdate) { + int portOld = ((RegistrationUpdate) arguments[0]).getPort(); + int portValueChange = 5; + arguments[0] = registrationUpdateNewPort((RegistrationUpdate) arguments[0], portValueChange); + int portNew = ((RegistrationUpdate) arguments[0]).getPort(); + Assert.assertEquals((portNew - portOld), portValueChange); + } + return invocation.callRealMethod(); + } + }).when(registrationStoreTest).updateRegistration(any(RegistrationUpdate.class)); + return lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS); + }); + Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatusesRegistrationLwm2mSuccessUpdate)); + } + + private RegistrationUpdate registrationUpdateNewPort (RegistrationUpdate update, int portValueChange) { + Integer portOld = update.getPort(); + Integer portNew = portOld + portValueChange; + log.warn("portOld: [{}], portNew: [{}]", portOld, portNew); + InetAddress addressOld = update.getAddress(); + InetSocketAddress socketAddressUpdate = new InetSocketAddress(addressOld, portNew); + SocketIdentity socketIdentity = new SocketIdentity(socketAddressUpdate); + LwM2mPeer sender = new IpPeer(new InetSocketAddress(addressOld, portNew), socketIdentity); + return new RegistrationUpdate(update.getRegistrationId(), sender, + update.getLifeTimeInSec(), update.getSmsNumber(), update.getBindingMode(), + update.getObjectLinks(), update.getAlternatePath(), + update.getSupportedContentFormats(), update.getSupportedObjects(), + update.getAvailableInstances(), update.getAdditionalAttributes(), + update.getApplicationData()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/LwM2MIntegrationDiffPortTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/LwM2MIntegrationDiffPortTest.java new file mode 100644 index 0000000000..204bc70b5f --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/LwM2MIntegrationDiffPortTest.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.security.diffPort; + +import org.junit.Test; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; + +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.NO_SEC; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; + +public class LwM2MIntegrationDiffPortTest extends AbstractLwM2MIntegrationDiffPortTest { + + @Test + public void testWithNoSecConnectLwm2mSuccess_AfterRegistration_UpdateRegistrationFromDifferentPort_Ok() throws Exception { + String awaitAlias = "await on client state (NoSec different port)"; + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(NO_SEC, NONE)); + initDeviceCredentialsNoSek(); + basicTestConnectionDifferentPort( + transportConfiguration, + awaitAlias); + } + @Test + public void testWithPskConnectLwm2mSuccess_AfterRegistration_UpdateRegistrationFromDifferentPort_Ok() throws Exception { + String awaitAlias = "await on client state (Psk different port)"; + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); + initDeviceCredentialsPsk(); + basicTestConnectionDifferentPort( + transportConfiguration, + awaitAlias); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java index a9f65ff0bf..60afd76ffb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.store; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.Token; import org.eclipse.californium.core.network.RandomTokenGenerator; import org.eclipse.californium.core.network.TokenGenerator; @@ -40,8 +41,6 @@ import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.UpdatedRegistration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider; @@ -68,8 +67,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import static org.eclipse.leshan.core.californium.ObserveUtil.CTX_CF_OBERSATION; import static org.eclipse.leshan.core.californium.ObserveUtil.extractSerializedObservation; +@Slf4j public class TbInMemoryRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable { - private final Logger LOG = LoggerFactory.getLogger(TbInMemoryRegistrationStore.class); // Data structure private final Map regsByEp = new HashMap<>(); @@ -339,10 +338,10 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable if (addIfAbsent && previousObservation != null) { if (!existingObservation.getPath().equals(observation.getPath())) { removed.add(previousObservation); - LOG.warn("Token collision ? observation [{}] will be replaced by observation [{}], that this observation includes input observation [{}]!", + log.warn("Token collision ? observation [{}] will be replaced by observation [{}], that this observation includes input observation [{}]!", previousObservation, observation, observation); } else { - LOG.warn("Token collision ? existing observation [{}] includes input observation [{}]", + log.warn("Token collision ? existing observation [{}] includes input observation [{}]", existingObservation, observation); } } @@ -576,7 +575,7 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable try { schedExecutor.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { - LOG.warn("Destroying InMemoryRegistrationStore was interrupted.", e); + log.warn("Destroying InMemoryRegistrationStore was interrupted.", e); } } @@ -602,7 +601,7 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable } } } catch (Exception e) { - LOG.warn("Unexpected Exception while registration cleaning", e); + log.warn("Unexpected Exception while registration cleaning", e); } } } From 3c306a2e703d4ce3750e260a0f1c49ee62883b3e Mon Sep 17 00:00:00 2001 From: Armin Felder Date: Wed, 14 Feb 2024 23:23:25 +0100 Subject: [PATCH 143/209] update logback to 1.4.14 to allow for json logging --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9079dc08e9..ba3ac35524 100755 --- a/pom.xml +++ b/pom.xml @@ -52,7 +52,7 @@ 0.9.1 2.0.7 2.19.0 - 1.4.7 + 1.4.14 0.10 4.15.0 4.0.5 From a4965b0ba618882b73a4d02ac570bda1d65e4620 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 15 Feb 2024 12:52:40 +0200 Subject: [PATCH 144/209] Fix scheduler time unit for mail refresh token check --- .../server/service/mail/RefreshTokenExpCheckService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/RefreshTokenExpCheckService.java b/application/src/main/java/org/thingsboard/server/service/mail/RefreshTokenExpCheckService.java index 56f3c4c4c5..9cb3fc01d7 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/RefreshTokenExpCheckService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/RefreshTokenExpCheckService.java @@ -35,6 +35,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import java.io.IOException; import java.time.Duration; import java.time.Instant; +import java.util.concurrent.TimeUnit; import static org.thingsboard.server.common.data.mail.MailOauth2Provider.OFFICE_365; @@ -46,7 +47,9 @@ public class RefreshTokenExpCheckService { public static final int AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS = 90; private final AdminSettingsService adminSettingsService; - @Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${mail.oauth2.refreshTokenCheckingInterval})}", fixedDelayString = "${mail.oauth2.refreshTokenCheckingInterval}") + @Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${mail.oauth2.refreshTokenCheckingInterval})}", + fixedDelayString = "${mail.oauth2.refreshTokenCheckingInterval}", + timeUnit = TimeUnit.SECONDS) public void check() throws IOException { AdminSettings settings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"); if (settings != null && settings.getJsonValue().has("enableOauth2") && settings.getJsonValue().get("enableOauth2").asBoolean()) { @@ -65,8 +68,8 @@ public class RefreshTokenExpCheckService { new GenericUrl(tokenUri), refreshToken) .setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret)) .execute(); - ((ObjectNode)jsonValue).put("refreshToken", tokenResponse.getRefreshToken()); - ((ObjectNode)jsonValue).put("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli()); + ((ObjectNode) jsonValue).put("refreshToken", tokenResponse.getRefreshToken()); + ((ObjectNode) jsonValue).put("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli()); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, settings); } } From 7ba158b2bc2fa3d81c9b6dc40e90998d0b63f508 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 16 Feb 2024 12:46:30 +0200 Subject: [PATCH 145/209] fix_bug: any(AttributeScope.class) --- .../rule/engine/geo/TbGpsGeofencingActionNodeTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java index 5cd7e02c59..a0b57ba948 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java @@ -30,6 +30,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.msg.TbMsgType; @@ -137,7 +138,7 @@ class TbGpsGeofencingActionNodeTest extends AbstractRuleNodeUpgradeTest { node.onMsg(ctx, msg); // THEN - verify(ctx.getAttributesService(), never()).find(any(), any(), any(), anyString()); + verify(ctx.getAttributesService(), never()).find(any(), any(), any(AttributeScope.class), anyString()); verify(ctx, never()).tellFailure(any(), any(Throwable.class)); verify(ctx, never()).enqueueForTellNext(any(), eq(expectedOutput), any(), any()); verify(ctx, never()).ack(any()); From 03641bc486bf9bda067c4f1f1f567bab4cb3d3bc Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 16 Feb 2024 13:46:08 +0200 Subject: [PATCH 146/209] fix_bug: delete deprecated Base64Utils --- .../server/controller/ImageController.java | 6 +++--- .../sql/X509_NoTrustLwM2MIntegrationTest.java | 10 +++++----- .../lwm2m/utils/LwM2mValueConverterImpl.java | 4 ++-- .../server/common/transport/util/SslUtil.java | 10 +++++----- .../server/dao/resource/BaseImageService.java | 13 ++++++------- .../validator/DeviceProfileDataValidator.java | 4 ++-- .../thingsboard/rule/engine/rest/TbHttpClient.java | 4 ++-- 7 files changed, 25 insertions(+), 26 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/ImageController.java b/application/src/main/java/org/thingsboard/server/controller/ImageController.java index f569f49b54..c8d24d6081 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ImageController.java +++ b/application/src/main/java/org/thingsboard/server/controller/ImageController.java @@ -27,7 +27,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.util.Base64Utils; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -60,6 +59,7 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import java.util.Base64; import java.util.concurrent.TimeUnit; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; @@ -196,7 +196,7 @@ public class ImageController extends BaseController { .resourceKey(imageInfo.getResourceKey()) .isPublic(imageInfo.isPublic()) .publicResourceKey(imageInfo.getPublicResourceKey()) - .data(Base64Utils.encodeToString(data)) + .data(Base64.getEncoder().encodeToString(data)) .build(); } @@ -221,7 +221,7 @@ public class ImageController extends BaseController { ImageDescriptor descriptor = new ImageDescriptor(); descriptor.setMediaType(imageData.getMediaType()); image.setDescriptorValue(descriptor); - image.setData(Base64Utils.decodeFromString(imageData.getData())); + image.setData(Base64.getDecoder().decode(imageData.getData())); return tbImageService.save(image, user); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java index 32db6538f6..ea627cbfad 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java @@ -15,19 +15,19 @@ */ package org.thingsboard.server.transport.lwm2m.security.sql; +import jakarta.servlet.http.HttpServletResponse; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.util.Hex; import org.junit.Test; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.Base64Utils; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; -import jakarta.servlet.http.HttpServletResponse; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.util.Base64; import static org.eclipse.leshan.client.object.Security.x509; import static org.eclipse.leshan.client.object.Security.x509Bootstrap; @@ -48,7 +48,7 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg PrivateKey privateKey = clientPrivateKeyFromCertTrustNo; X509ClientCredential clientCredentials = new X509ClientCredential(); clientCredentials.setEndpoint(clientEndpoint); - clientCredentials.setCert(Base64Utils.encodeToString(certificate.getEncoded())); + clientCredentials.setCert(Base64.getEncoder().encodeToString(certificate.getEncoded())); Security security = x509(SECURE_URI, shortServerId, certificate.getEncoded(), @@ -93,7 +93,7 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg PrivateKey privateKey = clientPrivateKeyFromCertTrustNo; X509ClientCredential clientCredentials = new X509ClientCredential(); clientCredentials.setEndpoint(clientEndpoint); - clientCredentials.setCert(Base64Utils.encodeToString(certificate.getEncoded())); + clientCredentials.setCert(Base64.getEncoder().encodeToString(certificate.getEncoded())); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, true); createDeviceProfile(transportConfiguration); @@ -111,7 +111,7 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg PrivateKey privateKey = clientPrivateKeyFromCertTrustNo; X509ClientCredential clientCredentials = new X509ClientCredential(); clientCredentials.setEndpoint(clientEndpoint); - clientCredentials.setCert(Base64Utils.encodeToString(certificate.getEncoded())); + clientCredentials.setCert(Base64.getEncoder().encodeToString(certificate.getEncoded())); Security security = x509Bootstrap(SECURE_URI_BS, certificate.getEncoded(), privateKey.getEncoded(), diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java index eb01c7d2a4..3125ae63f5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.utils; -import com.google.api.client.util.Base64; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mPath; @@ -28,6 +27,7 @@ import org.thingsboard.server.common.data.StringUtils; import java.math.BigInteger; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Base64; import java.util.Date; import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; @@ -172,7 +172,7 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { return Hex.decodeHex(((String)value).toCharArray()); } catch (IllegalArgumentException e) { try { - return Base64.decodeBase64(((String) value).getBytes()); + return Base64.getDecoder().decode(((String) value).getBytes()); } catch (IllegalArgumentException ea) { throw new CodecException("Unable to convert hexastring or base64 [%s] to byte array for resource %s", value, resourcePath); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java index 8c75821c0d..0e02d1e161 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java @@ -16,13 +16,11 @@ package org.thingsboard.server.common.transport.util; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.codec.binary.Base64; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x500.style.IETFUtils; import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; -import org.springframework.util.Base64Utils; import org.thingsboard.server.common.msg.EncryptionUtil; import java.io.ByteArrayInputStream; @@ -31,6 +29,8 @@ import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.Base64; + /** * @author Valerii Sosliuk @@ -43,7 +43,7 @@ public class SslUtil { public static String getCertificateString(Certificate cert) throws CertificateEncodingException { - return EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded())); + return EncryptionUtil.certTrimNewLines(Base64.getEncoder().encodeToString(cert.getEncoded())); } public static String getCertificateChainString(Certificate[] chain) @@ -52,7 +52,7 @@ public class SslUtil { String end = "-----END CERTIFICATE-----"; StringBuilder stringBuilder = new StringBuilder(); for (Certificate cert: chain) { - stringBuilder.append(begin).append(EncryptionUtil.certTrimNewLines(Base64Utils.encodeToString(cert.getEncoded()))).append(end).append("\n"); + stringBuilder.append(begin).append(EncryptionUtil.certTrimNewLines(Base64.getEncoder().encodeToString(cert.getEncoded()))).append(end).append("\n"); } return stringBuilder.toString(); } @@ -64,7 +64,7 @@ public class SslUtil { fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "") .replace("-----END CERTIFICATE-----", "") .replaceAll("\\s", ""); - byte[] decoded = Base64.decodeBase64(fileContent); + byte[] decoded = Base64.getDecoder().decode(fileContent); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); try (InputStream inStream = new ByteArrayInputStream(decoded)) { certificate = (X509Certificate) certFactory.generateCertificate(inStream); diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java index c7ac699355..ef05229124 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Strings; +import jakarta.annotation.PostConstruct; import lombok.Data; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -29,7 +30,6 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.Base64Utils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DataConstants; @@ -60,7 +60,6 @@ import org.thingsboard.server.dao.util.JsonPathProcessingTask; import org.thingsboard.server.dao.widget.WidgetTypeDao; import org.thingsboard.server.dao.widget.WidgetsBundleDao; -import jakarta.annotation.PostConstruct; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Collections; @@ -432,8 +431,8 @@ public class BaseImageService extends BaseResourceService implements ImageServic String mdResourceName = null; String mdMediaType; if (matches) { - mdResourceKey = new String(Base64Utils.decodeFromString(matcher.group(1)), StandardCharsets.UTF_8); - mdResourceName = new String(Base64Utils.decodeFromString(matcher.group(2)), StandardCharsets.UTF_8); + mdResourceKey = new String(Base64.getDecoder().decode(matcher.group(1)), StandardCharsets.UTF_8); + mdResourceName = new String(Base64.getDecoder().decode(matcher.group(2)), StandardCharsets.UTF_8); mdMediaType = matcher.group(3); } else if (data.startsWith(DataConstants.TB_IMAGE_PREFIX + "data:image/") || (!strict && data.startsWith("data:image/"))) { mdMediaType = StringUtils.substringBetween(data, "data:", ";base64"); @@ -623,10 +622,10 @@ public class BaseImageService extends BaseResourceService implements ImageServic ImageDescriptor descriptor = getImageDescriptor(imageInfo, key.isPreview()); String tbImagePrefix = ""; if (addTbImagePrefix) { - tbImagePrefix = "tb-image:" + Base64Utils.encodeToString(imageInfo.getResourceKey().getBytes(StandardCharsets.UTF_8)) + ":" - + Base64Utils.encodeToString(imageInfo.getName().getBytes(StandardCharsets.UTF_8)) + ";"; + tbImagePrefix = "tb-image:" + Base64.getEncoder().encodeToString(imageInfo.getResourceKey().getBytes(StandardCharsets.UTF_8)) + ":" + + Base64.getEncoder().encodeToString(imageInfo.getName().getBytes(StandardCharsets.UTF_8)) + ";"; } - return tbImagePrefix + "data:" + descriptor.getMediaType() + ";base64," + Base64Utils.encodeToString(data); + return tbImagePrefix + "data:" + descriptor.getMediaType() + ";base64," + Base64.getEncoder().encodeToString(data); } } } catch (Exception e) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 6e660e3f06..0a8d78fb86 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -23,7 +23,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; -import org.springframework.util.Base64Utils; import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DeviceProfile; @@ -64,6 +63,7 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.PKIXParameters; import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; +import java.util.Base64; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -411,6 +411,6 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator Date: Mon, 19 Feb 2024 15:23:10 +0200 Subject: [PATCH 147/209] lwm2m: refactoring tests --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 20 +++++++------------ .../lwm2m/client/LwM2MTestClient.java | 3 +-- .../ota/sql/OtaLwM2MIntegrationTest.java | 6 +++--- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 2 +- .../AbstractSecurityLwM2MIntegrationTest.java | 9 ++------- ...rityLwM2MIntegrationDtlsCidLengthTest.java | 2 +- .../AbstractLwM2MIntegrationDiffPortTest.java | 2 +- .../sql/NoSecLwM2MIntegrationTest.java | 4 ++-- .../security/sql/PskLwm2mIntegrationTest.java | 2 -- .../security/sql/RpkLwM2MIntegrationTest.java | 2 -- .../sql/X509_NoTrustLwM2MIntegrationTest.java | 2 -- .../sql/X509_TrustLwM2MIntegrationTest.java | 2 -- 12 files changed, 18 insertions(+), 38 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 1eedd74608..a44d2b6a62 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -21,7 +21,6 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; -import org.eclipse.californium.elements.config.Configuration; import org.eclipse.leshan.client.LeshanClient; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.ResponseCode; @@ -86,8 +85,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; -import static org.eclipse.californium.core.config.CoapConfig.COAP_PORT; -import static org.eclipse.californium.core.config.CoapConfig.COAP_SECURE_PORT; import static org.eclipse.leshan.client.object.Security.noSec; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.assertEquals; @@ -135,8 +132,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public static final String SECURE_URI = COAPS + host + ":" + securityPort; public static final String URI_BS = COAP + hostBs + ":" + portBs; public static final String SECURE_URI_BS = COAPS + hostBs + ":" + securityPortBs; - public static final Configuration COAP_CONFIG = new Configuration().set(COAP_PORT, port).set(COAP_SECURE_PORT, securityPort); - public static Configuration COAP_CONFIG_BS = new Configuration().set(COAP_PORT, portBs).set(COAP_SECURE_PORT, securityPortBs); public static final Security SECURITY_NO_SEC = noSec(URI, shortServerId); protected final String OBSERVE_ATTRIBUTES_WITHOUT_PARAMS = @@ -219,7 +214,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public void basicTestConnectionObserveTelemetry(Security security, LwM2MDeviceCredentials deviceCredentials, - Configuration coapConfig, String endpoint, boolean queueMode) throws Exception { Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE)); @@ -238,7 +232,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte getWsClient().waitForReply(); getWsClient().registerWaitForUpdate(); - createNewClient(security, null, coapConfig, false, endpoint, null, queueMode); + createNewClient(security, null, false, endpoint, null, queueMode); deviceId = device.getId().getId().toString(); awaitObserveReadAll(0, deviceId); String msg = getWsClient().waitForUpdate(); @@ -306,24 +300,24 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte this.resources = resources; } - public void createNewClient(Security security, Security securityBs, Configuration coapConfig, boolean isRpc, + public void createNewClient(Security security, Security securityBs, boolean isRpc, String endpoint) throws Exception { - this.createNewClient(security, securityBs, coapConfig, isRpc, endpoint, null, false); + this.createNewClient(security, securityBs, isRpc, endpoint, null, false); } - public void createNewClient(Security security, Security securityBs, Configuration coapConfig, boolean isRpc, + public void createNewClient(Security security, Security securityBs, boolean isRpc, String endpoint, Integer clientDtlsCidLength) throws Exception { - this.createNewClient(security, securityBs, coapConfig, isRpc, endpoint, clientDtlsCidLength, false); + this.createNewClient(security, securityBs, isRpc, endpoint, clientDtlsCidLength, false); } - public void createNewClient(Security security, Security securityBs, Configuration coapConfig, boolean isRpc, + public void createNewClient(Security security, Security securityBs, boolean isRpc, String endpoint, Integer clientDtlsCidLength, boolean queueMode) throws Exception { this.clientDestroy(); lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint); try (ServerSocket socket = new ServerSocket(0)) { int clientPort = socket.getLocalPort(); - lwM2MTestClient.init(security, securityBs, coapConfig, clientPort, isRpc, + lwM2MTestClient.init(security, securityBs, clientPort, isRpc, this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, isWriteAttribute , clientDtlsCidLength, queueMode); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 4363bca41c..3f2e281b73 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -127,7 +127,7 @@ public class LwM2MTestClient { private LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; private LwM2mClientContext clientContext; - public void init(Security security, Security securityBs,Configuration coapConfig, int port, boolean isRpc, + public void init(Security security, Security securityBs, int port, boolean isRpc, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, LwM2mClientContext clientContext, boolean isWriteAttribute, Integer cIdLength, boolean queueMode) throws InvalidDDFFileException, IOException { Assert.assertNull("client already initialized", leshanClient); @@ -218,7 +218,6 @@ public class LwM2MTestClient { // Create Californium Configuration Configuration clientCoapConfig = endpointsBuilder.createDefaultConfiguration(); - DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(coapConfig); // Set some DTLS stuff // These configuration values are always overwritten by CLI therefore set them to transient. diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java index 7e812f9846..c7027ed42a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java @@ -59,7 +59,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); - createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); + createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); awaitObserveReadAll(0, device.getId().getId().toString()); device.setFirmwareId(createFirmware().getId()); @@ -84,7 +84,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5); - createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5); + createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_OTA5); awaitObserveReadAll(9, device.getId().getId().toString()); device.setFirmwareId(createFirmware().getId()); @@ -114,7 +114,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9)); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9); - createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9); + createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_OTA9); awaitObserveReadAll(9, device.getId().getId().toString()); device.setSoftwareId(createSoftware().getId()); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index 1c83bed8eb..1c6e384ae1 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -91,7 +91,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg private void initRpc () throws Exception { String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet(); - createNewClient(SECURITY_NO_SEC, null, COAP_CONFIG, true, endpoint); + createNewClient(SECURITY_NO_SEC, null, true, endpoint); expectedObjects = ConcurrentHashMap.newKeySet(); expectedObjectIdVers = ConcurrentHashMap.newKeySet(); expectedInstances = ConcurrentHashMap.newKeySet(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java index f66d4ef4b8..e787d26265 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -18,7 +18,6 @@ package org.thingsboard.server.transport.lwm2m.security; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; -import org.eclipse.californium.elements.config.Configuration; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.util.Hex; @@ -186,7 +185,6 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); this.basicTestConnection(null , SECURITY_NO_SEC_BS, deviceCredentials, - COAP_CONFIG_BS, clientEndpoint, transportConfiguration, awaitAlias, @@ -198,7 +196,6 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M protected void basicTestConnection(Security security, Security securityBs, LwM2MDeviceCredentials deviceCredentials, - Configuration coapConfig, String endpoint, Lwm2mDeviceProfileTransportConfiguration transportConfiguration, String awaitAlias, @@ -208,7 +205,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M boolean isStartLw) throws Exception { createDeviceProfile(transportConfiguration); final Device device = createDevice(deviceCredentials, endpoint); - createNewClient(security, securityBs, coapConfig, true, endpoint); + createNewClient(security, securityBs, true, endpoint); lwM2MTestClient.start(isStartLw); if (isAwaitObserveReadAll) { awaitObserveReadAll(0, device.getId().getId().toString()); @@ -236,7 +233,6 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M SECURITY_NO_SEC, SECURITY_NO_SEC_BS, deviceCredentials, - COAP_CONFIG, clientEndpoint, transportConfiguration, awaitAlias, @@ -246,7 +242,6 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M private void basicTestConnectionBootstrapRequestTrigger(Security security, Security securityBs, LwM2MDeviceCredentials deviceCredentials, - Configuration coapConfig, String endpoint, Lwm2mDeviceProfileTransportConfiguration transportConfiguration, String awaitAlias, @@ -256,7 +251,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M createDeviceProfile(transportConfiguration); final Device device = createDevice(deviceCredentials, endpoint); String deviceIdStr = device.getId().getId().toString(); - createNewClient(security, securityBs, coapConfig, true, endpoint); + createNewClient(security, securityBs, true, endpoint); lwM2MTestClient.start(true); awaitObserveReadAll(0, deviceIdStr); await(awaitAlias) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java index f44a2b1874..291149b7e8 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java @@ -52,7 +52,7 @@ public abstract class AbstractSecurityLwM2MIntegrationDtlsCidLengthTest extends createDeviceProfile(transportConfiguration); final Device device = createDevice(deviceCredentials, clientEndpoint); device.getId().getId().toString(); - createNewClient(security, null, COAP_CONFIG, true, clientEndpoint, clientDtlsCidLength); + createNewClient(security, null, true, clientEndpoint, clientDtlsCidLength); lwM2MTestClient.start(true); await(awaitAlias) .atMost(40, TimeUnit.SECONDS) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java index 89ff598bc2..e61b1afb6c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java @@ -52,7 +52,7 @@ public abstract class AbstractLwM2MIntegrationDiffPortTest extends AbstractSecur createDeviceProfile(transportConfiguration); createDevice(deviceCredentials, clientEndpoint); - createNewClient(security, null, COAP_CONFIG, true, clientEndpoint); + createNewClient(security, null, true, clientEndpoint); lwM2MTestClient.start(true); await(awaitAlias) .atMost(40, TimeUnit.SECONDS) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java index 7a34ec4283..a5296a16b1 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java @@ -32,13 +32,13 @@ public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationT public void testWithNoSecConnectLwm2mSuccessAndObserveTelemetry() throws Exception { String clientEndpoint = CLIENT_ENDPOINT_NO_SEC; LwM2MDeviceCredentials clientCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); - super.basicTestConnectionObserveTelemetry(SECURITY_NO_SEC, clientCredentials, COAP_CONFIG, clientEndpoint, false); + super.basicTestConnectionObserveTelemetry(SECURITY_NO_SEC, clientCredentials, clientEndpoint, false); } @Test public void testWithNoSecQueueModeConnectLwm2mSuccessAndObserveTelemetry() throws Exception { String clientEndpoint = CLIENT_ENDPOINT_NO_SEC + "_QueueMode"; LwM2MDeviceCredentials clientCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); - super.basicTestConnectionObserveTelemetry(SECURITY_NO_SEC, clientCredentials, COAP_CONFIG, clientEndpoint, true); + super.basicTestConnectionObserveTelemetry(SECURITY_NO_SEC, clientCredentials, clientEndpoint, true); } // Bootstrap + Lwm2m diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java index 64d4080ab4..2684f210ca 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java @@ -57,7 +57,6 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes this.basicTestConnection(security, null, deviceCredentials, - COAP_CONFIG, clientEndpoint, transportConfiguration, "await on client state (Psk_Lwm2m)", @@ -103,7 +102,6 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); this.basicTestConnection(null, securityBs, deviceCredentials, - COAP_CONFIG_BS, clientEndpoint, transportConfiguration, "await on client state (PskBS two section)", diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java index 1f48375cdd..b6ebfc442e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java @@ -58,7 +58,6 @@ public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTes LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, false); this.basicTestConnection(security, null, deviceCredentials, - COAP_CONFIG, clientEndpoint, transportConfiguration, "await on client state (Rpk_Lwm2m)", @@ -119,7 +118,6 @@ public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTes LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, clientPrivateKeyFromCertTrust, certificate, RPK, false); this.basicTestConnection(null, securityBs, deviceCredentials, - COAP_CONFIG_BS, clientEndpoint, transportConfiguration, "await on client state (RpkBS two section)", diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java index ea627cbfad..072dea9a6e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java @@ -59,7 +59,6 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg this.basicTestConnection(security, null, deviceCredentials, - COAP_CONFIG, clientEndpoint, transportConfiguration, "await on client state (X509_Trust_Lwm2m)", @@ -121,7 +120,6 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg this.basicTestConnection(security, null, deviceCredentials, - COAP_CONFIG_BS, clientEndpoint, transportConfiguration, "await on client state (X509NoTrust two section)", diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java index b3357a5a9b..9bc1643902 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java @@ -53,7 +53,6 @@ public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegra this.basicTestConnection(security, null, deviceCredentials, - COAP_CONFIG, clientEndpoint, transportConfiguration, "await on client state (X509_Trust_Lwm2m)", @@ -81,7 +80,6 @@ public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegra this.basicTestConnection(security, null, deviceCredentials, - COAP_CONFIG_BS, clientEndpoint, transportConfiguration, "await on client state (X509Trust two section)", From d78b349dc39a803bfa828ec4cf8fb513b25458c6 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 19 Feb 2024 15:33:56 +0200 Subject: [PATCH 148/209] lwm2m: refactoring2 tests --- .../server/transport/lwm2m/client/LwM2MTestClient.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 3f2e281b73..54a21da51e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -209,8 +209,7 @@ public class LwM2MTestClient { /** * "Use java-coap for CoAP protocol instead of Californium." */ - boolean useJavaCoap = false; - if (!useJavaCoap) protocolProvider.add(new CoapOscoreProtocolProvider()); + protocolProvider.add(new CoapOscoreProtocolProvider()); protocolProvider.add(customCoapsProtocolProvider); CaliforniumClientEndpointsProvider.Builder endpointsBuilder = new CaliforniumClientEndpointsProvider.Builder( protocolProvider.toArray(new ClientProtocolProvider[protocolProvider.size()])); From d7cca237010aba4df383d1319a5457da29c68101 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 29 Feb 2024 14:06:57 +0200 Subject: [PATCH 149/209] UI: Implement Time series chart widget. --- .../json/system/widget_bundles/charts.json | 1 + .../widget_types/time_series_chart.json | 34 + ui-ngx/patches/echarts+5.4.3.patch | 208 +++++ .../src/app/core/api/widget-subscription.ts | 8 +- ...ggregated-value-card-widget.component.scss | 1 + .../aggregated-value-card-widget.component.ts | 93 +- .../value-chart-card-widget.component.html | 6 +- .../value-chart-card-widget.component.scss | 11 + .../value-chart-card-widget.component.ts | 100 +- .../bar-chart-with-labels-widget.component.ts | 9 +- .../bar-chart-with-labels-widget.models.ts | 1 + .../widget/lib/chart/echarts-widget.models.ts | 204 +++- .../lib/chart/range-chart-widget.models.ts | 1 + .../lib/chart/time-series-chart-bar.models.ts | 134 +++ .../time-series-chart-widget.component.html | 122 +++ .../time-series-chart-widget.component.scss | 181 ++++ .../time-series-chart-widget.component.ts | 163 ++++ .../chart/time-series-chart-widget.models.ts | 52 ++ .../lib/chart/time-series-chart.models.ts | 873 ++++++++++++++++++ .../widget/lib/chart/time-series-chart.ts | 642 +++++++++++++ .../widget/widget-components.module.ts | 7 +- ui-ngx/src/app/shared/models/color.models.ts | 24 + .../assets/locale/locale.constant-en_US.json | 5 + 23 files changed, 2752 insertions(+), 128 deletions(-) create mode 100644 application/src/main/data/json/system/widget_types/time_series_chart.json create mode 100644 ui-ngx/patches/echarts+5.4.3.patch create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts create mode 100644 ui-ngx/src/app/shared/models/color.models.ts diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json index 07929028ff..5421cfb607 100644 --- a/application/src/main/data/json/system/widget_bundles/charts.json +++ b/application/src/main/data/json/system/widget_bundles/charts.json @@ -8,6 +8,7 @@ "name": "Charts" }, "widgetTypeFqns": [ + "time_series_chart", "charts.basic_timeseries", "charts.state_chart", "range_chart", diff --git a/application/src/main/data/json/system/widget_types/time_series_chart.json b/application/src/main/data/json/system/widget_types/time_series_chart.json new file mode 100644 index 0000000000..b8cd24a151 --- /dev/null +++ b/application/src/main/data/json/system/widget_types/time_series_chart.json @@ -0,0 +1,34 @@ +{ + "fqn": "time_series_chart", + "name": "Time series chart", + "deprecated": false, + "image": "tb-image:Y2hhcnQuc3Zn:IlRpbWUgc2VyaWVzIGNoYXJ0IiBzeXN0ZW0gd2lkZ2V0IGltYWdl;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF80MDUzXzE4NTExMykiPgo8cGF0aCBkPSJNMi44NDc2NiAxLjI4MTI1VjdIMi4xMjVWMi4xODM1OUwwLjY2Nzk2OSAyLjcxNDg0VjIuMDYyNUwyLjczNDM4IDEuMjgxMjVIMi44NDc2NlpNOC42NzUxNyAzLjcwMzEyVjQuNTcwMzFDOC42NzUxNyA1LjAzNjQ2IDguNjMzNTEgNS40Mjk2OSA4LjU1MDE3IDUuNzVDOC40NjY4NCA2LjA3MDMxIDguMzQ3MDUgNi4zMjgxMiA4LjE5MDggNi41MjM0NEM4LjAzNDU1IDYuNzE4NzUgNy44NDU3NSA2Ljg2MDY4IDcuNjI0MzkgNi45NDkyMkM3LjQwNTY0IDcuMDM1MTYgNy4xNTgyNSA3LjA3ODEyIDYuODgyMiA3LjA3ODEyQzYuNjYzNDUgNy4wNzgxMiA2LjQ2MTYzIDcuMDUwNzggNi4yNzY3MyA2Ljk5NjA5QzYuMDkxODQgNi45NDE0MSA1LjkyNTE3IDYuODU0MTcgNS43NzY3MyA2LjczNDM4QzUuNjMwOSA2LjYxMTk4IDUuNTA1OSA2LjQ1MzEyIDUuNDAxNzMgNi4yNTc4MUM1LjI5NzU3IDYuMDYyNSA1LjIxODE0IDUuODI1NTIgNS4xNjM0NSA1LjU0Njg4QzUuMTA4NzcgNS4yNjgyMyA1LjA4MTQyIDQuOTQyNzEgNS4wODE0MiA0LjU3MDMxVjMuNzAzMTJDNS4wODE0MiAzLjIzNjk4IDUuMTIzMDkgMi44NDYzNSA1LjIwNjQyIDIuNTMxMjVDNS4yOTIzNiAyLjIxNjE1IDUuNDEzNDUgMS45NjM1NCA1LjU2OTcgMS43NzM0NEM1LjcyNTk1IDEuNTgwNzMgNS45MTM0NSAxLjQ0MjcxIDYuMTMyMiAxLjM1OTM4QzYuMzUzNTYgMS4yNzYwNCA2LjYwMDk1IDEuMjM0MzggNi44NzQzOSAxLjIzNDM4QzcuMDk1NzUgMS4yMzQzOCA3LjI5ODg3IDEuMjYxNzIgNy40ODM3NyAxLjMxNjQxQzcuNjcxMjcgMS4zNjg0OSA3LjgzNzkzIDEuNDUzMTIgNy45ODM3NyAxLjU3MDMxQzguMTI5NiAxLjY4NDkgOC4yNTMzIDEuODM4NTQgOC4zNTQ4NiAyLjAzMTI1QzguNDU5MDMgMi4yMjEzNSA4LjUzODQ1IDIuNDU0NDMgOC41OTMxNCAyLjczMDQ3QzguNjQ3ODMgMy4wMDY1MSA4LjY3NTE3IDMuMzMwNzMgOC42NzUxNyAzLjcwMzEyWk03Ljk0ODYxIDQuNjg3NVYzLjU4MjAzQzcuOTQ4NjEgMy4zMjY4MiA3LjkzMjk4IDMuMTAyODYgNy45MDE3MyAyLjkxMDE2QzcuODczMDkgMi43MTQ4NCA3LjgzMDEyIDIuNTQ4MTggNy43NzI4MyAyLjQxMDE2QzcuNzE1NTQgMi4yNzIxNCA3LjY0MjYyIDIuMTYwMTYgNy41NTQwOCAyLjA3NDIyQzcuNDY4MTQgMS45ODgyOCA3LjM2Nzg4IDEuOTI1NzggNy4yNTMzIDEuODg2NzJDNy4xNDEzMiAxLjg0NTA1IDcuMDE1MDIgMS44MjQyMiA2Ljg3NDM5IDEuODI0MjJDNi43MDI1MiAxLjgyNDIyIDYuNTUwMTcgMS44NTY3NyA2LjQxNzM2IDEuOTIxODhDNi4yODQ1NSAxLjk4NDM4IDYuMTcyNTcgMi4wODQ2NCA2LjA4MTQyIDIuMjIyNjZDNS45OTI4OCAyLjM2MDY4IDUuOTI1MTcgMi41NDE2NyA1Ljg3ODMgMi43NjU2MkM1LjgzMTQyIDIuOTg5NTggNS44MDc5OCAzLjI2MTcyIDUuODA3OTggMy41ODIwM1Y0LjY4NzVDNS44MDc5OCA0Ljk0MjcxIDUuODIyMzEgNS4xNjc5NyA1Ljg1MDk1IDUuMzYzMjhDNS44ODIyIDUuNTU4NTkgNS45Mjc3OCA1LjcyNzg2IDUuOTg3NjcgNS44NzEwOUM2LjA0NzU3IDYuMDExNzIgNi4xMjA0OCA2LjEyNzYgNi4yMDY0MiA2LjIxODc1QzYuMjkyMzYgNi4zMDk5IDYuMzkxMzIgNi4zNzc2IDYuNTAzMyA2LjQyMTg4QzYuNjE3ODggNi40NjM1NCA2Ljc0NDE4IDYuNDg0MzggNi44ODIyIDYuNDg0MzhDNy4wNTkyOSA2LjQ4NDM4IDcuMjE0MjMgNi40NTA1MiA3LjM0NzA1IDYuMzgyODFDNy40Nzk4NiA2LjMxNTEgNy41OTA1NCA2LjIwOTY0IDcuNjc5MDggNi4wNjY0MUM3Ljc3MDIyIDUuOTIwNTcgNy44Mzc5MyA1LjczNDM4IDcuODgyMiA1LjUwNzgxQzcuOTI2NDcgNS4yNzg2NSA3Ljk0ODYxIDUuMDA1MjEgNy45NDg2MSA0LjY4NzVaTTEzLjMwNzQgMy43MDMxMlY0LjU3MDMxQzEzLjMwNzQgNS4wMzY0NiAxMy4yNjU3IDUuNDI5NjkgMTMuMTgyNCA1Ljc1QzEzLjA5OSA2LjA3MDMxIDEyLjk3OTMgNi4zMjgxMiAxMi44MjMgNi41MjM0NEMxMi42NjY4IDYuNzE4NzUgMTIuNDc3OSA2Ljg2MDY4IDEyLjI1NjYgNi45NDkyMkMxMi4wMzc4IDcuMDM1MTYgMTEuNzkwNCA3LjA3ODEyIDExLjUxNDQgNy4wNzgxMkMxMS4yOTU3IDcuMDc4MTIgMTEuMDkzOCA3LjA1MDc4IDEwLjkwODkgNi45OTYwOUMxMC43MjQgNi45NDE0MSAxMC41NTc0IDYuODU0MTcgMTAuNDA4OSA2LjczNDM4QzEwLjI2MzEgNi42MTE5OCAxMC4xMzgxIDYuNDUzMTIgMTAuMDMzOSA2LjI1NzgxQzkuOTI5NzcgNi4wNjI1IDkuODUwMzQgNS44MjU1MiA5Ljc5NTY2IDUuNTQ2ODhDOS43NDA5NyA1LjI2ODIzIDkuNzEzNjMgNC45NDI3MSA5LjcxMzYzIDQuNTcwMzFWMy43MDMxMkM5LjcxMzYzIDMuMjM2OTggOS43NTUyOSAyLjg0NjM1IDkuODM4NjMgMi41MzEyNUM5LjkyNDU2IDIuMjE2MTUgMTAuMDQ1NyAxLjk2MzU0IDEwLjIwMTkgMS43NzM0NEMxMC4zNTgyIDEuNTgwNzMgMTAuNTQ1NyAxLjQ0MjcxIDEwLjc2NDQgMS4zNTkzOEMxMC45ODU4IDEuMjc2MDQgMTEuMjMzMiAxLjIzNDM4IDExLjUwNjYgMS4yMzQzOEMxMS43Mjc5IDEuMjM0MzggMTEuOTMxMSAxLjI2MTcyIDEyLjExNiAxLjMxNjQxQzEyLjMwMzUgMS4zNjg0OSAxMi40NzAxIDEuNDUzMTIgMTIuNjE2IDEuNTcwMzFDMTIuNzYxOCAxLjY4NDkgMTIuODg1NSAxLjgzODU0IDEyLjk4NzEgMi4wMzEyNUMxMy4wOTEyIDIuMjIxMzUgMTMuMTcwNyAyLjQ1NDQzIDEzLjIyNTMgMi43MzA0N0MxMy4yOCAzLjAwNjUxIDEzLjMwNzQgMy4zMzA3MyAxMy4zMDc0IDMuNzAzMTJaTTEyLjU4MDggNC42ODc1VjMuNTgyMDNDMTIuNTgwOCAzLjMyNjgyIDEyLjU2NTIgMy4xMDI4NiAxMi41MzM5IDIuOTEwMTZDMTIuNTA1MyAyLjcxNDg0IDEyLjQ2MjMgMi41NDgxOCAxMi40MDUgMi40MTAxNkMxMi4zNDc3IDIuMjcyMTQgMTIuMjc0OCAyLjE2MDE2IDEyLjE4NjMgMi4wNzQyMkMxMi4xMDAzIDEuOTg4MjggMTIuMDAwMSAxLjkyNTc4IDExLjg4NTUgMS44ODY3MkMxMS43NzM1IDEuODQ1MDUgMTEuNjQ3MiAxLjgyNDIyIDExLjUwNjYgMS44MjQyMkMxMS4zMzQ3IDEuODI0MjIgMTEuMTgyNCAxLjg1Njc3IDExLjA0OTYgMS45MjE4OEMxMC45MTY4IDEuOTg0MzggMTAuODA0OCAyLjA4NDY0IDEwLjcxMzYgMi4yMjI2NkMxMC42MjUxIDIuMzYwNjggMTAuNTU3NCAyLjU0MTY3IDEwLjUxMDUgMi43NjU2MkMxMC40NjM2IDIuOTg5NTggMTAuNDQwMiAzLjI2MTcyIDEwLjQ0MDIgMy41ODIwM1Y0LjY4NzVDMTAuNDQwMiA0Ljk0MjcxIDEwLjQ1NDUgNS4xNjc5NyAxMC40ODMyIDUuMzYzMjhDMTAuNTE0NCA1LjU1ODU5IDEwLjU2IDUuNzI3ODYgMTAuNjE5OSA1Ljg3MTA5QzEwLjY3OTggNi4wMTE3MiAxMC43NTI3IDYuMTI3NiAxMC44Mzg2IDYuMjE4NzVDMTAuOTI0NiA2LjMwOTkgMTEuMDIzNSA2LjM3NzYgMTEuMTM1NSA2LjQyMTg4QzExLjI1MDEgNi40NjM1NCAxMS4zNzY0IDYuNDg0MzggMTEuNTE0NCA2LjQ4NDM4QzExLjY5MTUgNi40ODQzOCAxMS44NDY0IDYuNDUwNTIgMTEuOTc5MyA2LjM4MjgxQzEyLjExMjEgNi4zMTUxIDEyLjIyMjcgNi4yMDk2NCAxMi4zMTEzIDYuMDY2NDFDMTIuNDAyNCA1LjkyMDU3IDEyLjQ3MDEgNS43MzQzOCAxMi41MTQ0IDUuNTA3ODFDMTIuNTU4NyA1LjI3ODY1IDEyLjU4MDggNS4wMDUyMSAxMi41ODA4IDQuNjg3NVpNMTQuMzA2OCAyLjcwNzAzVjIuNDA2MjVDMTQuMzA2OCAyLjE5MDEgMTQuMzUzNiAxLjk5MzQ5IDE0LjQ0NzQgMS44MTY0MUMxNC41NDExIDEuNjM5MzIgMTQuNjc1MyAxLjQ5NzQgMTQuODQ5NyAxLjM5MDYyQzE1LjAyNDIgMS4yODM4NSAxNS4yMzEyIDEuMjMwNDcgMTUuNDcwOCAxLjIzMDQ3QzE1LjcxNTYgMS4yMzA0NyAxNS45MjQgMS4yODM4NSAxNi4wOTU4IDEuMzkwNjJDMTYuMjcwMyAxLjQ5NzQgMTYuNDA0NCAxLjYzOTMyIDE2LjQ5ODIgMS44MTY0MUMxNi41OTE5IDEuOTkzNDkgMTYuNjM4OCAyLjE5MDEgMTYuNjM4OCAyLjQwNjI1VjIuNzA3MDNDMTYuNjM4OCAyLjkxNzk3IDE2LjU5MTkgMy4xMTE5OCAxNi40OTgyIDMuMjg5MDZDMTYuNDA3IDMuNDY2MTUgMTYuMjc0MiAzLjYwODA3IDE2LjA5OTcgMy43MTQ4NEMxNS45Mjc5IDMuODIxNjEgMTUuNzIwOCAzLjg3NSAxNS40Nzg2IDMuODc1QzE1LjIzNjUgMy44NzUgMTUuMDI2OCAzLjgyMTYxIDE0Ljg0OTcgMy43MTQ4NEMxNC42NzUzIDMuNjA4MDcgMTQuNTQxMSAzLjQ2NjE1IDE0LjQ0NzQgMy4yODkwNkMxNC4zNTM2IDMuMTExOTggMTQuMzA2OCAyLjkxNzk3IDE0LjMwNjggMi43MDcwM1pNMTQuODQ5NyAyLjQwNjI1VjIuNzA3MDNDMTQuODQ5NyAyLjgyNjgyIDE0Ljg3MTkgMi45NDAxIDE0LjkxNjEgMy4wNDY4OEMxNC45NjMgMy4xNTM2NSAxNS4wMzMzIDMuMjQwODkgMTUuMTI3MSAzLjMwODU5QzE1LjIyMDggMy4zNzM3IDE1LjMzOCAzLjQwNjI1IDE1LjQ3ODYgMy40MDYyNUMxNS42MTkzIDMuNDA2MjUgMTUuNzM1MiAzLjM3MzcgMTUuODI2MyAzLjMwODU5QzE1LjkxNzQgMy4yNDA4OSAxNS45ODUyIDMuMTUzNjUgMTYuMDI5NCAzLjA0Njg4QzE2LjA3MzcgMi45NDAxIDE2LjA5NTggMi44MjY4MiAxNi4wOTU4IDIuNzA3MDNWMi40MDYyNUMxNi4wOTU4IDIuMjgzODUgMTYuMDcyNCAyLjE2OTI3IDE2LjAyNTUgMi4wNjI1QzE1Ljk4MTIgMS45NTMxMiAxNS45MTIyIDEuODY1ODkgMTUuODE4NSAxLjgwMDc4QzE1LjcyNzMgMS43MzMwNyAxNS42MTE1IDEuNjk5MjIgMTUuNDcwOCAxLjY5OTIyQzE1LjMzMjggMS42OTkyMiAxNS4yMTY5IDEuNzMzMDcgMTUuMTIzMiAxLjgwMDc4QzE1LjAzMiAxLjg2NTg5IDE0Ljk2MyAxLjk1MzEyIDE0LjkxNjEgMi4wNjI1QzE0Ljg3MTkgMi4xNjkyNyAxNC44NDk3IDIuMjgzODUgMTQuODQ5NyAyLjQwNjI1Wk0xNy4wNzYzIDUuOTEwMTZWNS42MDU0N0MxNy4wNzYzIDUuMzkxOTMgMTcuMTIzMiA1LjE5NjYxIDE3LjIxNjkgNS4wMTk1M0MxNy4zMTA3IDQuODQyNDUgMTcuNDQ0OCA0LjcwMDUyIDE3LjYxOTMgNC41OTM3NUMxNy43OTM3IDQuNDg2OTggMTguMDAwOCA0LjQzMzU5IDE4LjI0MDQgNC40MzM1OUMxOC40ODUyIDQuNDMzNTkgMTguNjkzNSA0LjQ4Njk4IDE4Ljg2NTQgNC41OTM3NUMxOS4wMzk4IDQuNzAwNTIgMTkuMTc0IDQuODQyNDUgMTkuMjY3NyA1LjAxOTUzQzE5LjM2MTUgNS4xOTY2MSAxOS40MDgzIDUuMzkxOTMgMTkuNDA4MyA1LjYwNTQ3VjUuOTEwMTZDMTkuNDA4MyA2LjEyMzcgMTkuMzYxNSA2LjMxOTAxIDE5LjI2NzcgNi40OTYwOUMxOS4xNzY2IDYuNjczMTggMTkuMDQzNyA2LjgxNTEgMTguODY5MyA2LjkyMTg4QzE4LjY5NzQgNy4wMjg2NSAxOC40OTA0IDcuMDgyMDMgMTguMjQ4MiA3LjA4MjAzQzE4LjAwNiA3LjA4MjAzIDE3Ljc5NzcgNy4wMjg2NSAxNy42MjMyIDYuOTIxODhDMTcuNDQ4NyA2LjgxNTEgMTcuMzEzMyA2LjY3MzE4IDE3LjIxNjkgNi40OTYwOUMxNy4xMjMyIDYuMzE5MDEgMTcuMDc2MyA2LjEyMzcgMTcuMDc2MyA1LjkxMDE2Wk0xNy42MTkzIDUuNjA1NDdWNS45MTAxNkMxNy42MTkzIDYuMDI5OTUgMTcuNjQxNCA2LjE0NDUzIDE3LjY4NTcgNi4yNTM5MUMxNy43MzI1IDYuMzYwNjggMTcuODAyOSA2LjQ0NzkyIDE3Ljg5NjYgNi41MTU2MkMxNy45OTA0IDYuNTgwNzMgMTguMTA3NSA2LjYxMzI4IDE4LjI0ODIgNi42MTMyOEMxOC4zODg4IDYuNjEzMjggMTguNTA0NyA2LjU4MDczIDE4LjU5NTggNi41MTU2MkMxOC42ODk2IDYuNDQ3OTIgMTguNzU4NiA2LjM2MDY4IDE4LjgwMjkgNi4yNTM5MUMxOC44NDcxIDYuMTQ3MTQgMTguODY5MyA2LjAzMjU1IDE4Ljg2OTMgNS45MTAxNlY1LjYwNTQ3QzE4Ljg2OTMgNS40ODMwNyAxOC44NDU4IDUuMzY4NDkgMTguNzk5IDUuMjYxNzJDMTguNzU0NyA1LjE1NDk1IDE4LjY4NTcgNS4wNjkwMSAxOC41OTE5IDUuMDAzOTFDMTguNTAwOCA0LjkzNjIgMTguMzgzNiA0LjkwMjM0IDE4LjI0MDQgNC45MDIzNEMxOC4xMDIzIDQuOTAyMzQgMTcuOTg2NSA0LjkzNjIgMTcuODkyNyA1LjAwMzkxQzE3LjgwMTYgNS4wNjkwMSAxNy43MzI1IDUuMTU0OTUgMTcuNjg1NyA1LjI2MTcyQzE3LjY0MTQgNS4zNjg0OSAxNy42MTkzIDUuNDgzMDcgMTcuNjE5MyA1LjYwNTQ3Wk0xOC40MiAyLjEyMTA5TDE1LjY0MjcgNi41NjY0MUwxNS4yMzY1IDYuMzA4NTlMMTguMDEzOCAxLjg2MzI4TDE4LjQyIDIuMTIxMDlaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjxwYXRoIGQ9Ik04LjA1ODU5IDMzLjY2OEM4LjA1ODU5IDM0LjAxNDMgNy45Nzc4NiAzNC4zMDg2IDcuODE2NDEgMzQuNTUwOEM3LjY1NzU1IDM0Ljc5MDQgNy40NDE0MSAzNC45NzI3IDcuMTY3OTcgMzUuMDk3N0M2Ljg5NzE0IDM1LjIyMjcgNi41OTExNSAzNS4yODUyIDYuMjUgMzUuMjg1MkM1LjkwODg1IDM1LjI4NTIgNS42MDE1NiAzNS4yMjI3IDUuMzI4MTIgMzUuMDk3N0M1LjA1NDY5IDM0Ljk3MjcgNC44Mzg1NCAzNC43OTA0IDQuNjc5NjkgMzQuNTUwOEM0LjUyMDgzIDM0LjMwODYgNC40NDE0MSAzNC4wMTQzIDQuNDQxNDEgMzMuNjY4QzQuNDQxNDEgMzMuNDQxNCA0LjQ4NDM4IDMzLjIzNDQgNC41NzAzMSAzMy4wNDY5QzQuNjU4ODUgMzIuODU2OCA0Ljc4MjU1IDMyLjY5MTQgNC45NDE0MSAzMi41NTA4QzUuMTAyODYgMzIuNDEwMiA1LjI5Mjk3IDMyLjMwMjEgNS41MTE3MiAzMi4yMjY2QzUuNzMzMDcgMzIuMTQ4NCA1Ljk3NjU2IDMyLjEwOTQgNi4yNDIxOSAzMi4xMDk0QzYuNTkxMTUgMzIuMTA5NCA2LjkwMjM0IDMyLjE3NzEgNy4xNzU3OCAzMi4zMTI1QzcuNDQ5MjIgMzIuNDQ1MyA3LjY2NDA2IDMyLjYyODkgNy44MjAzMSAzMi44NjMzQzcuOTc5MTcgMzMuMDk3NyA4LjA1ODU5IDMzLjM2NTkgOC4wNTg1OSAzMy42NjhaTTcuMzMyMDMgMzMuNjUyM0M3LjMzMjAzIDMzLjQ0MTQgNy4yODY0NiAzMy4yNTUyIDcuMTk1MzEgMzMuMDkzOEM3LjEwNDE3IDMyLjkyOTcgNi45NzY1NiAzMi44MDIxIDYuODEyNSAzMi43MTA5QzYuNjQ4NDQgMzIuNjE5OCA2LjQ1ODMzIDMyLjU3NDIgNi4yNDIxOSAzMi41NzQyQzYuMDIwODMgMzIuNTc0MiA1LjgyOTQzIDMyLjYxOTggNS42Njc5NyAzMi43MTA5QzUuNTA5MTEgMzIuODAyMSA1LjM4NTQyIDMyLjkyOTcgNS4yOTY4OCAzMy4wOTM4QzUuMjA4MzMgMzMuMjU1MiA1LjE2NDA2IDMzLjQ0MTQgNS4xNjQwNiAzMy42NTIzQzUuMTY0MDYgMzMuODcxMSA1LjIwNzAzIDM0LjA1ODYgNS4yOTI5NyAzNC4yMTQ4QzUuMzgxNTEgMzQuMzY4NSA1LjUwNjUxIDM0LjQ4NyA1LjY2Nzk3IDM0LjU3MDNDNS44MzIwMyAzNC42NTEgNi4wMjYwNCAzNC42OTE0IDYuMjUgMzQuNjkxNEM2LjQ3Mzk2IDM0LjY5MTQgNi42NjY2NyAzNC42NTEgNi44MjgxMiAzNC41NzAzQzYuOTg5NTggMzQuNDg3IDcuMTEzMjggMzQuMzY4NSA3LjE5OTIyIDM0LjIxNDhDNy4yODc3NiAzNC4wNTg2IDcuMzMyMDMgMzMuODcxMSA3LjMzMjAzIDMzLjY1MjNaTTcuOTI1NzggMzFDNy45MjU3OCAzMS4yNzYgNy44NTI4NiAzMS41MjQ3IDcuNzA3MDMgMzEuNzQ2MUM3LjU2MTIgMzEuOTY3NCA3LjM2MTk4IDMyLjE0MTkgNy4xMDkzOCAzMi4yNjk1QzYuODU2NzcgMzIuMzk3MSA2LjU3MDMxIDMyLjQ2MDkgNi4yNSAzMi40NjA5QzUuOTI0NDggMzIuNDYwOSA1LjYzNDExIDMyLjM5NzEgNS4zNzg5MSAzMi4yNjk1QzUuMTI2MyAzMi4xNDE5IDQuOTI4MzkgMzEuOTY3NCA0Ljc4NTE2IDMxLjc0NjFDNC42NDE5MyAzMS41MjQ3IDQuNTcwMzEgMzEuMjc2IDQuNTcwMzEgMzFDNC41NzAzMSAzMC42NjkzIDQuNjQxOTMgMzAuMzg4IDQuNzg1MTYgMzAuMTU2MkM0LjkzMDk5IDI5LjkyNDUgNS4xMzAyMSAyOS43NDc0IDUuMzgyODEgMjkuNjI1QzUuNjM1NDIgMjkuNTAyNiA1LjkyMzE4IDI5LjQ0MTQgNi4yNDYwOSAyOS40NDE0QzYuNTcxNjEgMjkuNDQxNCA2Ljg2MDY4IDI5LjUwMjYgNy4xMTMyOCAyOS42MjVDNy4zNjU4OSAyOS43NDc0IDcuNTYzOCAyOS45MjQ1IDcuNzA3MDMgMzAuMTU2MkM3Ljg1Mjg2IDMwLjM4OCA3LjkyNTc4IDMwLjY2OTMgNy45MjU3OCAzMVpNNy4yMDMxMiAzMS4wMTE3QzcuMjAzMTIgMzAuODIxNiA3LjE2Mjc2IDMwLjY1MzYgNy4wODIwMyAzMC41MDc4QzcuMDAxMyAzMC4zNjIgNi44ODkzMiAzMC4yNDc0IDYuNzQ2MDkgMzAuMTY0MUM2LjYwMjg2IDMwLjA3ODEgNi40MzYyIDMwLjAzNTIgNi4yNDYwOSAzMC4wMzUyQzYuMDU1OTkgMzAuMDM1MiA1Ljg4OTMyIDMwLjA3NTUgNS43NDYwOSAzMC4xNTYyQzUuNjA1NDcgMzAuMjM0NCA1LjQ5NDc5IDMwLjM0NjQgNS40MTQwNiAzMC40OTIyQzUuMzM1OTQgMzAuNjM4IDUuMjk2ODggMzAuODExMiA1LjI5Njg4IDMxLjAxMTdDNS4yOTY4OCAzMS4yMDcgNS4zMzU5NCAzMS4zNzc2IDUuNDE0MDYgMzEuNTIzNEM1LjQ5NDc5IDMxLjY2OTMgNS42MDY3NyAzMS43ODI2IDUuNzUgMzEuODYzM0M1Ljg5MzIzIDMxLjk0NCA2LjA1OTkgMzEuOTg0NCA2LjI1IDMxLjk4NDRDNi40NDAxIDMxLjk4NDQgNi42MDU0NyAzMS45NDQgNi43NDYwOSAzMS44NjMzQzYuODg5MzIgMzEuNzgyNiA3LjAwMTMgMzEuNjY5MyA3LjA4MjAzIDMxLjUyMzRDNy4xNjI3NiAzMS4zNzc2IDcuMjAzMTIgMzEuMjA3IDcuMjAzMTIgMzEuMDExN1pNMTIuNjc1MiAzMS45MTAyVjMyLjc3NzNDMTIuNjc1MiAzMy4yNDM1IDEyLjYzMzUgMzMuNjM2NyAxMi41NTAyIDMzLjk1N0MxMi40NjY4IDM0LjI3NzMgMTIuMzQ3IDM0LjUzNTIgMTIuMTkwOCAzNC43MzA1QzEyLjAzNDUgMzQuOTI1OCAxMS44NDU3IDM1LjA2NzcgMTEuNjI0NCAzNS4xNTYyQzExLjQwNTYgMzUuMjQyMiAxMS4xNTgyIDM1LjI4NTIgMTAuODgyMiAzNS4yODUyQzEwLjY2MzUgMzUuMjg1MiAxMC40NjE2IDM1LjI1NzggMTAuMjc2NyAzNS4yMDMxQzEwLjA5MTggMzUuMTQ4NCA5LjkyNTE3IDM1LjA2MTIgOS43NzY3MyAzNC45NDE0QzkuNjMwOSAzNC44MTkgOS41MDU5IDM0LjY2MDIgOS40MDE3MyAzNC40NjQ4QzkuMjk3NTcgMzQuMjY5NSA5LjIxODE0IDM0LjAzMjYgOS4xNjM0NSAzMy43NTM5QzkuMTA4NzcgMzMuNDc1MyA5LjA4MTQyIDMzLjE0OTcgOS4wODE0MiAzMi43NzczVjMxLjkxMDJDOS4wODE0MiAzMS40NDQgOS4xMjMwOSAzMS4wNTM0IDkuMjA2NDIgMzAuNzM4M0M5LjI5MjM2IDMwLjQyMzIgOS40MTM0NSAzMC4xNzA2IDkuNTY5NyAyOS45ODA1QzkuNzI1OTUgMjkuNzg3OCA5LjkxMzQ1IDI5LjY0OTcgMTAuMTMyMiAyOS41NjY0QzEwLjM1MzYgMjkuNDgzMSAxMC42MDEgMjkuNDQxNCAxMC44NzQ0IDI5LjQ0MTRDMTEuMDk1NyAyOS40NDE0IDExLjI5ODkgMjkuNDY4OCAxMS40ODM4IDI5LjUyMzRDMTEuNjcxMyAyOS41NzU1IDExLjgzNzkgMjkuNjYwMiAxMS45ODM4IDI5Ljc3NzNDMTIuMTI5NiAyOS44OTE5IDEyLjI1MzMgMzAuMDQ1NiAxMi4zNTQ5IDMwLjIzODNDMTIuNDU5IDMwLjQyODQgMTIuNTM4NSAzMC42NjE1IDEyLjU5MzEgMzAuOTM3NUMxMi42NDc4IDMxLjIxMzUgMTIuNjc1MiAzMS41Mzc4IDEyLjY3NTIgMzEuOTEwMlpNMTEuOTQ4NiAzMi44OTQ1VjMxLjc4OTFDMTEuOTQ4NiAzMS41MzM5IDExLjkzMyAzMS4zMDk5IDExLjkwMTcgMzEuMTE3MkMxMS44NzMxIDMwLjkyMTkgMTEuODMwMSAzMC43NTUyIDExLjc3MjggMzAuNjE3MkMxMS43MTU1IDMwLjQ3OTIgMTEuNjQyNiAzMC4zNjcyIDExLjU1NDEgMzAuMjgxMkMxMS40NjgxIDMwLjE5NTMgMTEuMzY3OSAzMC4xMzI4IDExLjI1MzMgMzAuMDkzOEMxMS4xNDEzIDMwLjA1MjEgMTEuMDE1IDMwLjAzMTIgMTAuODc0NCAzMC4wMzEyQzEwLjcwMjUgMzAuMDMxMiAxMC41NTAyIDMwLjA2MzggMTAuNDE3NCAzMC4xMjg5QzEwLjI4NDUgMzAuMTkxNCAxMC4xNzI2IDMwLjI5MTcgMTAuMDgxNCAzMC40Mjk3QzkuOTkyODggMzAuNTY3NyA5LjkyNTE3IDMwLjc0ODcgOS44NzgzIDMwLjk3MjdDOS44MzE0MiAzMS4xOTY2IDkuODA3OTggMzEuNDY4OCA5LjgwNzk4IDMxLjc4OTFWMzIuODk0NUM5LjgwNzk4IDMzLjE0OTcgOS44MjIzMSAzMy4zNzUgOS44NTA5NSAzMy41NzAzQzkuODgyMiAzMy43NjU2IDkuOTI3NzggMzMuOTM0OSA5Ljk4NzY3IDM0LjA3ODFDMTAuMDQ3NiAzNC4yMTg4IDEwLjEyMDUgMzQuMzM0NiAxMC4yMDY0IDM0LjQyNThDMTAuMjkyNCAzNC41MTY5IDEwLjM5MTMgMzQuNTg0NiAxMC41MDMzIDM0LjYyODlDMTAuNjE3OSAzNC42NzA2IDEwLjc0NDIgMzQuNjkxNCAxMC44ODIyIDM0LjY5MTRDMTEuMDU5MyAzNC42OTE0IDExLjIxNDIgMzQuNjU3NiAxMS4zNDcgMzQuNTg5OEMxMS40Nzk5IDM0LjUyMjEgMTEuNTkwNSAzNC40MTY3IDExLjY3OTEgMzQuMjczNEMxMS43NzAyIDM0LjEyNzYgMTEuODM3OSAzMy45NDE0IDExLjg4MjIgMzMuNzE0OEMxMS45MjY1IDMzLjQ4NTcgMTEuOTQ4NiAzMy4yMTIyIDExLjk0ODYgMzIuODk0NVpNMTMuNjc0NiAzMC45MTQxVjMwLjYxMzNDMTMuNjc0NiAzMC4zOTcxIDEzLjcyMTQgMzAuMjAwNSAxMy44MTUyIDMwLjAyMzRDMTMuOTA4OSAyOS44NDY0IDE0LjA0MzEgMjkuNzA0NCAxNC4yMTc1IDI5LjU5NzdDMTQuMzkyIDI5LjQ5MDkgMTQuNTk5IDI5LjQzNzUgMTQuODM4NiAyOS40Mzc1QzE1LjA4MzQgMjkuNDM3NSAxNS4yOTE4IDI5LjQ5MDkgMTUuNDYzNiAyOS41OTc3QzE1LjYzODEgMjkuNzA0NCAxNS43NzIyIDI5Ljg0NjQgMTUuODY2IDMwLjAyMzRDMTUuOTU5NyAzMC4yMDA1IDE2LjAwNjYgMzAuMzk3MSAxNi4wMDY2IDMwLjYxMzNWMzAuOTE0MUMxNi4wMDY2IDMxLjEyNSAxNS45NTk3IDMxLjMxOSAxNS44NjYgMzEuNDk2MUMxNS43NzQ4IDMxLjY3MzIgMTUuNjQyIDMxLjgxNTEgMTUuNDY3NSAzMS45MjE5QzE1LjI5NTcgMzIuMDI4NiAxNS4wODg2IDMyLjA4MiAxNC44NDY0IDMyLjA4MkMxNC42MDQzIDMyLjA4MiAxNC4zOTQ2IDMyLjAyODYgMTQuMjE3NSAzMS45MjE5QzE0LjA0MzEgMzEuODE1MSAxMy45MDg5IDMxLjY3MzIgMTMuODE1MiAzMS40OTYxQzEzLjcyMTQgMzEuMzE5IDEzLjY3NDYgMzEuMTI1IDEzLjY3NDYgMzAuOTE0MVpNMTQuMjE3NSAzMC42MTMzVjMwLjkxNDFDMTQuMjE3NSAzMS4wMzM5IDE0LjIzOTcgMzEuMTQ3MSAxNC4yODM5IDMxLjI1MzlDMTQuMzMwOCAzMS4zNjA3IDE0LjQwMTEgMzEuNDQ3OSAxNC40OTQ5IDMxLjUxNTZDMTQuNTg4NiAzMS41ODA3IDE0LjcwNTggMzEuNjEzMyAxNC44NDY0IDMxLjYxMzNDMTQuOTg3MSAzMS42MTMzIDE1LjEwMjkgMzEuNTgwNyAxNS4xOTQxIDMxLjUxNTZDMTUuMjg1MiAzMS40NDc5IDE1LjM1MjkgMzEuMzYwNyAxNS4zOTcyIDMxLjI1MzlDMTUuNDQxNSAzMS4xNDcxIDE1LjQ2MzYgMzEuMDMzOSAxNS40NjM2IDMwLjkxNDFWMzAuNjEzM0MxNS40NjM2IDMwLjQ5MDkgMTUuNDQwMiAzMC4zNzYzIDE1LjM5MzMgMzAuMjY5NUMxNS4zNDkgMzAuMTYwMiAxNS4yOCAzMC4wNzI5IDE1LjE4NjMgMzAuMDA3OEMxNS4wOTUxIDI5Ljk0MDEgMTQuOTc5MyAyOS45MDYyIDE0LjgzODYgMjkuOTA2MkMxNC43MDA2IDI5LjkwNjIgMTQuNTg0NyAyOS45NDAxIDE0LjQ5MSAzMC4wMDc4QzE0LjM5OTggMzAuMDcyOSAxNC4zMzA4IDMwLjE2MDIgMTQuMjgzOSAzMC4yNjk1QzE0LjIzOTcgMzAuMzc2MyAxNC4yMTc1IDMwLjQ5MDkgMTQuMjE3NSAzMC42MTMzWk0xNi40NDQxIDM0LjExNzJWMzMuODEyNUMxNi40NDQxIDMzLjU5OSAxNi40OTEgMzMuNDAzNiAxNi41ODQ3IDMzLjIyNjZDMTYuNjc4NSAzMy4wNDk1IDE2LjgxMjYgMzIuOTA3NiAxNi45ODcxIDMyLjgwMDhDMTcuMTYxNSAzMi42OTQgMTcuMzY4NiAzMi42NDA2IDE3LjYwODIgMzIuNjQwNkMxNy44NTI5IDMyLjY0MDYgMTguMDYxMyAzMi42OTQgMTguMjMzMiAzMi44MDA4QzE4LjQwNzYgMzIuOTA3NiAxOC41NDE4IDMzLjA0OTUgMTguNjM1NSAzMy4yMjY2QzE4LjcyOTMgMzMuNDAzNiAxOC43NzYxIDMzLjU5OSAxOC43NzYxIDMzLjgxMjVWMzQuMTE3MkMxOC43NzYxIDM0LjMzMDcgMTguNzI5MyAzNC41MjYgMTguNjM1NSAzNC43MDMxQzE4LjU0NDQgMzQuODgwMiAxOC40MTE1IDM1LjAyMjEgMTguMjM3MSAzNS4xMjg5QzE4LjA2NTIgMzUuMjM1NyAxNy44NTgyIDM1LjI4OTEgMTcuNjE2IDM1LjI4OTFDMTcuMzczOCAzNS4yODkxIDE3LjE2NTQgMzUuMjM1NyAxNi45OTEgMzUuMTI4OUMxNi44MTY1IDM1LjAyMjEgMTYuNjgxMSAzNC44ODAyIDE2LjU4NDcgMzQuNzAzMUMxNi40OTEgMzQuNTI2IDE2LjQ0NDEgMzQuMzMwNyAxNi40NDQxIDM0LjExNzJaTTE2Ljk4NzEgMzMuODEyNVYzNC4xMTcyQzE2Ljk4NzEgMzQuMjM3IDE3LjAwOTIgMzQuMzUxNiAxNy4wNTM1IDM0LjQ2MDlDMTcuMTAwMyAzNC41Njc3IDE3LjE3MDcgMzQuNjU0OSAxNy4yNjQ0IDM0LjcyMjdDMTcuMzU4MiAzNC43ODc4IDE3LjQ3NTMgMzQuODIwMyAxNy42MTYgMzQuODIwM0MxNy43NTY2IDM0LjgyMDMgMTcuODcyNSAzNC43ODc4IDE3Ljk2MzYgMzQuNzIyN0MxOC4wNTc0IDM0LjY1NDkgMTguMTI2NCAzNC41Njc3IDE4LjE3MDcgMzQuNDYwOUMxOC4yMTQ5IDM0LjM1NDIgMTguMjM3MSAzNC4yMzk2IDE4LjIzNzEgMzQuMTE3MlYzMy44MTI1QzE4LjIzNzEgMzMuNjkwMSAxOC4yMTM2IDMzLjU3NTUgMTguMTY2OCAzMy40Njg4QzE4LjEyMjUgMzMuMzYyIDE4LjA1MzUgMzMuMjc2IDE3Ljk1OTcgMzMuMjEwOUMxNy44Njg2IDMzLjE0MzIgMTcuNzUxNCAzMy4xMDk0IDE3LjYwODIgMzMuMTA5NEMxNy40NzAxIDMzLjEwOTQgMTcuMzU0MyAzMy4xNDMyIDE3LjI2MDUgMzMuMjEwOUMxNy4xNjk0IDMzLjI3NiAxNy4xMDAzIDMzLjM2MiAxNy4wNTM1IDMzLjQ2ODhDMTcuMDA5MiAzMy41NzU1IDE2Ljk4NzEgMzMuNjkwMSAxNi45ODcxIDMzLjgxMjVaTTE3Ljc4NzggMzAuMzI4MUwxNS4wMTA1IDM0Ljc3MzRMMTQuNjA0MyAzNC41MTU2TDE3LjM4MTYgMzAuMDcwM0wxNy43ODc4IDMwLjMyODFaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjxwYXRoIGQ9Ik03LjI0NjA5IDU3LjcxNzhINy4zMDg1OVY1OC4zMzExSDcuMjQ2MDlDNi44NjMyOCA1OC4zMzExIDYuNTQyOTcgNTguMzkzNiA2LjI4NTE2IDU4LjUxODZDNi4wMjczNCA1OC42NDEgNS44MjI5MiA1OC44MDYzIDUuNjcxODggNTkuMDE0NkM1LjUyMDgzIDU5LjIyMDQgNS40MTE0NiA1OS40NTIxIDUuMzQzNzUgNTkuNzFDNS4yNzg2NSA1OS45Njc4IDUuMjQ2MDkgNjAuMjI5NSA1LjI0NjA5IDYwLjQ5NTFWNjEuMzMxMUM1LjI0NjA5IDYxLjU4MzcgNS4yNzYwNCA2MS44MDc2IDUuMzM1OTQgNjIuMDAyOUM1LjM5NTgzIDYyLjE5NTYgNS40Nzc4NiA2Mi4zNTg0IDUuNTgyMDMgNjIuNDkxMkM1LjY4NjIgNjIuNjI0IDUuODAzMzkgNjIuNzI0MyA1LjkzMzU5IDYyLjc5MkM2LjA2NjQxIDYyLjg1OTcgNi4yMDQ0MyA2Mi44OTM2IDYuMzQ3NjYgNjIuODkzNkM2LjUxNDMyIDYyLjg5MzYgNi42NjI3NiA2Mi44NjIzIDYuNzkyOTcgNjIuNzk5OEM2LjkyMzE4IDYyLjczNDcgNy4wMzI1NSA2Mi42NDQ5IDcuMTIxMDkgNjIuNTMwM0M3LjIxMjI0IDYyLjQxMzEgNy4yODEyNSA2Mi4yNzUxIDcuMzI4MTIgNjIuMTE2MkM3LjM3NSA2MS45NTc0IDcuMzk4NDQgNjEuNzgyOSA3LjM5ODQ0IDYxLjU5MjhDNy4zOTg0NCA2MS40MjM1IDcuMzc3NiA2MS4yNjA3IDcuMzM1OTQgNjEuMTA0NUM3LjI5NDI3IDYwLjk0NTYgNy4yMzA0NyA2MC44MDUgNy4xNDQ1MyA2MC42ODI2QzcuMDU4NTkgNjAuNTU3NiA2Ljk1MDUyIDYwLjQ2IDYuODIwMzEgNjAuMzg5NkM2LjY5MjcxIDYwLjMxNjcgNi41NDAzNiA2MC4yODAzIDYuMzYzMjggNjAuMjgwM0M2LjE2Mjc2IDYwLjI4MDMgNS45NzUyNiA2MC4zMjk4IDUuODAwNzggNjAuNDI4N0M1LjYyODkxIDYwLjUyNTEgNS40ODY5OCA2MC42NTI3IDUuMzc1IDYwLjgxMTVDNS4yNjU2MiA2MC45Njc4IDUuMjAzMTIgNjEuMTM4MyA1LjE4NzUgNjEuMzIzMkw0LjgwNDY5IDYxLjMxOTNDNC44NDExNSA2MS4wMjc3IDQuOTA4ODUgNjAuNzc5IDUuMDA3ODEgNjAuNTczMkM1LjEwOTM4IDYwLjM2NDkgNS4yMzQzOCA2MC4xOTU2IDUuMzgyODEgNjAuMDY1NEM1LjUzMzg1IDU5LjkzMjYgNS43MDE4MiA1OS44MzYzIDUuODg2NzIgNTkuNzc2NEM2LjA3NDIyIDU5LjcxMzkgNi4yNzIxNCA1OS42ODI2IDYuNDgwNDcgNTkuNjgyNkM2Ljc2NDMyIDU5LjY4MjYgNy4wMDkxMSA1OS43MzYgNy4yMTQ4NCA1OS44NDI4QzcuNDIwNTcgNTkuOTQ5NSA3LjU4OTg0IDYwLjA5MjggNy43MjI2NiA2MC4yNzI1QzcuODU1NDcgNjAuNDQ5NSA3Ljk1MzEyIDYwLjY1MDEgOC4wMTU2MiA2MC44NzRDOC4wODA3MyA2MS4wOTU0IDguMTEzMjggNjEuMzIzMiA4LjExMzI4IDYxLjU1NzZDOC4xMTMyOCA2MS44MjU4IDguMDc1NTIgNjIuMDc3MSA4IDYyLjMxMTVDNy45MjQ0OCA2Mi41NDU5IDcuODExMiA2Mi43NTE2IDcuNjYwMTYgNjIuOTI4N0M3LjUxMTcyIDYzLjEwNTggNy4zMjgxMiA2My4yNDM4IDcuMTA5MzggNjMuMzQyOEM2Ljg5MDYyIDYzLjQ0MTcgNi42MzY3MiA2My40OTEyIDYuMzQ3NjYgNjMuNDkxMkM2LjA0MDM2IDYzLjQ5MTIgNS43NzIxNCA2My40Mjg3IDUuNTQyOTcgNjMuMzAzN0M1LjMxMzggNjMuMTc2MSA1LjEyMzcgNjMuMDA2OCA0Ljk3MjY2IDYyLjc5NTlDNC44MjE2MSA2Mi41ODUgNC43MDgzMyA2Mi4zNTA2IDQuNjMyODEgNjIuMDkyOEM0LjU1NzI5IDYxLjgzNSA0LjUxOTUzIDYxLjU3MzIgNC41MTk1MyA2MS4zMDc2VjYwLjk2NzhDNC41MTk1MyA2MC41NjY3IDQuNTU5OSA2MC4xNzM1IDQuNjQwNjIgNTkuNzg4MUM0LjcyMTM1IDU5LjQwMjcgNC44NjA2OCA1OS4wNTM3IDUuMDU4NTkgNTguNzQxMkM1LjI1OTExIDU4LjQyODcgNS41MzY0NiA1OC4xOCA1Ljg5MDYyIDU3Ljk5NTFDNi4yNDQ3OSA1Ny44MTAyIDYuNjk2NjEgNTcuNzE3OCA3LjI0NjA5IDU3LjcxNzhaTTEyLjY3NTIgNjAuMTE2MlY2MC45ODM0QzEyLjY3NTIgNjEuNDQ5NSAxMi42MzM1IDYxLjg0MjggMTIuNTUwMiA2Mi4xNjMxQzEyLjQ2NjggNjIuNDgzNCAxMi4zNDcgNjIuNzQxMiAxMi4xOTA4IDYyLjkzNjVDMTIuMDM0NSA2My4xMzE4IDExLjg0NTcgNjMuMjczOCAxMS42MjQ0IDYzLjM2MjNDMTEuNDA1NiA2My40NDgyIDExLjE1ODIgNjMuNDkxMiAxMC44ODIyIDYzLjQ5MTJDMTAuNjYzNSA2My40OTEyIDEwLjQ2MTYgNjMuNDYzOSAxMC4yNzY3IDYzLjQwOTJDMTAuMDkxOCA2My4zNTQ1IDkuOTI1MTcgNjMuMjY3MyA5Ljc3NjczIDYzLjE0NzVDOS42MzA5IDYzLjAyNTEgOS41MDU5IDYyLjg2NjIgOS40MDE3MyA2Mi42NzA5QzkuMjk3NTcgNjIuNDc1NiA5LjIxODE0IDYyLjIzODYgOS4xNjM0NSA2MS45NkM5LjEwODc3IDYxLjY4MTMgOS4wODE0MiA2MS4zNTU4IDkuMDgxNDIgNjAuOTgzNFY2MC4xMTYyQzkuMDgxNDIgNTkuNjUwMSA5LjEyMzA5IDU5LjI1OTQgOS4yMDY0MiA1OC45NDQzQzkuMjkyMzYgNTguNjI5MiA5LjQxMzQ1IDU4LjM3NjYgOS41Njk3IDU4LjE4NjVDOS43MjU5NSA1Ny45OTM4IDkuOTEzNDUgNTcuODU1OCAxMC4xMzIyIDU3Ljc3MjVDMTAuMzUzNiA1Ny42ODkxIDEwLjYwMSA1Ny42NDc1IDEwLjg3NDQgNTcuNjQ3NUMxMS4wOTU3IDU3LjY0NzUgMTEuMjk4OSA1Ny42NzQ4IDExLjQ4MzggNTcuNzI5NUMxMS42NzEzIDU3Ljc4MTYgMTEuODM3OSA1Ny44NjYyIDExLjk4MzggNTcuOTgzNEMxMi4xMjk2IDU4LjA5OCAxMi4yNTMzIDU4LjI1MTYgMTIuMzU0OSA1OC40NDQzQzEyLjQ1OSA1OC42MzQ0IDEyLjUzODUgNTguODY3NSAxMi41OTMxIDU5LjE0MzZDMTIuNjQ3OCA1OS40MTk2IDEyLjY3NTIgNTkuNzQzOCAxMi42NzUyIDYwLjExNjJaTTExLjk0ODYgNjEuMTAwNlY1OS45OTUxQzExLjk0ODYgNTkuNzM5OSAxMS45MzMgNTkuNTE2IDExLjkwMTcgNTkuMzIzMkMxMS44NzMxIDU5LjEyNzkgMTEuODMwMSA1OC45NjEzIDExLjc3MjggNTguODIzMkMxMS43MTU1IDU4LjY4NTIgMTEuNjQyNiA1OC41NzMyIDExLjU1NDEgNTguNDg3M0MxMS40NjgxIDU4LjQwMTQgMTEuMzY3OSA1OC4zMzg5IDExLjI1MzMgNTguMjk5OEMxMS4xNDEzIDU4LjI1ODEgMTEuMDE1IDU4LjIzNzMgMTAuODc0NCA1OC4yMzczQzEwLjcwMjUgNTguMjM3MyAxMC41NTAyIDU4LjI2OTkgMTAuNDE3NCA1OC4zMzVDMTAuMjg0NSA1OC4zOTc1IDEwLjE3MjYgNTguNDk3NyAxMC4wODE0IDU4LjYzNTdDOS45OTI4OCA1OC43NzM4IDkuOTI1MTcgNTguOTU0OCA5Ljg3ODMgNTkuMTc4N0M5LjgzMTQyIDU5LjQwMjcgOS44MDc5OCA1OS42NzQ4IDkuODA3OTggNTkuOTk1MVY2MS4xMDA2QzkuODA3OTggNjEuMzU1OCA5LjgyMjMxIDYxLjU4MTEgOS44NTA5NSA2MS43NzY0QzkuODgyMiA2MS45NzE3IDkuOTI3NzggNjIuMTQxIDkuOTg3NjcgNjIuMjg0MkMxMC4wNDc2IDYyLjQyNDggMTAuMTIwNSA2Mi41NDA3IDEwLjIwNjQgNjIuNjMxOEMxMC4yOTI0IDYyLjcyMyAxMC4zOTEzIDYyLjc5MDcgMTAuNTAzMyA2Mi44MzVDMTAuNjE3OSA2Mi44NzY2IDEwLjc0NDIgNjIuODk3NSAxMC44ODIyIDYyLjg5NzVDMTEuMDU5MyA2Mi44OTc1IDExLjIxNDIgNjIuODYzNiAxMS4zNDcgNjIuNzk1OUMxMS40Nzk5IDYyLjcyODIgMTEuNTkwNSA2Mi42MjI3IDExLjY3OTEgNjIuNDc5NUMxMS43NzAyIDYyLjMzMzcgMTEuODM3OSA2Mi4xNDc1IDExLjg4MjIgNjEuOTIwOUMxMS45MjY1IDYxLjY5MTcgMTEuOTQ4NiA2MS40MTgzIDExLjk0ODYgNjEuMTAwNlpNMTMuNjc0NiA1OS4xMjAxVjU4LjgxOTNDMTMuNjc0NiA1OC42MDMyIDEzLjcyMTQgNTguNDA2NiAxMy44MTUyIDU4LjIyOTVDMTMuOTA4OSA1OC4wNTI0IDE0LjA0MzEgNTcuOTEwNSAxNC4yMTc1IDU3LjgwMzdDMTQuMzkyIDU3LjY5NjkgMTQuNTk5IDU3LjY0MzYgMTQuODM4NiA1Ny42NDM2QzE1LjA4MzQgNTcuNjQzNiAxNS4yOTE4IDU3LjY5NjkgMTUuNDYzNiA1Ny44MDM3QzE1LjYzODEgNTcuOTEwNSAxNS43NzIyIDU4LjA1MjQgMTUuODY2IDU4LjIyOTVDMTUuOTU5NyA1OC40MDY2IDE2LjAwNjYgNTguNjAzMiAxNi4wMDY2IDU4LjgxOTNWNTkuMTIwMUMxNi4wMDY2IDU5LjMzMTEgMTUuOTU5NyA1OS41MjUxIDE1Ljg2NiA1OS43MDIxQzE1Ljc3NDggNTkuODc5MiAxNS42NDIgNjAuMDIxMiAxNS40Njc1IDYwLjEyNzlDMTUuMjk1NyA2MC4yMzQ3IDE1LjA4ODYgNjAuMjg4MSAxNC44NDY0IDYwLjI4ODFDMTQuNjA0MyA2MC4yODgxIDE0LjM5NDYgNjAuMjM0NyAxNC4yMTc1IDYwLjEyNzlDMTQuMDQzMSA2MC4wMjEyIDEzLjkwODkgNTkuODc5MiAxMy44MTUyIDU5LjcwMjFDMTMuNzIxNCA1OS41MjUxIDEzLjY3NDYgNTkuMzMxMSAxMy42NzQ2IDU5LjEyMDFaTTE0LjIxNzUgNTguODE5M1Y1OS4xMjAxQzE0LjIxNzUgNTkuMjM5OSAxNC4yMzk3IDU5LjM1MzIgMTQuMjgzOSA1OS40NkMxNC4zMzA4IDU5LjU2NjcgMTQuNDAxMSA1OS42NTQgMTQuNDk0OSA1OS43MjE3QzE0LjU4ODYgNTkuNzg2OCAxNC43MDU4IDU5LjgxOTMgMTQuODQ2NCA1OS44MTkzQzE0Ljk4NzEgNTkuODE5MyAxNS4xMDI5IDU5Ljc4NjggMTUuMTk0MSA1OS43MjE3QzE1LjI4NTIgNTkuNjU0IDE1LjM1MjkgNTkuNTY2NyAxNS4zOTcyIDU5LjQ2QzE1LjQ0MTUgNTkuMzUzMiAxNS40NjM2IDU5LjIzOTkgMTUuNDYzNiA1OS4xMjAxVjU4LjgxOTNDMTUuNDYzNiA1OC42OTY5IDE1LjQ0MDIgNTguNTgyNCAxNS4zOTMzIDU4LjQ3NTZDMTUuMzQ5IDU4LjM2NjIgMTUuMjggNTguMjc5IDE1LjE4NjMgNTguMjEzOUMxNS4wOTUxIDU4LjE0NjIgMTQuOTc5MyA1OC4xMTIzIDE0LjgzODYgNTguMTEyM0MxNC43MDA2IDU4LjExMjMgMTQuNTg0NyA1OC4xNDYyIDE0LjQ5MSA1OC4yMTM5QzE0LjM5OTggNTguMjc5IDE0LjMzMDggNTguMzY2MiAxNC4yODM5IDU4LjQ3NTZDMTQuMjM5NyA1OC41ODI0IDE0LjIxNzUgNTguNjk2OSAxNC4yMTc1IDU4LjgxOTNaTTE2LjQ0NDEgNjIuMzIzMlY2Mi4wMTg2QzE2LjQ0NDEgNjEuODA1IDE2LjQ5MSA2MS42MDk3IDE2LjU4NDcgNjEuNDMyNkMxNi42Nzg1IDYxLjI1NTUgMTYuODEyNiA2MS4xMTM2IDE2Ljk4NzEgNjEuMDA2OEMxNy4xNjE1IDYwLjkwMDEgMTcuMzY4NiA2MC44NDY3IDE3LjYwODIgNjAuODQ2N0MxNy44NTI5IDYwLjg0NjcgMTguMDYxMyA2MC45MDAxIDE4LjIzMzIgNjEuMDA2OEMxOC40MDc2IDYxLjExMzYgMTguNTQxOCA2MS4yNTU1IDE4LjYzNTUgNjEuNDMyNkMxOC43MjkzIDYxLjYwOTcgMTguNzc2MSA2MS44MDUgMTguNzc2MSA2Mi4wMTg2VjYyLjMyMzJDMTguNzc2MSA2Mi41MzY4IDE4LjcyOTMgNjIuNzMyMSAxOC42MzU1IDYyLjkwOTJDMTguNTQ0NCA2My4wODYzIDE4LjQxMTUgNjMuMjI4MiAxOC4yMzcxIDYzLjMzNUMxOC4wNjUyIDYzLjQ0MTcgMTcuODU4MiA2My40OTUxIDE3LjYxNiA2My40OTUxQzE3LjM3MzggNjMuNDk1MSAxNy4xNjU0IDYzLjQ0MTcgMTYuOTkxIDYzLjMzNUMxNi44MTY1IDYzLjIyODIgMTYuNjgxMSA2My4wODYzIDE2LjU4NDcgNjIuOTA5MkMxNi40OTEgNjIuNzMyMSAxNi40NDQxIDYyLjUzNjggMTYuNDQ0MSA2Mi4zMjMyWk0xNi45ODcxIDYyLjAxODZWNjIuMzIzMkMxNi45ODcxIDYyLjQ0MyAxNy4wMDkyIDYyLjU1NzYgMTcuMDUzNSA2Mi42NjdDMTcuMTAwMyA2Mi43NzM4IDE3LjE3MDcgNjIuODYxIDE3LjI2NDQgNjIuOTI4N0MxNy4zNTgyIDYyLjk5MzggMTcuNDc1MyA2My4wMjY0IDE3LjYxNiA2My4wMjY0QzE3Ljc1NjYgNjMuMDI2NCAxNy44NzI1IDYyLjk5MzggMTcuOTYzNiA2Mi45Mjg3QzE4LjA1NzQgNjIuODYxIDE4LjEyNjQgNjIuNzczOCAxOC4xNzA3IDYyLjY2N0MxOC4yMTQ5IDYyLjU2MDIgMTguMjM3MSA2Mi40NDU2IDE4LjIzNzEgNjIuMzIzMlY2Mi4wMTg2QzE4LjIzNzEgNjEuODk2MiAxOC4yMTM2IDYxLjc4MTYgMTguMTY2OCA2MS42NzQ4QzE4LjEyMjUgNjEuNTY4IDE4LjA1MzUgNjEuNDgyMSAxNy45NTk3IDYxLjQxN0MxNy44Njg2IDYxLjM0OTMgMTcuNzUxNCA2MS4zMTU0IDE3LjYwODIgNjEuMzE1NEMxNy40NzAxIDYxLjMxNTQgMTcuMzU0MyA2MS4zNDkzIDE3LjI2MDUgNjEuNDE3QzE3LjE2OTQgNjEuNDgyMSAxNy4xMDAzIDYxLjU2OCAxNy4wNTM1IDYxLjY3NDhDMTcuMDA5MiA2MS43ODE2IDE2Ljk4NzEgNjEuODk2MiAxNi45ODcxIDYyLjAxODZaTTE3Ljc4NzggNTguNTM0MkwxNS4wMTA1IDYyLjk3OTVMMTQuNjA0MyA2Mi43MjE3TDE3LjM4MTYgNTguMjc2NEwxNy43ODc4IDU4LjUzNDJaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjxwYXRoIGQ9Ik04LjMxNjQxIDg5LjcwNjFWOTAuMjk5OEg0LjIwNzAzVjg5Ljg3NEw2Ljc1MzkxIDg1LjkzMjZINy4zNDM3NUw2LjcxMDk0IDg3LjA3MzJMNS4wMjczNCA4OS43MDYxSDguMzE2NDFaTTcuNTIzNDQgODUuOTMyNlY5MS42MjAxSDYuODAwNzhWODUuOTMyNkg3LjUyMzQ0Wk0xMi42NzUyIDg4LjMyMzJWODkuMTkwNEMxMi42NzUyIDg5LjY1NjYgMTIuNjMzNSA5MC4wNDk4IDEyLjU1MDIgOTAuMzcwMUMxMi40NjY4IDkwLjY5MDQgMTIuMzQ3IDkwLjk0ODIgMTIuMTkwOCA5MS4xNDM2QzEyLjAzNDUgOTEuMzM4OSAxMS44NDU3IDkxLjQ4MDggMTEuNjI0NCA5MS41NjkzQzExLjQwNTYgOTEuNjU1MyAxMS4xNTgyIDkxLjY5ODIgMTAuODgyMiA5MS42OTgyQzEwLjY2MzUgOTEuNjk4MiAxMC40NjE2IDkxLjY3MDkgMTAuMjc2NyA5MS42MTYyQzEwLjA5MTggOTEuNTYxNSA5LjkyNTE3IDkxLjQ3NDMgOS43NzY3MyA5MS4zNTQ1QzkuNjMwOSA5MS4yMzIxIDkuNTA1OSA5MS4wNzMyIDkuNDAxNzMgOTAuODc3OUM5LjI5NzU3IDkwLjY4MjYgOS4yMTgxNCA5MC40NDU2IDkuMTYzNDUgOTAuMTY3QzkuMTA4NzcgODkuODg4MyA5LjA4MTQyIDg5LjU2MjggOS4wODE0MiA4OS4xOTA0Vjg4LjMyMzJDOS4wODE0MiA4Ny44NTcxIDkuMTIzMDkgODcuNDY2NSA5LjIwNjQyIDg3LjE1MTRDOS4yOTIzNiA4Ni44MzYzIDkuNDEzNDUgODYuNTgzNyA5LjU2OTcgODYuMzkzNkM5LjcyNTk1IDg2LjIwMDggOS45MTM0NSA4Ni4wNjI4IDEwLjEzMjIgODUuOTc5NUMxMC4zNTM2IDg1Ljg5NjIgMTAuNjAxIDg1Ljg1NDUgMTAuODc0NCA4NS44NTQ1QzExLjA5NTcgODUuODU0NSAxMS4yOTg5IDg1Ljg4MTggMTEuNDgzOCA4NS45MzY1QzExLjY3MTMgODUuOTg4NiAxMS44Mzc5IDg2LjA3MzIgMTEuOTgzOCA4Ni4xOTA0QzEyLjEyOTYgODYuMzA1IDEyLjI1MzMgODYuNDU4NyAxMi4zNTQ5IDg2LjY1MTRDMTIuNDU5IDg2Ljg0MTUgMTIuNTM4NSA4Ny4wNzQ1IDEyLjU5MzEgODcuMzUwNkMxMi42NDc4IDg3LjYyNjYgMTIuNjc1MiA4Ny45NTA4IDEyLjY3NTIgODguMzIzMlpNMTEuOTQ4NiA4OS4zMDc2Vjg4LjIwMjFDMTEuOTQ4NiA4Ny45NDY5IDExLjkzMyA4Ny43MjMgMTEuOTAxNyA4Ny41MzAzQzExLjg3MzEgODcuMzM1IDExLjgzMDEgODcuMTY4MyAxMS43NzI4IDg3LjAzMDNDMTEuNzE1NSA4Ni44OTIzIDExLjY0MjYgODYuNzgwMyAxMS41NTQxIDg2LjY5NDNDMTEuNDY4MSA4Ni42MDg0IDExLjM2NzkgODYuNTQ1OSAxMS4yNTMzIDg2LjUwNjhDMTEuMTQxMyA4Ni40NjUyIDExLjAxNSA4Ni40NDQzIDEwLjg3NDQgODYuNDQ0M0MxMC43MDI1IDg2LjQ0NDMgMTAuNTUwMiA4Ni40NzY5IDEwLjQxNzQgODYuNTQyQzEwLjI4NDUgODYuNjA0NSAxMC4xNzI2IDg2LjcwNDggMTAuMDgxNCA4Ni44NDI4QzkuOTkyODggODYuOTgwOCA5LjkyNTE3IDg3LjE2MTggOS44NzgzIDg3LjM4NTdDOS44MzE0MiA4Ny42MDk3IDkuODA3OTggODcuODgxOCA5LjgwNzk4IDg4LjIwMjFWODkuMzA3NkM5LjgwNzk4IDg5LjU2MjggOS44MjIzMSA4OS43ODgxIDkuODUwOTUgODkuOTgzNEM5Ljg4MjIgOTAuMTc4NyA5LjkyNzc4IDkwLjM0OCA5Ljk4NzY3IDkwLjQ5MTJDMTAuMDQ3NiA5MC42MzE4IDEwLjEyMDUgOTAuNzQ3NyAxMC4yMDY0IDkwLjgzODlDMTAuMjkyNCA5MC45MyAxMC4zOTEzIDkwLjk5NzcgMTAuNTAzMyA5MS4wNDJDMTAuNjE3OSA5MS4wODM3IDEwLjc0NDIgOTEuMTA0NSAxMC44ODIyIDkxLjEwNDVDMTEuMDU5MyA5MS4xMDQ1IDExLjIxNDIgOTEuMDcwNiAxMS4zNDcgOTEuMDAyOUMxMS40Nzk5IDkwLjkzNTIgMTEuNTkwNSA5MC44Mjk4IDExLjY3OTEgOTAuNjg2NUMxMS43NzAyIDkwLjU0MDcgMTEuODM3OSA5MC4zNTQ1IDExLjg4MjIgOTAuMTI3OUMxMS45MjY1IDg5Ljg5ODggMTEuOTQ4NiA4OS42MjUzIDExLjk0ODYgODkuMzA3NlpNMTMuNjc0NiA4Ny4zMjcxVjg3LjAyNjRDMTMuNjc0NiA4Ni44MTAyIDEzLjcyMTQgODYuNjEzNiAxMy44MTUyIDg2LjQzNjVDMTMuOTA4OSA4Ni4yNTk0IDE0LjA0MzEgODYuMTE3NSAxNC4yMTc1IDg2LjAxMDdDMTQuMzkyIDg1LjkwNCAxNC41OTkgODUuODUwNiAxNC44Mzg2IDg1Ljg1MDZDMTUuMDgzNCA4NS44NTA2IDE1LjI5MTggODUuOTA0IDE1LjQ2MzYgODYuMDEwN0MxNS42MzgxIDg2LjExNzUgMTUuNzcyMiA4Ni4yNTk0IDE1Ljg2NiA4Ni40MzY1QzE1Ljk1OTcgODYuNjEzNiAxNi4wMDY2IDg2LjgxMDIgMTYuMDA2NiA4Ny4wMjY0Vjg3LjMyNzFDMTYuMDA2NiA4Ny41MzgxIDE1Ljk1OTcgODcuNzMyMSAxNS44NjYgODcuOTA5MkMxNS43NzQ4IDg4LjA4NjMgMTUuNjQyIDg4LjIyODIgMTUuNDY3NSA4OC4zMzVDMTUuMjk1NyA4OC40NDE3IDE1LjA4ODYgODguNDk1MSAxNC44NDY0IDg4LjQ5NTFDMTQuNjA0MyA4OC40OTUxIDE0LjM5NDYgODguNDQxNyAxNC4yMTc1IDg4LjMzNUMxNC4wNDMxIDg4LjIyODIgMTMuOTA4OSA4OC4wODYzIDEzLjgxNTIgODcuOTA5MkMxMy43MjE0IDg3LjczMjEgMTMuNjc0NiA4Ny41MzgxIDEzLjY3NDYgODcuMzI3MVpNMTQuMjE3NSA4Ny4wMjY0Vjg3LjMyNzFDMTQuMjE3NSA4Ny40NDY5IDE0LjIzOTcgODcuNTYwMiAxNC4yODM5IDg3LjY2N0MxNC4zMzA4IDg3Ljc3MzggMTQuNDAxMSA4Ny44NjEgMTQuNDk0OSA4Ny45Mjg3QzE0LjU4ODYgODcuOTkzOCAxNC43MDU4IDg4LjAyNjQgMTQuODQ2NCA4OC4wMjY0QzE0Ljk4NzEgODguMDI2NCAxNS4xMDI5IDg3Ljk5MzggMTUuMTk0MSA4Ny45Mjg3QzE1LjI4NTIgODcuODYxIDE1LjM1MjkgODcuNzczOCAxNS4zOTcyIDg3LjY2N0MxNS40NDE1IDg3LjU2MDIgMTUuNDYzNiA4Ny40NDY5IDE1LjQ2MzYgODcuMzI3MVY4Ny4wMjY0QzE1LjQ2MzYgODYuOTA0IDE1LjQ0MDIgODYuNzg5NCAxNS4zOTMzIDg2LjY4MjZDMTUuMzQ5IDg2LjU3MzIgMTUuMjggODYuNDg2IDE1LjE4NjMgODYuNDIwOUMxNS4wOTUxIDg2LjM1MzIgMTQuOTc5MyA4Ni4zMTkzIDE0LjgzODYgODYuMzE5M0MxNC43MDA2IDg2LjMxOTMgMTQuNTg0NyA4Ni4zNTMyIDE0LjQ5MSA4Ni40MjA5QzE0LjM5OTggODYuNDg2IDE0LjMzMDggODYuNTczMiAxNC4yODM5IDg2LjY4MjZDMTQuMjM5NyA4Ni43ODk0IDE0LjIxNzUgODYuOTA0IDE0LjIxNzUgODcuMDI2NFpNMTYuNDQ0MSA5MC41MzAzVjkwLjIyNTZDMTYuNDQ0MSA5MC4wMTIgMTYuNDkxIDg5LjgxNjcgMTYuNTg0NyA4OS42Mzk2QzE2LjY3ODUgODkuNDYyNiAxNi44MTI2IDg5LjMyMDYgMTYuOTg3MSA4OS4yMTM5QzE3LjE2MTUgODkuMTA3MSAxNy4zNjg2IDg5LjA1MzcgMTcuNjA4MiA4OS4wNTM3QzE3Ljg1MjkgODkuMDUzNyAxOC4wNjEzIDg5LjEwNzEgMTguMjMzMiA4OS4yMTM5QzE4LjQwNzYgODkuMzIwNiAxOC41NDE4IDg5LjQ2MjYgMTguNjM1NSA4OS42Mzk2QzE4LjcyOTMgODkuODE2NyAxOC43NzYxIDkwLjAxMiAxOC43NzYxIDkwLjIyNTZWOTAuNTMwM0MxOC43NzYxIDkwLjc0MzggMTguNzI5MyA5MC45MzkxIDE4LjYzNTUgOTEuMTE2MkMxOC41NDQ0IDkxLjI5MzMgMTguNDExNSA5MS40MzUyIDE4LjIzNzEgOTEuNTQyQzE4LjA2NTIgOTEuNjQ4OCAxNy44NTgyIDkxLjcwMjEgMTcuNjE2IDkxLjcwMjFDMTcuMzczOCA5MS43MDIxIDE3LjE2NTQgOTEuNjQ4OCAxNi45OTEgOTEuNTQyQzE2LjgxNjUgOTEuNDM1MiAxNi42ODExIDkxLjI5MzMgMTYuNTg0NyA5MS4xMTYyQzE2LjQ5MSA5MC45MzkxIDE2LjQ0NDEgOTAuNzQzOCAxNi40NDQxIDkwLjUzMDNaTTE2Ljk4NzEgOTAuMjI1NlY5MC41MzAzQzE2Ljk4NzEgOTAuNjUwMSAxNy4wMDkyIDkwLjc2NDYgMTcuMDUzNSA5MC44NzRDMTcuMTAwMyA5MC45ODA4IDE3LjE3MDcgOTEuMDY4IDE3LjI2NDQgOTEuMTM1N0MxNy4zNTgyIDkxLjIwMDggMTcuNDc1MyA5MS4yMzM0IDE3LjYxNiA5MS4yMzM0QzE3Ljc1NjYgOTEuMjMzNCAxNy44NzI1IDkxLjIwMDggMTcuOTYzNiA5MS4xMzU3QzE4LjA1NzQgOTEuMDY4IDE4LjEyNjQgOTAuOTgwOCAxOC4xNzA3IDkwLjg3NEMxOC4yMTQ5IDkwLjc2NzMgMTguMjM3MSA5MC42NTI3IDE4LjIzNzEgOTAuNTMwM1Y5MC4yMjU2QzE4LjIzNzEgOTAuMTAzMiAxOC4yMTM2IDg5Ljk4ODYgMTguMTY2OCA4OS44ODE4QzE4LjEyMjUgODkuNzc1MSAxOC4wNTM1IDg5LjY4OTEgMTcuOTU5NyA4OS42MjRDMTcuODY4NiA4OS41NTYzIDE3Ljc1MTQgODkuNTIyNSAxNy42MDgyIDg5LjUyMjVDMTcuNDcwMSA4OS41MjI1IDE3LjM1NDMgODkuNTU2MyAxNy4yNjA1IDg5LjYyNEMxNy4xNjk0IDg5LjY4OTEgMTcuMTAwMyA4OS43NzUxIDE3LjA1MzUgODkuODgxOEMxNy4wMDkyIDg5Ljk4ODYgMTYuOTg3MSA5MC4xMDMyIDE2Ljk4NzEgOTAuMjI1NlpNMTcuNzg3OCA4Ni43NDEyTDE1LjAxMDUgOTEuMTg2NUwxNC42MDQzIDkwLjkyODdMMTcuMzgxNiA4Ni40ODM0TDE3Ljc4NzggODYuNzQxMloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTguMTk5MjIgMTE5LjIzM1YxMTkuODI3SDQuNDc2NTZWMTE5LjMwOEw2LjMzOTg0IDExNy4yMzNDNi41NjkwMSAxMTYuOTc4IDYuNzQ2MDkgMTE2Ljc2MiA2Ljg3MTA5IDExNi41ODVDNi45OTg3IDExNi40MDUgNy4wODcyNCAxMTYuMjQ1IDcuMTM2NzIgMTE2LjEwNEM3LjE4ODggMTE1Ljk2MSA3LjIxNDg0IDExNS44MTUgNy4yMTQ4NCAxMTUuNjY3QzcuMjE0ODQgMTE1LjQ3OSA3LjE3NTc4IDExNS4zMSA3LjA5NzY2IDExNS4xNTlDNy4wMjIxNCAxMTUuMDA2IDYuOTEwMTYgMTE0Ljg4MyA2Ljc2MTcyIDExNC43OTJDNi42MTMyOCAxMTQuNzAxIDYuNDMzNTkgMTE0LjY1NSA2LjIyMjY2IDExNC42NTVDNS45NzAwNSAxMTQuNjU1IDUuNzU5MTEgMTE0LjcwNSA1LjU4OTg0IDExNC44MDRDNS40MjMxOCAxMTQuOSA1LjI5ODE4IDExNS4wMzUgNS4yMTQ4NCAxMTUuMjFDNS4xMzE1MSAxMTUuMzg0IDUuMDg5ODQgMTE1LjU4NSA1LjA4OTg0IDExNS44MTJINC4zNjcxOUM0LjM2NzE5IDExNS40OTEgNC40Mzc1IDExNS4xOTggNC41NzgxMiAxMTQuOTMzQzQuNzE4NzUgMTE0LjY2NyA0LjkyNzA4IDExNC40NTYgNS4yMDMxMiAxMTQuM0M1LjQ3OTE3IDExNC4xNDEgNS44MTkwMSAxMTQuMDYyIDYuMjIyNjYgMTE0LjA2MkM2LjU4MjAzIDExNC4wNjIgNi44ODkzMiAxMTQuMTI1IDcuMTQ0NTMgMTE0LjI1M0M3LjM5OTc0IDExNC4zNzggNy41OTUwNSAxMTQuNTU1IDcuNzMwNDcgMTE0Ljc4NEM3Ljg2ODQ5IDExNS4wMTEgNy45Mzc1IDExNS4yNzYgNy45Mzc1IDExNS41ODFDNy45Mzc1IDExNS43NDggNy45MDg4NSAxMTUuOTE3IDcuODUxNTYgMTE2LjA4OUM3Ljc5Njg4IDExNi4yNTggNy43MjAwNSAxMTYuNDI3IDcuNjIxMDkgMTE2LjU5N0M3LjUyNDc0IDExNi43NjYgNy40MTE0NiAxMTYuOTMzIDcuMjgxMjUgMTE3LjA5N0M3LjE1MzY1IDExNy4yNjEgNy4wMTY5MyAxMTcuNDIyIDYuODcxMDkgMTE3LjU4MUw1LjM0NzY2IDExOS4yMzNIOC4xOTkyMlpNMTIuNjc1MiAxMTYuNTNWMTE3LjM5N0MxMi42NzUyIDExNy44NjQgMTIuNjMzNSAxMTguMjU3IDEyLjU1MDIgMTE4LjU3N0MxMi40NjY4IDExOC44OTcgMTIuMzQ3IDExOS4xNTUgMTIuMTkwOCAxMTkuMzUxQzEyLjAzNDUgMTE5LjU0NiAxMS44NDU3IDExOS42ODggMTEuNjI0NCAxMTkuNzc2QzExLjQwNTYgMTE5Ljg2MiAxMS4xNTgyIDExOS45MDUgMTAuODgyMiAxMTkuOTA1QzEwLjY2MzUgMTE5LjkwNSAxMC40NjE2IDExOS44NzggMTAuMjc2NyAxMTkuODIzQzEwLjA5MTggMTE5Ljc2OSA5LjkyNTE3IDExOS42ODEgOS43NzY3MyAxMTkuNTYyQzkuNjMwOSAxMTkuNDM5IDkuNTA1OSAxMTkuMjggOS40MDE3MyAxMTkuMDg1QzkuMjk3NTcgMTE4Ljg5IDkuMjE4MTQgMTE4LjY1MyA5LjE2MzQ1IDExOC4zNzRDOS4xMDg3NyAxMTguMDk1IDkuMDgxNDIgMTE3Ljc3IDkuMDgxNDIgMTE3LjM5N1YxMTYuNTNDOS4wODE0MiAxMTYuMDY0IDkuMTIzMDkgMTE1LjY3NCA5LjIwNjQyIDExNS4zNThDOS4yOTIzNiAxMTUuMDQzIDkuNDEzNDUgMTE0Ljc5MSA5LjU2OTcgMTE0LjYwMUM5LjcyNTk1IDExNC40MDggOS45MTM0NSAxMTQuMjcgMTAuMTMyMiAxMTQuMTg3QzEwLjM1MzYgMTE0LjEwMyAxMC42MDEgMTE0LjA2MiAxMC44NzQ0IDExNC4wNjJDMTEuMDk1NyAxMTQuMDYyIDExLjI5ODkgMTE0LjA4OSAxMS40ODM4IDExNC4xNDRDMTEuNjcxMyAxMTQuMTk2IDExLjgzNzkgMTE0LjI4IDExLjk4MzggMTE0LjM5N0MxMi4xMjk2IDExNC41MTIgMTIuMjUzMyAxMTQuNjY2IDEyLjM1NDkgMTE0Ljg1OEMxMi40NTkgMTE1LjA0OSAxMi41Mzg1IDExNS4yODIgMTIuNTkzMSAxMTUuNTU4QzEyLjY0NzggMTE1LjgzNCAxMi42NzUyIDExNi4xNTggMTIuNjc1MiAxMTYuNTNaTTExLjk0ODYgMTE3LjUxNVYxMTYuNDA5QzExLjk0ODYgMTE2LjE1NCAxMS45MzMgMTE1LjkzIDExLjkwMTcgMTE1LjczN0MxMS44NzMxIDExNS41NDIgMTEuODMwMSAxMTUuMzc1IDExLjc3MjggMTE1LjIzN0MxMS43MTU1IDExNS4wOTkgMTEuNjQyNiAxMTQuOTg3IDExLjU1NDEgMTE0LjkwMUMxMS40NjgxIDExNC44MTUgMTEuMzY3OSAxMTQuNzUzIDExLjI1MzMgMTE0LjcxNEMxMS4xNDEzIDExNC42NzIgMTEuMDE1IDExNC42NTEgMTAuODc0NCAxMTQuNjUxQzEwLjcwMjUgMTE0LjY1MSAxMC41NTAyIDExNC42ODQgMTAuNDE3NCAxMTQuNzQ5QzEwLjI4NDUgMTE0LjgxMiAxMC4xNzI2IDExNC45MTIgMTAuMDgxNCAxMTUuMDVDOS45OTI4OCAxMTUuMTg4IDkuOTI1MTcgMTE1LjM2OSA5Ljg3ODMgMTE1LjU5M0M5LjgzMTQyIDExNS44MTcgOS44MDc5OCAxMTYuMDg5IDkuODA3OTggMTE2LjQwOVYxMTcuNTE1QzkuODA3OTggMTE3Ljc3IDkuODIyMzEgMTE3Ljk5NSA5Ljg1MDk1IDExOC4xOUM5Ljg4MjIgMTE4LjM4NiA5LjkyNzc4IDExOC41NTUgOS45ODc2NyAxMTguNjk4QzEwLjA0NzYgMTE4LjgzOSAxMC4xMjA1IDExOC45NTUgMTAuMjA2NCAxMTkuMDQ2QzEwLjI5MjQgMTE5LjEzNyAxMC4zOTEzIDExOS4yMDUgMTAuNTAzMyAxMTkuMjQ5QzEwLjYxNzkgMTE5LjI5MSAxMC43NDQyIDExOS4zMTIgMTAuODgyMiAxMTkuMzEyQzExLjA1OTMgMTE5LjMxMiAxMS4yMTQyIDExOS4yNzggMTEuMzQ3IDExOS4yMUMxMS40Nzk5IDExOS4xNDIgMTEuNTkwNSAxMTkuMDM3IDExLjY3OTEgMTE4Ljg5NEMxMS43NzAyIDExOC43NDggMTEuODM3OSAxMTguNTYyIDExLjg4MjIgMTE4LjMzNUMxMS45MjY1IDExOC4xMDYgMTEuOTQ4NiAxMTcuODMyIDExLjk0ODYgMTE3LjUxNVpNMTMuNjc0NiAxMTUuNTM0VjExNS4yMzNDMTMuNjc0NiAxMTUuMDE3IDEzLjcyMTQgMTE0LjgyMSAxMy44MTUyIDExNC42NDRDMTMuOTA4OSAxMTQuNDY2IDE0LjA0MzEgMTE0LjMyNSAxNC4yMTc1IDExNC4yMThDMTQuMzkyIDExNC4xMTEgMTQuNTk5IDExNC4wNTggMTQuODM4NiAxMTQuMDU4QzE1LjA4MzQgMTE0LjA1OCAxNS4yOTE4IDExNC4xMTEgMTUuNDYzNiAxMTQuMjE4QzE1LjYzODEgMTE0LjMyNSAxNS43NzIyIDExNC40NjYgMTUuODY2IDExNC42NDRDMTUuOTU5NyAxMTQuODIxIDE2LjAwNjYgMTE1LjAxNyAxNi4wMDY2IDExNS4yMzNWMTE1LjUzNEMxNi4wMDY2IDExNS43NDUgMTUuOTU5NyAxMTUuOTM5IDE1Ljg2NiAxMTYuMTE2QzE1Ljc3NDggMTE2LjI5MyAxNS42NDIgMTE2LjQzNSAxNS40Njc1IDExNi41NDJDMTUuMjk1NyAxMTYuNjQ5IDE1LjA4ODYgMTE2LjcwMiAxNC44NDY0IDExNi43MDJDMTQuNjA0MyAxMTYuNzAyIDE0LjM5NDYgMTE2LjY0OSAxNC4yMTc1IDExNi41NDJDMTQuMDQzMSAxMTYuNDM1IDEzLjkwODkgMTE2LjI5MyAxMy44MTUyIDExNi4xMTZDMTMuNzIxNCAxMTUuOTM5IDEzLjY3NDYgMTE1Ljc0NSAxMy42NzQ2IDExNS41MzRaTTE0LjIxNzUgMTE1LjIzM1YxMTUuNTM0QzE0LjIxNzUgMTE1LjY1NCAxNC4yMzk3IDExNS43NjcgMTQuMjgzOSAxMTUuODc0QzE0LjMzMDggMTE1Ljk4MSAxNC40MDExIDExNi4wNjggMTQuNDk0OSAxMTYuMTM2QzE0LjU4ODYgMTE2LjIwMSAxNC43MDU4IDExNi4yMzMgMTQuODQ2NCAxMTYuMjMzQzE0Ljk4NzEgMTE2LjIzMyAxNS4xMDI5IDExNi4yMDEgMTUuMTk0MSAxMTYuMTM2QzE1LjI4NTIgMTE2LjA2OCAxNS4zNTI5IDExNS45ODEgMTUuMzk3MiAxMTUuODc0QzE1LjQ0MTUgMTE1Ljc2NyAxNS40NjM2IDExNS42NTQgMTUuNDYzNiAxMTUuNTM0VjExNS4yMzNDMTUuNDYzNiAxMTUuMTExIDE1LjQ0MDIgMTE0Ljk5NiAxNS4zOTMzIDExNC44OUMxNS4zNDkgMTE0Ljc4IDE1LjI4IDExNC42OTMgMTUuMTg2MyAxMTQuNjI4QzE1LjA5NTEgMTE0LjU2IDE0Ljk3OTMgMTE0LjUyNiAxNC44Mzg2IDExNC41MjZDMTQuNzAwNiAxMTQuNTI2IDE0LjU4NDcgMTE0LjU2IDE0LjQ5MSAxMTQuNjI4QzE0LjM5OTggMTE0LjY5MyAxNC4zMzA4IDExNC43OCAxNC4yODM5IDExNC44OUMxNC4yMzk3IDExNC45OTYgMTQuMjE3NSAxMTUuMTExIDE0LjIxNzUgMTE1LjIzM1pNMTYuNDQ0MSAxMTguNzM3VjExOC40MzNDMTYuNDQ0MSAxMTguMjE5IDE2LjQ5MSAxMTguMDI0IDE2LjU4NDcgMTE3Ljg0N0MxNi42Nzg1IDExNy42NyAxNi44MTI2IDExNy41MjggMTYuOTg3MSAxMTcuNDIxQzE3LjE2MTUgMTE3LjMxNCAxNy4zNjg2IDExNy4yNjEgMTcuNjA4MiAxMTcuMjYxQzE3Ljg1MjkgMTE3LjI2MSAxOC4wNjEzIDExNy4zMTQgMTguMjMzMiAxMTcuNDIxQzE4LjQwNzYgMTE3LjUyOCAxOC41NDE4IDExNy42NyAxOC42MzU1IDExNy44NDdDMTguNzI5MyAxMTguMDI0IDE4Ljc3NjEgMTE4LjIxOSAxOC43NzYxIDExOC40MzNWMTE4LjczN0MxOC43NzYxIDExOC45NTEgMTguNzI5MyAxMTkuMTQ2IDE4LjYzNTUgMTE5LjMyM0MxOC41NDQ0IDExOS41IDE4LjQxMTUgMTE5LjY0MiAxOC4yMzcxIDExOS43NDlDMTguMDY1MiAxMTkuODU2IDE3Ljg1ODIgMTE5LjkwOSAxNy42MTYgMTE5LjkwOUMxNy4zNzM4IDExOS45MDkgMTcuMTY1NCAxMTkuODU2IDE2Ljk5MSAxMTkuNzQ5QzE2LjgxNjUgMTE5LjY0MiAxNi42ODExIDExOS41IDE2LjU4NDcgMTE5LjMyM0MxNi40OTEgMTE5LjE0NiAxNi40NDQxIDExOC45NTEgMTYuNDQ0MSAxMTguNzM3Wk0xNi45ODcxIDExOC40MzNWMTE4LjczN0MxNi45ODcxIDExOC44NTcgMTcuMDA5MiAxMTguOTcyIDE3LjA1MzUgMTE5LjA4MUMxNy4xMDAzIDExOS4xODggMTcuMTcwNyAxMTkuMjc1IDE3LjI2NDQgMTE5LjM0M0MxNy4zNTgyIDExOS40MDggMTcuNDc1MyAxMTkuNDQgMTcuNjE2IDExOS40NEMxNy43NTY2IDExOS40NCAxNy44NzI1IDExOS40MDggMTcuOTYzNiAxMTkuMzQzQzE4LjA1NzQgMTE5LjI3NSAxOC4xMjY0IDExOS4xODggMTguMTcwNyAxMTkuMDgxQzE4LjIxNDkgMTE4Ljk3NCAxOC4yMzcxIDExOC44NiAxOC4yMzcxIDExOC43MzdWMTE4LjQzM0MxOC4yMzcxIDExOC4zMSAxOC4yMTM2IDExOC4xOTYgMTguMTY2OCAxMTguMDg5QzE4LjEyMjUgMTE3Ljk4MiAxOC4wNTM1IDExNy44OTYgMTcuOTU5NyAxMTcuODMxQzE3Ljg2ODYgMTE3Ljc2MyAxNy43NTE0IDExNy43MjkgMTcuNjA4MiAxMTcuNzI5QzE3LjQ3MDEgMTE3LjcyOSAxNy4zNTQzIDExNy43NjMgMTcuMjYwNSAxMTcuODMxQzE3LjE2OTQgMTE3Ljg5NiAxNy4xMDAzIDExNy45ODIgMTcuMDUzNSAxMTguMDg5QzE3LjAwOTIgMTE4LjE5NiAxNi45ODcxIDExOC4zMSAxNi45ODcxIDExOC40MzNaTTE3Ljc4NzggMTE0Ljk0OEwxNS4wMTA1IDExOS4zOTRMMTQuNjA0MyAxMTkuMTM2TDE3LjM4MTYgMTE0LjY5TDE3Ljc4NzggMTE0Ljk0OFoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTEzLjA0MyAxNDQuNzM3VjE0NS42MDRDMTMuMDQzIDE0Ni4wNzEgMTMuMDAxMyAxNDYuNDY0IDEyLjkxOCAxNDYuNzg0QzEyLjgzNDYgMTQ3LjEwNCAxMi43MTQ4IDE0Ny4zNjIgMTIuNTU4NiAxNDcuNTU4QzEyLjQwMjMgMTQ3Ljc1MyAxMi4yMTM1IDE0Ny44OTUgMTEuOTkyMiAxNDcuOTgzQzExLjc3MzQgMTQ4LjA2OSAxMS41MjYgMTQ4LjExMiAxMS4yNSAxNDguMTEyQzExLjAzMTIgMTQ4LjExMiAxMC44Mjk0IDE0OC4wODUgMTAuNjQ0NSAxNDguMDNDMTAuNDU5NiAxNDcuOTc2IDEwLjI5MyAxNDcuODg4IDEwLjE0NDUgMTQ3Ljc2OUM5Ljk5ODcgMTQ3LjY0NiA5Ljg3MzcgMTQ3LjQ4NyA5Ljc2OTUzIDE0Ny4yOTJDOS42NjUzNiAxNDcuMDk3IDkuNTg1OTQgMTQ2Ljg2IDkuNTMxMjUgMTQ2LjU4MUM5LjQ3NjU2IDE0Ni4zMDIgOS40NDkyMiAxNDUuOTc3IDkuNDQ5MjIgMTQ1LjYwNFYxNDQuNzM3QzkuNDQ5MjIgMTQ0LjI3MSA5LjQ5MDg5IDE0My44ODEgOS41NzQyMiAxNDMuNTY1QzkuNjYwMTYgMTQzLjI1IDkuNzgxMjUgMTQyLjk5OCA5LjkzNzUgMTQyLjgwOEMxMC4wOTM4IDE0Mi42MTUgMTAuMjgxMiAxNDIuNDc3IDEwLjUgMTQyLjM5NEMxMC43MjE0IDE0Mi4zMSAxMC45Njg4IDE0Mi4yNjkgMTEuMjQyMiAxNDIuMjY5QzExLjQ2MzUgMTQyLjI2OSAxMS42NjY3IDE0Mi4yOTYgMTEuODUxNiAxNDIuMzUxQzEyLjAzOTEgMTQyLjQwMyAxMi4yMDU3IDE0Mi40ODcgMTIuMzUxNiAxNDIuNjA0QzEyLjQ5NzQgMTQyLjcxOSAxMi42MjExIDE0Mi44NzMgMTIuNzIyNyAxNDMuMDY1QzEyLjgyNjggMTQzLjI1NiAxMi45MDYyIDE0My40ODkgMTIuOTYwOSAxNDMuNzY1QzEzLjAxNTYgMTQ0LjA0MSAxMy4wNDMgMTQ0LjM2NSAxMy4wNDMgMTQ0LjczN1pNMTIuMzE2NCAxNDUuNzIyVjE0NC42MTZDMTIuMzE2NCAxNDQuMzYxIDEyLjMwMDggMTQ0LjEzNyAxMi4yNjk1IDE0My45NDRDMTIuMjQwOSAxNDMuNzQ5IDEyLjE5NzkgMTQzLjU4MiAxMi4xNDA2IDE0My40NDRDMTIuMDgzMyAxNDMuMzA2IDEyLjAxMDQgMTQzLjE5NCAxMS45MjE5IDE0My4xMDhDMTEuODM1OSAxNDMuMDIyIDExLjczNTcgMTQyLjk2IDExLjYyMTEgMTQyLjkyMUMxMS41MDkxIDE0Mi44NzkgMTEuMzgyOCAxNDIuODU4IDExLjI0MjIgMTQyLjg1OEMxMS4wNzAzIDE0Mi44NTggMTAuOTE4IDE0Mi44OTEgMTAuNzg1MiAxNDIuOTU2QzEwLjY1MjMgMTQzLjAxOSAxMC41NDA0IDE0My4xMTkgMTAuNDQ5MiAxNDMuMjU3QzEwLjM2MDcgMTQzLjM5NSAxMC4yOTMgMTQzLjU3NiAxMC4yNDYxIDE0My44QzEwLjE5OTIgMTQ0LjAyNCAxMC4xNzU4IDE0NC4yOTYgMTAuMTc1OCAxNDQuNjE2VjE0NS43MjJDMTAuMTc1OCAxNDUuOTc3IDEwLjE5MDEgMTQ2LjIwMiAxMC4yMTg4IDE0Ni4zOTdDMTAuMjUgMTQ2LjU5MyAxMC4yOTU2IDE0Ni43NjIgMTAuMzU1NSAxNDYuOTA1QzEwLjQxNTQgMTQ3LjA0NiAxMC40ODgzIDE0Ny4xNjIgMTAuNTc0MiAxNDcuMjUzQzEwLjY2MDIgMTQ3LjM0NCAxMC43NTkxIDE0Ny40MTIgMTAuODcxMSAxNDcuNDU2QzEwLjk4NTcgMTQ3LjQ5OCAxMS4xMTIgMTQ3LjUxOSAxMS4yNSAxNDcuNTE5QzExLjQyNzEgMTQ3LjUxOSAxMS41ODIgMTQ3LjQ4NSAxMS43MTQ4IDE0Ny40MTdDMTEuODQ3NyAxNDcuMzQ5IDExLjk1ODMgMTQ3LjI0NCAxMi4wNDY5IDE0Ny4xMDFDMTIuMTM4IDE0Ni45NTUgMTIuMjA1NyAxNDYuNzY5IDEyLjI1IDE0Ni41NDJDMTIuMjk0MyAxNDYuMzEzIDEyLjMxNjQgMTQ2LjAzOSAxMi4zMTY0IDE0NS43MjJaTTE0LjA0MjQgMTQzLjc0MVYxNDMuNDRDMTQuMDQyNCAxNDMuMjI0IDE0LjA4OTIgMTQzLjAyOCAxNC4xODMgMTQyLjg1MUMxNC4yNzY3IDE0Mi42NzQgMTQuNDEwOCAxNDIuNTMyIDE0LjU4NTMgMTQyLjQyNUMxNC43NTk4IDE0Mi4zMTggMTQuOTY2OCAxNDIuMjY1IDE1LjIwNjQgMTQyLjI2NUMxNS40NTEyIDE0Mi4yNjUgMTUuNjU5NSAxNDIuMzE4IDE1LjgzMTQgMTQyLjQyNUMxNi4wMDU5IDE0Mi41MzIgMTYuMTQgMTQyLjY3NCAxNi4yMzM4IDE0Mi44NTFDMTYuMzI3NSAxNDMuMDI4IDE2LjM3NDQgMTQzLjIyNCAxNi4zNzQ0IDE0My40NFYxNDMuNzQxQzE2LjM3NDQgMTQzLjk1MiAxNi4zMjc1IDE0NC4xNDYgMTYuMjMzOCAxNDQuMzIzQzE2LjE0MjYgMTQ0LjUgMTYuMDA5OCAxNDQuNjQyIDE1LjgzNTMgMTQ0Ljc0OUMxNS42NjM1IDE0NC44NTYgMTUuNDU2NCAxNDQuOTA5IDE1LjIxNDIgMTQ0LjkwOUMxNC45NzIgMTQ0LjkwOSAxNC43NjI0IDE0NC44NTYgMTQuNTg1MyAxNDQuNzQ5QzE0LjQxMDggMTQ0LjY0MiAxNC4yNzY3IDE0NC41IDE0LjE4MyAxNDQuMzIzQzE0LjA4OTIgMTQ0LjE0NiAxNC4wNDI0IDE0My45NTIgMTQuMDQyNCAxNDMuNzQxWk0xNC41ODUzIDE0My40NFYxNDMuNzQxQzE0LjU4NTMgMTQzLjg2MSAxNC42MDc1IDE0My45NzQgMTQuNjUxNyAxNDQuMDgxQzE0LjY5ODYgMTQ0LjE4OCAxNC43Njg5IDE0NC4yNzUgMTQuODYyNyAxNDQuMzQzQzE0Ljk1NjQgMTQ0LjQwOCAxNS4wNzM2IDE0NC40NCAxNS4yMTQyIDE0NC40NEMxNS4zNTQ5IDE0NC40NCAxNS40NzA3IDE0NC40MDggMTUuNTYxOSAxNDQuMzQzQzE1LjY1MyAxNDQuMjc1IDE1LjcyMDcgMTQ0LjE4OCAxNS43NjUgMTQ0LjA4MUMxNS44MDkzIDE0My45NzQgMTUuODMxNCAxNDMuODYxIDE1LjgzMTQgMTQzLjc0MVYxNDMuNDRDMTUuODMxNCAxNDMuMzE4IDE1LjgwOCAxNDMuMjAzIDE1Ljc2MTEgMTQzLjA5N0MxNS43MTY4IDE0Mi45ODcgMTUuNjQ3OCAxNDIuOSAxNS41NTQxIDE0Mi44MzVDMTUuNDYyOSAxNDIuNzY3IDE1LjM0NyAxNDIuNzMzIDE1LjIwNjQgMTQyLjczM0MxNS4wNjg0IDE0Mi43MzMgMTQuOTUyNSAxNDIuNzY3IDE0Ljg1ODggMTQyLjgzNUMxNC43Njc2IDE0Mi45IDE0LjY5ODYgMTQyLjk4NyAxNC42NTE3IDE0My4wOTdDMTQuNjA3NSAxNDMuMjAzIDE0LjU4NTMgMTQzLjMxOCAxNC41ODUzIDE0My40NFpNMTYuODExOSAxNDYuOTQ0VjE0Ni42NEMxNi44MTE5IDE0Ni40MjYgMTYuODU4OCAxNDYuMjMxIDE2Ljk1MjUgMTQ2LjA1NEMxNy4wNDYzIDE0NS44NzcgMTcuMTgwNCAxNDUuNzM1IDE3LjM1NDkgMTQ1LjYyOEMxNy41MjkzIDE0NS41MjEgMTcuNzM2NCAxNDUuNDY4IDE3Ljk3NiAxNDUuNDY4QzE4LjIyMDcgMTQ1LjQ2OCAxOC40MjkxIDE0NS41MjEgMTguNjAxIDE0NS42MjhDMTguNzc1NCAxNDUuNzM1IDE4LjkwOTUgMTQ1Ljg3NyAxOS4wMDMzIDE0Ni4wNTRDMTkuMDk3IDE0Ni4yMzEgMTkuMTQzOSAxNDYuNDI2IDE5LjE0MzkgMTQ2LjY0VjE0Ni45NDRDMTkuMTQzOSAxNDcuMTU4IDE5LjA5NyAxNDcuMzUzIDE5LjAwMzMgMTQ3LjUzQzE4LjkxMjIgMTQ3LjcwNyAxOC43NzkzIDE0Ny44NDkgMTguNjA0OSAxNDcuOTU2QzE4LjQzMyAxNDguMDYzIDE4LjIyNiAxNDguMTE2IDE3Ljk4MzggMTQ4LjExNkMxNy43NDE2IDE0OC4xMTYgMTcuNTMzMiAxNDguMDYzIDE3LjM1ODggMTQ3Ljk1NkMxNy4xODQzIDE0Ny44NDkgMTcuMDQ4OSAxNDcuNzA3IDE2Ljk1MjUgMTQ3LjUzQzE2Ljg1ODggMTQ3LjM1MyAxNi44MTE5IDE0Ny4xNTggMTYuODExOSAxNDYuOTQ0Wk0xNy4zNTQ5IDE0Ni42NFYxNDYuOTQ0QzE3LjM1NDkgMTQ3LjA2NCAxNy4zNzcgMTQ3LjE3OSAxNy40MjEzIDE0Ny4yODhDMTcuNDY4MSAxNDcuMzk1IDE3LjUzODUgMTQ3LjQ4MiAxNy42MzIyIDE0Ny41NUMxNy43MjYgMTQ3LjYxNSAxNy44NDMxIDE0Ny42NDcgMTcuOTgzOCAxNDcuNjQ3QzE4LjEyNDQgMTQ3LjY0NyAxOC4yNDAzIDE0Ny42MTUgMTguMzMxNCAxNDcuNTVDMTguNDI1MiAxNDcuNDgyIDE4LjQ5NDIgMTQ3LjM5NSAxOC41Mzg1IDE0Ny4yODhDMTguNTgyNyAxNDcuMTgxIDE4LjYwNDkgMTQ3LjA2NyAxOC42MDQ5IDE0Ni45NDRWMTQ2LjY0QzE4LjYwNDkgMTQ2LjUxNyAxOC41ODE0IDE0Ni40MDMgMTguNTM0NSAxNDYuMjk2QzE4LjQ5MDMgMTQ2LjE4OSAxOC40MjEzIDE0Ni4xMDMgMTguMzI3NSAxNDYuMDM4QzE4LjIzNjQgMTQ1Ljk3IDE4LjExOTIgMTQ1LjkzNyAxNy45NzYgMTQ1LjkzN0MxNy44Mzc5IDE0NS45MzcgMTcuNzIyIDE0NS45NyAxNy42MjgzIDE0Ni4wMzhDMTcuNTM3MiAxNDYuMTAzIDE3LjQ2ODEgMTQ2LjE4OSAxNy40MjEzIDE0Ni4yOTZDMTcuMzc3IDE0Ni40MDMgMTcuMzU0OSAxNDYuNTE3IDE3LjM1NDkgMTQ2LjY0Wk0xOC4xNTU2IDE0My4xNTVMMTUuMzc4MyAxNDcuNjAxTDE0Ljk3MiAxNDcuMzQzTDE3Ljc0OTQgMTQyLjg5N0wxOC4xNTU2IDE0My4xNTVaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjxwYXRoIGQ9Ik0xMzggNThDMTM4IDU2Ljg5NTQgMTM4Ljg5NSA1NiAxNDAgNTZIMTU0QzE1NS4xMDUgNTYgMTU2IDU2Ljg5NTQgMTU2IDU4VjE0NkgxMzhWNThaIiBmaWxsPSIjM0ZBNzFBIi8+CjxwYXRoIGQ9Ik0yNSA0LjE2MTEzTDIwMCA0LjE2MTE2IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIvPgo8cGF0aCBkPSJNMjUgMzMuMTYxMUwyMDAgMzMuMTYxMiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPHBhdGggZD0iTTI1IDYxLjE2MTFMMjAwIDYxLjE2MTIiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIi8+CjxwYXRoIGQ9Ik0yNSA4OS4xNjExTDIwMCA4OS4xNjEyIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIvPgo8cGF0aCBkPSJNMjUgMTE4LjE2MUwyMDAgMTE4LjE2MSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPGxpbmUgeDE9IjIzLjIiIHkxPSIxNDUuOTYxIiB4Mj0iMjAyLjgiIHkyPSIxNDUuOTYxIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC43IiBzdHJva2Utd2lkdGg9IjAuNCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8bGluZSB4MT0iNDAuNDUwMiIgeTE9IjE0OC4wNzIiIHgyPSI0MC40NTAyIiB5Mj0iMTQ3LjI1IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNMzIuNTA5MSAxNTIuMDI1VjE1Mi44OTNDMzIuNTA5MSAxNTMuMzU5IDMyLjQ2NzQgMTUzLjc1MiAzMi4zODQxIDE1NC4wNzJDMzIuMzAwNyAxNTQuMzkzIDMyLjE4MDkgMTU0LjY1IDMyLjAyNDcgMTU0Ljg0NkMzMS44Njg0IDE1NS4wNDEgMzEuNjc5NiAxNTUuMTgzIDMxLjQ1ODMgMTU1LjI3MUMzMS4yMzk1IDE1NS4zNTcgMzAuOTkyMSAxNTUuNCAzMC43MTYxIDE1NS40QzMwLjQ5NzMgMTU1LjQgMzAuMjk1NSAxNTUuMzczIDMwLjExMDYgMTU1LjMxOEMyOS45MjU3IDE1NS4yNjQgMjkuNzU5MSAxNTUuMTc2IDI5LjYxMDYgMTU1LjA1N0MyOS40NjQ4IDE1NC45MzQgMjkuMzM5OCAxNTQuNzc1IDI5LjIzNTYgMTU0LjU4QzI5LjEzMTUgMTU0LjM4NSAyOS4wNTIgMTU0LjE0OCAyOC45OTczIDE1My44NjlDMjguOTQyNyAxNTMuNTkgMjguOTE1MyAxNTMuMjY1IDI4LjkxNTMgMTUyLjg5M1YxNTIuMDI1QzI4LjkxNTMgMTUxLjU1OSAyOC45NTcgMTUxLjE2OSAyOS4wNDAzIDE1MC44NTRDMjkuMTI2MiAxNTAuNTM4IDI5LjI0NzMgMTUwLjI4NiAyOS40MDM2IDE1MC4wOTZDMjkuNTU5OCAxNDkuOTAzIDI5Ljc0NzMgMTQ5Ljc2NSAyOS45NjYxIDE0OS42ODJDMzAuMTg3NCAxNDkuNTk4IDMwLjQzNDggMTQ5LjU1NyAzMC43MDgzIDE0OS41NTdDMzAuOTI5NiAxNDkuNTU3IDMxLjEzMjggMTQ5LjU4NCAzMS4zMTc3IDE0OS42MzlDMzEuNTA1MiAxNDkuNjkxIDMxLjY3MTggMTQ5Ljc3NSAzMS44MTc3IDE0OS44OTNDMzEuOTYzNSAxNTAuMDA3IDMyLjA4NzIgMTUwLjE2MSAzMi4xODg3IDE1MC4zNTRDMzIuMjkyOSAxNTAuNTQ0IDMyLjM3MjMgMTUwLjc3NyAzMi40MjcgMTUxLjA1M0MzMi40ODE3IDE1MS4zMjkgMzIuNTA5MSAxNTEuNjUzIDMyLjUwOTEgMTUyLjAyNVpNMzEuNzgyNSAxNTMuMDFWMTUxLjkwNEMzMS43ODI1IDE1MS42NDkgMzEuNzY2OSAxNTEuNDI1IDMxLjczNTYgMTUxLjIzMkMzMS43MDcgMTUxLjAzNyAzMS42NjQgMTUwLjg3IDMxLjYwNjcgMTUwLjczMkMzMS41NDk0IDE1MC41OTQgMzEuNDc2NSAxNTAuNDgyIDMxLjM4OCAxNTAuMzk2QzMxLjMwMiAxNTAuMzExIDMxLjIwMTggMTUwLjI0OCAzMS4wODcyIDE1MC4yMDlDMzAuOTc1MiAxNTAuMTY3IDMwLjg0ODkgMTUwLjE0NiAzMC43MDgzIDE1MC4xNDZDMzAuNTM2NCAxNTAuMTQ2IDMwLjM4NDEgMTUwLjE3OSAzMC4yNTEyIDE1MC4yNDRDMzAuMTE4NCAxNTAuMzA3IDMwLjAwNjUgMTUwLjQwNyAyOS45MTUzIDE1MC41NDVDMjkuODI2OCAxNTAuNjgzIDI5Ljc1OTEgMTUwLjg2NCAyOS43MTIyIDE1MS4wODhDMjkuNjY1MyAxNTEuMzEyIDI5LjY0MTkgMTUxLjU4NCAyOS42NDE5IDE1MS45MDRWMTUzLjAxQzI5LjY0MTkgMTUzLjI2NSAyOS42NTYyIDE1My40OSAyOS42ODQ4IDE1My42ODZDMjkuNzE2MSAxNTMuODgxIDI5Ljc2MTcgMTU0LjA1IDI5LjgyMTYgMTU0LjE5M0MyOS44ODE1IDE1NC4zMzQgMjkuOTU0NCAxNTQuNDUgMzAuMDQwMyAxNTQuNTQxQzMwLjEyNjIgMTU0LjYzMiAzMC4yMjUyIDE1NC43IDMwLjMzNzIgMTU0Ljc0NEMzMC40NTE4IDE1NC43ODYgMzAuNTc4MSAxNTQuODA3IDMwLjcxNjEgMTU0LjgwN0MzMC44OTMyIDE1NC44MDcgMzEuMDQ4MSAxNTQuNzczIDMxLjE4MDkgMTU0LjcwNUMzMS4zMTM3IDE1NC42MzcgMzEuNDI0NCAxNTQuNTMyIDMxLjUxMyAxNTQuMzg5QzMxLjYwNDEgMTU0LjI0MyAzMS42NzE4IDE1NC4wNTcgMzEuNzE2MSAxNTMuODNDMzEuNzYwNCAxNTMuNjAxIDMxLjc4MjUgMTUzLjMyNyAzMS43ODI1IDE1My4wMVpNMzUuODk2NCAxNDkuNjA0VjE1NS4zMjJIMzUuMTczN1YxNTAuNTA2TDMzLjcxNjcgMTUxLjAzN1YxNTAuMzg1TDM1Ljc4MzEgMTQ5LjYwNEgzNS44OTY0Wk00MS4xMTI0IDE0OS42MzVWMTU1LjMyMkg0MC4zNTg1VjE0OS42MzVINDEuMTEyNFpNNDMuNDk1MiAxNTIuMTkzVjE1Mi44MTFINDAuOTQ4M1YxNTIuMTkzSDQzLjQ5NTJaTTQzLjg4MTkgMTQ5LjYzNVYxNTAuMjUySDQwLjk0ODNWMTQ5LjYzNUg0My44ODE5Wk00Ni40MjE2IDE1NS40QzQ2LjEyNzMgMTU1LjQgNDUuODYwNCAxNTUuMzUxIDQ1LjYyMDggMTU1LjI1MkM0NS4zODM4IDE1NS4xNSA0NS4xNzk0IDE1NS4wMDggNDUuMDA3NSAxNTQuODI2QzQ0LjgzODMgMTU0LjY0NCA0NC43MDgxIDE1NC40MjggNDQuNjE2OSAxNTQuMTc4QzQ0LjUyNTggMTUzLjkyOCA0NC40ODAyIDE1My42NTQgNDQuNDgwMiAxNTMuMzU3VjE1My4xOTNDNDQuNDgwMiAxNTIuODUgNDQuNTMxIDE1Mi41NDQgNDQuNjMyNSAxNTIuMjc1QzQ0LjczNDEgMTUyLjAwNSA0NC44NzIxIDE1MS43NzUgNDUuMDQ2NiAxNTEuNTg4QzQ1LjIyMTEgMTUxLjQgNDUuNDE5IDE1MS4yNTggNDUuNjQwMyAxNTEuMTYyQzQ1Ljg2MTcgMTUxLjA2NiA0Ni4wOTA5IDE1MS4wMTggNDYuMzI3OCAxNTEuMDE4QzQ2LjYyOTkgMTUxLjAxOCA0Ni44OTAzIDE1MS4wNyA0Ny4xMDkxIDE1MS4xNzRDNDcuMzMwNSAxNTEuMjc4IDQ3LjUxMTQgMTUxLjQyNCA0Ny42NTIxIDE1MS42MTFDNDcuNzkyNyAxNTEuNzk2IDQ3Ljg5NjkgMTUyLjAxNSA0Ny45NjQ2IDE1Mi4yNjhDNDguMDMyMyAxNTIuNTE4IDQ4LjA2NjEgMTUyLjc5MSA0OC4wNjYxIDE1My4wODhWMTUzLjQxMkg0NC45MDk5VjE1Mi44MjJINDcuMzQzNVYxNTIuNzY4QzQ3LjMzMzEgMTUyLjU4IDQ3LjI5NCAxNTIuMzk4IDQ3LjIyNjMgMTUyLjIyMUM0Ny4xNjEyIDE1Mi4wNDQgNDcuMDU3IDE1MS44OTggNDYuOTEzOCAxNTEuNzgzQzQ2Ljc3MDYgMTUxLjY2OSA0Ni41NzUyIDE1MS42MTEgNDYuMzI3OCAxNTEuNjExQzQ2LjE2MzggMTUxLjYxMSA0Ni4wMTI3IDE1MS42NDYgNDUuODc0NyAxNTEuNzE3QzQ1LjczNjcgMTUxLjc4NSA0NS42MTgyIDE1MS44ODYgNDUuNTE5MyAxNTIuMDIxQzQ1LjQyMDMgMTUyLjE1NyA0NS4zNDM1IDE1Mi4zMjIgNDUuMjg4OCAxNTIuNTE4QzQ1LjIzNDEgMTUyLjcxMyA0NS4yMDY4IDE1Mi45MzggNDUuMjA2OCAxNTMuMTkzVjE1My4zNTdDNDUuMjA2OCAxNTMuNTU4IDQ1LjIzNDEgMTUzLjc0NyA0NS4yODg4IDE1My45MjRDNDUuMzQ2MSAxNTQuMDk4IDQ1LjQyODEgMTU0LjI1MiA0NS41MzQ5IDE1NC4zODVDNDUuNjQ0MyAxNTQuNTE4IDQ1Ljc3NTggMTU0LjYyMiA0NS45Mjk0IDE1NC42OTdDNDYuMDg1NyAxNTQuNzczIDQ2LjI2MjcgMTU0LjgxMSA0Ni40NjA3IDE1NC44MTFDNDYuNzE1OSAxNTQuODExIDQ2LjkzMiAxNTQuNzU4IDQ3LjEwOTEgMTU0LjY1NEM0Ny4yODYyIDE1NC41NSA0Ny40NDExIDE1NC40MTEgNDcuNTczOSAxNTQuMjM2TDQ4LjAxMTQgMTU0LjU4NEM0Ny45MjAzIDE1NC43MjIgNDcuODA0NCAxNTQuODU0IDQ3LjY2MzggMTU0Ljk3OUM0Ny41MjMyIDE1NS4xMDQgNDcuMzUgMTU1LjIwNSA0Ny4xNDQzIDE1NS4yODNDNDYuOTQxMSAxNTUuMzYxIDQ2LjcwMDIgMTU1LjQgNDYuNDIxNiAxNTUuNFpNNDguOTg4NiAxNDkuMzIySDQ5LjcxNTJWMTU0LjUwMkw0OS42NTI3IDE1NS4zMjJINDguOTg4NlYxNDkuMzIyWk01Mi41NzA2IDE1My4xNzRWMTUzLjI1NkM1Mi41NzA2IDE1My41NjMgNTIuNTM0MiAxNTMuODQ4IDUyLjQ2MTMgMTU0LjExMUM1Mi4zODgzIDE1NC4zNzIgNTIuMjgxNiAxNTQuNTk4IDUyLjE0MDkgMTU0Ljc5MUM1Mi4wMDAzIDE1NC45ODQgNTEuODI4NCAxNTUuMTMzIDUxLjYyNTMgMTU1LjI0QzUxLjQyMjIgMTU1LjM0NyA1MS4xODkxIDE1NS40IDUwLjkyNjEgMTU1LjRDNTAuNjU3OSAxNTUuNCA1MC40MjIyIDE1NS4zNTUgNTAuMjE5MSAxNTUuMjY0QzUwLjAxODUgMTU1LjE3IDQ5Ljg0OTMgMTU1LjAzNiA0OS43MTEzIDE1NC44NjFDNDkuNTczMiAxNTQuNjg3IDQ5LjQ2MjYgMTU0LjQ3NiA0OS4zNzkyIDE1NC4yMjlDNDkuMjk4NSAxNTMuOTgxIDQ5LjI0MjUgMTUzLjcwMiA0OS4yMTEzIDE1My4zOTNWMTUzLjAzM0M0OS4yNDI1IDE1Mi43MjEgNDkuMjk4NSAxNTIuNDQxIDQ5LjM3OTIgMTUyLjE5M0M0OS40NjI2IDE1MS45NDYgNDkuNTczMiAxNTEuNzM1IDQ5LjcxMTMgMTUxLjU2MUM0OS44NDkzIDE1MS4zODMgNTAuMDE4NSAxNTEuMjQ5IDUwLjIxOTEgMTUxLjE1OEM1MC40MTk2IDE1MS4wNjQgNTAuNjUyNyAxNTEuMDE4IDUwLjkxODMgMTUxLjAxOEM1MS4xODM5IDE1MS4wMTggNTEuNDE5NiAxNTEuMDcgNTEuNjI1MyAxNTEuMTc0QzUxLjgzMSAxNTEuMjc1IDUyLjAwMjkgMTUxLjQyMSA1Mi4xNDA5IDE1MS42MTFDNTIuMjgxNiAxNTEuODAxIDUyLjM4ODMgMTUyLjAyOSA1Mi40NjEzIDE1Mi4yOTVDNTIuNTM0MiAxNTIuNTU4IDUyLjU3MDYgMTUyLjg1MSA1Mi41NzA2IDE1My4xNzRaTTUxLjg0NDEgMTUzLjI1NlYxNTMuMTc0QzUxLjg0NDEgMTUyLjk2MyA1MS44MjQ1IDE1Mi43NjUgNTEuNzg1NSAxNTIuNThDNTEuNzQ2NCAxNTIuMzkzIDUxLjY4MzkgMTUyLjIyOSA1MS41OTggMTUyLjA4OEM1MS41MTIgMTUxLjk0NSA1MS4zOTg4IDE1MS44MzMgNTEuMjU4MSAxNTEuNzUyQzUxLjExNzUgMTUxLjY2OSA1MC45NDQzIDE1MS42MjcgNTAuNzM4NiAxNTEuNjI3QzUwLjU1NjMgMTUxLjYyNyA1MC4zOTc1IDE1MS42NTggNTAuMjYyIDE1MS43MjFDNTAuMTI5MiAxNTEuNzgzIDUwLjAxNTkgMTUxLjg2OCA0OS45MjIyIDE1MS45NzVDNDkuODI4NCAxNTIuMDc5IDQ5Ljc1MTYgMTUyLjE5OSA0OS42OTE3IDE1Mi4zMzRDNDkuNjM0NCAxNTIuNDY3IDQ5LjU5MTUgMTUyLjYwNSA0OS41NjI4IDE1Mi43NDhWMTUzLjY4OUM0OS42MDQ1IDE1My44NzIgNDkuNjcyMiAxNTQuMDQ4IDQ5Ljc2NTkgMTU0LjIxN0M0OS44NjIzIDE1NC4zODMgNDkuOTg5OSAxNTQuNTIgNTAuMTQ4OCAxNTQuNjI3QzUwLjMxMDIgMTU0LjczNCA1MC41MDk0IDE1NC43ODcgNTAuNzQ2NCAxNTQuNzg3QzUwLjk0MTcgMTU0Ljc4NyA1MS4xMDg0IDE1NC43NDggNTEuMjQ2NCAxNTQuNjdDNTEuMzg3IDE1NC41ODkgNTEuNTAwMyAxNTQuNDc5IDUxLjU4NjMgMTU0LjMzOEM1MS42NzQ4IDE1NC4xOTcgNTEuNzM5OSAxNTQuMDM1IDUxLjc4MTYgMTUzLjg1QzUxLjgyMzIgMTUzLjY2NSA1MS44NDQxIDE1My40NjcgNTEuODQ0MSAxNTMuMjU2WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDFfNDA1M18xODUxMTMpIj4KPGxpbmUgeDE9Ijc1Ljg1MDEiIHkxPSIxNDcuMDcyIiB4Mj0iNzUuODUwMSIgeTI9IjE0Ni4yNSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuNSIgc3Ryb2tlLXdpZHRoPSIwLjUiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiLz4KPHBhdGggZD0iTTY3LjkwOSAxNTIuMDI1VjE1Mi44OTNDNjcuOTA5IDE1My4zNTkgNjcuODY3MyAxNTMuNzUyIDY3Ljc4NCAxNTQuMDcyQzY3LjcwMDYgMTU0LjM5MyA2Ny41ODA4IDE1NC42NSA2Ny40MjQ2IDE1NC44NDZDNjcuMjY4MyAxNTUuMDQxIDY3LjA3OTUgMTU1LjE4MyA2Ni44NTgyIDE1NS4yNzFDNjYuNjM5NCAxNTUuMzU3IDY2LjM5MiAxNTUuNCA2Ni4xMTYgMTU1LjRDNjUuODk3MiAxNTUuNCA2NS42OTU0IDE1NS4zNzMgNjUuNTEwNSAxNTUuMzE4QzY1LjMyNTYgMTU1LjI2NCA2NS4xNTkgMTU1LjE3NiA2NS4wMTA1IDE1NS4wNTdDNjQuODY0NyAxNTQuOTM0IDY0LjczOTcgMTU0Ljc3NSA2NC42MzU1IDE1NC41OEM2NC41MzE0IDE1NC4zODUgNjQuNDUxOSAxNTQuMTQ4IDY0LjM5NzIgMTUzLjg2OUM2NC4zNDI2IDE1My41OSA2NC4zMTUyIDE1My4yNjUgNjQuMzE1MiAxNTIuODkzVjE1Mi4wMjVDNjQuMzE1MiAxNTEuNTU5IDY0LjM1NjkgMTUxLjE2OSA2NC40NDAyIDE1MC44NTRDNjQuNTI2MSAxNTAuNTM4IDY0LjY0NzIgMTUwLjI4NiA2NC44MDM1IDE1MC4wOTZDNjQuOTU5NyAxNDkuOTAzIDY1LjE0NzIgMTQ5Ljc2NSA2NS4zNjYgMTQ5LjY4MkM2NS41ODczIDE0OS41OTggNjUuODM0NyAxNDkuNTU3IDY2LjEwODIgMTQ5LjU1N0M2Ni4zMjk1IDE0OS41NTcgNjYuNTMyNyAxNDkuNTg0IDY2LjcxNzYgMTQ5LjYzOUM2Ni45MDUxIDE0OS42OTEgNjcuMDcxNyAxNDkuNzc1IDY3LjIxNzYgMTQ5Ljg5M0M2Ny4zNjM0IDE1MC4wMDcgNjcuNDg3MSAxNTAuMTYxIDY3LjU4ODYgMTUwLjM1NEM2Ny42OTI4IDE1MC41NDQgNjcuNzcyMiAxNTAuNzc3IDY3LjgyNjkgMTUxLjA1M0M2Ny44ODE2IDE1MS4zMjkgNjcuOTA5IDE1MS42NTMgNjcuOTA5IDE1Mi4wMjVaTTY3LjE4MjQgMTUzLjAxVjE1MS45MDRDNjcuMTgyNCAxNTEuNjQ5IDY3LjE2NjggMTUxLjQyNSA2Ny4xMzU1IDE1MS4yMzJDNjcuMTA2OSAxNTEuMDM3IDY3LjA2MzkgMTUwLjg3IDY3LjAwNjYgMTUwLjczMkM2Ni45NDkzIDE1MC41OTQgNjYuODc2NCAxNTAuNDgyIDY2Ljc4NzkgMTUwLjM5NkM2Ni43MDE5IDE1MC4zMTEgNjYuNjAxNyAxNTAuMjQ4IDY2LjQ4NzEgMTUwLjIwOUM2Ni4zNzUxIDE1MC4xNjcgNjYuMjQ4OCAxNTAuMTQ2IDY2LjEwODIgMTUwLjE0NkM2NS45MzYzIDE1MC4xNDYgNjUuNzg0IDE1MC4xNzkgNjUuNjUxMSAxNTAuMjQ0QzY1LjUxODMgMTUwLjMwNyA2NS40MDY0IDE1MC40MDcgNjUuMzE1MiAxNTAuNTQ1QzY1LjIyNjcgMTUwLjY4MyA2NS4xNTkgMTUwLjg2NCA2NS4xMTIxIDE1MS4wODhDNjUuMDY1MiAxNTEuMzEyIDY1LjA0MTggMTUxLjU4NCA2NS4wNDE4IDE1MS45MDRWMTUzLjAxQzY1LjA0MTggMTUzLjI2NSA2NS4wNTYxIDE1My40OSA2NS4wODQ3IDE1My42ODZDNjUuMTE2IDE1My44ODEgNjUuMTYxNiAxNTQuMDUgNjUuMjIxNSAxNTQuMTkzQzY1LjI4MTQgMTU0LjMzNCA2NS4zNTQzIDE1NC40NSA2NS40NDAyIDE1NC41NDFDNjUuNTI2MSAxNTQuNjMyIDY1LjYyNTEgMTU0LjcgNjUuNzM3MSAxNTQuNzQ0QzY1Ljg1MTcgMTU0Ljc4NiA2NS45NzggMTU0LjgwNyA2Ni4xMTYgMTU0LjgwN0M2Ni4yOTMxIDE1NC44MDcgNjYuNDQ4IDE1NC43NzMgNjYuNTgwOCAxNTQuNzA1QzY2LjcxMzYgMTU0LjYzNyA2Ni44MjQzIDE1NC41MzIgNjYuOTEyOSAxNTQuMzg5QzY3LjAwNCAxNTQuMjQzIDY3LjA3MTcgMTU0LjA1NyA2Ny4xMTYgMTUzLjgzQzY3LjE2MDMgMTUzLjYwMSA2Ny4xODI0IDE1My4zMjcgNjcuMTgyNCAxNTMuMDFaTTcyLjY0NzggMTU0LjcyOVYxNTUuMzIySDY4LjkyNTJWMTU0LjgwM0w3MC43ODg1IDE1Mi43MjlDNzEuMDE3NiAxNTIuNDczIDcxLjE5NDcgMTUyLjI1NyA3MS4zMTk3IDE1Mi4wOEM3MS40NDczIDE1MS45IDcxLjUzNTkgMTUxLjc0IDcxLjU4NTMgMTUxLjZDNzEuNjM3NCAxNTEuNDU2IDcxLjY2MzUgMTUxLjMxMSA3MS42NjM1IDE1MS4xNjJDNzEuNjYzNSAxNTAuOTc1IDcxLjYyNDQgMTUwLjgwNSA3MS41NDYzIDE1MC42NTRDNzEuNDcwOCAxNTAuNTAxIDcxLjM1ODggMTUwLjM3OCA3MS4yMTAzIDE1MC4yODdDNzEuMDYxOSAxNTAuMTk2IDcwLjg4MjIgMTUwLjE1IDcwLjY3MTMgMTUwLjE1QzcwLjQxODcgMTUwLjE1IDcwLjIwNzcgMTUwLjIgNzAuMDM4NSAxNTAuMjk5QzY5Ljg3MTggMTUwLjM5NSA2OS43NDY4IDE1MC41MzEgNjkuNjYzNSAxNTAuNzA1QzY5LjU4MDEgMTUwLjg4IDY5LjUzODUgMTUxLjA4IDY5LjUzODUgMTUxLjMwN0g2OC44MTU4QzY4LjgxNTggMTUwLjk4NiA2OC44ODYxIDE1MC42OTMgNjkuMDI2NyAxNTAuNDI4QzY5LjE2NzQgMTUwLjE2MiA2OS4zNzU3IDE0OS45NTEgNjkuNjUxNyAxNDkuNzk1QzY5LjkyNzggMTQ5LjYzNiA3MC4yNjc2IDE0OS41NTcgNzAuNjcxMyAxNDkuNTU3QzcxLjAzMDcgMTQ5LjU1NyA3MS4zMzc5IDE0OS42MiA3MS41OTMyIDE0OS43NDhDNzEuODQ4NCAxNDkuODczIDcyLjA0MzcgMTUwLjA1IDcyLjE3OTEgMTUwLjI3OUM3Mi4zMTcxIDE1MC41MDYgNzIuMzg2MSAxNTAuNzcxIDcyLjM4NjEgMTUxLjA3NkM3Mi4zODYxIDE1MS4yNDMgNzIuMzU3NSAxNTEuNDEyIDcyLjMwMDIgMTUxLjU4NEM3Mi4yNDU1IDE1MS43NTMgNzIuMTY4NyAxNTEuOTIzIDcyLjA2OTcgMTUyLjA5MkM3MS45NzM0IDE1Mi4yNjEgNzEuODYwMSAxNTIuNDI4IDcxLjcyOTkgMTUyLjU5MkM3MS42MDIzIDE1Mi43NTYgNzEuNDY1NSAxNTIuOTE3IDcxLjMxOTcgMTUzLjA3Nkw2OS43OTYzIDE1NC43MjlINzIuNjQ3OFpNNzYuNTEyMyAxNDkuNjM1VjE1NS4zMjJINzUuNzU4NFYxNDkuNjM1SDc2LjUxMjNaTTc4Ljg5NTEgMTUyLjE5M1YxNTIuODExSDc2LjM0ODJWMTUyLjE5M0g3OC44OTUxWk03OS4yODE4IDE0OS42MzVWMTUwLjI1Mkg3Ni4zNDgyVjE0OS42MzVINzkuMjgxOFpNODEuODIxNSAxNTUuNEM4MS41MjcyIDE1NS40IDgxLjI2MDMgMTU1LjM1MSA4MS4wMjA3IDE1NS4yNTJDODAuNzgzNyAxNTUuMTUgODAuNTc5MyAxNTUuMDA4IDgwLjQwNzQgMTU0LjgyNkM4MC4yMzgyIDE1NC42NDQgODAuMTA4IDE1NC40MjggODAuMDE2OCAxNTQuMTc4Qzc5LjkyNTcgMTUzLjkyOCA3OS44ODAxIDE1My42NTQgNzkuODgwMSAxNTMuMzU3VjE1My4xOTNDNzkuODgwMSAxNTIuODUgNzkuOTMwOSAxNTIuNTQ0IDgwLjAzMjQgMTUyLjI3NUM4MC4xMzQgMTUyLjAwNSA4MC4yNzIgMTUxLjc3NSA4MC40NDY1IDE1MS41ODhDODAuNjIxIDE1MS40IDgwLjgxODkgMTUxLjI1OCA4MS4wNDAyIDE1MS4xNjJDODEuMjYxNiAxNTEuMDY2IDgxLjQ5MDggMTUxLjAxOCA4MS43Mjc3IDE1MS4wMThDODIuMDI5OCAxNTEuMDE4IDgyLjI5MDIgMTUxLjA3IDgyLjUwOSAxNTEuMTc0QzgyLjczMDQgMTUxLjI3OCA4Mi45MTEzIDE1MS40MjQgODMuMDUyIDE1MS42MTFDODMuMTkyNiAxNTEuNzk2IDgzLjI5NjggMTUyLjAxNSA4My4zNjQ1IDE1Mi4yNjhDODMuNDMyMiAxNTIuNTE4IDgzLjQ2NiAxNTIuNzkxIDgzLjQ2NiAxNTMuMDg4VjE1My40MTJIODAuMzA5OFYxNTIuODIySDgyLjc0MzRWMTUyLjc2OEM4Mi43MzMgMTUyLjU4IDgyLjY5MzkgMTUyLjM5OCA4Mi42MjYyIDE1Mi4yMjFDODIuNTYxMSAxNTIuMDQ0IDgyLjQ1NjkgMTUxLjg5OCA4Mi4zMTM3IDE1MS43ODNDODIuMTcwNSAxNTEuNjY5IDgxLjk3NTEgMTUxLjYxMSA4MS43Mjc3IDE1MS42MTFDODEuNTYzNyAxNTEuNjExIDgxLjQxMjYgMTUxLjY0NiA4MS4yNzQ2IDE1MS43MTdDODEuMTM2NiAxNTEuNzg1IDgxLjAxODEgMTUxLjg4NiA4MC45MTkyIDE1Mi4wMjFDODAuODIwMiAxNTIuMTU3IDgwLjc0MzQgMTUyLjMyMiA4MC42ODg3IDE1Mi41MThDODAuNjM0IDE1Mi43MTMgODAuNjA2NyAxNTIuOTM4IDgwLjYwNjcgMTUzLjE5M1YxNTMuMzU3QzgwLjYwNjcgMTUzLjU1OCA4MC42MzQgMTUzLjc0NyA4MC42ODg3IDE1My45MjRDODAuNzQ2IDE1NC4wOTggODAuODI4IDE1NC4yNTIgODAuOTM0OCAxNTQuMzg1QzgxLjA0NDIgMTU0LjUxOCA4MS4xNzU3IDE1NC42MjIgODEuMzI5MyAxNTQuNjk3QzgxLjQ4NTYgMTU0Ljc3MyA4MS42NjI2IDE1NC44MTEgODEuODYwNiAxNTQuODExQzgyLjExNTggMTU0LjgxMSA4Mi4zMzE5IDE1NC43NTggODIuNTA5IDE1NC42NTRDODIuNjg2MSAxNTQuNTUgODIuODQxIDE1NC40MTEgODIuOTczOCAxNTQuMjM2TDgzLjQxMTMgMTU0LjU4NEM4My4zMjAyIDE1NC43MjIgODMuMjA0MyAxNTQuODU0IDgzLjA2MzcgMTU0Ljk3OUM4Mi45MjMxIDE1NS4xMDQgODIuNzQ5OSAxNTUuMjA1IDgyLjU0NDIgMTU1LjI4M0M4Mi4zNDEgMTU1LjM2MSA4Mi4xMDAxIDE1NS40IDgxLjgyMTUgMTU1LjRaTTg0LjM4ODUgMTQ5LjMyMkg4NS4xMTUxVjE1NC41MDJMODUuMDUyNiAxNTUuMzIySDg0LjM4ODVWMTQ5LjMyMlpNODcuOTcwNSAxNTMuMTc0VjE1My4yNTZDODcuOTcwNSAxNTMuNTYzIDg3LjkzNDEgMTUzLjg0OCA4Ny44NjEyIDE1NC4xMTFDODcuNzg4MiAxNTQuMzcyIDg3LjY4MTUgMTU0LjU5OCA4Ny41NDA4IDE1NC43OTFDODcuNDAwMiAxNTQuOTg0IDg3LjIyODMgMTU1LjEzMyA4Ny4wMjUyIDE1NS4yNEM4Ni44MjIxIDE1NS4zNDcgODYuNTg5IDE1NS40IDg2LjMyNiAxNTUuNEM4Ni4wNTc4IDE1NS40IDg1LjgyMjEgMTU1LjM1NSA4NS42MTkgMTU1LjI2NEM4NS40MTg1IDE1NS4xNyA4NS4yNDkyIDE1NS4wMzYgODUuMTExMiAxNTQuODYxQzg0Ljk3MzEgMTU0LjY4NyA4NC44NjI1IDE1NC40NzYgODQuNzc5MSAxNTQuMjI5Qzg0LjY5ODQgMTUzLjk4MSA4NC42NDI0IDE1My43MDIgODQuNjExMiAxNTMuMzkzVjE1My4wMzNDODQuNjQyNCAxNTIuNzIxIDg0LjY5ODQgMTUyLjQ0MSA4NC43NzkxIDE1Mi4xOTNDODQuODYyNSAxNTEuOTQ2IDg0Ljk3MzEgMTUxLjczNSA4NS4xMTEyIDE1MS41NjFDODUuMjQ5MiAxNTEuMzgzIDg1LjQxODUgMTUxLjI0OSA4NS42MTkgMTUxLjE1OEM4NS44MTk1IDE1MS4wNjQgODYuMDUyNiAxNTEuMDE4IDg2LjMxODIgMTUxLjAxOEM4Ni41ODM4IDE1MS4wMTggODYuODE5NSAxNTEuMDcgODcuMDI1MiAxNTEuMTc0Qzg3LjIzMSAxNTEuMjc1IDg3LjQwMjggMTUxLjQyMSA4Ny41NDA4IDE1MS42MTFDODcuNjgxNSAxNTEuODAxIDg3Ljc4ODIgMTUyLjAyOSA4Ny44NjEyIDE1Mi4yOTVDODcuOTM0MSAxNTIuNTU4IDg3Ljk3MDUgMTUyLjg1MSA4Ny45NzA1IDE1My4xNzRaTTg3LjI0NCAxNTMuMjU2VjE1My4xNzRDODcuMjQ0IDE1Mi45NjMgODcuMjI0NCAxNTIuNzY1IDg3LjE4NTQgMTUyLjU4Qzg3LjE0NjMgMTUyLjM5MyA4Ny4wODM4IDE1Mi4yMjkgODYuOTk3OSAxNTIuMDg4Qzg2LjkxMTkgMTUxLjk0NSA4Ni43OTg3IDE1MS44MzMgODYuNjU4IDE1MS43NTJDODYuNTE3NCAxNTEuNjY5IDg2LjM0NDIgMTUxLjYyNyA4Ni4xMzg1IDE1MS42MjdDODUuOTU2MiAxNTEuNjI3IDg1Ljc5NzQgMTUxLjY1OCA4NS42NjE5IDE1MS43MjFDODUuNTI5MSAxNTEuNzgzIDg1LjQxNTggMTUxLjg2OCA4NS4zMjIxIDE1MS45NzVDODUuMjI4MyAxNTIuMDc5IDg1LjE1MTUgMTUyLjE5OSA4NS4wOTE2IDE1Mi4zMzRDODUuMDM0MyAxNTIuNDY3IDg0Ljk5MTQgMTUyLjYwNSA4NC45NjI3IDE1Mi43NDhWMTUzLjY4OUM4NS4wMDQ0IDE1My44NzIgODUuMDcyMSAxNTQuMDQ4IDg1LjE2NTggMTU0LjIxN0M4NS4yNjIyIDE1NC4zODMgODUuMzg5OCAxNTQuNTIgODUuNTQ4NyAxNTQuNjI3Qzg1LjcxMDEgMTU0LjczNCA4NS45MDkzIDE1NC43ODcgODYuMTQ2MyAxNTQuNzg3Qzg2LjM0MTYgMTU0Ljc4NyA4Ni41MDgzIDE1NC43NDggODYuNjQ2MyAxNTQuNjdDODYuNzg2OSAxNTQuNTg5IDg2LjkwMDIgMTU0LjQ3OSA4Ni45ODYyIDE1NC4zMzhDODcuMDc0NyAxNTQuMTk3IDg3LjEzOTggMTU0LjAzNSA4Ny4xODE1IDE1My44NUM4Ny4yMjMxIDE1My42NjUgODcuMjQ0IDE1My40NjcgODcuMjQ0IDE1My4yNTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjwvZz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAyXzQwNTNfMTg1MTEzKSI+CjxsaW5lIHgxPSIxMTEuMjUiIHkxPSIxNDcuMDcyIiB4Mj0iMTExLjI1IiB5Mj0iMTQ2LjI1IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNMTAzLjMwOSAxNTIuMDI1VjE1Mi44OTNDMTAzLjMwOSAxNTMuMzU5IDEwMy4yNjcgMTUzLjc1MiAxMDMuMTg0IDE1NC4wNzJDMTAzLjEwMSAxNTQuMzkzIDEwMi45ODEgMTU0LjY1IDEwMi44MjQgMTU0Ljg0NkMxMDIuNjY4IDE1NS4wNDEgMTAyLjQ3OSAxNTUuMTgzIDEwMi4yNTggMTU1LjI3MUMxMDIuMDM5IDE1NS4zNTcgMTAxLjc5MiAxNTUuNCAxMDEuNTE2IDE1NS40QzEwMS4yOTcgMTU1LjQgMTAxLjA5NSAxNTUuMzczIDEwMC45MSAxNTUuMzE4QzEwMC43MjYgMTU1LjI2NCAxMDAuNTU5IDE1NS4xNzYgMTAwLjQxIDE1NS4wNTdDMTAwLjI2NSAxNTQuOTM0IDEwMC4xNCAxNTQuNzc1IDEwMC4wMzUgMTU0LjU4Qzk5LjkzMTMgMTU0LjM4NSA5OS44NTE4IDE1NC4xNDggOTkuNzk3MSAxNTMuODY5Qzk5Ljc0MjUgMTUzLjU5IDk5LjcxNTEgMTUzLjI2NSA5OS43MTUxIDE1Mi44OTNWMTUyLjAyNUM5OS43MTUxIDE1MS41NTkgOTkuNzU2OCAxNTEuMTY5IDk5Ljg0MDEgMTUwLjg1NEM5OS45MjYxIDE1MC41MzggMTAwLjA0NyAxNTAuMjg2IDEwMC4yMDMgMTUwLjA5NkMxMDAuMzYgMTQ5LjkwMyAxMDAuNTQ3IDE0OS43NjUgMTAwLjc2NiAxNDkuNjgyQzEwMC45ODcgMTQ5LjU5OCAxMDEuMjM1IDE0OS41NTcgMTAxLjUwOCAxNDkuNTU3QzEwMS43MjkgMTQ5LjU1NyAxMDEuOTMzIDE0OS41ODQgMTAyLjExNyAxNDkuNjM5QzEwMi4zMDUgMTQ5LjY5MSAxMDIuNDcyIDE0OS43NzUgMTAyLjYxNyAxNDkuODkzQzEwMi43NjMgMTUwLjAwNyAxMDIuODg3IDE1MC4xNjEgMTAyLjk4OSAxNTAuMzU0QzEwMy4wOTMgMTUwLjU0NCAxMDMuMTcyIDE1MC43NzcgMTAzLjIyNyAxNTEuMDUzQzEwMy4yODIgMTUxLjMyOSAxMDMuMzA5IDE1MS42NTMgMTAzLjMwOSAxNTIuMDI1Wk0xMDIuNTgyIDE1My4wMVYxNTEuOTA0QzEwMi41ODIgMTUxLjY0OSAxMDIuNTY3IDE1MS40MjUgMTAyLjUzNSAxNTEuMjMyQzEwMi41MDcgMTUxLjAzNyAxMDIuNDY0IDE1MC44NyAxMDIuNDA3IDE1MC43MzJDMTAyLjM0OSAxNTAuNTk0IDEwMi4yNzYgMTUwLjQ4MiAxMDIuMTg4IDE1MC4zOTZDMTAyLjEwMiAxNTAuMzExIDEwMi4wMDIgMTUwLjI0OCAxMDEuODg3IDE1MC4yMDlDMTAxLjc3NSAxNTAuMTY3IDEwMS42NDkgMTUwLjE0NiAxMDEuNTA4IDE1MC4xNDZDMTAxLjMzNiAxNTAuMTQ2IDEwMS4xODQgMTUwLjE3OSAxMDEuMDUxIDE1MC4yNDRDMTAwLjkxOCAxNTAuMzA3IDEwMC44MDYgMTUwLjQwNyAxMDAuNzE1IDE1MC41NDVDMTAwLjYyNyAxNTAuNjgzIDEwMC41NTkgMTUwLjg2NCAxMDAuNTEyIDE1MS4wODhDMTAwLjQ2NSAxNTEuMzEyIDEwMC40NDIgMTUxLjU4NCAxMDAuNDQyIDE1MS45MDRWMTUzLjAxQzEwMC40NDIgMTUzLjI2NSAxMDAuNDU2IDE1My40OSAxMDAuNDg1IDE1My42ODZDMTAwLjUxNiAxNTMuODgxIDEwMC41NjEgMTU0LjA1IDEwMC42MjEgMTU0LjE5M0MxMDAuNjgxIDE1NC4zMzQgMTAwLjc1NCAxNTQuNDUgMTAwLjg0IDE1NC41NDFDMTAwLjkyNiAxNTQuNjMyIDEwMS4wMjUgMTU0LjcgMTAxLjEzNyAxNTQuNzQ0QzEwMS4yNTIgMTU0Ljc4NiAxMDEuMzc4IDE1NC44MDcgMTAxLjUxNiAxNTQuODA3QzEwMS42OTMgMTU0LjgwNyAxMDEuODQ4IDE1NC43NzMgMTAxLjk4MSAxNTQuNzA1QzEwMi4xMTQgMTU0LjYzNyAxMDIuMjI0IDE1NC41MzIgMTAyLjMxMyAxNTQuMzg5QzEwMi40MDQgMTU0LjI0MyAxMDIuNDcyIDE1NC4wNTcgMTAyLjUxNiAxNTMuODNDMTAyLjU2IDE1My42MDEgMTAyLjU4MiAxNTMuMzI3IDEwMi41ODIgMTUzLjAxWk0xMDUuMzc2IDE1Mi4xMjNIMTA1Ljg5MUMxMDYuMTQ0IDE1Mi4xMjMgMTA2LjM1MiAxNTIuMDgxIDEwNi41MTYgMTUxLjk5OEMxMDYuNjgzIDE1MS45MTIgMTA2LjgwNyAxNTEuNzk2IDEwNi44ODggMTUxLjY1QzEwNi45NzEgMTUxLjUwMiAxMDcuMDEzIDE1MS4zMzUgMTA3LjAxMyAxNTEuMTVDMTA3LjAxMyAxNTAuOTMyIDEwNi45NzYgMTUwLjc0OCAxMDYuOTAzIDE1MC42QzEwNi44MyAxNTAuNDUxIDEwNi43MjEgMTUwLjMzOSAxMDYuNTc1IDE1MC4yNjRDMTA2LjQyOSAxNTAuMTg4IDEwNi4yNDQgMTUwLjE1IDEwNi4wMiAxNTAuMTVDMTA1LjgxNyAxNTAuMTUgMTA1LjYzOCAxNTAuMTkxIDEwNS40ODEgMTUwLjI3MUMxMDUuMzI4IDE1MC4zNSAxMDUuMjA3IDE1MC40NjIgMTA1LjExOCAxNTAuNjA3QzEwNS4wMzIgMTUwLjc1MyAxMDQuOTg5IDE1MC45MjUgMTA0Ljk4OSAxNTEuMTIzSDEwNC4yNjZDMTA0LjI2NiAxNTAuODM0IDEwNC4zMzkgMTUwLjU3MSAxMDQuNDg1IDE1MC4zMzRDMTA0LjYzMSAxNTAuMDk3IDEwNC44MzYgMTQ5LjkwOCAxMDUuMDk5IDE0OS43NjhDMTA1LjM2NCAxNDkuNjI3IDEwNS42NzEgMTQ5LjU1NyAxMDYuMDIgMTQ5LjU1N0MxMDYuMzY0IDE0OS41NTcgMTA2LjY2NSAxNDkuNjE4IDEwNi45MjMgMTQ5Ljc0QzEwNy4xODEgMTQ5Ljg2IDEwNy4zODEgMTUwLjA0IDEwNy41MjQgMTUwLjI3OUMxMDcuNjY4IDE1MC41MTYgMTA3LjczOSAxNTAuODEyIDEwNy43MzkgMTUxLjE2NkMxMDcuNzM5IDE1MS4zMDkgMTA3LjcwNSAxNTEuNDYzIDEwNy42MzggMTUxLjYyN0MxMDcuNTcyIDE1MS43ODggMTA3LjQ3IDE1MS45MzkgMTA3LjMyOSAxNTIuMDhDMTA3LjE5MSAxNTIuMjIxIDEwNy4wMTEgMTUyLjMzNyAxMDYuNzkgMTUyLjQyOEMxMDYuNTY5IDE1Mi41MTYgMTA2LjMwMyAxNTIuNTYxIDEwNS45OTMgMTUyLjU2MUgxMDUuMzc2VjE1Mi4xMjNaTTEwNS4zNzYgMTUyLjcxN1YxNTIuMjgzSDEwNS45OTNDMTA2LjM1NSAxNTIuMjgzIDEwNi42NTUgMTUyLjMyNiAxMDYuODkxIDE1Mi40MTJDMTA3LjEyOCAxNTIuNDk4IDEwNy4zMTUgMTUyLjYxMyAxMDcuNDUgMTUyLjc1NkMxMDcuNTg4IDE1Mi44OTkgMTA3LjY4NCAxNTMuMDU3IDEwNy43MzkgMTUzLjIyOUMxMDcuNzk2IDE1My4zOTggMTA3LjgyNSAxNTMuNTY3IDEwNy44MjUgMTUzLjczNkMxMDcuODI1IDE1NC4wMDIgMTA3Ljc4IDE1NC4yMzggMTA3LjY4OCAxNTQuNDQzQzEwNy42IDE1NC42NDkgMTA3LjQ3NCAxNTQuODI0IDEwNy4zMDkgMTU0Ljk2N0MxMDcuMTQ4IDE1NS4xMSAxMDYuOTU4IDE1NS4yMTggMTA2LjczOSAxNTUuMjkxQzEwNi41MiAxNTUuMzY0IDEwNi4yODIgMTU1LjQgMTA2LjAyNCAxNTUuNEMxMDUuNzc3IDE1NS40IDEwNS41NDQgMTU1LjM2NSAxMDUuMzI1IDE1NS4yOTVDMTA1LjEwOSAxNTUuMjI1IDEwNC45MTggMTU1LjEyMyAxMDQuNzUxIDE1NC45OUMxMDQuNTg0IDE1NC44NTUgMTA0LjQ1NCAxNTQuNjg5IDEwNC4zNiAxNTQuNDk0QzEwNC4yNjYgMTU0LjI5NiAxMDQuMjIgMTU0LjA3MSAxMDQuMjIgMTUzLjgxOEgxMDQuOTQyQzEwNC45NDIgMTU0LjAxNiAxMDQuOTg1IDE1NC4xODkgMTA1LjA3MSAxNTQuMzM4QzEwNS4xNiAxNTQuNDg2IDEwNS4yODUgMTU0LjYwMiAxMDUuNDQ2IDE1NC42ODZDMTA1LjYxIDE1NC43NjYgMTA1LjgwMyAxNTQuODA3IDEwNi4wMjQgMTU0LjgwN0MxMDYuMjQ2IDE1NC44MDcgMTA2LjQzNiAxNTQuNzY5IDEwNi41OTUgMTU0LjY5M0MxMDYuNzU2IDE1NC42MTUgMTA2Ljg4IDE1NC40OTggMTA2Ljk2NiAxNTQuMzQyQzEwNy4wNTQgMTU0LjE4NiAxMDcuMDk5IDE1My45ODkgMTA3LjA5OSAxNTMuNzUyQzEwNy4wOTkgMTUzLjUxNSAxMDcuMDQ5IDE1My4zMjEgMTA2Ljk1IDE1My4xN0MxMDYuODUxIDE1My4wMTYgMTA2LjcxMSAxNTIuOTAzIDEwNi41MjggMTUyLjgzQzEwNi4zNDkgMTUyLjc1NSAxMDYuMTM2IDE1Mi43MTcgMTA1Ljg5MSAxNTIuNzE3SDEwNS4zNzZaTTExMS45MTIgMTQ5LjYzNVYxNTUuMzIySDExMS4xNThWMTQ5LjYzNUgxMTEuOTEyWk0xMTQuMjk1IDE1Mi4xOTNWMTUyLjgxMUgxMTEuNzQ4VjE1Mi4xOTNIMTE0LjI5NVpNMTE0LjY4MiAxNDkuNjM1VjE1MC4yNTJIMTExLjc0OFYxNDkuNjM1SDExNC42ODJaTTExNy4yMjEgMTU1LjRDMTE2LjkyNyAxNTUuNCAxMTYuNjYgMTU1LjM1MSAxMTYuNDIxIDE1NS4yNTJDMTE2LjE4NCAxNTUuMTUgMTE1Ljk3OSAxNTUuMDA4IDExNS44MDcgMTU0LjgyNkMxMTUuNjM4IDE1NC42NDQgMTE1LjUwOCAxNTQuNDI4IDExNS40MTcgMTU0LjE3OEMxMTUuMzI2IDE1My45MjggMTE1LjI4IDE1My42NTQgMTE1LjI4IDE1My4zNTdWMTUzLjE5M0MxMTUuMjggMTUyLjg1IDExNS4zMzEgMTUyLjU0NCAxMTUuNDMyIDE1Mi4yNzVDMTE1LjUzNCAxNTIuMDA1IDExNS42NzIgMTUxLjc3NSAxMTUuODQ2IDE1MS41ODhDMTE2LjAyMSAxNTEuNCAxMTYuMjE5IDE1MS4yNTggMTE2LjQ0IDE1MS4xNjJDMTE2LjY2MiAxNTEuMDY2IDExNi44OTEgMTUxLjAxOCAxMTcuMTI4IDE1MS4wMThDMTE3LjQzIDE1MS4wMTggMTE3LjY5IDE1MS4wNyAxMTcuOTA5IDE1MS4xNzRDMTE4LjEzIDE1MS4yNzggMTE4LjMxMSAxNTEuNDI0IDExOC40NTIgMTUxLjYxMUMxMTguNTkyIDE1MS43OTYgMTE4LjY5NyAxNTIuMDE1IDExOC43NjQgMTUyLjI2OEMxMTguODMyIDE1Mi41MTggMTE4Ljg2NiAxNTIuNzkxIDExOC44NjYgMTUzLjA4OFYxNTMuNDEySDExNS43MVYxNTIuODIySDExOC4xNDNWMTUyLjc2OEMxMTguMTMzIDE1Mi41OCAxMTguMDk0IDE1Mi4zOTggMTE4LjAyNiAxNTIuMjIxQzExNy45NjEgMTUyLjA0NCAxMTcuODU3IDE1MS44OTggMTE3LjcxNCAxNTEuNzgzQzExNy41NyAxNTEuNjY5IDExNy4zNzUgMTUxLjYxMSAxMTcuMTI4IDE1MS42MTFDMTE2Ljk2NCAxNTEuNjExIDExNi44MTMgMTUxLjY0NiAxMTYuNjc1IDE1MS43MTdDMTE2LjUzNyAxNTEuNzg1IDExNi40MTggMTUxLjg4NiAxMTYuMzE5IDE1Mi4wMjFDMTE2LjIyIDE1Mi4xNTcgMTE2LjE0MyAxNTIuMzIyIDExNi4wODkgMTUyLjUxOEMxMTYuMDM0IDE1Mi43MTMgMTE2LjAwNyAxNTIuOTM4IDExNi4wMDcgMTUzLjE5M1YxNTMuMzU3QzExNi4wMDcgMTUzLjU1OCAxMTYuMDM0IDE1My43NDcgMTE2LjA4OSAxNTMuOTI0QzExNi4xNDYgMTU0LjA5OCAxMTYuMjI4IDE1NC4yNTIgMTE2LjMzNSAxNTQuMzg1QzExNi40NDQgMTU0LjUxOCAxMTYuNTc2IDE1NC42MjIgMTE2LjcyOSAxNTQuNjk3QzExNi44ODUgMTU0Ljc3MyAxMTcuMDYzIDE1NC44MTEgMTE3LjI2IDE1NC44MTFDMTE3LjUxNiAxNTQuODExIDExNy43MzIgMTU0Ljc1OCAxMTcuOTA5IDE1NC42NTRDMTE4LjA4NiAxNTQuNTUgMTE4LjI0MSAxNTQuNDExIDExOC4zNzQgMTU0LjIzNkwxMTguODExIDE1NC41ODRDMTE4LjcyIDE1NC43MjIgMTE4LjYwNCAxNTQuODU0IDExOC40NjQgMTU0Ljk3OUMxMTguMzIzIDE1NS4xMDQgMTE4LjE1IDE1NS4yMDUgMTE3Ljk0NCAxNTUuMjgzQzExNy43NDEgMTU1LjM2MSAxMTcuNSAxNTUuNCAxMTcuMjIxIDE1NS40Wk0xMTkuNzg4IDE0OS4zMjJIMTIwLjUxNVYxNTQuNTAyTDEyMC40NTIgMTU1LjMyMkgxMTkuNzg4VjE0OS4zMjJaTTEyMy4zNyAxNTMuMTc0VjE1My4yNTZDMTIzLjM3IDE1My41NjMgMTIzLjMzNCAxNTMuODQ4IDEyMy4yNjEgMTU0LjExMUMxMjMuMTg4IDE1NC4zNzIgMTIzLjA4MSAxNTQuNTk4IDEyMi45NDEgMTU0Ljc5MUMxMjIuOCAxNTQuOTg0IDEyMi42MjggMTU1LjEzMyAxMjIuNDI1IDE1NS4yNEMxMjIuMjIyIDE1NS4zNDcgMTIxLjk4OSAxNTUuNCAxMjEuNzI2IDE1NS40QzEyMS40NTggMTU1LjQgMTIxLjIyMiAxNTUuMzU1IDEyMS4wMTkgMTU1LjI2NEMxMjAuODE4IDE1NS4xNyAxMjAuNjQ5IDE1NS4wMzYgMTIwLjUxMSAxNTQuODYxQzEyMC4zNzMgMTU0LjY4NyAxMjAuMjYyIDE1NC40NzYgMTIwLjE3OSAxNTQuMjI5QzEyMC4wOTggMTUzLjk4MSAxMjAuMDQyIDE1My43MDIgMTIwLjAxMSAxNTMuMzkzVjE1My4wMzNDMTIwLjA0MiAxNTIuNzIxIDEyMC4wOTggMTUyLjQ0MSAxMjAuMTc5IDE1Mi4xOTNDMTIwLjI2MiAxNTEuOTQ2IDEyMC4zNzMgMTUxLjczNSAxMjAuNTExIDE1MS41NjFDMTIwLjY0OSAxNTEuMzgzIDEyMC44MTggMTUxLjI0OSAxMjEuMDE5IDE1MS4xNThDMTIxLjIxOSAxNTEuMDY0IDEyMS40NTIgMTUxLjAxOCAxMjEuNzE4IDE1MS4wMThDMTIxLjk4NCAxNTEuMDE4IDEyMi4yMTkgMTUxLjA3IDEyMi40MjUgMTUxLjE3NEMxMjIuNjMxIDE1MS4yNzUgMTIyLjgwMyAxNTEuNDIxIDEyMi45NDEgMTUxLjYxMUMxMjMuMDgxIDE1MS44MDEgMTIzLjE4OCAxNTIuMDI5IDEyMy4yNjEgMTUyLjI5NUMxMjMuMzM0IDE1Mi41NTggMTIzLjM3IDE1Mi44NTEgMTIzLjM3IDE1My4xNzRaTTEyMi42NDQgMTUzLjI1NlYxNTMuMTc0QzEyMi42NDQgMTUyLjk2MyAxMjIuNjI0IDE1Mi43NjUgMTIyLjU4NSAxNTIuNThDMTIyLjU0NiAxNTIuMzkzIDEyMi40ODQgMTUyLjIyOSAxMjIuMzk4IDE1Mi4wODhDMTIyLjMxMiAxNTEuOTQ1IDEyMi4xOTkgMTUxLjgzMyAxMjIuMDU4IDE1MS43NTJDMTIxLjkxNyAxNTEuNjY5IDEyMS43NDQgMTUxLjYyNyAxMjEuNTM4IDE1MS42MjdDMTIxLjM1NiAxNTEuNjI3IDEyMS4xOTcgMTUxLjY1OCAxMjEuMDYyIDE1MS43MjFDMTIwLjkyOSAxNTEuNzgzIDEyMC44MTYgMTUxLjg2OCAxMjAuNzIyIDE1MS45NzVDMTIwLjYyOCAxNTIuMDc5IDEyMC41NTEgMTUyLjE5OSAxMjAuNDkyIDE1Mi4zMzRDMTIwLjQzNCAxNTIuNDY3IDEyMC4zOTEgMTUyLjYwNSAxMjAuMzYzIDE1Mi43NDhWMTUzLjY4OUMxMjAuNDA0IDE1My44NzIgMTIwLjQ3MiAxNTQuMDQ4IDEyMC41NjYgMTU0LjIxN0MxMjAuNjYyIDE1NC4zODMgMTIwLjc5IDE1NC41MiAxMjAuOTQ5IDE1NC42MjdDMTIxLjExIDE1NC43MzQgMTIxLjMwOSAxNTQuNzg3IDEyMS41NDYgMTU0Ljc4N0MxMjEuNzQyIDE1NC43ODcgMTIxLjkwOCAxNTQuNzQ4IDEyMi4wNDYgMTU0LjY3QzEyMi4xODcgMTU0LjU4OSAxMjIuMyAxNTQuNDc5IDEyMi4zODYgMTU0LjMzOEMxMjIuNDc1IDE1NC4xOTcgMTIyLjU0IDE1NC4wMzUgMTIyLjU4MSAxNTMuODVDMTIyLjYyMyAxNTMuNjY1IDEyMi42NDQgMTUzLjQ2NyAxMjIuNjQ0IDE1My4yNTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjwvZz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAzXzQwNTNfMTg1MTEzKSI+CjxsaW5lIHgxPSIxNDYuNjUiIHkxPSIxNDcuMDcyIiB4Mj0iMTQ2LjY1IiB5Mj0iMTQ2LjI1IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNMTM4LjcwOSAxNTIuMDI1VjE1Mi44OTNDMTM4LjcwOSAxNTMuMzU5IDEzOC42NjggMTUzLjc1MiAxMzguNTg0IDE1NC4wNzJDMTM4LjUwMSAxNTQuMzkzIDEzOC4zODEgMTU0LjY1IDEzOC4yMjUgMTU0Ljg0NkMxMzguMDY5IDE1NS4wNDEgMTM3Ljg4IDE1NS4xODMgMTM3LjY1OCAxNTUuMjcxQzEzNy40NCAxNTUuMzU3IDEzNy4xOTIgMTU1LjQgMTM2LjkxNiAxNTUuNEMxMzYuNjk4IDE1NS40IDEzNi40OTYgMTU1LjM3MyAxMzYuMzExIDE1NS4zMThDMTM2LjEyNiAxNTUuMjY0IDEzNS45NTkgMTU1LjE3NiAxMzUuODExIDE1NS4wNTdDMTM1LjY2NSAxNTQuOTM0IDEzNS41NCAxNTQuNzc1IDEzNS40MzYgMTU0LjU4QzEzNS4zMzIgMTU0LjM4NSAxMzUuMjUyIDE1NC4xNDggMTM1LjE5OCAxNTMuODY5QzEzNS4xNDMgMTUzLjU5IDEzNS4xMTYgMTUzLjI2NSAxMzUuMTE2IDE1Mi44OTNWMTUyLjAyNUMxMzUuMTE2IDE1MS41NTkgMTM1LjE1NyAxNTEuMTY5IDEzNS4yNDEgMTUwLjg1NEMxMzUuMzI2IDE1MC41MzggMTM1LjQ0OCAxNTAuMjg2IDEzNS42MDQgMTUwLjA5NkMxMzUuNzYgMTQ5LjkwMyAxMzUuOTQ4IDE0OS43NjUgMTM2LjE2NiAxNDkuNjgyQzEzNi4zODggMTQ5LjU5OCAxMzYuNjM1IDE0OS41NTcgMTM2LjkwOCAxNDkuNTU3QzEzNy4xMyAxNDkuNTU3IDEzNy4zMzMgMTQ5LjU4NCAxMzcuNTE4IDE0OS42MzlDMTM3LjcwNSAxNDkuNjkxIDEzNy44NzIgMTQ5Ljc3NSAxMzguMDE4IDE0OS44OTNDMTM4LjE2NCAxNTAuMDA3IDEzOC4yODcgMTUwLjE2MSAxMzguMzg5IDE1MC4zNTRDMTM4LjQ5MyAxNTAuNTQ0IDEzOC41NzMgMTUwLjc3NyAxMzguNjI3IDE1MS4wNTNDMTM4LjY4MiAxNTEuMzI5IDEzOC43MDkgMTUxLjY1MyAxMzguNzA5IDE1Mi4wMjVaTTEzNy45ODMgMTUzLjAxVjE1MS45MDRDMTM3Ljk4MyAxNTEuNjQ5IDEzNy45NjcgMTUxLjQyNSAxMzcuOTM2IDE1MS4yMzJDMTM3LjkwNyAxNTEuMDM3IDEzNy44NjQgMTUwLjg3IDEzNy44MDcgMTUwLjczMkMxMzcuNzUgMTUwLjU5NCAxMzcuNjc3IDE1MC40ODIgMTM3LjU4OCAxNTAuMzk2QzEzNy41MDIgMTUwLjMxMSAxMzcuNDAyIDE1MC4yNDggMTM3LjI4NyAxNTAuMjA5QzEzNy4xNzUgMTUwLjE2NyAxMzcuMDQ5IDE1MC4xNDYgMTM2LjkwOCAxNTAuMTQ2QzEzNi43MzcgMTUwLjE0NiAxMzYuNTg0IDE1MC4xNzkgMTM2LjQ1MSAxNTAuMjQ0QzEzNi4zMTkgMTUwLjMwNyAxMzYuMjA3IDE1MC40MDcgMTM2LjExNiAxNTAuNTQ1QzEzNi4wMjcgMTUwLjY4MyAxMzUuOTU5IDE1MC44NjQgMTM1LjkxMiAxNTEuMDg4QzEzNS44NjYgMTUxLjMxMiAxMzUuODQyIDE1MS41ODQgMTM1Ljg0MiAxNTEuOTA0VjE1My4wMUMxMzUuODQyIDE1My4yNjUgMTM1Ljg1NiAxNTMuNDkgMTM1Ljg4NSAxNTMuNjg2QzEzNS45MTYgMTUzLjg4MSAxMzUuOTYyIDE1NC4wNSAxMzYuMDIyIDE1NC4xOTNDMTM2LjA4MiAxNTQuMzM0IDEzNi4xNTUgMTU0LjQ1IDEzNi4yNDEgMTU0LjU0MUMxMzYuMzI2IDE1NC42MzIgMTM2LjQyNSAxNTQuNyAxMzYuNTM3IDE1NC43NDRDMTM2LjY1MiAxNTQuNzg2IDEzNi43NzggMTU0LjgwNyAxMzYuOTE2IDE1NC44MDdDMTM3LjA5MyAxNTQuODA3IDEzNy4yNDggMTU0Ljc3MyAxMzcuMzgxIDE1NC43MDVDMTM3LjUxNCAxNTQuNjM3IDEzNy42MjUgMTU0LjUzMiAxMzcuNzEzIDE1NC4zODlDMTM3LjgwNCAxNTQuMjQzIDEzNy44NzIgMTU0LjA1NyAxMzcuOTE2IDE1My44M0MxMzcuOTYxIDE1My42MDEgMTM3Ljk4MyAxNTMuMzI3IDEzNy45ODMgMTUzLjAxWk0xNDMuNTY1IDE1My40MDhWMTU0LjAwMkgxMzkuNDU2VjE1My41NzZMMTQyLjAwMyAxNDkuNjM1SDE0Mi41OTNMMTQxLjk2IDE1MC43NzVMMTQwLjI3NiAxNTMuNDA4SDE0My41NjVaTTE0Mi43NzIgMTQ5LjYzNVYxNTUuMzIySDE0Mi4wNVYxNDkuNjM1SDE0Mi43NzJaTTE0Ny4zMTMgMTQ5LjYzNVYxNTUuMzIySDE0Ni41NTlWMTQ5LjYzNUgxNDcuMzEzWk0xNDkuNjk1IDE1Mi4xOTNWMTUyLjgxMUgxNDcuMTQ5VjE1Mi4xOTNIMTQ5LjY5NVpNMTUwLjA4MiAxNDkuNjM1VjE1MC4yNTJIMTQ3LjE0OVYxNDkuNjM1SDE1MC4wODJaTTE1Mi42MjIgMTU1LjRDMTUyLjMyOCAxNTUuNCAxNTIuMDYxIDE1NS4zNTEgMTUxLjgyMSAxNTUuMjUyQzE1MS41ODQgMTU1LjE1IDE1MS4zOCAxNTUuMDA4IDE1MS4yMDggMTU0LjgyNkMxNTEuMDM4IDE1NC42NDQgMTUwLjkwOCAxNTQuNDI4IDE1MC44MTcgMTU0LjE3OEMxNTAuNzI2IDE1My45MjggMTUwLjY4IDE1My42NTQgMTUwLjY4IDE1My4zNTdWMTUzLjE5M0MxNTAuNjggMTUyLjg1IDE1MC43MzEgMTUyLjU0NCAxNTAuODMzIDE1Mi4yNzVDMTUwLjkzNCAxNTIuMDA1IDE1MS4wNzIgMTUxLjc3NSAxNTEuMjQ3IDE1MS41ODhDMTUxLjQyMSAxNTEuNCAxNTEuNjE5IDE1MS4yNTggMTUxLjg0MSAxNTEuMTYyQzE1Mi4wNjIgMTUxLjA2NiAxNTIuMjkxIDE1MS4wMTggMTUyLjUyOCAxNTEuMDE4QzE1Mi44MyAxNTEuMDE4IDE1My4wOTEgMTUxLjA3IDE1My4zMDkgMTUxLjE3NEMxNTMuNTMxIDE1MS4yNzggMTUzLjcxMiAxNTEuNDI0IDE1My44NTIgMTUxLjYxMUMxNTMuOTkzIDE1MS43OTYgMTU0LjA5NyAxNTIuMDE1IDE1NC4xNjUgMTUyLjI2OEMxNTQuMjMyIDE1Mi41MTggMTU0LjI2NiAxNTIuNzkxIDE1NC4yNjYgMTUzLjA4OFYxNTMuNDEySDE1MS4xMVYxNTIuODIySDE1My41NDRWMTUyLjc2OEMxNTMuNTMzIDE1Mi41OCAxNTMuNDk0IDE1Mi4zOTggMTUzLjQyNiAxNTIuMjIxQzE1My4zNjEgMTUyLjA0NCAxNTMuMjU3IDE1MS44OTggMTUzLjExNCAxNTEuNzgzQzE1Mi45NzEgMTUxLjY2OSAxNTIuNzc1IDE1MS42MTEgMTUyLjUyOCAxNTEuNjExQzE1Mi4zNjQgMTUxLjYxMSAxNTIuMjEzIDE1MS42NDYgMTUyLjA3NSAxNTEuNzE3QzE1MS45MzcgMTUxLjc4NSAxNTEuODE4IDE1MS44ODYgMTUxLjcxOSAxNTIuMDIxQzE1MS42MiAxNTIuMTU3IDE1MS41NDQgMTUyLjMyMiAxNTEuNDg5IDE1Mi41MThDMTUxLjQzNCAxNTIuNzEzIDE1MS40MDcgMTUyLjkzOCAxNTEuNDA3IDE1My4xOTNWMTUzLjM1N0MxNTEuNDA3IDE1My41NTggMTUxLjQzNCAxNTMuNzQ3IDE1MS40ODkgMTUzLjkyNEMxNTEuNTQ2IDE1NC4wOTggMTUxLjYyOCAxNTQuMjUyIDE1MS43MzUgMTU0LjM4NUMxNTEuODQ0IDE1NC41MTggMTUxLjk3NiAxNTQuNjIyIDE1Mi4xMyAxNTQuNjk3QzE1Mi4yODYgMTU0Ljc3MyAxNTIuNDYzIDE1NC44MTEgMTUyLjY2MSAxNTQuODExQzE1Mi45MTYgMTU0LjgxMSAxNTMuMTMyIDE1NC43NTggMTUzLjMwOSAxNTQuNjU0QzE1My40ODYgMTU0LjU1IDE1My42NDEgMTU0LjQxMSAxNTMuNzc0IDE1NC4yMzZMMTU0LjIxMiAxNTQuNTg0QzE1NC4xMiAxNTQuNzIyIDE1NC4wMDUgMTU0Ljg1NCAxNTMuODY0IDE1NC45NzlDMTUzLjcyMyAxNTUuMTA0IDE1My41NSAxNTUuMjA1IDE1My4zNDQgMTU1LjI4M0MxNTMuMTQxIDE1NS4zNjEgMTUyLjkgMTU1LjQgMTUyLjYyMiAxNTUuNFpNMTU1LjE4OSAxNDkuMzIySDE1NS45MTVWMTU0LjUwMkwxNTUuODUzIDE1NS4zMjJIMTU1LjE4OVYxNDkuMzIyWk0xNTguNzcxIDE1My4xNzRWMTUzLjI1NkMxNTguNzcxIDE1My41NjMgMTU4LjczNCAxNTMuODQ4IDE1OC42NjEgMTU0LjExMUMxNTguNTg5IDE1NC4zNzIgMTU4LjQ4MiAxNTQuNTk4IDE1OC4zNDEgMTU0Ljc5MUMxNTguMjAxIDE1NC45ODQgMTU4LjAyOSAxNTUuMTMzIDE1Ny44MjYgMTU1LjI0QzE1Ny42MjIgMTU1LjM0NyAxNTcuMzg5IDE1NS40IDE1Ny4xMjYgMTU1LjRDMTU2Ljg1OCAxNTUuNCAxNTYuNjIyIDE1NS4zNTUgMTU2LjQxOSAxNTUuMjY0QzE1Ni4yMTkgMTU1LjE3IDE1Ni4wNDkgMTU1LjAzNiAxNTUuOTExIDE1NC44NjFDMTU1Ljc3MyAxNTQuNjg3IDE1NS42NjMgMTU0LjQ3NiAxNTUuNTc5IDE1NC4yMjlDMTU1LjQ5OSAxNTMuOTgxIDE1NS40NDMgMTUzLjcwMiAxNTUuNDExIDE1My4zOTNWMTUzLjAzM0MxNTUuNDQzIDE1Mi43MjEgMTU1LjQ5OSAxNTIuNDQxIDE1NS41NzkgMTUyLjE5M0MxNTUuNjYzIDE1MS45NDYgMTU1Ljc3MyAxNTEuNzM1IDE1NS45MTEgMTUxLjU2MUMxNTYuMDQ5IDE1MS4zODMgMTU2LjIxOSAxNTEuMjQ5IDE1Ni40MTkgMTUxLjE1OEMxNTYuNjIgMTUxLjA2NCAxNTYuODUzIDE1MS4wMTggMTU3LjExOCAxNTEuMDE4QzE1Ny4zODQgMTUxLjAxOCAxNTcuNjIgMTUxLjA3IDE1Ny44MjYgMTUxLjE3NEMxNTguMDMxIDE1MS4yNzUgMTU4LjIwMyAxNTEuNDIxIDE1OC4zNDEgMTUxLjYxMUMxNTguNDgyIDE1MS44MDEgMTU4LjU4OSAxNTIuMDI5IDE1OC42NjEgMTUyLjI5NUMxNTguNzM0IDE1Mi41NTggMTU4Ljc3MSAxNTIuODUxIDE1OC43NzEgMTUzLjE3NFpNMTU4LjA0NCAxNTMuMjU2VjE1My4xNzRDMTU4LjA0NCAxNTIuOTYzIDE1OC4wMjUgMTUyLjc2NSAxNTcuOTg2IDE1Mi41OEMxNTcuOTQ3IDE1Mi4zOTMgMTU3Ljg4NCAxNTIuMjI5IDE1Ny43OTggMTUyLjA4OEMxNTcuNzEyIDE1MS45NDUgMTU3LjU5OSAxNTEuODMzIDE1Ny40NTggMTUxLjc1MkMxNTcuMzE4IDE1MS42NjkgMTU3LjE0NSAxNTEuNjI3IDE1Ni45MzkgMTUxLjYyN0MxNTYuNzU3IDE1MS42MjcgMTU2LjU5OCAxNTEuNjU4IDE1Ni40NjIgMTUxLjcyMUMxNTYuMzI5IDE1MS43ODMgMTU2LjIxNiAxNTEuODY4IDE1Ni4xMjIgMTUxLjk3NUMxNTYuMDI5IDE1Mi4wNzkgMTU1Ljk1MiAxNTIuMTk5IDE1NS44OTIgMTUyLjMzNEMxNTUuODM1IDE1Mi40NjcgMTU1Ljc5MiAxNTIuNjA1IDE1NS43NjMgMTUyLjc0OFYxNTMuNjg5QzE1NS44MDUgMTUzLjg3MiAxNTUuODcyIDE1NC4wNDggMTU1Ljk2NiAxNTQuMjE3QzE1Ni4wNjIgMTU0LjM4MyAxNTYuMTkgMTU0LjUyIDE1Ni4zNDkgMTU0LjYyN0MxNTYuNTEgMTU0LjczNCAxNTYuNzEgMTU0Ljc4NyAxNTYuOTQ3IDE1NC43ODdDMTU3LjE0MiAxNTQuNzg3IDE1Ny4zMDkgMTU0Ljc0OCAxNTcuNDQ3IDE1NC42N0MxNTcuNTg3IDE1NC41ODkgMTU3LjcwMSAxNTQuNDc5IDE1Ny43ODYgMTU0LjMzOEMxNTcuODc1IDE1NC4xOTcgMTU3Ljk0IDE1NC4wMzUgMTU3Ljk4MiAxNTMuODVDMTU4LjAyMyAxNTMuNjY1IDE1OC4wNDQgMTUzLjQ2NyAxNTguMDQ0IDE1My4yNTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjwvZz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXA0XzQwNTNfMTg1MTEzKSI+CjxsaW5lIHgxPSIxODIuMDUiIHkxPSIxNDcuMDcyIiB4Mj0iMTgyLjA1IiB5Mj0iMTQ2LjI1IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNMTc0LjEwOSAxNTIuMDI1VjE1Mi44OTNDMTc0LjEwOSAxNTMuMzU5IDE3NC4wNjcgMTUzLjc1MiAxNzMuOTg0IDE1NC4wNzJDMTczLjkwMSAxNTQuMzkzIDE3My43ODEgMTU0LjY1IDE3My42MjUgMTU0Ljg0NkMxNzMuNDY5IDE1NS4wNDEgMTczLjI4IDE1NS4xODMgMTczLjA1OCAxNTUuMjcxQzE3Mi44NCAxNTUuMzU3IDE3Mi41OTIgMTU1LjQgMTcyLjMxNiAxNTUuNEMxNzIuMDk3IDE1NS40IDE3MS44OTYgMTU1LjM3MyAxNzEuNzExIDE1NS4zMThDMTcxLjUyNiAxNTUuMjY0IDE3MS4zNTkgMTU1LjE3NiAxNzEuMjExIDE1NS4wNTdDMTcxLjA2NSAxNTQuOTM0IDE3MC45NCAxNTQuNzc1IDE3MC44MzYgMTU0LjU4QzE3MC43MzIgMTU0LjM4NSAxNzAuNjUyIDE1NC4xNDggMTcwLjU5NyAxNTMuODY5QzE3MC41NDMgMTUzLjU5IDE3MC41MTUgMTUzLjI2NSAxNzAuNTE1IDE1Mi44OTNWMTUyLjAyNUMxNzAuNTE1IDE1MS41NTkgMTcwLjU1NyAxNTEuMTY5IDE3MC42NCAxNTAuODU0QzE3MC43MjYgMTUwLjUzOCAxNzAuODQ3IDE1MC4yODYgMTcxLjAwNCAxNTAuMDk2QzE3MS4xNiAxNDkuOTAzIDE3MS4zNDcgMTQ5Ljc2NSAxNzEuNTY2IDE0OS42ODJDMTcxLjc4OCAxNDkuNTk4IDE3Mi4wMzUgMTQ5LjU1NyAxNzIuMzA4IDE0OS41NTdDMTcyLjUzIDE0OS41NTcgMTcyLjczMyAxNDkuNTg0IDE3Mi45MTggMTQ5LjYzOUMxNzMuMTA1IDE0OS42OTEgMTczLjI3MiAxNDkuNzc1IDE3My40MTggMTQ5Ljg5M0MxNzMuNTY0IDE1MC4wMDcgMTczLjY4NyAxNTAuMTYxIDE3My43ODkgMTUwLjM1NEMxNzMuODkzIDE1MC41NDQgMTczLjk3MiAxNTAuNzc3IDE3NC4wMjcgMTUxLjA1M0MxNzQuMDgyIDE1MS4zMjkgMTc0LjEwOSAxNTEuNjUzIDE3NC4xMDkgMTUyLjAyNVpNMTczLjM4MyAxNTMuMDFWMTUxLjkwNEMxNzMuMzgzIDE1MS42NDkgMTczLjM2NyAxNTEuNDI1IDE3My4zMzYgMTUxLjIzMkMxNzMuMzA3IDE1MS4wMzcgMTczLjI2NCAxNTAuODcgMTczLjIwNyAxNTAuNzMyQzE3My4xNSAxNTAuNTk0IDE3My4wNzcgMTUwLjQ4MiAxNzIuOTg4IDE1MC4zOTZDMTcyLjkwMiAxNTAuMzExIDE3Mi44MDIgMTUwLjI0OCAxNzIuNjg3IDE1MC4yMDlDMTcyLjU3NSAxNTAuMTY3IDE3Mi40NDkgMTUwLjE0NiAxNzIuMzA4IDE1MC4xNDZDMTcyLjEzNiAxNTAuMTQ2IDE3MS45ODQgMTUwLjE3OSAxNzEuODUxIDE1MC4yNDRDMTcxLjcxOSAxNTAuMzA3IDE3MS42MDcgMTUwLjQwNyAxNzEuNTE1IDE1MC41NDVDMTcxLjQyNyAxNTAuNjgzIDE3MS4zNTkgMTUwLjg2NCAxNzEuMzEyIDE1MS4wODhDMTcxLjI2NSAxNTEuMzEyIDE3MS4yNDIgMTUxLjU4NCAxNzEuMjQyIDE1MS45MDRWMTUzLjAxQzE3MS4yNDIgMTUzLjI2NSAxNzEuMjU2IDE1My40OSAxNzEuMjg1IDE1My42ODZDMTcxLjMxNiAxNTMuODgxIDE3MS4zNjIgMTU0LjA1IDE3MS40MjIgMTU0LjE5M0MxNzEuNDgyIDE1NC4zMzQgMTcxLjU1NCAxNTQuNDUgMTcxLjY0IDE1NC41NDFDMTcxLjcyNiAxNTQuNjMyIDE3MS44MjUgMTU0LjcgMTcxLjkzNyAxNTQuNzQ0QzE3Mi4wNTIgMTU0Ljc4NiAxNzIuMTc4IDE1NC44MDcgMTcyLjMxNiAxNTQuODA3QzE3Mi40OTMgMTU0LjgwNyAxNzIuNjQ4IDE1NC43NzMgMTcyLjc4MSAxNTQuNzA1QzE3Mi45MTQgMTU0LjYzNyAxNzMuMDI1IDE1NC41MzIgMTczLjExMyAxNTQuMzg5QzE3My4yMDQgMTU0LjI0MyAxNzMuMjcyIDE1NC4wNTcgMTczLjMxNiAxNTMuODNDMTczLjM2IDE1My42MDEgMTczLjM4MyAxNTMuMzI3IDE3My4zODMgMTUzLjAxWk0xNzYuMDM2IDE1Mi42MTVMMTc1LjQ1NyAxNTIuNDY3TDE3NS43NDMgMTQ5LjYzNUgxNzguNjYxVjE1MC4zMDNIMTc2LjM1NkwxNzYuMTg0IDE1MS44NUMxNzYuMjg4IDE1MS43OSAxNzYuNDIgMTUxLjczNCAxNzYuNTc5IDE1MS42ODJDMTc2Ljc0IDE1MS42MyAxNzYuOTI1IDE1MS42MDQgMTc3LjEzMyAxNTEuNjA0QzE3Ny4zOTYgMTUxLjYwNCAxNzcuNjMyIDE1MS42NDkgMTc3Ljg0IDE1MS43NEMxNzguMDQ5IDE1MS44MjkgMTc4LjIyNiAxNTEuOTU2IDE3OC4zNzEgMTUyLjEyM0MxNzguNTIgMTUyLjI5IDE3OC42MzMgMTUyLjQ5IDE3OC43MTEgMTUyLjcyNUMxNzguNzg5IDE1Mi45NTkgMTc4LjgyOSAxNTMuMjIxIDE3OC44MjkgMTUzLjUxQzE3OC44MjkgMTUzLjc4MyAxNzguNzkxIDE1NC4wMzUgMTc4LjcxNSAxNTQuMjY0QzE3OC42NDIgMTU0LjQ5MyAxNzguNTMyIDE1NC42OTMgMTc4LjM4MyAxNTQuODY1QzE3OC4yMzUgMTU1LjAzNSAxNzguMDQ3IDE1NS4xNjYgMTc3LjgyMSAxNTUuMjZDMTc3LjU5NyAxNTUuMzU0IDE3Ny4zMzIgMTU1LjQgMTc3LjAyOCAxNTUuNEMxNzYuNzk5IDE1NS40IDE3Ni41ODEgMTU1LjM2OSAxNzYuMzc1IDE1NS4zMDdDMTc2LjE3MiAxNTUuMjQyIDE3NS45OSAxNTUuMTQ0IDE3NS44MjkgMTU1LjAxNEMxNzUuNjcgMTU0Ljg4MSAxNzUuNTM5IDE1NC43MTcgMTc1LjQzOCAxNTQuNTIxQzE3NS4zMzkgMTU0LjMyNCAxNzUuMjc2IDE1NC4wOTIgMTc1LjI1IDE1My44MjZIMTc1LjkzOEMxNzUuOTY5IDE1NC4wNCAxNzYuMDMyIDE1NC4yMTkgMTc2LjEyNSAxNTQuMzY1QzE3Ni4yMTkgMTU0LjUxMSAxNzYuMzQyIDE1NC42MjIgMTc2LjQ5MyAxNTQuNjk3QzE3Ni42NDYgMTU0Ljc3IDE3Ni44MjUgMTU0LjgwNyAxNzcuMDI4IDE1NC44MDdDMTc3LjIgMTU0LjgwNyAxNzcuMzUyIDE1NC43NzcgMTc3LjQ4NSAxNTQuNzE3QzE3Ny42MTggMTU0LjY1NyAxNzcuNzMgMTU0LjU3MSAxNzcuODIxIDE1NC40NTlDMTc3LjkxMiAxNTQuMzQ3IDE3Ny45ODEgMTU0LjIxMiAxNzguMDI4IDE1NC4wNTNDMTc4LjA3NyAxNTMuODk0IDE3OC4xMDIgMTUzLjcxNSAxNzguMTAyIDE1My41MThDMTc4LjEwMiAxNTMuMzM4IDE3OC4wNzcgMTUzLjE3MSAxNzguMDI4IDE1My4wMThDMTc3Ljk3OCAxNTIuODY0IDE3Ny45MDQgMTUyLjczIDE3Ny44MDUgMTUyLjYxNUMxNzcuNzA5IDE1Mi41MDEgMTc3LjU5IDE1Mi40MTIgMTc3LjQ1IDE1Mi4zNUMxNzcuMzA5IDE1Mi4yODUgMTc3LjE0OCAxNTIuMjUyIDE3Ni45NjUgMTUyLjI1MkMxNzYuNzIzIDE1Mi4yNTIgMTc2LjUzOSAxNTIuMjg1IDE3Ni40MTQgMTUyLjM1QzE3Ni4yOTIgMTUyLjQxNSAxNzYuMTY2IDE1Mi41MDMgMTc2LjAzNiAxNTIuNjE1Wk0xODIuNzEzIDE0OS42MzVWMTU1LjMyMkgxODEuOTU5VjE0OS42MzVIMTgyLjcxM1pNMTg1LjA5NSAxNTIuMTkzVjE1Mi44MTFIMTgyLjU0OFYxNTIuMTkzSDE4NS4wOTVaTTE4NS40ODIgMTQ5LjYzNVYxNTAuMjUySDE4Mi41NDhWMTQ5LjYzNUgxODUuNDgyWk0xODguMDIyIDE1NS40QzE4Ny43MjcgMTU1LjQgMTg3LjQ2IDE1NS4zNTEgMTg3LjIyMSAxNTUuMjUyQzE4Ni45ODQgMTU1LjE1IDE4Ni43OCAxNTUuMDA4IDE4Ni42MDggMTU0LjgyNkMxODYuNDM4IDE1NC42NDQgMTg2LjMwOCAxNTQuNDI4IDE4Ni4yMTcgMTU0LjE3OEMxODYuMTI2IDE1My45MjggMTg2LjA4IDE1My42NTQgMTg2LjA4IDE1My4zNTdWMTUzLjE5M0MxODYuMDggMTUyLjg1IDE4Ni4xMzEgMTUyLjU0NCAxODYuMjMzIDE1Mi4yNzVDMTg2LjMzNCAxNTIuMDA1IDE4Ni40NzIgMTUxLjc3NSAxODYuNjQ3IDE1MS41ODhDMTg2LjgyMSAxNTEuNCAxODcuMDE5IDE1MS4yNTggMTg3LjI0IDE1MS4xNjJDMTg3LjQ2MiAxNTEuMDY2IDE4Ny42OTEgMTUxLjAxOCAxODcuOTI4IDE1MS4wMThDMTg4LjIzIDE1MS4wMTggMTg4LjQ5IDE1MS4wNyAxODguNzA5IDE1MS4xNzRDMTg4LjkzMSAxNTEuMjc4IDE4OS4xMTIgMTUxLjQyNCAxODkuMjUyIDE1MS42MTFDMTg5LjM5MyAxNTEuNzk2IDE4OS40OTcgMTUyLjAxNSAxODkuNTY1IDE1Mi4yNjhDMTg5LjYzMiAxNTIuNTE4IDE4OS42NjYgMTUyLjc5MSAxODkuNjY2IDE1My4wODhWMTUzLjQxMkgxODYuNTFWMTUyLjgyMkgxODguOTQ0VjE1Mi43NjhDMTg4LjkzMyAxNTIuNTggMTg4Ljg5NCAxNTIuMzk4IDE4OC44MjYgMTUyLjIyMUMxODguNzYxIDE1Mi4wNDQgMTg4LjY1NyAxNTEuODk4IDE4OC41MTQgMTUxLjc4M0MxODguMzcxIDE1MS42NjkgMTg4LjE3NSAxNTEuNjExIDE4Ny45MjggMTUxLjYxMUMxODcuNzY0IDE1MS42MTEgMTg3LjYxMyAxNTEuNjQ2IDE4Ny40NzUgMTUxLjcxN0MxODcuMzM3IDE1MS43ODUgMTg3LjIxOCAxNTEuODg2IDE4Ny4xMTkgMTUyLjAyMUMxODcuMDIgMTUyLjE1NyAxODYuOTQ0IDE1Mi4zMjIgMTg2Ljg4OSAxNTIuNTE4QzE4Ni44MzQgMTUyLjcxMyAxODYuODA3IDE1Mi45MzggMTg2LjgwNyAxNTMuMTkzVjE1My4zNTdDMTg2LjgwNyAxNTMuNTU4IDE4Ni44MzQgMTUzLjc0NyAxODYuODg5IDE1My45MjRDMTg2Ljk0NiAxNTQuMDk4IDE4Ny4wMjggMTU0LjI1MiAxODcuMTM1IDE1NC4zODVDMTg3LjI0NCAxNTQuNTE4IDE4Ny4zNzYgMTU0LjYyMiAxODcuNTMgMTU0LjY5N0MxODcuNjg2IDE1NC43NzMgMTg3Ljg2MyAxNTQuODExIDE4OC4wNjEgMTU0LjgxMUMxODguMzE2IDE1NC44MTEgMTg4LjUzMiAxNTQuNzU4IDE4OC43MDkgMTU0LjY1NEMxODguODg2IDE1NC41NSAxODkuMDQxIDE1NC40MTEgMTg5LjE3NCAxNTQuMjM2TDE4OS42MTIgMTU0LjU4NEMxODkuNTIgMTU0LjcyMiAxODkuNDA1IDE1NC44NTQgMTg5LjI2NCAxNTQuOTc5QzE4OS4xMjMgMTU1LjEwNCAxODguOTUgMTU1LjIwNSAxODguNzQ0IDE1NS4yODNDMTg4LjU0MSAxNTUuMzYxIDE4OC4zIDE1NS40IDE4OC4wMjIgMTU1LjRaTTE5MC41ODkgMTQ5LjMyMkgxOTEuMzE1VjE1NC41MDJMMTkxLjI1MyAxNTUuMzIySDE5MC41ODlWMTQ5LjMyMlpNMTk0LjE3MSAxNTMuMTc0VjE1My4yNTZDMTk0LjE3MSAxNTMuNTYzIDE5NC4xMzQgMTUzLjg0OCAxOTQuMDYxIDE1NC4xMTFDMTkzLjk4OCAxNTQuMzcyIDE5My44ODIgMTU0LjU5OCAxOTMuNzQxIDE1NC43OTFDMTkzLjYgMTU0Ljk4NCAxOTMuNDI5IDE1NS4xMzMgMTkzLjIyNSAxNTUuMjRDMTkzLjAyMiAxNTUuMzQ3IDE5Mi43ODkgMTU1LjQgMTkyLjUyNiAxNTUuNEMxOTIuMjU4IDE1NS40IDE5Mi4wMjIgMTU1LjM1NSAxOTEuODE5IDE1NS4yNjRDMTkxLjYxOSAxNTUuMTcgMTkxLjQ0OSAxNTUuMDM2IDE5MS4zMTEgMTU0Ljg2MUMxOTEuMTczIDE1NC42ODcgMTkxLjA2MyAxNTQuNDc2IDE5MC45NzkgMTU0LjIyOUMxOTAuODk5IDE1My45ODEgMTkwLjg0MyAxNTMuNzAyIDE5MC44MTEgMTUzLjM5M1YxNTMuMDMzQzE5MC44NDMgMTUyLjcyMSAxOTAuODk5IDE1Mi40NDEgMTkwLjk3OSAxNTIuMTkzQzE5MS4wNjMgMTUxLjk0NiAxOTEuMTczIDE1MS43MzUgMTkxLjMxMSAxNTEuNTYxQzE5MS40NDkgMTUxLjM4MyAxOTEuNjE5IDE1MS4yNDkgMTkxLjgxOSAxNTEuMTU4QzE5Mi4wMiAxNTEuMDY0IDE5Mi4yNTMgMTUxLjAxOCAxOTIuNTE4IDE1MS4wMThDMTkyLjc4NCAxNTEuMDE4IDE5My4wMiAxNTEuMDcgMTkzLjIyNSAxNTEuMTc0QzE5My40MzEgMTUxLjI3NSAxOTMuNjAzIDE1MS40MjEgMTkzLjc0MSAxNTEuNjExQzE5My44ODIgMTUxLjgwMSAxOTMuOTg4IDE1Mi4wMjkgMTk0LjA2MSAxNTIuMjk1QzE5NC4xMzQgMTUyLjU1OCAxOTQuMTcxIDE1Mi44NTEgMTk0LjE3MSAxNTMuMTc0Wk0xOTMuNDQ0IDE1My4yNTZWMTUzLjE3NEMxOTMuNDQ0IDE1Mi45NjMgMTkzLjQyNSAxNTIuNzY1IDE5My4zODYgMTUyLjU4QzE5My4zNDcgMTUyLjM5MyAxOTMuMjg0IDE1Mi4yMjkgMTkzLjE5OCAxNTIuMDg4QzE5My4xMTIgMTUxLjk0NSAxOTIuOTk5IDE1MS44MzMgMTkyLjg1OCAxNTEuNzUyQzE5Mi43MTggMTUxLjY2OSAxOTIuNTQ0IDE1MS42MjcgMTkyLjMzOSAxNTEuNjI3QzE5Mi4xNTYgMTUxLjYyNyAxOTEuOTk4IDE1MS42NTggMTkxLjg2MiAxNTEuNzIxQzE5MS43MjkgMTUxLjc4MyAxOTEuNjE2IDE1MS44NjggMTkxLjUyMiAxNTEuOTc1QzE5MS40MjkgMTUyLjA3OSAxOTEuMzUyIDE1Mi4xOTkgMTkxLjI5MiAxNTIuMzM0QzE5MS4yMzUgMTUyLjQ2NyAxOTEuMTkyIDE1Mi42MDUgMTkxLjE2MyAxNTIuNzQ4VjE1My42ODlDMTkxLjIwNSAxNTMuODcyIDE5MS4yNzIgMTU0LjA0OCAxOTEuMzY2IDE1NC4yMTdDMTkxLjQ2MiAxNTQuMzgzIDE5MS41OSAxNTQuNTIgMTkxLjc0OSAxNTQuNjI3QzE5MS45MSAxNTQuNzM0IDE5Mi4xMSAxNTQuNzg3IDE5Mi4zNDcgMTU0Ljc4N0MxOTIuNTQyIDE1NC43ODcgMTkyLjcwOCAxNTQuNzQ4IDE5Mi44NDcgMTU0LjY3QzE5Mi45ODcgMTU0LjU4OSAxOTMuMSAxNTQuNDc5IDE5My4xODYgMTU0LjMzOEMxOTMuMjc1IDE1NC4xOTcgMTkzLjM0IDE1NC4wMzUgMTkzLjM4MiAxNTMuODVDMTkzLjQyMyAxNTMuNjY1IDE5My40NDQgMTUzLjQ2NyAxOTMuNDQ0IDE1My4yNTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjwvZz4KPHBhdGggZD0iTTI3IDEwNy40NDVMNDAgODguMDk2Mkw3NS44NjUgNDUuMDgwMkM3Ni4yMTY2IDQ0LjY1ODYgNzYuODQyMyA0NC41OTkyIDc3LjI2NjkgNDQuOTQ3MUwxMTEuMTM1IDcyLjcwMDZDMTExLjM2NiA3Mi44OTAyIDExMS42NyA3Mi45NjYyIDExMS45NjMgNzIuOTA4TDE0Ni43OTQgNjUuOTkxQzE0Ni45MyA2NS45NjQgMTQ3LjA1OSA2NS45MDg5IDE0Ny4xNzIgNjUuODI5MkwxODQuMjY3IDM5Ljg0NjhDMTg0LjQxOSAzOS43NCAxODQuNTM5IDM5LjU5MjcgMTg0LjYxMiAzOS40MjE1TDE5OC41IDciIHN0cm9rZT0iI0ZCREIwRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4KPGNpcmNsZSBjeD0iNzYiIGN5PSI0NS4xNjExIiByPSIxLjUiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNGQkRCMEYiLz4KPGNpcmNsZSBjeD0iMTEyIiBjeT0iNzMuMTYxMSIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjRkJEQjBGIi8+CjxjaXJjbGUgY3g9IjE0NyIgY3k9IjY2LjE2MTEiIHI9IjEuNSIgZmlsbD0id2hpdGUiIHN0cm9rZT0iI0ZCREIwRiIvPgo8Y2lyY2xlIGN4PSIxODUiIGN5PSIzOS4xNjExIiByPSIxLjUiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNGQkRCMEYiLz4KPGNpcmNsZSBjeD0iMTk4IiBjeT0iNyIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjRkJEQjBGIi8+CjxwYXRoIGQ9Ik0yOC41IDEwNy4xMTRDMjguNSAxMDcuOTcyIDI3LjgxOTcgMTA4LjY1MSAyNyAxMDguNjUxQzI2LjE4MDMgMTA4LjY1MSAyNS41IDEwNy45NzIgMjUuNSAxMDcuMTE0QzI1LjUgMTA2LjI1NiAyNi4xODAzIDEwNS41NzYgMjcgMTA1LjU3NkMyNy44MTk3IDEwNS41NzYgMjguNSAxMDYuMjU2IDI4LjUgMTA3LjExNFoiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNGQkRCMEYiLz4KPHBhdGggZD0iTTQxLjUgODcuNzc2OEM0MS41IDg4LjYzNDcgNDAuODE5NyA4OS4zMTQzIDQwIDg5LjMxNDNDMzkuMTgwMyA4OS4zMTQzIDM4LjUgODguNjM0NyAzOC41IDg3Ljc3NjhDMzguNSA4Ni45MTg4IDM5LjE4MDMgODYuMjM5MyA0MCA4Ni4yMzkzQzQwLjgxOTcgODYuMjM5MyA0MS41IDg2LjkxODggNDEuNSA4Ny43NzY4WiIgZmlsbD0id2hpdGUiIHN0cm9rZT0iI0ZCREIwRiIvPgo8cGF0aCBkPSJNMzIgMTIyQzMyIDEyMC44OTUgMzIuODk1NCAxMjAgMzQgMTIwSDQ4QzQ5LjEwNDYgMTIwIDUwIDEyMC44OTUgNTAgMTIyVjE0NkgzMlYxMjJaIiBmaWxsPSIjM0ZBNzFBIi8+CjxwYXRoIGQ9Ik02NyA3OEM2NyA3Ni44OTU0IDY3Ljg5NTQgNzYgNjkgNzZIODNDODQuMTA0NiA3NiA4NSA3Ni44OTU0IDg1IDc4VjE0Nkg2N1Y3OFoiIGZpbGw9IiMzRkE3MUEiLz4KPHBhdGggZD0iTTEwMiA5MkMxMDIgOTAuODk1NCAxMDIuODk1IDkwIDEwNCA5MEgxMThDMTE5LjEwNSA5MCAxMjAgOTAuODk1NCAxMjAgOTJWMTQ2SDEwMlY5MloiIGZpbGw9IiMzRkE3MUEiLz4KPHBhdGggZD0iTTE3MyAxMDVDMTczIDEwMy44OTUgMTczLjg5NSAxMDMgMTc1IDEwM0gxODlDMTkwLjEwNSAxMDMgMTkxIDEwMy44OTUgMTkxIDEwNVYxNDZIMTczVjEwNVoiIGZpbGw9IiMzRkE3MUEiLz4KPHBhdGggZD0iTTI3IDExOEw0MCA3MS43NzkyTDc4LjUgODRMMTE5IDMyTDE0OCAzNi41TDE4MiA3MS43NzkyTDE5NS4zMTEgMTAwLjIxNkwxOTcgMTA3IiBzdHJva2U9IiM0QjcwREQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+CjxjaXJjbGUgY3g9Ijc4IiBjeT0iODQuMTYxMSIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjNEI3MEREIi8+CjxjaXJjbGUgY3g9IjQwIiBjeT0iNzIuMTYxMSIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjNEI3MEREIi8+CjxjaXJjbGUgY3g9IjExOSIgY3k9IjMyIiByPSIxLjUiIGZpbGw9IndoaXRlIiBzdHJva2U9IiM0QjcwREQiLz4KPGNpcmNsZSBjeD0iMTgyIiBjeT0iNzIuMTYxMSIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjNEI3MEREIi8+CjxjaXJjbGUgY3g9IjE5NyIgY3k9IjEwNyIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjNEI3MEREIi8+CjxjaXJjbGUgY3g9IjI3IiBjeT0iMTE3LjE2MSIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjNEI3MEREIi8+CjxjaXJjbGUgY3g9IjE0NyIgY3k9IjM3LjE2MTEiIHI9IjEuNSIgZmlsbD0id2hpdGUiIHN0cm9rZT0iIzRCNzBERCIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzQwNTNfMTg1MTEzIj4KPHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIxNjAiIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjxjbGlwUGF0aCBpZD0iY2xpcDFfNDA1M18xODUxMTMiPgo8cmVjdCB3aWR0aD0iMzUuNCIgaGVpZ2h0PSIxMC4zMjIiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1OC4zOTk5IDE0NikiLz4KPC9jbGlwUGF0aD4KPGNsaXBQYXRoIGlkPSJjbGlwMl80MDUzXzE4NTExMyI+CjxyZWN0IHdpZHRoPSIzNS40IiBoZWlnaHQ9IjEwLjMyMiIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDkzLjc5OTggMTQ2KSIvPgo8L2NsaXBQYXRoPgo8Y2xpcFBhdGggaWQ9ImNsaXAzXzQwNTNfMTg1MTEzIj4KPHJlY3Qgd2lkdGg9IjM1LjQiIGhlaWdodD0iMTAuMzIyIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI5LjIgMTQ2KSIvPgo8L2NsaXBQYXRoPgo8Y2xpcFBhdGggaWQ9ImNsaXA0XzQwNTNfMTg1MTEzIj4KPHJlY3Qgd2lkdGg9IjM1LjQiIGhlaWdodD0iMTAuMzIyIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTY0LjYgMTQ2KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=", + "description": "Displays changes to time-series data over time—for example, temperature or humidity readings.", + "descriptor": { + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "resources": [], + "templateHtml": "\n", + "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n", + "settingsSchema": "{}", + "dataKeySettingsSchema": "{}", + "latestDataKeySettingsSchema": "{}", + "settingsDirective": "", + "dataKeySettingsDirective": "", + "latestDataKeySettingsDirective": "", + "hasBasicMode": false, + "basicModeDirective": "", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false}},\"title\":\"Time series chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + }, + "tags": [ + "chart", + "time series", + "time-series", + "line", + "line chart", + "bar", + "bar chart" + ] +} \ No newline at end of file diff --git a/ui-ngx/patches/echarts+5.4.3.patch b/ui-ngx/patches/echarts+5.4.3.patch new file mode 100644 index 0000000000..bca89c616c --- /dev/null +++ b/ui-ngx/patches/echarts+5.4.3.patch @@ -0,0 +1,208 @@ +diff --git a/node_modules/echarts/lib/component/dataZoom/DataZoomModel.js b/node_modules/echarts/lib/component/dataZoom/DataZoomModel.js +index 112ebe3..9afc9b7 100644 +--- a/node_modules/echarts/lib/component/dataZoom/DataZoomModel.js ++++ b/node_modules/echarts/lib/component/dataZoom/DataZoomModel.js +@@ -422,7 +422,10 @@ function (_super) { + return axisProxy.getDataValueWindow(); + } + } else { +- return this.getAxisProxy(axisDim, axisIndex).getDataValueWindow(); ++ var axisProxy = this.getAxisProxy(axisDim, axisIndex); ++ if (axisProxy) { ++ return axisProxy.getDataValueWindow(); ++ } + } + }; + /** +@@ -449,11 +452,11 @@ function (_super) { + for (var j = 0; j < axisInfo.indexList.length; j++) { + var proxy = this.getAxisProxy(axisDim, axisInfo.indexList[j]); + +- if (proxy.hostedBy(this)) { ++ if (proxy && proxy.hostedBy(this)) { + return proxy; + } + +- if (!firstProxy) { ++ if (proxy && !firstProxy) { + firstProxy = proxy; + } + } +diff --git a/node_modules/echarts/lib/component/dataZoom/InsideZoomView.js b/node_modules/echarts/lib/component/dataZoom/InsideZoomView.js +index 7163279..c37f9c2 100644 +--- a/node_modules/echarts/lib/component/dataZoom/InsideZoomView.js ++++ b/node_modules/echarts/lib/component/dataZoom/InsideZoomView.js +@@ -112,12 +112,15 @@ var getRangeHandlers = { + range[0] = (range[0] - percentPoint) * scale + percentPoint; + range[1] = (range[1] - percentPoint) * scale + percentPoint; // Restrict range. + +- var minMaxSpan = this.dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan(); +- sliderMove(0, range, [0, 100], 0, minMaxSpan.minSpan, minMaxSpan.maxSpan); +- this.range = range; +- +- if (lastRange[0] !== range[0] || lastRange[1] !== range[1]) { +- return range; ++ var proxy = this.dataZoomModel.findRepresentativeAxisProxy(); ++ if (proxy) { ++ var minMaxSpan = proxy.getMinMaxSpan(); ++ sliderMove(0, range, [0, 100], 0, minMaxSpan.minSpan, minMaxSpan.maxSpan); ++ this.range = range; ++ ++ if (lastRange[0] !== range[0] || lastRange[1] !== range[1]) { ++ return range; ++ } + } + }, + pan: makeMover(function (range, axisModel, coordSysInfo, coordSysMainType, controller, e) { +diff --git a/node_modules/echarts/lib/component/dataZoom/SliderZoomView.js b/node_modules/echarts/lib/component/dataZoom/SliderZoomView.js +index 590ef51..aff8a0a 100644 +--- a/node_modules/echarts/lib/component/dataZoom/SliderZoomView.js ++++ b/node_modules/echarts/lib/component/dataZoom/SliderZoomView.js +@@ -64,7 +64,7 @@ var DEFAULT_MOVE_HANDLE_SIZE = 7; + var HORIZONTAL = 'horizontal'; + var VERTICAL = 'vertical'; + var LABEL_GAP = 5; +-var SHOW_DATA_SHADOW_SERIES_TYPE = ['line', 'bar', 'candlestick', 'scatter']; ++var SHOW_DATA_SHADOW_SERIES_TYPE = ['line', 'bar', 'candlestick', 'scatter', 'custom']; + var REALTIME_ANIMATION_CONFIG = { + easing: 'cubicOut', + duration: 100, +@@ -406,34 +406,37 @@ function (_super) { + var result; + var ecModel = this.ecModel; + dataZoomModel.eachTargetAxis(function (axisDim, axisIndex) { +- var seriesModels = dataZoomModel.getAxisProxy(axisDim, axisIndex).getTargetSeriesModels(); +- each(seriesModels, function (seriesModel) { +- if (result) { +- return; +- } +- +- if (showDataShadow !== true && indexOf(SHOW_DATA_SHADOW_SERIES_TYPE, seriesModel.get('type')) < 0) { +- return; +- } +- +- var thisAxis = ecModel.getComponent(getAxisMainType(axisDim), axisIndex).axis; +- var otherDim = getOtherDim(axisDim); +- var otherAxisInverse; +- var coordSys = seriesModel.coordinateSystem; +- +- if (otherDim != null && coordSys.getOtherAxis) { +- otherAxisInverse = coordSys.getOtherAxis(thisAxis).inverse; +- } +- +- otherDim = seriesModel.getData().mapDimension(otherDim); +- result = { +- thisAxis: thisAxis, +- series: seriesModel, +- thisDim: axisDim, +- otherDim: otherDim, +- otherAxisInverse: otherAxisInverse +- }; +- }, this); ++ var axisProxy = dataZoomModel.getAxisProxy(axisDim, axisIndex); ++ if (axisProxy) { ++ var seriesModels = axisProxy.getTargetSeriesModels(); ++ each(seriesModels, function (seriesModel) { ++ if (result) { ++ return; ++ } ++ ++ if (showDataShadow !== true && indexOf(SHOW_DATA_SHADOW_SERIES_TYPE, seriesModel.get('type')) < 0) { ++ return; ++ } ++ ++ var thisAxis = ecModel.getComponent(getAxisMainType(axisDim), axisIndex).axis; ++ var otherDim = getOtherDim(axisDim); ++ var otherAxisInverse; ++ var coordSys = seriesModel.coordinateSystem; ++ ++ if (otherDim != null && coordSys.getOtherAxis) { ++ otherAxisInverse = coordSys.getOtherAxis(thisAxis).inverse; ++ } ++ ++ otherDim = seriesModel.getData().mapDimension(otherDim); ++ result = { ++ thisAxis: thisAxis, ++ series: seriesModel, ++ thisDim: axisDim, ++ otherDim: otherDim, ++ otherAxisInverse: otherAxisInverse ++ }; ++ }, this); ++ } + }, this); + return result; + }; +@@ -595,12 +598,17 @@ function (_super) { + + var viewExtend = this._getViewExtent(); + +- var minMaxSpan = dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan(); +- var percentExtent = [0, 100]; +- sliderMove(delta, handleEnds, viewExtend, dataZoomModel.get('zoomLock') ? 'all' : handleIndex, minMaxSpan.minSpan != null ? linearMap(minMaxSpan.minSpan, percentExtent, viewExtend, true) : null, minMaxSpan.maxSpan != null ? linearMap(minMaxSpan.maxSpan, percentExtent, viewExtend, true) : null); +- var lastRange = this._range; +- var range = this._range = asc([linearMap(handleEnds[0], viewExtend, percentExtent, true), linearMap(handleEnds[1], viewExtend, percentExtent, true)]); +- return !lastRange || lastRange[0] !== range[0] || lastRange[1] !== range[1]; ++ var proxy = dataZoomModel.findRepresentativeAxisProxy(); ++ if (proxy) { ++ var minMaxSpan = proxy.getMinMaxSpan(); ++ var percentExtent = [0, 100]; ++ sliderMove(delta, handleEnds, viewExtend, dataZoomModel.get('zoomLock') ? 'all' : handleIndex, minMaxSpan.minSpan != null ? linearMap(minMaxSpan.minSpan, percentExtent, viewExtend, true) : null, minMaxSpan.maxSpan != null ? linearMap(minMaxSpan.maxSpan, percentExtent, viewExtend, true) : null); ++ var lastRange = this._range; ++ var range = this._range = asc([linearMap(handleEnds[0], viewExtend, percentExtent, true), linearMap(handleEnds[1], viewExtend, percentExtent, true)]); ++ return !lastRange || lastRange[0] !== range[0] || lastRange[1] !== range[1]; ++ } else { ++ return false; ++ } + }; + + SliderZoomView.prototype._updateView = function (nonRealtime) { +diff --git a/node_modules/echarts/lib/component/dataZoom/dataZoomProcessor.js b/node_modules/echarts/lib/component/dataZoom/dataZoomProcessor.js +index 3871784..9bab428 100644 +--- a/node_modules/echarts/lib/component/dataZoom/dataZoomProcessor.js ++++ b/node_modules/echarts/lib/component/dataZoom/dataZoomProcessor.js +@@ -91,7 +91,10 @@ var dataZoomProcessor = { + // init stage and not after action dispatch handler, because + // reset should be called after seriesData.restoreData. + dataZoomModel.eachTargetAxis(function (axisDim, axisIndex) { +- dataZoomModel.getAxisProxy(axisDim, axisIndex).reset(dataZoomModel); ++ var axisProxy = dataZoomModel.getAxisProxy(axisDim, axisIndex); ++ if (axisProxy) { ++ axisProxy.reset(dataZoomModel); ++ } + }); // Caution: data zoom filtering is order sensitive when using + // percent range and no min/max/scale set on axis. + // For example, we have dataZoom definition: +@@ -108,7 +111,10 @@ var dataZoomProcessor = { + // and then reset y-axis and filter y-axis. + + dataZoomModel.eachTargetAxis(function (axisDim, axisIndex) { +- dataZoomModel.getAxisProxy(axisDim, axisIndex).filterData(dataZoomModel, api); ++ var axisProxy = dataZoomModel.getAxisProxy(axisDim, axisIndex); ++ if (axisProxy) { ++ axisProxy.filterData(dataZoomModel, api); ++ } + }); + }); + ecModel.eachComponent('dataZoom', function (dataZoomModel) { +diff --git a/node_modules/echarts/lib/component/toolbox/feature/DataZoom.js b/node_modules/echarts/lib/component/toolbox/feature/DataZoom.js +index c5ee405..957ffd2 100644 +--- a/node_modules/echarts/lib/component/toolbox/feature/DataZoom.js ++++ b/node_modules/echarts/lib/component/toolbox/feature/DataZoom.js +@@ -129,10 +129,13 @@ function (_super) { + var axisModel = axis.model; + var dataZoomModel = findDataZoom(dimName, axisModel, ecModel); // Restrict range. + +- var minMaxSpan = dataZoomModel.findRepresentativeAxisProxy(axisModel).getMinMaxSpan(); ++ var proxy = dataZoomModel.findRepresentativeAxisProxy(axisModel); ++ if (proxy) { ++ var minMaxSpan = proxy.getMinMaxSpan(); + +- if (minMaxSpan.minValueSpan != null || minMaxSpan.maxValueSpan != null) { +- minMax = sliderMove(0, minMax.slice(), axis.scale.getExtent(), 0, minMaxSpan.minValueSpan, minMaxSpan.maxValueSpan); ++ if (minMaxSpan.minValueSpan != null || minMaxSpan.maxValueSpan != null) { ++ minMax = sliderMove(0, minMax.slice(), axis.scale.getExtent(), 0, minMaxSpan.minValueSpan, minMaxSpan.maxValueSpan); ++ } + } + + dataZoomModel && (snapshot[dataZoomModel.id] = { diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index ff16af7740..933d836ffd 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -61,9 +61,8 @@ import { deepClone, flatFormattedData, formattedDataFormDatasourceData, - isDefined, isDefinedAndNotNull, - isEqual, + isEqual, isUndefined, parseHttpErrorMessage } from '@core/utils'; import { EntityId } from '@app/shared/models/id/entity-id'; @@ -355,7 +354,7 @@ export class WidgetSubscription implements IWidgetSubscription { } this.units = options.units || ''; - this.decimals = isDefined(options.decimals) ? options.decimals : 2; + this.decimals = isDefinedAndNotNull(options.decimals) ? options.decimals : 2; this.loadingData = false; @@ -1491,7 +1490,8 @@ export class WidgetSubscription implements IWidgetSubscription { let datasourceDataArray: Array = []; datasourceDataArray = datasourceDataArray.concat(datasource.dataKeys.map((dataKey, keyIndex) => { dataKey.hidden = !!dataKey.settings.hideDataByDefault; - dataKey.inLegend = !dataKey.settings.removeFromLegend; + dataKey.inLegend = dataKey.settings.showInLegend || + (isUndefined(dataKey.settings.showInLegend) && !dataKey.settings.removeFromLegend); dataKey.label = this.ctx.utils.customTranslation(dataKey.label, dataKey.label); const datasourceData: DatasourceData = { datasource, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.scss index 65be3c924f..8304400818 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.scss @@ -92,6 +92,7 @@ .tb-aggregated-value-card-chart-container { position: relative; flex: 1; + min-width: 0; margin-top: 8px; margin-bottom: 8px; .tb-aggregated-value-card-chart-element { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts index 25be87451e..ae7c3c5eee 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts @@ -43,16 +43,21 @@ import { getDataKey, getLatestSingleTsValue, overlayStyle, + simpleDateFormat, textStyle } from '@shared/models/widget-settings.models'; -import { TbFlot } from '@home/components/widget/lib/flot-widget'; -import { TbFlotKeySettings, TbFlotSettings } from '@home/components/widget/lib/flot-widget.models'; import { DataKey } from '@shared/models/widget.models'; import { formatNumberValue, formatValue, isDefined, isDefinedAndNotNull, isNumeric } from '@core/utils'; import { map } from 'rxjs/operators'; import { ResizeObserver } from '@juggle/resize-observer'; import { ImagePipe } from '@shared/pipe/image.pipe'; import { DomSanitizer } from '@angular/platform-browser'; +import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart'; +import { + TimeSeriesChartKeySettings, + TimeSeriesChartSeriesType, + TimeSeriesChartSettings +} from '@home/components/widget/lib/chart/time-series-chart.models'; const valuesLayoutHeight = 66; const valuesLayoutVerticalPadding = 16; @@ -101,8 +106,8 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit backgroundStyle$: Observable; overlayStyle: ComponentStyle = {}; - private flot: TbFlot; - private flotDataKey: DataKey; + private lineChart: TbTimeSeriesChart; + private lineChartDataKey: DataKey; private lastUpdateTs: number; @@ -141,12 +146,17 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit this.showChart = this.settings.showChart; if (this.showChart) { if (this.ctx.defaultSubscription.firstDatasource?.dataKeys?.length) { - this.flotDataKey = this.ctx.defaultSubscription.firstDatasource?.dataKeys[0]; - this.flotDataKey.settings = { - fillLines: false, - showLines: true, - lineWidth: 2 - } as TbFlotKeySettings; + this.lineChartDataKey = this.ctx.defaultSubscription.firstDatasource?.dataKeys[0]; + this.lineChartDataKey.settings = { + showPointLabel: false, + type: TimeSeriesChartSeriesType.line, + lineSettings: { + smooth: false, + showLine: true, + lineWidth: 2, + showPoints: false + } + } as TimeSeriesChartKeySettings; } } @@ -161,33 +171,34 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit ngAfterViewInit(): void { if (this.showChart && this.ctx.datasources?.length) { - const settings = { - shadowSize: 0, - enableSelection: false, - smoothLines: false, - grid: { - tickColor: 'rgba(0,0,0,0.12)', - horizontalLines: true, - verticalLines: false, - outlineWidth: 0, - minBorderMargin: 0, - margin: 0 - }, - yaxis: { - showLabels: false, - tickGenerator: 'return [(axis.max + axis.min) / 2];' - }, - xaxis: { - showLabels: false - } - } as TbFlotSettings; - this.flot = new TbFlot(this.ctx, 'line', $(this.chartElement.nativeElement), settings); - this.tickMin$ = this.flot.yMin$.pipe( - map((value) => formatValue(value, (this.flotDataKey?.decimals || this.ctx.decimals)) + const settings: TimeSeriesChartSettings = { + dataZoom: false, + xAxis: { + show: false + }, + yAxis: { + show: true, + showLine: false, + showTicks: false, + showTickLabels: false, + showSplitLines: true, + min: 'dataMin', + max: 'dataMax', + intervalCalculator: + 'var scale = axis.scale; return !scale.isBlank() ? ((scale.getExtent()[1] - scale.getExtent()[0]) / 2) : undefined;' + }, + tooltipDateInterval: false, + tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss') + } as TimeSeriesChartSettings; + + this.lineChart = new TbTimeSeriesChart(this.ctx, settings, this.chartElement.nativeElement, this.renderer, true); + + this.tickMin$ =this.lineChart.yMin$.pipe( + map((value) => formatValue(value, (this.lineChartDataKey?.decimals || this.ctx.decimals)) + )); + this.tickMax$ = this.lineChart.yMax$.pipe( + map((value) => formatValue(value, (this.lineChartDataKey?.decimals || this.ctx.decimals)) )); - this.tickMax$ = this.flot.yMax$.pipe( - map((value) => formatValue(value, (this.flotDataKey?.decimals || this.ctx.decimals)) - )); } if (this.settings.autoScale && this.showValues) { this.renderer.setStyle(this.valueCardValueContainer.nativeElement, 'height', valuesLayoutHeight + 'px'); @@ -221,7 +232,7 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit } if (this.showChart) { - this.flot.update(); + this.lineChart.update(); } this.updateLastUpdateTs(ts); @@ -256,20 +267,14 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit } public onResize() { - if (this.showChart) { - this.flot.resize(); - } } public onEditModeChanged() { - if (this.showChart) { - this.flot.checkMouseEvents(); - } } public onDestroy() { if (this.showChart) { - this.flot.destroy(); + this.lineChart.destroy(); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.html index d945380a10..ba538d8104 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.html @@ -18,8 +18,10 @@
-
-
{{ valueText }}
+
+
+
{{ valueText }}
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.scss index f958bb6354..22e52e05dd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.scss @@ -52,7 +52,18 @@ } .tb-value-chart-card-chart { flex: 1; + min-width: 0; height: 100%; } + &.auto-scale { + .tb-value-chart-card-value-container { + width: 35%; + display: flex; + justify-content: flex-start; + } + .tb-value-chart-card-chart { + width: 65%; + } + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts index d4b8404275..b59098de17 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts @@ -34,7 +34,9 @@ import { ColorProcessor, ComponentStyle, getDataKey, - overlayStyle, resolveCssSize, + overlayStyle, + resolveCssSize, + simpleDateFormat, textStyle } from '@shared/models/widget-settings.models'; import { WidgetComponent } from '@home/components/widget/widget.component'; @@ -44,16 +46,20 @@ import { ValueChartCardLayout, ValueChartCardWidgetSettings } from '@home/components/widget/lib/cards/value-chart-card-widget.models'; -import { TbFlot } from '@home/components/widget/lib/flot-widget'; import { DataKey } from '@shared/models/widget.models'; -import { TbFlotKeySettings, TbFlotSettings } from '@home/components/widget/lib/flot-widget.models'; import { getTsValueByLatestDataKey } from '@home/components/widget/lib/cards/aggregated-value-card.models'; import { Observable } from 'rxjs'; import { ImagePipe } from '@shared/pipe/image.pipe'; import { DomSanitizer } from '@angular/platform-browser'; +import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart'; +import { + TimeSeriesChartKeySettings, + TimeSeriesChartSeriesType, + TimeSeriesChartSettings +} from '@home/components/widget/lib/chart/time-series-chart.models'; const layoutHeight = 56; -const valueRelativeMaxWidth = 0.5; +const valueRelativeWidth = 0.35; @Component({ selector: 'tb-value-chart-card-widget', @@ -81,6 +87,7 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD widgetTitlePanel: TemplateRef; layout: ValueChartCardLayout; + autoScale: boolean; showValue = true; valueText = 'N/A'; @@ -90,8 +97,8 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD backgroundStyle$: Observable; overlayStyle: ComponentStyle = {}; - private flot: TbFlot; - private flotDataKey: DataKey; + private lineChart: TbTimeSeriesChart; + private lineChartDataKey: DataKey; private valueKey: DataKey; private contentResize$: ResizeObserver; @@ -129,6 +136,7 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD } this.layout = this.settings.layout; + this.autoScale = this.settings.autoScale; this.showValue = this.settings.showValue; this.valueStyle = textStyle(this.settings.valueFont); @@ -138,37 +146,34 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD this.overlayStyle = overlayStyle(this.settings.background.overlay); if (this.ctx.defaultSubscription.firstDatasource?.dataKeys?.length) { - this.flotDataKey = this.ctx.defaultSubscription.firstDatasource?.dataKeys[0]; - this.flotDataKey.settings = { - fillLines: false, - showLines: true, - lineWidth: 2 - } as TbFlotKeySettings; + this.lineChartDataKey = this.ctx.defaultSubscription.firstDatasource?.dataKeys[0]; + this.lineChartDataKey.settings = { + showPointLabel: false, + type: TimeSeriesChartSeriesType.line, + lineSettings: { + smooth: true, + showLine: true, + lineWidth: 2, + showPoints: false + } + } as TimeSeriesChartKeySettings; } } public ngAfterViewInit() { - const settings = { - shadowSize: 0, - enableSelection: false, - smoothLines: true, - grid: { - tickColor: 'rgba(0,0,0,0.12)', - horizontalLines: false, - verticalLines: false, - outlineWidth: 0, - minBorderMargin: 0, - margin: 0 + const settings: TimeSeriesChartSettings = { + dataZoom: false, + xAxis: { + show: false }, - yaxis: { - showLabels: false, - tickGenerator: 'return [(axis.max + axis.min) / 2];' + yAxis: { + show: false }, - xaxis: { - showLabels: false - } - } as TbFlotSettings; - this.flot = new TbFlot(this.ctx, 'line', $(this.chartElement.nativeElement), settings); + tooltipDateInterval: false, + tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss') + } as TimeSeriesChartSettings; + + this.lineChart = new TbTimeSeriesChart(this.ctx, settings, this.chartElement.nativeElement, this.renderer, false); this.contentResize$ = new ResizeObserver(() => { this.onResize(); @@ -190,7 +195,9 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD } public onDataUpdated() { - this.flot.update(); + if (this.lineChart) { + this.lineChart.update(); + } } public onLatestDataUpdated() { @@ -206,20 +213,21 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD this.valueColor.update(value); this.cd.detectChanges(); setTimeout(() => { - this.onResize(false); + this.onResize(); }, 0); } } public onEditModeChanged() { - this.flot.checkMouseEvents(); } public onDestroy() { - this.flot.destroy(); + if (this.lineChart) { + this.lineChart.destroy(); + } } - private onResize(fitTargetHeight = true) { + private onResize(fitTargetWidth = true) { if (this.settings.autoScale && this.showValue) { const contentWidth = this.valueChartCardContent.nativeElement.getBoundingClientRect().width; const contentHeight = this.valueChartCardContent.nativeElement.getBoundingClientRect().height; @@ -228,26 +236,26 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD this.valueFontSize = resolveCssSize(fontSize)[0]; } const valueRelativeHeight = Math.min(this.valueFontSize / layoutHeight, 1); - const targetValueHeight = contentHeight * valueRelativeHeight; - const maxValueWidth = contentWidth * valueRelativeMaxWidth; - this.setValueFontSize(targetValueHeight, maxValueWidth, fitTargetHeight); + const targetValueWidth = contentWidth * valueRelativeWidth; + const maxValueHeight = contentHeight * valueRelativeHeight; + this.setValueFontSize(targetValueWidth, maxValueHeight, fitTargetWidth); } - this.flot.resize(); + this.lineChart.resize(); } - private setValueFontSize(targetHeight: number, maxWidth: number, fitTargetHeight = true) { + private setValueFontSize(targetWidth: number, maxHeight: number, fitTargetWidth = true) { const fontSize = getComputedStyle(this.valueChartCardValue.nativeElement).fontSize; let valueFontSize = resolveCssSize(fontSize)[0]; this.renderer.setStyle(this.valueChartCardValue.nativeElement, 'fontSize', valueFontSize + 'px'); this.renderer.setStyle(this.valueChartCardValue.nativeElement, 'lineHeight', '1'); - let valueHeight = this.valueChartCardValue.nativeElement.getBoundingClientRect().height; - while (fitTargetHeight && valueHeight < targetHeight) { + let valueWidth = this.valueChartCardValue.nativeElement.getBoundingClientRect().width; + while (fitTargetWidth && valueWidth < targetWidth) { valueFontSize++; this.renderer.setStyle(this.valueChartCardValue.nativeElement, 'fontSize', valueFontSize + 'px'); - valueHeight = this.valueChartCardValue.nativeElement.getBoundingClientRect().height; + valueWidth = this.valueChartCardValue.nativeElement.getBoundingClientRect().width; } - let valueWidth = this.valueChartCardValue.nativeElement.getBoundingClientRect().width; - while ((valueHeight > targetHeight || valueWidth > maxWidth) && valueFontSize > 6) { + let valueHeight = this.valueChartCardValue.nativeElement.getBoundingClientRect().height; + while ((valueWidth > targetWidth || valueHeight > maxHeight) && valueFontSize > 6) { valueFontSize--; this.renderer.setStyle(this.valueChartCardValue.nativeElement, 'fontSize', valueFontSize + 'px'); valueWidth = this.valueChartCardValue.nativeElement.getBoundingClientRect().width; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts index 2e2faf35e8..c523bf7927 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts @@ -53,19 +53,14 @@ import { CallbackDataParams, CustomSeriesRenderItem, LabelLayoutOptionCallback } import { ECharts, echartsModule, - EChartsOption, + EChartsOption, EChartsSeriesItem, echartsTooltipFormatter, NamedDataSet, toNamedData } from '@home/components/widget/lib/chart/echarts-widget.models'; import { IntervalMath } from '@shared/models/time/time.models'; -interface BarChartDataItem { - id: string; - dataKey: DataKey; - data: NamedDataSet; - enabled: boolean; -} +type BarChartDataItem = EChartsSeriesItem; interface BarChartLegendItem { id: string; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts index d0b54fed53..99ea088c92 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts @@ -75,6 +75,7 @@ export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings }, tooltipValueColor: 'rgba(0, 0, 0, 0.76)', tooltipShowDate: true, + tooltipDateInterval: true, tooltipDateFormat: customDateFormat('MMMM y'), tooltipDateFont: { family: 'Roboto', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts index 515e47559d..7564bd9cab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts @@ -17,35 +17,45 @@ import * as echarts from 'echarts/core'; import { Axis } from 'echarts'; import AxisModel from 'echarts/types/src/coord/cartesian/AxisModel'; -import { formatValue, isDefinedAndNotNull, isNumber, isString } from '@core/utils'; +import { estimateLabelUnionRect } from 'echarts/lib/coord/axisHelper'; +import { formatValue, isDefinedAndNotNull } from '@core/utils'; import TimeScale from 'echarts/types/src/scale/Time'; import { - DataZoomComponent, DataZoomComponentOption, - GridComponent, GridComponentOption, - MarkLineComponent, MarkLineComponentOption, - TooltipComponent, TooltipComponentOption, - VisualMapComponent, VisualMapComponentOption + DataZoomComponent, + DataZoomComponentOption, + GridComponent, + GridComponentOption, + MarkLineComponent, + MarkLineComponentOption, + TooltipComponent, + TooltipComponentOption, + VisualMapComponent, + VisualMapComponentOption } from 'echarts/components'; import { BarChart, - LineChart, + BarSeriesOption, CustomChart, CustomSeriesOption, + LineChart, LineSeriesOption, - BarSeriesOption, PieSeriesOption, PieChart + PieChart, + PieSeriesOption } from 'echarts/charts'; import { LabelLayout } from 'echarts/features'; import { CanvasRenderer, SVGRenderer } from 'echarts/renderers'; -import { DataEntry, DataSet } from '@shared/models/widget.models'; +import { DataEntry, DataKey, DataSet } from '@shared/models/widget.models'; import { calculateAggIntervalWithWidgetTimeWindow, - Interval, IntervalMath, WidgetTimewindow } from '@shared/models/time/time.models'; import { CallbackDataParams } from 'echarts/types/dist/shared'; import { Renderer2 } from '@angular/core'; import { DateFormatProcessor, DateFormatSettings, Font } from '@shared/models/widget-settings.models'; +import GlobalModel from 'echarts/types/src/model/Global'; +import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D'; +import SeriesModel from 'echarts/types/src/model/Series'; class EChartsModule { private initialized = false; @@ -139,6 +149,133 @@ export type EChartsDataItem = [number, any, number, number]; export type NamedDataSet = {name: string; value: EChartsDataItem}[]; +export type EChartsSeriesItem = { + id: string; + dataKey: DataKey; + data: NamedDataSet; + enabled: boolean; + units?: string; + decimals?: number; +}; + +export const getXAxis = (chart: ECharts): Axis2D => { + const model: GlobalModel = (chart as any).getModel(); + const models = model.queryComponents({mainType: 'xAxis'}); + if (models?.length) { + const axisModel = models[0] as AxisModel; + return axisModel.axis; + } + return null; +}; + +export const getYAxis = (chart: ECharts, axisId: string): Axis2D => { + const model: GlobalModel = (chart as any).getModel(); + const models = model.queryComponents({mainType: 'yAxis', id: axisId}); + if (models?.length) { + const axisModel = models[0] as AxisModel; + return axisModel.axis; + } + return null; +}; + +export const calculateYAxisWidth = (chart: ECharts, axisId: string): number => { + const axis = getYAxis(chart, axisId); + return calculateAxisSize(axis); +}; + +export const calculateXAxisHeight = (chart: ECharts): number => { + const axis = getXAxis(chart); + return calculateAxisSize(axis); +}; + +const calculateAxisSize = (axis: Axis2D): number => { + let size = 0; + if (axis && axis.model.option.show) { + const labelUnionRect = estimateLabelUnionRect(axis); + if (labelUnionRect) { + const margin = axis.model.get(['axisLabel', 'margin']); + const dimension = axis.isHorizontal() ? 'height' : 'width'; + size += labelUnionRect[dimension] + margin; + } + if (!axis.scale.isBlank() && axis.model.get(['axisTick', 'show'])) { + const tickLength = axis.model.get(['axisTick', 'length']); + size += tickLength; + } + } + return size; +}; + +export const measureYAxisNameWidth = (chart: ECharts, axisId: string, name: string): number => { + const axis = getYAxis(chart, axisId); + if (axis) { + return axis.model.getModel('nameTextStyle').getTextRect(name).height; + } + return 0; +}; + +export const measureXAxisNameHeight = (chart: ECharts, name: string): number => { + const axis = getXAxis(chart); + if (axis) { + return axis.model.getModel('nameTextStyle').getTextRect(name).height; + } + return 0; +}; + +export const measureThresholdLabelOffset = (chart: ECharts, axisId: string, thresholdId: string, value: any): [number, number] => { + const axis = getYAxis(chart, axisId); + if (axis && !axis.scale.isBlank()) { + const extent = axis.scale.getExtent(); + const model: GlobalModel = (chart as any).getModel(); + const models = model.queryComponents({mainType: 'series', id: thresholdId}); + if (models?.length) { + const lineSeriesModel = models[0] as SeriesModel; + const markLineModel = lineSeriesModel.getModel('markLine'); + const labelPosition = markLineModel.get(['label', 'position']); + if (labelPosition === 'start' || labelPosition === 'end') { + const labelModel = markLineModel.getModel('label'); + const formatter = markLineModel.get(['label', 'formatter']); + let textWidth = 0; + if (Array.isArray(value)) { + for (const val of value) { + if (val >= extent[0] && val <= extent[1]) { + const textVal = typeof formatter === 'string' ? formatter : formatter({value: val} as CallbackDataParams); + textWidth = Math.max(textWidth, labelModel.getTextRect(textVal).width); + } + } + } else { + if (value >= extent[0] && value <= extent[1]) { + const textVal = typeof formatter === 'string' ? formatter : formatter({value} as CallbackDataParams); + textWidth = labelModel.getTextRect(textVal).width; + } + } + if (!textWidth) { + return [0,0]; + } + const distanceOpt = markLineModel.get(['label', 'distance']); + let distance = 5; + if (distanceOpt) { + distance = typeof distanceOpt === 'number' ? distanceOpt : distanceOpt[0]; + } + const offset = distance + textWidth; + if (labelPosition === 'start') { + return [offset, 0]; + } else { + return [0, offset]; + } + } + } + } + return [0,0]; +}; + +export const getAxisExtent = (chart: ECharts, axisId: string): [number, number] => { + const axis = getYAxis(chart, axisId); + if (axis) { + return axis.scale.getExtent(); + } + return [0,0]; +}; + export const toNamedData = (data: DataSet): NamedDataSet => { if (!data?.length) { return []; @@ -162,11 +299,18 @@ const toEChartsDataItem = (entry: DataEntry): EChartsDataItem => { return item; }; +export enum EChartsTooltipTrigger { + point = 'point', + axis = 'axis' +} + export interface EChartsTooltipWidgetSettings { showTooltip: boolean; + tooltipTrigger?: EChartsTooltipTrigger; tooltipValueFont: Font; tooltipValueColor: string; tooltipShowDate: boolean; + tooltipDateInterval?: boolean; tooltipDateFormat: DateFormatSettings; tooltipDateFont: Font; tooltipDateColor: string; @@ -177,13 +321,15 @@ export interface EChartsTooltipWidgetSettings { export const echartsTooltipFormatter = (renderer: Renderer2, tooltipDateFormat: DateFormatProcessor, settings: EChartsTooltipWidgetSettings, - params: CallbackDataParams[], + params: CallbackDataParams[] | CallbackDataParams, decimals: number, units: string, - focusedSeriesIndex: number): null | HTMLElement => { - if (!params.length || !params[0]) { + focusedSeriesIndex: number, + series?: EChartsSeriesItem[]): null | HTMLElement => { + if (!params || Array.isArray(params) && !params[0]) { return null; } + const firstParam = Array.isArray(params) ? params[0] : params; const tooltipElement: HTMLElement = renderer.createElement('div'); renderer.setStyle(tooltipElement, 'display', 'flex'); renderer.setStyle(tooltipElement, 'flex-direction', 'column'); @@ -192,9 +338,9 @@ export const echartsTooltipFormatter = (renderer: Renderer2, if (settings.tooltipShowDate) { const dateElement: HTMLElement = renderer.createElement('div'); let dateText: string; - const startTs = params[0].value[2]; - const endTs = params[0].value[3]; - if (startTs && endTs && (endTs - 1) > startTs) { + const startTs = firstParam.value[2]; + const endTs = firstParam.value[3]; + if (settings.tooltipDateInterval && startTs && endTs && (endTs - 1) > startTs) { const startDateText = tooltipDateFormat.update(startTs); const endDateText = tooltipDateFormat.update(endTs - 1); if (startDateText === endDateText) { @@ -203,7 +349,7 @@ export const echartsTooltipFormatter = (renderer: Renderer2, dateText = startDateText + ' - ' + endDateText; } } else { - const ts = params[0].value[0]; + const ts = firstParam.value[0]; dateText = tooltipDateFormat.update(ts); } renderer.appendChild(dateElement, renderer.createText(dateText)); @@ -216,14 +362,16 @@ export const echartsTooltipFormatter = (renderer: Renderer2, renderer.appendChild(tooltipElement, dateElement); } let seriesParams: CallbackDataParams = null; - if (focusedSeriesIndex > -1) { + if (Array.isArray(params) && focusedSeriesIndex > -1) { seriesParams = params.find(param => param.seriesIndex === focusedSeriesIndex); + } else if (!Array.isArray(params)) { + seriesParams = params; } if (seriesParams) { - renderer.appendChild(tooltipElement, constructEchartsTooltipSeriesElement(renderer, settings, seriesParams, decimals, units)); - } else { + renderer.appendChild(tooltipElement, constructEchartsTooltipSeriesElement(renderer, settings, seriesParams, decimals, units, series)); + } else if (Array.isArray(params)) { for (seriesParams of params) { - renderer.appendChild(tooltipElement, constructEchartsTooltipSeriesElement(renderer, settings, seriesParams, decimals, units)); + renderer.appendChild(tooltipElement, constructEchartsTooltipSeriesElement(renderer, settings, seriesParams, decimals, units, series)); } } return tooltipElement; @@ -233,7 +381,8 @@ const constructEchartsTooltipSeriesElement = (renderer: Renderer2, settings: EChartsTooltipWidgetSettings, seriesParams: CallbackDataParams, decimals: number, - units: string): HTMLElement => { + units: string, + series?: EChartsSeriesItem[]): HTMLElement => { const labelValueElement: HTMLElement = renderer.createElement('div'); renderer.setStyle(labelValueElement, 'display', 'flex'); renderer.setStyle(labelValueElement, 'flex-direction', 'row'); @@ -262,7 +411,16 @@ const constructEchartsTooltipSeriesElement = (renderer: Renderer2, renderer.setStyle(labelTextElement, 'color', 'rgba(0, 0, 0, 0.76)'); renderer.appendChild(labelElement, labelTextElement); const valueElement: HTMLElement = renderer.createElement('div'); - const value = formatValue(seriesParams.value[1], decimals, units, false); + let formatDecimals = decimals; + let formatUnits = units; + if (series) { + const item = series.find(s => s.id === seriesParams.seriesId); + if (item) { + formatDecimals = item.decimals; + formatUnits = item.units; + } + } + const value = formatValue(seriesParams.value[1], formatDecimals, formatUnits, false); renderer.appendChild(valueElement, renderer.createText(value)); renderer.setStyle(valueElement, 'flex', '1'); renderer.setStyle(valueElement, 'text-align', 'end'); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts index c50813f7b6..31905e70ba 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts @@ -71,6 +71,7 @@ export const rangeChartDefaultSettings: RangeChartWidgetSettings = { }, tooltipValueColor: 'rgba(0, 0, 0, 0.76)', tooltipShowDate: true, + tooltipDateInterval: true, tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm'), tooltipDateFont: { family: 'Roboto', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts new file mode 100644 index 0000000000..37ce7a9c7f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts @@ -0,0 +1,134 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; +import { Interval, IntervalMath } from '@shared/models/time/time.models'; +import { LabelFormatterCallback, SeriesLabelOption } from 'echarts/types/src/util/types'; +import { TimeSeriesChartDataItem } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { CustomSeriesRenderItemParams } from 'echarts'; +import { CustomSeriesRenderItemAPI, CustomSeriesRenderItemReturn } from 'echarts/types/dist/shared'; +import { isNumeric } from '@core/utils'; +import * as echarts from 'echarts/core'; + +export interface BarVisualSettings { + color: string | LinearGradientObject; + borderColor: string; + borderWidth: number; + borderRadius: number; +} + +export interface BarRenderContext { + barsCount?: number; + barIndex?: number; + timeInterval?: Interval; + visualSettings?: BarVisualSettings; + labelOption?: SeriesLabelOption; + barStackIndex?: number; + currentStackItems?: TimeSeriesChartDataItem[]; +} + +export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: CustomSeriesRenderItemAPI, + renderCtx: BarRenderContext): CustomSeriesRenderItemReturn => { + const time = api.value(0) as number; + let start = api.value(2) as number; + const end = api.value(3) as number; + let interval = end - start; + const ts = start ? start : time; + if (!start || !end || !interval) { + interval = IntervalMath.numberValue(renderCtx.timeInterval); + start = time - interval / 2; + } + const gap = 0.3; + const barInterval = interval / (renderCtx.barsCount + gap * (renderCtx.barsCount + 3)); + const intervalGap = barInterval * gap * 2; + const barGap = barInterval * gap; + const value = api.value(1); + const startTime = start + intervalGap + (barInterval + barGap) * renderCtx.barIndex; + const delta = barInterval; + let offset = 0; + if (renderCtx.currentStackItems?.length) { + for (let i = 0; i < renderCtx.barStackIndex; i++) { + const stackItem = renderCtx.currentStackItems[i]; + const dataName = ts + ''; + const data = stackItem.data.find(d => d.name === dataName); + if (data) { + const val = data.value[1]; + if (isNumeric(val)) { + offset += Number(val); + } + } + } + } + let lowerLeft: number[]; + if (offset !== 0 && isNumeric(value)) { + lowerLeft = api.coord([startTime, value >= 0 ? Number(value) + offset : offset]); + } else { + lowerLeft = api.coord([startTime, value >= 0 ? value : 0]); + } + const size = api.size([delta, value]); + const width = size[0]; + const height = size[1]; + + const coordSys: {x: number; y: number; width: number; height: number} = params.coordSys as any; + + const rectShape = echarts.graphic.clipRectByRect({ + x: lowerLeft[0], + y: lowerLeft[1], + width, + height + }, { + x: coordSys.x, + y: coordSys.y, + width: coordSys.width, + height: coordSys.height + }); + + const zeroPos = api.coord([0, offset]); + + const style: any = { + fill: renderCtx.visualSettings.color, + stroke: renderCtx.visualSettings.borderColor, + lineWidth: renderCtx.visualSettings.borderWidth + }; + + if (renderCtx.labelOption.show) { + let position = renderCtx.labelOption.position; + if (value < 0) { + if (position === 'top') { + position = 'bottom'; + } else if (position === 'bottom') { + position = 'top'; + } + } + style.text = (renderCtx.labelOption.formatter as LabelFormatterCallback)({value: [null, value]} as any); + style.textDistance = 5; + style.textPosition = position; + style.rich = renderCtx.labelOption.rich; + } + + return rectShape && { + type: 'rect', + id: time + '', + shape: {...rectShape, r: renderCtx.visualSettings.borderRadius}, + style, + focus: 'series', + transition: 'all', + enterFrom: { + style: { opacity: 0 }, + shape: { height: 0, y: zeroPos[1] } + } + }; +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html new file mode 100644 index 0000000000..1b96aac669 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html @@ -0,0 +1,122 @@ + +
+
+ +
+
+
+
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +
{{ 'legend.Min' | translate }}{{ 'legend.Max' | translate }}{{ 'legend.Avg' | translate }}{{ 'legend.Total' | translate }}{{ 'legend.Latest' | translate }}
+ + + {{ legendData.data[legendKey.dataIndex].min }} + + {{ legendData.data[legendKey.dataIndex].max }} + + {{ legendData.data[legendKey.dataIndex].avg }} + + {{ legendData.data[legendKey.dataIndex].total }} + + {{ legendData.data[legendKey.dataIndex].latest }} +
+
+ + + + + + +
+
+
+
{{ legendKey.dataKey.label }}
+
+
+
+ + + {{ label | translate }} + + {{ legendData.data[legendKey.dataIndex][type] }} + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss new file mode 100644 index 0000000000..9d990c50a7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss @@ -0,0 +1,181 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +$maxLegendWidth: 25%; +$maxLegendHeight: 35%; + +.tb-time-series-chart-panel { + width: 100%; + height: 100%; + position: relative; + display: flex; + flex-direction: column; + gap: 16px; + padding: 20px 24px 24px 24px; + > div:not(.tb-time-series-chart-overlay) { + z-index: 1; + } + .tb-time-series-chart-overlay { + position: absolute; + top: 12px; + left: 12px; + bottom: 12px; + right: 12px; + } + div.tb-widget-title { + padding: 0; + } + .tb-time-series-chart-content { + flex: 1; + min-width: 0; + min-height: 0; + display: flex; + flex-direction: column; + gap: 16px; + &.legend-top { + flex-direction: column-reverse; + } + &.legend-right { + flex-direction: row; + } + &.legend-left { + flex-direction: row-reverse; + } + .tb-time-series-chart-shape { + flex: 1; + min-width: 0; + min-height: 0; + display: flex; + align-items: center; + justify-content: center; + } + &.legend-right, &.legend-left { + .tb-time-series-chart-legend { + display: inline-grid; + grid-auto-flow: column; + grid-template-rows: repeat(auto-fit, minmax(16px, min-content)); + max-width: calc($maxLegendWidth - 8px); + height: fit-content; + max-height: 100%; + } + } + &.legend-top, &.legend-bottom { + .tb-time-series-chart-legend { + align-self: center; + &.tb-simple-legend { + justify-content: center; + } + &:not(.tb-simple-legend) { + width: 100%; + } + } + } + .tb-time-series-chart-legend { + display: flex; + align-items: flex-start; + align-self: stretch; + column-gap: 16px; + row-gap: 8px; + flex-wrap: wrap; + overflow: auto; + width: fit-content; + max-width: 100%; + max-height: calc($maxLegendHeight - 8px); + .tb-time-series-chart-legend-table { + border-spacing: 0; + table-layout: fixed; + &.vertical { + width: 100%; + table-layout: auto; + } + + thead th, tbody th { + position: sticky; + z-index: 1; + backdrop-filter: blur(5000px); + } + thead th { + top: 0; + &:first-child { + left: 0; + z-index: 2; + } + } + tbody th { + left: 0; + } + th, td { + &:not(:last-child) { + padding-right: 8px; + } + } + thead tr, tbody tr:not(:last-child) { + th, td { + padding-bottom: 8px; + } + } + .tb-time-series-chart-legend-item { + align-items: flex-end; + &.left { + align-items: flex-start; + } + } + } + .tb-time-series-chart-legend-item { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + user-select: none; + .tb-time-series-chart-legend-item-label { + display: flex; + align-items: center; + gap: 4px; + color: #ccc; + white-space: nowrap; + cursor: pointer; + .tb-time-series-chart-legend-item-label-circle { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #ccc; + } + } + } + .tb-time-series-chart-legend-type-label { + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; + color: rgba(0, 0, 0, 0.38); + white-space: nowrap; + text-align: left; + &.right { + text-align: right; + } + } + .tb-time-series-chart-legend-value { + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 16px; + color: rgba(0, 0, 0, 0.87); + white-space: nowrap; + text-align: right; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts new file mode 100644 index 0000000000..560309b08b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts @@ -0,0 +1,163 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + Input, + OnDestroy, + OnInit, + Renderer2, + TemplateRef, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { timeSeriesChartKeyDefaultSettings, TimeSeriesChartKeySettings } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { Observable } from 'rxjs'; +import { backgroundStyle, ComponentStyle, overlayStyle, textStyle } from '@shared/models/widget-settings.models'; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; +import { LegendConfig, LegendData, LegendKey, LegendPosition } from '@shared/models/widget.models'; +import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart'; +import { + timeSeriesChartWidgetDefaultSettings, + TimeSeriesChartWidgetSettings +} from '@home/components/widget/lib/chart/time-series-chart-widget.models'; +import { mergeDeep } from '@core/utils'; + +@Component({ + selector: 'tb-time-series-chart-widget', + templateUrl: './time-series-chart-widget.component.html', + styleUrls: ['./time-series-chart-widget.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterViewInit { + + @ViewChild('chartShape', {static: false}) + chartShape: ElementRef; + + settings: TimeSeriesChartWidgetSettings; + + @Input() + ctx: WidgetContext; + + @Input() + widgetTitlePanel: TemplateRef; + + horizontalLegendPosition = false; + + showLegend: boolean; + legendClass: string; + legendConfig: LegendConfig; + legendData: LegendData; + legendKeys: LegendKey[]; + + backgroundStyle$: Observable; + overlayStyle: ComponentStyle = {}; + + legendLabelStyle: ComponentStyle; + disabledLegendLabelStyle: ComponentStyle; + + displayLegendValues = false; + + private timeSeriesChart: TbTimeSeriesChart; + + constructor(private imagePipe: ImagePipe, + private sanitizer: DomSanitizer, + private renderer: Renderer2, + private cd: ChangeDetectorRef) { + } + + ngOnInit(): void { + this.ctx.$scope.timeSeriesChartWidget = this; + this.settings = {...timeSeriesChartWidgetDefaultSettings, ...this.ctx.settings}; + + this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); + this.overlayStyle = overlayStyle(this.settings.background.overlay); + + this.showLegend = this.settings.showLegend; + if (this.showLegend) { + this.legendData = this.ctx.defaultSubscription.legendData; + this.legendConfig = this.settings.legendConfig; + this.legendKeys = this.legendData.keys; + if (this.legendConfig.sortDataKeys) { + this.legendKeys = this.legendData.keys.sort((key1, key2) => key1.dataKey.label.localeCompare(key2.dataKey.label)); + } + this.legendKeys.forEach(legendKey => { + legendKey.dataKey.settings = mergeDeep({} as TimeSeriesChartKeySettings, + timeSeriesChartKeyDefaultSettings, legendKey.dataKey.settings); + legendKey.dataKey.hidden = legendKey.dataKey.settings.dataHiddenByDefault; + }); + this.legendKeys = this.legendKeys.filter(legendKey => legendKey.dataKey.settings.showInLegend); + if (!this.legendKeys.length) { + this.showLegend = false; + } + } + + if (this.showLegend) { + this.horizontalLegendPosition = [LegendPosition.left, LegendPosition.right].includes(this.legendConfig.position); + this.legendClass = `legend-${this.legendConfig.position}`; + this.legendLabelStyle = textStyle(this.settings.legendLabelFont); + this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont); + this.legendLabelStyle.color = this.settings.legendLabelColor; + this.displayLegendValues = this.legendConfig.showMin || this.legendConfig.showMax || + this.legendConfig.showAvg || this.legendConfig.showTotal || this.legendConfig.showLatest; + } + } + + ngAfterViewInit() { + this.timeSeriesChart = new TbTimeSeriesChart(this.ctx, this.settings, this.chartShape.nativeElement, this.renderer); + } + + ngOnDestroy() { + if (this.timeSeriesChart) { + this.timeSeriesChart.destroy(); + } + } + + public onInit() { + const borderRadius = this.ctx.$widgetElement.css('borderRadius'); + this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; + this.cd.detectChanges(); + } + + public onDataUpdated() { + if (this.timeSeriesChart) { + this.timeSeriesChart.update(); + } + } + + public onLatestDataUpdated() { + if (this.timeSeriesChart) { + this.timeSeriesChart.latestUpdated(); + } + } + + public onLegendKeyEnter(legendKey: LegendKey) { + this.timeSeriesChart.keyEnter(legendKey.dataKey); + } + + public onLegendKeyLeave(legendKey: LegendKey) { + this.timeSeriesChart.keyLeave(legendKey.dataKey); + } + + public toggleLegendKey(legendKey: LegendKey) { + this.timeSeriesChart.toggleKey(legendKey.dataKey); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.models.ts new file mode 100644 index 0000000000..ccfca7c4d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.models.ts @@ -0,0 +1,52 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { timeSeriesChartDefaultSettings, TimeSeriesChartSettings } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { BackgroundSettings, BackgroundType, Font } from '@shared/models/widget-settings.models'; +import { defaultLegendConfig, LegendConfig, LegendPosition, widgetType } from '@shared/models/widget.models'; +import { mergeDeep } from '@core/utils'; + +export interface TimeSeriesChartWidgetSettings extends TimeSeriesChartSettings { + showLegend: boolean; + legendLabelFont: Font; + legendLabelColor: string; + legendConfig: LegendConfig; + background: BackgroundSettings; +} + +export const timeSeriesChartWidgetDefaultSettings: TimeSeriesChartWidgetSettings = + mergeDeep({} as TimeSeriesChartWidgetSettings, timeSeriesChartDefaultSettings as TimeSeriesChartWidgetSettings, { + showLegend: true, + legendLabelFont: { + family: 'Roboto', + size: 12, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '16px' + }, + legendLabelColor: 'rgba(0, 0, 0, 0.76)', + legendConfig: {...defaultLegendConfig(widgetType.timeseries), position: LegendPosition.top}, + background: { + type: BackgroundType.color, + color: '#fff', + overlay: { + enabled: false, + color: 'rgba(255,255,255,0.72)', + blur: 3 + } + } + } as TimeSeriesChartWidgetSettings); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts new file mode 100644 index 0000000000..ef8b432e46 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -0,0 +1,873 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ECharts, + EChartsOption, + EChartsSeriesItem, + EChartsTooltipTrigger, + EChartsTooltipWidgetSettings, + measureThresholdLabelOffset +} from '@home/components/widget/lib/chart/echarts-widget.models'; +import { ComponentStyle, Font, simpleDateFormat, textStyle } from '@shared/models/widget-settings.models'; +import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared'; +import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts'; +import { formatValue, isDefinedAndNotNull, parseFunction } from '@core/utils'; +import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; +import tinycolor from 'tinycolor2'; +import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D'; +import { Interval } from '@shared/models/time/time.models'; +import { ValueAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes'; +import { SeriesLabelOption } from 'echarts/types/src/util/types'; +import { + BarRenderContext, + BarVisualSettings, + renderTimeSeriesBar +} from '@home/components/widget/lib/chart/time-series-chart-bar.models'; +import { DataKey } from '@shared/models/widget.models'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { TbColorScheme } from '@shared/models/color.models'; + +export enum PointLabelPosition { + top = 'top', + bottom = 'bottom' +} + +export enum AxisPosition { + left = 'left', + right = 'right', + top = 'top', + bottom = 'bottom' +} + +export enum TimeSeriesChartShape { + emptyCircle = 'emptyCircle', + circle = 'circle', + rect = 'rect', + roundRect = 'roundRect', + triangle = 'triangle', + diamond = 'diamond', + pin = 'pin', + arrow = 'arrow', + none = 'none' +} + +export enum TimeSeriesChartLineType { + solid = 'solid', + dashed = 'dashed', + dotted = 'dotted' +} + +export enum ThresholdLabelPosition { + start = 'start', + middle = 'middle', + end = 'end', + insideStart = 'insideStart', + insideStartTop = 'insideStartTop', + insideStartBottom = 'insideStartBottom', + insideMiddle = 'insideMiddle', + insideMiddleTop = 'insideMiddleTop', + insideMiddleBottom = 'insideMiddleBottom', + insideEnd = 'insideEnd', + insideEndTop = 'insideEndTop', + insideEndBottom = 'insideEndBottom' +} + +export interface TimeSeriesChartAxisSettings { + show: boolean; + position: AxisPosition; + label?: string; + labelFont?: Font; + labelColor?: string; + showLine: boolean; + lineColor: string; + showTicks: boolean; + ticksColor: string; + showTickLabels: boolean; + tickLabelFont: Font; + tickLabelColor: string; + showSplitLines: boolean; + splitLinesColor: string; +} + +export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSettings { + min?: number | string; + max?: number | string; + intervalCalculator?: string; +} + +export enum TimeSeriesChartThresholdType { + constant = 'constant', + latestKey = 'latestKey', + entity = 'entity' +} + +export interface TimeSeriesChartThreshold { + type: TimeSeriesChartThresholdType; + value?: number; + latestKeyName?: string; + entityAlias?: string; + entityKeyType?: DataKeyType.attribute | DataKeyType.timeseries; + entityKey?: string; + units?: string; + decimals?: number; + lineWidth: number; + lineType: TimeSeriesChartLineType; + lineColor: string; + startSymbol: TimeSeriesChartShape; + startSymbolSize: number; + endSymbol: TimeSeriesChartShape; + endSymbolSize: number; + showLabel: boolean; + labelPosition: ThresholdLabelPosition; + labelFont: Font; + labelColor: string; +} + +const timeSeriesChartColorScheme: TbColorScheme = { + 'threshold.line': { + light: 'rgba(0, 0, 0, 0.76)', + dark: '#eee' + }, + 'threshold.label': { + light: 'rgba(0, 0, 0, 0.76)', + dark: '#eee' + }, + 'axis.line': { + light: 'rgba(0, 0, 0, 0.54)', + dark: '#B9B8CE' + }, + 'axis.label': { + light: 'rgba(0, 0, 0, 0.54)', + dark: '#B9B8CE' + }, + 'axis.ticks': { + light: 'rgba(0, 0, 0, 0.54)', + dark: '#B9B8CE' + }, + 'axis.tickLabel': { + light: 'rgba(0, 0, 0, 0.54)', + dark: '#B9B8CE' + }, + 'axis.splitLine': { + light: 'rgba(0, 0, 0, 0.12)', + dark: '#484753' + }, + 'series.pointLabel': { + light: 'rgba(0, 0, 0, 0.76)', + dark: '#eee' + } +}; + +export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold = { + type: TimeSeriesChartThresholdType.constant, + units: '', + decimals: 0, + lineWidth: 1, + lineType: TimeSeriesChartLineType.solid, + lineColor: timeSeriesChartColorScheme['threshold.line'].light, + startSymbol: TimeSeriesChartShape.none, + startSymbolSize: 5, + endSymbol: TimeSeriesChartShape.arrow, + endSymbolSize: 5, + showLabel: true, + labelPosition: ThresholdLabelPosition.end, + labelFont: { + family: 'Roboto', + size: 12, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '1' + }, + labelColor: timeSeriesChartColorScheme['threshold.label'].light +}; + +export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings { + darkMode: boolean; + dataZoom: boolean; + stack: boolean; + thresholds: TimeSeriesChartThreshold[]; + xAxis: TimeSeriesChartAxisSettings; + yAxis: TimeSeriesChartYAxisSettings; +} + +export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { + darkMode: false, + dataZoom: true, + stack: false, + thresholds: [], + xAxis: { + show: true, + position: AxisPosition.bottom, + label: '', + labelFont: { + family: 'Roboto', + size: 12, + sizeUnit: 'px', + style: 'normal', + weight: '600', + lineHeight: '1' + }, + labelColor: timeSeriesChartColorScheme['axis.label'].light, + showLine: true, + lineColor: timeSeriesChartColorScheme['axis.line'].light, + showTicks: true, + ticksColor: timeSeriesChartColorScheme['axis.ticks'].light, + showTickLabels: true, + tickLabelFont: { + family: 'Roboto', + size: 10, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '1' + }, + tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light, + showSplitLines: true, + splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light + }, + yAxis: { + show: true, + position: AxisPosition.left, + label: '', + labelFont: { + family: 'Roboto', + size: 12, + sizeUnit: 'px', + style: 'normal', + weight: '600', + lineHeight: '1' + }, + labelColor: timeSeriesChartColorScheme['axis.label'].light, + showLine: true, + lineColor: timeSeriesChartColorScheme['axis.line'].light, + showTicks: true, + ticksColor: timeSeriesChartColorScheme['axis.ticks'].light, + showTickLabels: true, + tickLabelFont: { + family: 'Roboto', + size: 12, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '1' + }, + tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light, + showSplitLines: true, + splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light + }, + showTooltip: true, + tooltipTrigger: EChartsTooltipTrigger.axis, + tooltipValueFont: { + family: 'Roboto', + size: 12, + sizeUnit: 'px', + style: 'normal', + weight: '500', + lineHeight: '16px' + }, + tooltipValueColor: 'rgba(0, 0, 0, 0.76)', + tooltipShowDate: true, + tooltipDateInterval: true, + tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss'), + tooltipDateFont: { + family: 'Roboto', + size: 11, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '16px' + }, + tooltipDateColor: 'rgba(0, 0, 0, 0.76)', + tooltipBackgroundColor: 'rgba(255, 255, 255, 0.76)', + tooltipBackgroundBlur: 4 +}; + +export enum SeriesFillType { + none = 'none', + opacity = 'opacity', + gradient = 'gradient' +} + +export interface SeriesFillSettings { + type: SeriesFillType; + opacity: number; + gradient: { + start: number; + end: number; + }; +} + +export interface LineSeriesSettings { + step: false | 'start' | 'end' | 'middle'; + smooth: boolean; + showLine: boolean; + lineWidth: number; + lineType: TimeSeriesChartLineType; + fillAreaSettings: SeriesFillSettings; + showPoints: boolean; + pointShape: TimeSeriesChartShape; + pointSize: number; +} + +export interface BarSeriesSettings { + showBorder: boolean; + borderWidth: number; + borderRadius: number; + backgroundSettings: SeriesFillSettings; +} + +export enum TimeSeriesChartSeriesType { + line = 'line', + bar = 'bar' +} + +export interface TimeSeriesChartKeySettings { + dataHiddenByDefault: boolean; + showInLegend: boolean; + showPointLabel: boolean; + pointLabelPosition: PointLabelPosition; + pointLabelFont: Font; + pointLabelColor: string; + type: TimeSeriesChartSeriesType; + lineSettings: LineSeriesSettings; + barSettings: BarSeriesSettings; +} + +export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = { + showInLegend: true, + dataHiddenByDefault: false, + showPointLabel: false, + pointLabelPosition: PointLabelPosition.top, + pointLabelFont: { + family: 'Roboto', + size: 11, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '1' + }, + pointLabelColor: timeSeriesChartColorScheme['series.pointLabel'].light, + type: TimeSeriesChartSeriesType.line, + lineSettings: { + step: false, + smooth: false, + showLine: true, + lineWidth: 2, + lineType: TimeSeriesChartLineType.solid, + fillAreaSettings: { + type: SeriesFillType.none, + opacity: 0.4, + gradient: { + start: 100, + end: 0 + } + }, + showPoints: false, + pointShape: TimeSeriesChartShape.emptyCircle, + pointSize: 4 + }, + barSettings: { + showBorder: false, + borderWidth: 2, + borderRadius: 0, + backgroundSettings: { + type: SeriesFillType.none, + opacity: 0.4, + gradient: { + start: 100, + end: 0 + } + } + } +}; + +export interface TimeSeriesChartDataItem extends EChartsSeriesItem { + yAxisIndex: number; + option?: LineSeriesOption | CustomSeriesOption; + barRenderContext?: BarRenderContext; +} + +type TimeSeriesChartThresholdValue = number | string | (number | string)[]; + +export interface TimeSeriesChartThresholdItem { + id: string; + yAxisIndex: number; + latestDataKey?: DataKey; + units?: string; + decimals?: number; + value: TimeSeriesChartThresholdValue; + settings: TimeSeriesChartThreshold; + option?: LineSeriesOption; +} + +export interface TimeSeriesChartYAxis { + id: string; + units: string; + option: YAXisOption & ValueAxisBaseOption; + intervalCalculator?: (axis: Axis2D) => number; +} + +export const createTimeSeriesYAxis = (axisId: string, units: string, + decimals: number, settings: TimeSeriesChartYAxisSettings, + darkMode: boolean): TimeSeriesChartYAxis => { + const yAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont, + settings.tickLabelColor, darkMode, 'axis.tickLabel'); + const yAxisNameStyle = createChartTextStyle(settings.labelFont, + settings.labelColor, darkMode, 'axis.label'); + const yAxis: TimeSeriesChartYAxis = { + id: axisId, + units, + option: { + show: settings.show, + type: 'value', + position: settings.position, + id: axisId, + offset: 0, + alignTicks: true, + scale: true, + min: settings.min, + max: settings.max, + name: settings.label, + nameLocation: 'middle', + nameRotate: settings.position === AxisPosition.left ? 90 : -90, + nameTextStyle: { + color: yAxisNameStyle.color, + fontStyle: yAxisNameStyle.fontStyle, + fontWeight: yAxisNameStyle.fontWeight, + fontFamily: yAxisNameStyle.fontFamily, + fontSize: yAxisNameStyle.fontSize + }, + axisLine: { + show: settings.showLine, + onZero: false, + lineStyle: { + color: prepareChartThemeColor(settings.lineColor, darkMode, 'axis.line') + } + }, + axisTick: { + show: settings.showTicks, + lineStyle: { + color: prepareChartThemeColor(settings.ticksColor, darkMode, 'axis.ticks') + } + }, + axisLabel: { + show: settings.showTickLabels, + color: yAxisTickLabelStyle.color, + fontStyle: yAxisTickLabelStyle.fontStyle, + fontWeight: yAxisTickLabelStyle.fontWeight, + fontFamily: yAxisTickLabelStyle.fontFamily, + fontSize: yAxisTickLabelStyle.fontSize, + formatter: (value: any) => formatValue(value, decimals, units, false) + }, + splitLine: { + show: settings.showSplitLines, + lineStyle: { + color: prepareChartThemeColor(settings.splitLinesColor, darkMode, 'axis.splitLine') + } + } + } + }; + if (settings.intervalCalculator && settings.intervalCalculator.length) { + yAxis.intervalCalculator = parseFunction(settings.intervalCalculator, ['axis']); + } + return yAxis; +}; + +export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartAxisSettings, + min: number, max: number, darkMode: boolean): XAXisOption => { + const xAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont, + settings.tickLabelColor, darkMode, 'axis.tickLabel'); + const xAxisNameStyle = createChartTextStyle(settings.labelFont, + settings.labelColor, darkMode, 'axis.label'); + return { + show: settings.show, + type: 'time', + scale: true, + position: settings.position, + name: settings.label, + nameLocation: 'middle', + nameTextStyle: { + color: xAxisNameStyle.color, + fontStyle: xAxisNameStyle.fontStyle, + fontWeight: xAxisNameStyle.fontWeight, + fontFamily: xAxisNameStyle.fontFamily, + fontSize: xAxisNameStyle.fontSize + }, + axisTick: { + show: settings.showTicks, + lineStyle: { + color: prepareChartThemeColor(settings.ticksColor, darkMode, 'axis.ticks') + } + }, + axisLabel: { + show: settings.showTickLabels, + color: xAxisTickLabelStyle.color, + fontStyle: xAxisTickLabelStyle.fontStyle, + fontWeight: xAxisTickLabelStyle.fontWeight, + fontFamily: xAxisTickLabelStyle.fontFamily, + fontSize: xAxisTickLabelStyle.fontSize, + hideOverlap: true + }, + axisLine: { + show: settings.showLine, + onZero: false, + lineStyle: { + color: prepareChartThemeColor(settings.lineColor, darkMode, 'axis.line') + } + }, + splitLine: { + show: settings.showSplitLines, + lineStyle: { + color: prepareChartThemeColor(settings.splitLinesColor, darkMode, 'axis.splitLine') + } + }, + min, + max + }; +}; + +export const generateChartData = (dataItems: TimeSeriesChartDataItem[], + thresholdItems: TimeSeriesChartThresholdItem[], + timeInterval: Interval, + stack: boolean, + darkMode: boolean): Array => { + let series = generateChartSeries(dataItems, timeInterval, stack, darkMode); + if (thresholdItems.length) { + const thresholds = generateChartThresholds(thresholdItems, darkMode); + series = series.concat(thresholds); + } + return series; +}; + +export const calculateThresholdsOffset = (chart: ECharts, + thresholdItems: TimeSeriesChartThresholdItem[], + yAxisList: TimeSeriesChartYAxis[]): [number, number] => { + const result: [number, number] = [0, 0]; + for (const item of thresholdItems) { + const yAxis = yAxisList[item.yAxisIndex]; + const offset = measureThresholdLabelOffset(chart, yAxis.id, item.id, item.value); + result[0] = Math.max(result[0], offset[0]); + result[1] = Math.max(result[1], offset[1]); + } + return result; +}; + +export const parseThresholdData = (value: any): TimeSeriesChartThresholdValue => { + let thresholdValue: TimeSeriesChartThresholdValue; + if (Array.isArray(value)) { + thresholdValue = value; + } else { + try { + const parsedData = JSON.parse(value); + thresholdValue = Array.isArray(parsedData) ? parsedData : [parsedData]; + } catch (e) { + thresholdValue = [value]; + } + } + return thresholdValue; +}; + +const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[], darkMode: boolean): Array => { + const series: Array = []; + for (const item of thresholdItems) { + if (isDefinedAndNotNull(item.value)) { + let seriesOption = item.option; + if (!item.option) { + const thresholdLabelStyle = createChartTextStyle(item.settings.labelFont, + item.settings.labelColor, darkMode, 'threshold.label'); + seriesOption = { + type: 'line', + id: item.id, + dataGroupId: item.id, + yAxisIndex: item.yAxisIndex, + animation: true, + data: [], + tooltip: { + show: false + }, + markLine: { + animation: true, + symbol: [item.settings.startSymbol, item.settings.endSymbol], + symbolSize: [item.settings.startSymbolSize, item.settings.endSymbolSize], + lineStyle: { + width: item.settings.lineWidth, + color: prepareChartThemeColor(item.settings.lineColor, darkMode, 'threshold.line'), + type: item.settings.lineType + }, + label: { + show: item.settings.showLabel, + position: item.settings.labelPosition, + color: thresholdLabelStyle.color, + fontStyle: thresholdLabelStyle.fontStyle, + fontWeight: thresholdLabelStyle.fontWeight, + fontFamily: thresholdLabelStyle.fontFamily, + fontSize: thresholdLabelStyle.fontSize, + formatter: params => formatValue(params.value, item.decimals, + item.units, false) + }, + emphasis: { + disabled: true + } + } + }; + item.option = seriesOption; + } + seriesOption.markLine.data = []; + if (Array.isArray(item.value)) { + for (const val of item.value) { + seriesOption.markLine.data.push({ + yAxis: val + }); + } + } else { + seriesOption.markLine.data.push({ + yAxis: item.value + }); + } + series.push(seriesOption); + } + } + return series; +}; + +const generateChartSeries = (dataItems: TimeSeriesChartDataItem[], + timeInterval: Interval, + stack: boolean, + darkMode: boolean): Array => { + const series: Array = []; + const enabledDataItems = dataItems.filter(d => d.enabled); + const barDataItems = enabledDataItems.filter(d => + d.dataKey.settings.type === TimeSeriesChartSeriesType.bar && d.data.length); + let barsCount = barDataItems.length; + const barGroups: number[] = []; + if (stack) { + barDataItems.forEach(item => { + if (barGroups.indexOf(item.yAxisIndex) === -1) { + barGroups.push(item.yAxisIndex); + } + }); + barsCount = barGroups.length; + } + for (const item of enabledDataItems) { + if (item.dataKey.settings.type === TimeSeriesChartSeriesType.bar) { + if (!item.barRenderContext) { + item.barRenderContext = {}; + } + item.barRenderContext.barsCount = barsCount; + item.barRenderContext.barIndex = stack ? barGroups.indexOf(item.yAxisIndex) : barDataItems.indexOf(item); + item.barRenderContext.timeInterval = timeInterval; + if (stack) { + const stackItems = enabledDataItems.filter(d => d.yAxisIndex === item.yAxisIndex); + item.barRenderContext.currentStackItems = stackItems; + item.barRenderContext.barStackIndex = stackItems.indexOf(item); + } + } + const seriesOption = createTimeSeriesChartSeries(item, stack, darkMode); + series.push(seriesOption); + } + return series; +}; + +export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings, + dataItems: TimeSeriesChartDataItem[], thresholdDataItems: TimeSeriesChartThresholdItem[], + darkMode: boolean): EChartsOption => { + options.darkMode = darkMode; + if (Array.isArray(options.yAxis)) { + for (const yAxis of options.yAxis) { + yAxis.nameTextStyle.color = prepareChartThemeColor(settings.yAxis.labelColor, darkMode, 'axis.label'); + yAxis.axisLabel.color = prepareChartThemeColor(settings.yAxis.tickLabelColor, darkMode, 'axis.tickLabel'); + yAxis.axisLine.lineStyle.color = prepareChartThemeColor(settings.yAxis.lineColor, darkMode, 'axis.line'); + yAxis.axisTick.lineStyle.color = prepareChartThemeColor(settings.yAxis.ticksColor, darkMode, 'axis.ticks'); + yAxis.splitLine.lineStyle.color = prepareChartThemeColor(settings.yAxis.splitLinesColor, darkMode, 'axis.splitLine'); + } + } + if (Array.isArray(options.xAxis)) { + for (const xAxis of options.xAxis) { + xAxis.nameTextStyle.color = prepareChartThemeColor(settings.xAxis.labelColor, darkMode, 'axis.label'); + xAxis.axisLabel.color = prepareChartThemeColor(settings.xAxis.tickLabelColor, darkMode, 'axis.tickLabel'); + xAxis.axisLine.lineStyle.color = prepareChartThemeColor(settings.xAxis.lineColor, darkMode, 'axis.line'); + xAxis.axisTick.lineStyle.color = prepareChartThemeColor(settings.xAxis.ticksColor, darkMode, 'axis.ticks'); + xAxis.splitLine.lineStyle.color = prepareChartThemeColor(settings.xAxis.splitLinesColor, darkMode, 'axis.splitLine'); + } + } + for (const item of dataItems) { + if (item.dataKey.settings.type === TimeSeriesChartSeriesType.line) { + if (item.option.label?.show) { + item.option.label.rich.value.color = prepareChartThemeColor(item.dataKey.settings.pointLabelColor, darkMode, 'series.pointLabel'); + } + if (Array.isArray(options.series)) { + const series = options.series.find(s => s.id === item.id); + if (series) { + if (series.label?.show) { + series.label.rich.value.color = prepareChartThemeColor(item.dataKey.settings.pointLabelColor, darkMode, 'series.pointLabel'); + } + } + } + } else { + if (item.barRenderContext?.labelOption?.show) { + item.barRenderContext.labelOption.rich.value.color = prepareChartThemeColor(item.dataKey.settings.pointLabelColor, + darkMode, 'series.pointLabel'); + } + } + } + for (const item of thresholdDataItems) { + if (Array.isArray(options.series)) { + const series = options.series.find(s => s.id === item.id); + if (series) { + series.markLine.lineStyle.color = prepareChartThemeColor(item.settings.lineColor, darkMode, 'threshold.line'); + if (series.markLine?.label?.show) { + series.markLine.label.color = prepareChartThemeColor(item.settings.labelColor, darkMode, 'threshold.label'); + } + } + } + } + return options; +}; + +const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem, + stack: boolean, + darkMode: boolean): LineSeriesOption | CustomSeriesOption => { + let seriesOption = item.option; + if (!seriesOption) { + const dataKey = item.dataKey; + const settings: TimeSeriesChartKeySettings = dataKey.settings; + const seriesColor = item.dataKey.color; + let pointLabelStyle: ComponentStyle = {}; + if (settings.showPointLabel) { + pointLabelStyle = createChartTextStyle(settings.pointLabelFont, settings.pointLabelColor, darkMode, 'series.pointLabel'); + } + const label: SeriesLabelOption = { + show: settings.showPointLabel, + position: settings.pointLabelPosition, + formatter: (params): string => { + const value = formatValue(params.value[1], item.decimals, item.units, false); + return `{value|${value}}`; + }, + rich: { + value: pointLabelStyle + } + }; + seriesOption = { + id: item.id, + dataGroupId: item.id, + yAxisIndex: item.yAxisIndex, + name: item.dataKey.label, + color: seriesColor, + stack: stack ? (item.yAxisIndex + '') : undefined, + stackStrategy: 'all', + emphasis: { + focus: 'series' + }, + animation: true, + dimensions: [ + {name: 'intervalStart', type: 'number'}, + {name: 'intervalEnd', type: 'number'} + ], + encode: { + intervalStart: 2, + intervalEnd: 3 + } + }; + item.option = seriesOption; + if (settings.type === TimeSeriesChartSeriesType.line) { + const lineSettings = settings.lineSettings; + const lineSeriesOption = seriesOption as LineSeriesOption; + lineSeriesOption.type = 'line'; + lineSeriesOption.label = label; + lineSeriesOption.step = lineSettings.step; + lineSeriesOption.smooth = lineSettings.smooth; + lineSeriesOption.lineStyle = { + width: lineSettings.showLine ? lineSettings.lineWidth : 0, + type: lineSettings.lineType + }; + if (lineSettings.fillAreaSettings.type !== SeriesFillType.none) { + lineSeriesOption.areaStyle = {}; + if (lineSettings.fillAreaSettings.type === SeriesFillType.opacity) { + lineSeriesOption.areaStyle.opacity = lineSettings.fillAreaSettings.opacity; + } else { + lineSeriesOption.areaStyle.opacity = 1; + lineSeriesOption.areaStyle.color = createLinearOpacityGradient(seriesColor, lineSettings.fillAreaSettings.gradient); + } + } + lineSeriesOption.showSymbol = lineSettings.showPoints; + lineSeriesOption.symbol = lineSettings.pointShape; + lineSeriesOption.symbolSize = lineSettings.pointSize; + } else { + const barSettings = settings.barSettings; + const barSeriesOption = (seriesOption as any) as CustomSeriesOption; + barSeriesOption.type = 'custom'; + const barVisualSettings: BarVisualSettings = { + color: seriesColor, + borderColor: seriesColor, + borderWidth: barSettings.showBorder ? barSettings.borderWidth : 0, + borderRadius: barSettings.borderRadius + }; + if (barSettings.backgroundSettings.type === SeriesFillType.none) { + barVisualSettings.color = seriesColor; + } else if (barSettings.backgroundSettings.type === SeriesFillType.opacity) { + barVisualSettings.color = tinycolor(seriesColor).setAlpha(barSettings.backgroundSettings.opacity).toRgbString(); + } else { + barVisualSettings.color = createLinearOpacityGradient(seriesColor, barSettings.backgroundSettings.gradient); + } + item.barRenderContext.visualSettings = barVisualSettings; + item.barRenderContext.labelOption = label; + barSeriesOption.renderItem = (params, api) => + renderTimeSeriesBar(params, api, item.barRenderContext); + } + } + seriesOption.data = item.data; + return seriesOption; +}; + +const createChartTextStyle = (font: Font, color: string, darkMode: boolean, colorKey?: string): ComponentStyle => { + const style = textStyle(font); + delete style.lineHeight; + style.fontSize = font.size; + style.color = prepareChartThemeColor(color, darkMode, colorKey); + return style; +}; + +const createLinearOpacityGradient = (color: string, gradient: {start: number; end: number}): LinearGradientObject => ({ + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [{ + offset: 0, color: tinycolor(color).setAlpha(gradient.start / 100).toRgbString() // color at 0% + }, { + offset: 1, color: tinycolor(color).setAlpha(gradient.end / 100).toRgbString() // color at 100% + }], + global: false +}); + +const prepareChartThemeColor = (color: string, darkMode: boolean, colorKey?: string): string => { + if (darkMode) { + let colorInstance = tinycolor(color); + if (colorInstance.isDark()) { + if (colorKey && timeSeriesChartColorScheme[colorKey]) { + return timeSeriesChartColorScheme[colorKey].dark; + } else { + const rgb = colorInstance.toRgb(); + colorInstance = tinycolor({r: 255 - rgb.r, g: 255 - rgb.g, b: 255 - rgb.b, a: rgb.a}); + return colorInstance.toRgbString(); + } + } + } + return color; +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts new file mode 100644 index 0000000000..48c2dfaf57 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -0,0 +1,642 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { WidgetContext } from '@home/models/widget-component.models'; +import { + AxisPosition, + calculateThresholdsOffset, + createTimeSeriesXAxisOption, + createTimeSeriesYAxis, + generateChartData, + TimeSeriesChartDataItem, + timeSeriesChartDefaultSettings, + timeSeriesChartKeyDefaultSettings, + TimeSeriesChartKeySettings, + TimeSeriesChartSeriesType, + TimeSeriesChartSettings, + TimeSeriesChartThresholdItem, + TimeSeriesChartThresholdType, + TimeSeriesChartYAxis, + parseThresholdData, + PointLabelPosition, + updateDarkMode +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { ResizeObserver } from '@juggle/resize-observer'; +import { + calculateXAxisHeight, + calculateYAxisWidth, + ECharts, + echartsModule, + EChartsOption, + echartsTooltipFormatter, + EChartsTooltipTrigger, + getAxisExtent, + getYAxis, + measureXAxisNameHeight, + measureYAxisNameWidth, + toNamedData +} from '@home/components/widget/lib/chart/echarts-widget.models'; +import { DateFormatProcessor } from '@shared/models/widget-settings.models'; +import { isDefinedAndNotNull, mergeDeep } from '@core/utils'; +import { DataKey, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; +import * as echarts from 'echarts/core'; +import { CallbackDataParams } from 'echarts/types/dist/shared'; +import { Renderer2 } from '@angular/core'; +import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts'; +import { BehaviorSubject } from 'rxjs'; +import { AggregationType } from '@shared/models/time/time.models'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { WidgetSubscriptionOptions } from '@core/api/widget-api.models'; + +export class TbTimeSeriesChart { + + private readonly shapeResize$: ResizeObserver; + + private dataItems: TimeSeriesChartDataItem[] = []; + private thresholdItems: TimeSeriesChartThresholdItem[] = []; + private yAxisList: TimeSeriesChartYAxis[] = []; + + private timeSeriesChart: ECharts; + private timeSeriesChartOptions: EChartsOption; + + private readonly tooltipDateFormat: DateFormatProcessor; + + private yMinSubject = new BehaviorSubject(-1); + private yMaxSubject = new BehaviorSubject(1); + + private darkMode = false; + + private messageChannel = new BroadcastChannel('tbMessageChannel'); + + private topPointLabels = false; + + private componentIndexCounter = 0; + + private highlightedDataKey: DataKey; + + yMin$ = this.yMinSubject.asObservable(); + yMax$ = this.yMaxSubject.asObservable(); + + constructor(private ctx: WidgetContext, + private readonly settings: TimeSeriesChartSettings, + private chartElement: HTMLElement, + private renderer: Renderer2, + private autoResize = true) { + + this.settings = mergeDeep({} as TimeSeriesChartSettings, timeSeriesChartDefaultSettings, this.settings); + this.darkMode = this.settings.darkMode; + this.setupData(); + this.setupThresholds(); + if (this.settings.showTooltip && this.settings.tooltipShowDate) { + this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat); + } + this.onResize(); + if (this.autoResize) { + this.shapeResize$ = new ResizeObserver(() => { + this.onResize(); + }); + this.shapeResize$.observe(this.chartElement); + } + this.messageChannel.addEventListener('message', (event) => { + if (event?.data?.type === 'tbDarkMode') { + const darkMode = !!event?.data?.darkMode; + this.setDarkMode(darkMode); + } + }); + } + + public update(): void { + for (const item of this.dataItems) { + const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === item.dataKey) : null; + item.data = datasourceData?.data ? toNamedData(datasourceData.data) : []; + } + this.onResize(); + if (this.timeSeriesChart) { + this.timeSeriesChartOptions.xAxis[0].min = this.ctx.defaultSubscription.timeWindow.minTime; + this.timeSeriesChartOptions.xAxis[0].max = this.ctx.defaultSubscription.timeWindow.maxTime; + this.timeSeriesChartOptions.xAxis[0].tbTimeWindow = this.ctx.defaultSubscription.timeWindow; + if (this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE) { + this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'line'; + } else { + this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'shadow'; + } + this.updateSeriesData(true); + if (this.highlightedDataKey) { + this.keyEnter(this.highlightedDataKey); + } + } + } + + public latestUpdated() { + let update = false; + if (this.ctx.latestData) { + for (const item of this.thresholdItems) { + if (item.settings.type === TimeSeriesChartThresholdType.latestKey && item.latestDataKey) { + const data = this.ctx.latestData.find(d => d.dataKey === item.latestDataKey); + if (data.data[0]) { + item.value = parseThresholdData(data.data[0][1]); + update = true; + } + } + } + } + if (this.timeSeriesChart && update) { + this.updateSeriesData(); + } + } + + public keyEnter(dataKey: DataKey): void { + this.highlightedDataKey = dataKey; + const item = this.dataItems.find(d => d.dataKey === dataKey); + if (item) { + this.timeSeriesChart.dispatchAction({ + type: 'highlight', + seriesId: item.id + }); + } + } + + public keyLeave(dataKey: DataKey): void { + this.highlightedDataKey = null; + const item = this.dataItems.find(d => d.dataKey === dataKey); + if (item) { + this.timeSeriesChart.dispatchAction({ + type: 'downplay', + seriesId: item.id + }); + } + } + + public toggleKey(dataKey: DataKey): void { + const enable = dataKey.hidden; + const dataItem = this.dataItems.find(d => d.dataKey === dataKey); + if (dataItem) { + dataItem.enabled = enable; + if (!enable) { + this.timeSeriesChart.dispatchAction({ + type: 'downplay', + seriesId: dataItem.id + }); + } + this.timeSeriesChartOptions.series = this.updateSeries(); + const mergeList = ['series']; + if (this.updateYAxisScale(this.yAxisList)) { + this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); + mergeList.push('yAxis'); + } + this.timeSeriesChart.setOption(this.timeSeriesChartOptions, this.settings.stack ? {notMerge: true} : {replaceMerge: mergeList}); + this.updateAxes(); + dataKey.hidden = !enable; + if (enable) { + this.timeSeriesChart.dispatchAction({ + type: 'highlight', + seriesId: dataItem.id + }); + } + } + } + + public destroy(): void { + if (this.shapeResize$) { + this.shapeResize$.disconnect(); + } + if (this.timeSeriesChart) { + this.timeSeriesChart.dispose(); + } + this.yMinSubject.complete(); + this.yMaxSubject.complete(); + this.messageChannel.close(); + } + + public resize(): void { + this.onResize(); + } + + public setDarkMode(darkMode: boolean): void { + if (this.darkMode !== darkMode) { + this.darkMode = darkMode; + if (this.timeSeriesChart) { + this.timeSeriesChartOptions = updateDarkMode(this.timeSeriesChartOptions, this.settings, this.dataItems, + this.thresholdItems, darkMode); + this.timeSeriesChart.setOption(this.timeSeriesChartOptions); + } + } + } + + public isDarkMode(): boolean { + return this.darkMode; + } + + private setupData(): void { + if (this.ctx.datasources.length) { + for (const datasource of this.ctx.datasources) { + const dataKeys = datasource.dataKeys; + for (const dataKey of dataKeys) { + const keySettings = mergeDeep({} as TimeSeriesChartKeySettings, + timeSeriesChartKeyDefaultSettings, dataKey.settings); + if (keySettings.showPointLabel && keySettings.pointLabelPosition === PointLabelPosition.top) { + this.topPointLabels = true; + } + dataKey.settings = keySettings; + const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === dataKey) : null; + const namedData = datasourceData?.data ? toNamedData(datasourceData.data) : []; + const units = dataKey.units && dataKey.units.length ? dataKey.units : this.ctx.units; + const decimals = isDefinedAndNotNull(dataKey.decimals) ? dataKey.decimals : + (isDefinedAndNotNull(this.ctx.decimals) ? this.ctx.decimals : 2); + this.dataItems.push({ + id: this.nextComponentId(), + units, + decimals, + yAxisIndex: this.getYAxis(units, decimals), + dataKey, + data: namedData, + enabled: !keySettings.dataHiddenByDefault + }); + } + } + } + } + + private setupThresholds(): void { + const thresholdDatasources: Datasource[] = []; + for (const threshold of this.settings.thresholds) { + let latestDataKey: DataKey = null; + let entityDataKey: DataKey = null; + let value = null; + if (threshold.type === TimeSeriesChartThresholdType.latestKey) { + if (this.ctx.datasources.length) { + for (const datasource of this.ctx.datasources) { + latestDataKey = datasource.latestDataKeys?.find(d => + (d.type === DataKeyType.function && d.label === threshold.latestKeyName) || + (d.type !== DataKeyType.function && d.name === threshold.latestKeyName)); + if (latestDataKey) { + break; + } + } + } + if (!latestDataKey) { + continue; + } + } else if (threshold.type === TimeSeriesChartThresholdType.entity) { + const entityAliasId = this.ctx.aliasController.getEntityAliasId(threshold.entityAlias); + if (!entityAliasId) { + continue; + } + let datasource = thresholdDatasources.find(d => d.entityAliasId === entityAliasId); + entityDataKey = { + type: threshold.entityKeyType, + name: threshold.entityKey, + label: threshold.entityKey, + settings: {} + }; + if (datasource) { + datasource.dataKeys.push(entityDataKey); + } + datasource = { + type: DatasourceType.entity, + name: threshold.entityAlias, + aliasName: threshold.entityAlias, + entityAliasId, + dataKeys: [ entityDataKey ] + }; + thresholdDatasources.push(datasource); + } else { // constant + value = threshold.value; + } + const units = threshold.units && threshold.units.length ? threshold.units : this.ctx.units; + const decimals = isDefinedAndNotNull(threshold.decimals) ? threshold.decimals : + (isDefinedAndNotNull(this.ctx.decimals) ? this.ctx.decimals : 2); + const thresholdItem: TimeSeriesChartThresholdItem = { + id: this.nextComponentId(), + units, + decimals, + yAxisIndex: this.getYAxis(units, decimals), + value, + latestDataKey, + settings: threshold + }; + if (entityDataKey) { + entityDataKey.settings.thresholdItemId = thresholdItem.id; + } + this.thresholdItems.push(thresholdItem); + } + this.subscribeForEntityThresholds(thresholdDatasources); + } + + private nextComponentId(): string { + return (this.componentIndexCounter++) + ''; + } + + private getYAxis(units: string, decimals: number): number { + let yAxisIndex = this.yAxisList.findIndex(axis => axis.units === units); + if (yAxisIndex === -1) { + const yAxisId = this.yAxisList.length + ''; + const yAxis = createTimeSeriesYAxis(yAxisId, units, decimals, this.settings.yAxis, this.darkMode); + this.yAxisList.push(yAxis); + yAxisIndex = this.yAxisList.length - 1; + } + return yAxisIndex; + } + + private subscribeForEntityThresholds(datasources: Datasource[]) { + if (datasources.length) { + const thresholdsSourcesSubscriptionOptions: WidgetSubscriptionOptions = { + datasources, + useDashboardTimewindow: false, + type: widgetType.latest, + callbacks: { + onDataUpdated: (subscription) => { + let update = false; + if (subscription.data) { + for (const item of this.thresholdItems) { + if (item.settings.type === TimeSeriesChartThresholdType.entity) { + const data = subscription.data.find(d => d.dataKey.settings?.thresholdItemId === item.id); + if (data.data[0]) { + item.value = parseThresholdData(data.data[0][1]); + update = true; + } + } + } + } + if (this.timeSeriesChart && update) { + this.updateSeriesData(); + } + } + } + }; + this.ctx.subscriptionApi.createSubscription(thresholdsSourcesSubscriptionOptions, true).subscribe(); + } + } + + private drawChart() { + echartsModule.init(); + this.timeSeriesChart = echarts.init(this.chartElement, null, { + renderer: 'canvas' + }); + const noAggregation = this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE; + this.timeSeriesChartOptions = { + darkMode: this.darkMode, + backgroundColor: 'transparent', + tooltip: [{ + trigger: this.settings.tooltipTrigger === EChartsTooltipTrigger.axis ? 'axis' : 'item', + confine: true, + appendToBody: true, + axisPointer: { + type: noAggregation ? 'line' : 'shadow' + }, + formatter: (params: CallbackDataParams[]) => + this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat, + this.settings, params, 0, '', -1, this.dataItems) : undefined, + padding: [8, 12], + backgroundColor: this.settings.tooltipBackgroundColor, + borderWidth: 0, + extraCssText: `line-height: 1; backdrop-filter: blur(${this.settings.tooltipBackgroundBlur}px);` + }], + grid: [{ + top: this.minTopOffset(), + left: this.settings.dataZoom ? 5 : 0, + right: this.settings.dataZoom ? 5 : 0, + bottom: this.minBottomOffset() + }], + xAxis: [ + createTimeSeriesXAxisOption(this.settings.xAxis, this.ctx.defaultSubscription.timeWindow.minTime, + this.ctx.defaultSubscription.timeWindow.maxTime, this.darkMode) + ], + yAxis: this.yAxisList.map(axis => axis.option), + dataZoom: [ + { + type: 'inside', + disabled: !this.settings.dataZoom, + realtime: true + }, + { + type: 'slider', + show: this.settings.dataZoom, + showDetail: false, + realtime: true, + bottom: 10 + } + ] + }; + + this.timeSeriesChartOptions.xAxis[0].tbTimeWindow = this.ctx.defaultSubscription.timeWindow; + + this.timeSeriesChartOptions.series = this.updateSeries(); + if (this.updateYAxisScale(this.yAxisList)) { + this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); + } + + this.timeSeriesChart.setOption(this.timeSeriesChartOptions); + this.updateAxes(); + + if (this.settings.dataZoom) { + this.timeSeriesChart.on('datazoom', () => { + this.updateAxes(); + }); + } + } + + private updateSeriesData(updateScale = false): void { + this.timeSeriesChartOptions.series = this.updateSeries(); + if (updateScale && this.updateYAxisScale(this.yAxisList)) { + this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); + } + this.timeSeriesChart.setOption(this.timeSeriesChartOptions); + this.updateAxes(); + } + + private updateSeries(): Array { + return generateChartData(this.dataItems, this.thresholdItems, + this.ctx.timeWindow.interval, this.settings.stack, this.darkMode); + } + + private updateAxes() { + const leftAxisList = this.yAxisList.filter(axis => axis.option.position === 'left'); + let res = this.updateYAxisOffset(leftAxisList); + let leftOffset = res.offset + (!res.offset && this.settings.dataZoom ? 5 : 0); + let changed = res.changed; + const rightAxisList = this.yAxisList.filter(axis => axis.option.position === 'right'); + res = this.updateYAxisOffset(rightAxisList); + let rightOffset = res.offset + (!res.offset && this.settings.dataZoom ? 5 : 0); + changed = changed || res.changed; + let bottomOffset = this.minBottomOffset(); + const minTopOffset = this.minTopOffset(); + let topOffset = minTopOffset; + if (this.timeSeriesChartOptions.xAxis[0].show) { + const xAxisHeight = calculateXAxisHeight(this.timeSeriesChart); + if (this.timeSeriesChartOptions.xAxis[0].position === AxisPosition.bottom) { + bottomOffset += xAxisHeight; + } else { + topOffset = Math.max(minTopOffset, xAxisHeight); + } + if (this.settings.xAxis.label) { + const nameHeight = measureXAxisNameHeight(this.timeSeriesChart, this.timeSeriesChartOptions.xAxis[0].name); + if (this.timeSeriesChartOptions.xAxis[0].position === AxisPosition.bottom) { + bottomOffset += nameHeight; + } else { + topOffset = Math.max(minTopOffset, xAxisHeight + nameHeight); + } + const nameGap = xAxisHeight; + if (this.timeSeriesChartOptions.xAxis[0].nameGap !== nameGap) { + this.timeSeriesChartOptions.xAxis[0].nameGap = nameGap; + changed = true; + } + } + } + + const thresholdsOffset = calculateThresholdsOffset(this.timeSeriesChart, this.thresholdItems, this.yAxisList); + leftOffset = Math.max(leftOffset, thresholdsOffset[0]); + rightOffset = Math.max(rightOffset, thresholdsOffset[1]); + + if (this.timeSeriesChartOptions.grid[0].left !== leftOffset || + this.timeSeriesChartOptions.grid[0].right !== rightOffset || + this.timeSeriesChartOptions.grid[0].bottom !== bottomOffset || + this.timeSeriesChartOptions.grid[0].top !== topOffset) { + this.timeSeriesChartOptions.grid[0].left = leftOffset; + this.timeSeriesChartOptions.grid[0].right = rightOffset; + this.timeSeriesChartOptions.grid[0].bottom = bottomOffset; + this.timeSeriesChartOptions.grid[0].top = topOffset; + changed = true; + } + if (changed) { + this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); + this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis', 'xAxis', 'grid'], lazyUpdate: true}); + } + changed = this.calculateYAxisInterval(this.yAxisList); + if (changed) { + this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); + this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis'], lazyUpdate: true}); + } + if (this.yAxisList.length) { + const extent = getAxisExtent(this.timeSeriesChart, this.yAxisList[0].id); + const min = extent[0]; + const max = extent[1]; + if (this.yMinSubject.value !== min) { + this.yMinSubject.next(min); + } + if (this.yMaxSubject.value !== max) { + this.yMaxSubject.next(max); + } + } + } + + private updateYAxisScale(axisList: TimeSeriesChartYAxis[]): boolean { + let changed = false; + for (const yAxis of axisList) { + const scaleYAxis = this.scaleYAxis(yAxis); + if (yAxis.option.scale !== scaleYAxis) { + yAxis.option.scale = scaleYAxis; + changed = true; + } + } + return changed; + } + + private updateYAxisOffset(axisList: TimeSeriesChartYAxis[]): {offset: number; changed: boolean} { + const result = {offset: 0, changed: false}; + let width = 0; + for (const yAxis of axisList) { + const newWidth = calculateYAxisWidth(this.timeSeriesChart, yAxis.id); + if (width && newWidth) { + result.offset += 5; + } + width = newWidth; + const showLine = !!width && this.settings.yAxis.showLine; + if (yAxis.option.axisLine.show !== showLine) { + yAxis.option.axisLine.show = showLine; + result.changed = true; + } + if (yAxis.option.offset !== result.offset) { + yAxis.option.offset = result.offset; + result.changed = true; + } + if (this.settings.yAxis.label) { + if (!width) { + if (yAxis.option.name) { + yAxis.option.name = null; + result.changed = true; + } + } else { + if (!yAxis.option.name) { + yAxis.option.name = this.settings.yAxis.label; + result.changed = true; + } + const nameGap = width; + if (yAxis.option.nameGap !== nameGap) { + yAxis.option.nameGap = nameGap; + result.changed = true; + } + const nameWidth = measureYAxisNameWidth(this.timeSeriesChart, yAxis.id, this.settings.yAxis.label); + result.offset += nameWidth; + } + } + result.offset += width; + } + return result; + } + + private scaleYAxis(yAxis: TimeSeriesChartYAxis): boolean { + const yAxisIndex = this.yAxisList.indexOf(yAxis); + const axisBarDataItems = this.dataItems.filter(d => d.yAxisIndex === yAxisIndex && d.enabled && + d.data.length && d.dataKey.settings.type === TimeSeriesChartSeriesType.bar); + return !axisBarDataItems.length; + } + + private calculateYAxisInterval(axisList: TimeSeriesChartYAxis[]): boolean { + let changed = false; + for (const yAxis of axisList) { + if (yAxis.intervalCalculator) { + const axis = getYAxis(this.timeSeriesChart, yAxis.id); + if (axis) { + try { + const interval = yAxis.intervalCalculator(axis); + if ((yAxis.option as any).interval !== interval) { + (yAxis.option as any).interval = interval; + changed = true; + } + } catch (_e) {} + } + } + } + return changed; + } + + private minTopOffset(): number { + return (this.topPointLabels) ? 20 : + ((this.settings.yAxis.show && this.settings.yAxis.showTickLabels) ? 10 : 5); + } + + private minBottomOffset(): number { + return this.settings.dataZoom ? 45 : 5; + } + + private onResize() { + const shapeWidth = this.chartElement.offsetWidth; + const shapeHeight = this.chartElement.offsetHeight; + if (shapeWidth && shapeHeight) { + if (!this.timeSeriesChart) { + this.drawChart(); + } else { + const width = this.timeSeriesChart.getWidth(); + const height = this.timeSeriesChart.getHeight(); + if (width !== shapeWidth || height !== shapeHeight) { + this.timeSeriesChart.resize(); + } + } + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 03bd97dd41..0ae45d1eff 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -76,6 +76,7 @@ import { CommandButtonWidgetComponent } from '@home/components/widget/lib/button import { PowerButtonWidgetComponent } from '@home/components/widget/lib/rpc/power-button-widget.component'; import { SliderWidgetComponent } from '@home/components/widget/lib/rpc/slider-widget.component'; import { ToggleButtonWidgetComponent } from '@home/components/widget/lib/button/toggle-button-widget.component'; +import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/chart/time-series-chart-widget.component'; @NgModule({ declarations: @@ -124,7 +125,8 @@ import { ToggleButtonWidgetComponent } from '@home/components/widget/lib/button/ CommandButtonWidgetComponent, PowerButtonWidgetComponent, SliderWidgetComponent, - ToggleButtonWidgetComponent + ToggleButtonWidgetComponent, + TimeSeriesChartWidgetComponent ], imports: [ CommonModule, @@ -177,7 +179,8 @@ import { ToggleButtonWidgetComponent } from '@home/components/widget/lib/button/ CommandButtonWidgetComponent, PowerButtonWidgetComponent, SliderWidgetComponent, - ToggleButtonWidgetComponent + ToggleButtonWidgetComponent, + TimeSeriesChartWidgetComponent ], providers: [ {provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule } diff --git a/ui-ngx/src/app/shared/models/color.models.ts b/ui-ngx/src/app/shared/models/color.models.ts new file mode 100644 index 0000000000..7c1d08baf8 --- /dev/null +++ b/ui-ngx/src/app/shared/models/color.models.ts @@ -0,0 +1,24 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +export interface TbColor { + light: string; + dark: string; +} + +export interface TbColorScheme { + [key: string]: TbColor; +} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 288db8c9d2..96d8ddebf7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3169,6 +3169,11 @@ "avg": "avg", "total": "total", "latest": "latest", + "Min": "Min", + "Max": "Max", + "Avg": "Avg", + "Total": "Total", + "Latest": "Latest", "comparison-time-ago": { "previousInterval": "(previous interval)", "customInterval": "(custom interval)", From 0ebd22be2d80ce5da9f62190a55062752891c812 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 1 Mar 2024 19:58:00 +0200 Subject: [PATCH 150/209] UI: Implement Time series chart widget config. --- .../widget_types/time_series_chart.json | 10 +- .../add-widget-dialog.component.ts | 3 + .../dashboard-page/edit-widget.component.ts | 3 + .../basic/basic-widget-config.module.ts | 12 +- .../aggregated-data-keys-panel.component.ts | 3 +- ...e-series-chart-basic-config.component.html | 236 +++++++++++++ ...ime-series-chart-basic-config.component.ts | 314 +++++++++++++++++ .../basic/common/data-key-row.component.html | 15 + .../basic/common/data-key-row.component.scss | 18 +- .../basic/common/data-key-row.component.ts | 44 ++- .../common/data-keys-panel.component.html | 2 + .../common/data-keys-panel.component.scss | 4 + .../basic/common/data-keys-panel.component.ts | 13 +- .../config/data-key-config.component.html | 1 + .../config/data-key-config.component.ts | 27 +- .../config/data-keys.component.models.ts | 5 +- .../widget/config/data-keys.component.ts | 15 +- .../widget/config/datasource.component.html | 3 + .../widget/config/datasource.component.ts | 6 +- .../widget/config/datasources.component.ts | 9 +- .../config/widget-config.component.models.ts | 12 +- .../aggregated-value-card-widget.component.ts | 7 +- .../value-chart-card-widget.component.ts | 7 +- .../widget/lib/chart/echarts-widget.models.ts | 9 +- .../lib/chart/time-series-chart.models.ts | 316 ++++++++++-------- .../widget/lib/chart/time-series-chart.ts | 24 +- .../components/widget/lib/maps/map-widget2.ts | 2 +- ...e-series-chart-key-settings.component.html | 36 ++ ...ime-series-chart-key-settings.component.ts | 82 +++++ .../common/legend-config.component.html | 7 +- .../common/legend-config.component.ts | 28 +- .../lib/settings/widget-settings.module.ts | 12 +- .../widget/widget-component.service.ts | 9 + .../widget/widget-config.component.ts | 9 +- .../home/models/widget-component.models.ts | 2 + .../shared/models/widget-settings.models.ts | 2 +- ui-ngx/src/app/shared/models/widget.models.ts | 2 + .../assets/locale/locale.constant-en_US.json | 23 ++ ui-ngx/src/form.scss | 21 ++ 39 files changed, 1163 insertions(+), 190 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts diff --git a/application/src/main/data/json/system/widget_types/time_series_chart.json b/application/src/main/data/json/system/widget_types/time_series_chart.json index b8cd24a151..7d2fff170f 100644 --- a/application/src/main/data/json/system/widget_types/time_series_chart.json +++ b/application/src/main/data/json/system/widget_types/time_series_chart.json @@ -11,16 +11,16 @@ "resources": [], "templateHtml": "\n", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings(),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", "latestDataKeySettingsSchema": "{}", "settingsDirective": "", - "dataKeySettingsDirective": "", + "dataKeySettingsDirective": "tb-time-series-chart-key-settings", "latestDataKeySettingsDirective": "", - "hasBasicMode": false, - "basicModeDirective": "", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false}},\"title\":\"Time series chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + "hasBasicMode": true, + "basicModeDirective": "tb-time-series-chart-basic-config", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":\"dd MMM yyyy HH:mm:ss\",\"lastUpdateAgo\":false,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Time series chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" }, "tags": [ "chart", diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts index 0d02fc956f..2089c03d5d 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts @@ -29,6 +29,7 @@ import { WidgetConfigComponentData, WidgetInfo } from '@home/models/widget-compo import { isDefined, isDefinedAndNotNull, isString } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; +import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; export interface AddWidgetDialogData { dashboard: Dashboard; @@ -97,6 +98,7 @@ export class AddWidgetDialogComponent extends DialogComponent + + + + + + + +
+
TODO: Thresholds
+
+
+
widget-config.appearance
+
+ + {{ 'widget-config.title' | translate }} + +
+ + + + + + + +
+
+
+ + {{ 'widget-config.card-icon' | translate }} + +
+ + + + + + + + +
+
+
+
+
widgets.time-series-chart.chart
+
+ + {{ 'widgets.time-series-chart.data-zoom' | translate }} + +
+
+ +
+ {{ 'widgets.time-series-chart.stack-mode' | translate }} +
+
+
+
+
+
widgets.time-series-chart.axes
+
+ TODO: Y Axis +
+
+ TODO: X Axis +
+
+
+ + + + + {{ 'widget-config.legend' | translate }} + + + + +
+
{{ 'legend.label' | translate }}
+
+ + + + +
+
+ + +
+
+
+
+ + + + + {{ 'widget-config.tooltip' | translate }} + + + + +
+
{{ 'tooltip.trigger' | translate }}
+ + {{ 'tooltip.trigger-point' | translate }} + {{ 'tooltip.trigger-axis' | translate }} + +
+
+
{{ 'tooltip.value' | translate }}
+
+ + + + +
+
+
+ + {{ 'tooltip.date' | translate }} + +
+ + + + + +
+
+
+ +
+ {{ 'tooltip.show-date-time-interval' | translate }} +
+
+
+
+
{{ 'tooltip.background-color' | translate }}
+ + +
+
+
{{ 'tooltip.background-blur' | translate }}
+ + +
px
+
+
+
+
+
+
+
widget-config.card-appearance
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
widget-config.show-card-buttons
+ + {{ 'fullscreen.fullscreen' | translate }} + +
+
+
{{ 'widget-config.card-border-radius' | translate }}
+ + + +
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts new file mode 100644 index 0000000000..4387e5d52b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts @@ -0,0 +1,314 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Injector } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { + DataKey, + Datasource, + legendPositions, + legendPositionTranslationMap, + WidgetConfig, +} from '@shared/models/widget.models'; +import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { + getTimewindowConfig, + setTimewindowConfig +} from '@home/components/widget/config/timewindow-config-panel.component'; +import { formatValue, isUndefined, mergeDeep } from '@core/utils'; +import { + cssSizeToStrSize, + DateFormatProcessor, + DateFormatSettings, + resolveCssSize +} from '@shared/models/widget-settings.models'; +import { + timeSeriesChartWidgetDefaultSettings, + TimeSeriesChartWidgetSettings +} from '@home/components/widget/lib/chart/time-series-chart-widget.models'; +import { ValueType } from '@shared/models/constants'; +import { EChartsTooltipTrigger } from '@home/components/widget/lib/chart/echarts-widget.models'; + +@Component({ + selector: 'tb-time-series-chart-basic-config', + templateUrl: './time-series-chart-basic-config.component.html', + styleUrls: ['../basic-config.scss'] +}) +export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigComponent { + + public get datasource(): Datasource { + const datasources: Datasource[] = this.timeSeriesChartWidgetConfigForm.get('datasources').value; + if (datasources && datasources.length) { + return datasources[0]; + } else { + return null; + } + } + + EChartsTooltipTrigger = EChartsTooltipTrigger; + + legendPositions = legendPositions; + + legendPositionTranslationMap = legendPositionTranslationMap; + + timeSeriesChartWidgetConfigForm: UntypedFormGroup; + + tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); + + tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this); + + constructor(protected store: Store, + protected widgetConfigComponent: WidgetConfigComponent, + private $injector: Injector, + private fb: UntypedFormBuilder) { + super(store, widgetConfigComponent); + } + + protected configForm(): UntypedFormGroup { + return this.timeSeriesChartWidgetConfigForm; + } + + protected defaultDataKeys(configData: WidgetConfigComponentData): DataKey[] { + return [{ name: 'temperature', label: 'Temperature', type: DataKeyType.timeseries, units: '°C', decimals: 0 }]; + } + + protected onConfigSet(configData: WidgetConfigComponentData) { + const settings: TimeSeriesChartWidgetSettings = mergeDeep({} as TimeSeriesChartWidgetSettings, + timeSeriesChartWidgetDefaultSettings, configData.config.settings as TimeSeriesChartWidgetSettings); + const iconSize = resolveCssSize(configData.config.iconSize); + this.timeSeriesChartWidgetConfigForm = this.fb.group({ + timewindowConfig: [getTimewindowConfig(configData.config), []], + datasources: [configData.config.datasources, []], + series: [this.getSeries(configData.config.datasources), []], + thresholds: [settings.thresholds, []], + + showTitle: [configData.config.showTitle, []], + title: [configData.config.title, []], + titleFont: [configData.config.titleFont, []], + titleColor: [configData.config.titleColor, []], + + showIcon: [configData.config.showTitleIcon, []], + iconSize: [iconSize[0], [Validators.min(0)]], + iconSizeUnit: [iconSize[1], []], + icon: [configData.config.titleIcon, []], + iconColor: [configData.config.iconColor, []], + + dataZoom: [settings.dataZoom, []], + stack: [settings.stack, []], + + yAxis: [settings.yAxis, []], + xAxis: [settings.xAxis, []], + + showLegend: [settings.showLegend, []], + legendLabelFont: [settings.legendLabelFont, []], + legendLabelColor: [settings.legendLabelColor, []], + legendConfig: [settings.legendConfig, []], + + showTooltip: [settings.showTooltip, []], + tooltipTrigger: [settings.tooltipTrigger, []], + tooltipValueFont: [settings.tooltipValueFont, []], + tooltipValueColor: [settings.tooltipValueColor, []], + tooltipShowDate: [settings.tooltipShowDate, []], + tooltipDateFormat: [settings.tooltipDateFormat, []], + tooltipDateFont: [settings.tooltipDateFont, []], + tooltipDateColor: [settings.tooltipDateColor, []], + tooltipDateInterval: [settings.tooltipDateInterval, []], + + tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], + tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], + + background: [settings.background, []], + + cardButtons: [this.getCardButtons(configData.config), []], + borderRadius: [configData.config.borderRadius, []], + + actions: [configData.config.actions || {}, []] + }); + } + + protected prepareOutputConfig(config: any): WidgetConfigComponentData { + setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig); + this.widgetConfig.config.datasources = config.datasources; + this.setSeries(config.series, this.widgetConfig.config.datasources); + + this.widgetConfig.config.showTitle = config.showTitle; + this.widgetConfig.config.title = config.title; + this.widgetConfig.config.titleFont = config.titleFont; + this.widgetConfig.config.titleColor = config.titleColor; + + this.widgetConfig.config.showTitleIcon = config.showIcon; + this.widgetConfig.config.iconSize = cssSizeToStrSize(config.iconSize, config.iconSizeUnit); + this.widgetConfig.config.titleIcon = config.icon; + this.widgetConfig.config.iconColor = config.iconColor; + + this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; + + this.widgetConfig.config.settings.thresholds = config.thresholds; + + this.widgetConfig.config.settings.dataZoom = config.dataZoom; + this.widgetConfig.config.settings.stack = config.stack; + + this.widgetConfig.config.settings.yAxis = config.yAxis; + this.widgetConfig.config.settings.xAxis = config.xAxis; + + this.widgetConfig.config.settings.showLegend = config.showLegend; + this.widgetConfig.config.settings.legendLabelFont = config.legendLabelFont; + this.widgetConfig.config.settings.legendLabelColor = config.legendLabelColor; + this.widgetConfig.config.settings.legendConfig = config.legendConfig; + + this.widgetConfig.config.settings.showTooltip = config.showTooltip; + this.widgetConfig.config.settings.tooltipTrigger = config.tooltipTrigger; + this.widgetConfig.config.settings.tooltipValueFont = config.tooltipValueFont; + this.widgetConfig.config.settings.tooltipValueColor = config.tooltipValueColor; + this.widgetConfig.config.settings.tooltipShowDate = config.tooltipShowDate; + this.widgetConfig.config.settings.tooltipDateFormat = config.tooltipDateFormat; + this.widgetConfig.config.settings.tooltipDateFont = config.tooltipDateFont; + this.widgetConfig.config.settings.tooltipDateColor = config.tooltipDateColor; + this.widgetConfig.config.settings.tooltipDateInterval = config.tooltipDateInterval; + this.widgetConfig.config.settings.tooltipBackgroundColor = config.tooltipBackgroundColor; + this.widgetConfig.config.settings.tooltipBackgroundBlur = config.tooltipBackgroundBlur; + + this.widgetConfig.config.settings.background = config.background; + + this.setCardButtons(config.cardButtons, this.widgetConfig.config); + this.widgetConfig.config.borderRadius = config.borderRadius; + + this.widgetConfig.config.actions = config.actions; + return this.widgetConfig; + } + + protected validatorTriggers(): string[] { + return ['showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate']; + } + + protected updateValidators(emitEvent: boolean, trigger?: string) { + const showTitle: boolean = this.timeSeriesChartWidgetConfigForm.get('showTitle').value; + const showIcon: boolean = this.timeSeriesChartWidgetConfigForm.get('showIcon').value; + const showLegend: boolean = this.timeSeriesChartWidgetConfigForm.get('showLegend').value; + const showTooltip: boolean = this.timeSeriesChartWidgetConfigForm.get('showTooltip').value; + const tooltipShowDate: boolean = this.timeSeriesChartWidgetConfigForm.get('tooltipShowDate').value; + + if (showTitle) { + this.timeSeriesChartWidgetConfigForm.get('title').enable(); + this.timeSeriesChartWidgetConfigForm.get('titleFont').enable(); + this.timeSeriesChartWidgetConfigForm.get('titleColor').enable(); + this.timeSeriesChartWidgetConfigForm.get('showIcon').enable({emitEvent: false}); + if (showIcon) { + this.timeSeriesChartWidgetConfigForm.get('iconSize').enable(); + this.timeSeriesChartWidgetConfigForm.get('iconSizeUnit').enable(); + this.timeSeriesChartWidgetConfigForm.get('icon').enable(); + this.timeSeriesChartWidgetConfigForm.get('iconColor').enable(); + } else { + this.timeSeriesChartWidgetConfigForm.get('iconSize').disable(); + this.timeSeriesChartWidgetConfigForm.get('iconSizeUnit').disable(); + this.timeSeriesChartWidgetConfigForm.get('icon').disable(); + this.timeSeriesChartWidgetConfigForm.get('iconColor').disable(); + } + } else { + this.timeSeriesChartWidgetConfigForm.get('title').disable(); + this.timeSeriesChartWidgetConfigForm.get('titleFont').disable(); + this.timeSeriesChartWidgetConfigForm.get('titleColor').disable(); + this.timeSeriesChartWidgetConfigForm.get('showIcon').disable({emitEvent: false}); + this.timeSeriesChartWidgetConfigForm.get('iconSize').disable(); + this.timeSeriesChartWidgetConfigForm.get('iconSizeUnit').disable(); + this.timeSeriesChartWidgetConfigForm.get('icon').disable(); + this.timeSeriesChartWidgetConfigForm.get('iconColor').disable(); + } + + if (showLegend) { + this.timeSeriesChartWidgetConfigForm.get('legendLabelFont').enable(); + this.timeSeriesChartWidgetConfigForm.get('legendLabelColor').enable(); + this.timeSeriesChartWidgetConfigForm.get('legendConfig').enable(); + } else { + this.timeSeriesChartWidgetConfigForm.get('legendLabelFont').disable(); + this.timeSeriesChartWidgetConfigForm.get('legendLabelColor').disable(); + this.timeSeriesChartWidgetConfigForm.get('legendConfig').disable(); + } + + if (showTooltip) { + this.timeSeriesChartWidgetConfigForm.get('tooltipTrigger').enable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipValueFont').enable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipValueColor').enable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipShowDate').enable({emitEvent: false}); + this.timeSeriesChartWidgetConfigForm.get('tooltipBackgroundColor').enable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipBackgroundBlur').enable(); + if (tooltipShowDate) { + this.timeSeriesChartWidgetConfigForm.get('tooltipDateFormat').enable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipDateFont').enable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipDateColor').enable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipDateInterval').enable(); + } else { + this.timeSeriesChartWidgetConfigForm.get('tooltipDateFormat').disable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipDateFont').disable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipDateColor').disable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipDateInterval').disable(); + } + } else { + this.timeSeriesChartWidgetConfigForm.get('tooltipValueFont').disable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipValueColor').disable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipShowDate').disable({emitEvent: false}); + this.timeSeriesChartWidgetConfigForm.get('tooltipDateFormat').disable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipDateFont').disable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipDateColor').disable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipDateInterval').disable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipBackgroundColor').disable(); + this.timeSeriesChartWidgetConfigForm.get('tooltipBackgroundBlur').disable(); + } + } + + private getSeries(datasources?: Datasource[]): DataKey[] { + if (datasources && datasources.length) { + return datasources[0].dataKeys || []; + } + return []; + } + + private setSeries(series: DataKey[], datasources?: Datasource[]) { + if (datasources && datasources.length) { + datasources[0].dataKeys = series; + } + } + + private getCardButtons(config: WidgetConfig): string[] { + const buttons: string[] = []; + if (isUndefined(config.enableFullscreen) || config.enableFullscreen) { + buttons.push('fullscreen'); + } + return buttons; + } + + private setCardButtons(buttons: string[], config: WidgetConfig) { + config.enableFullscreen = buttons.includes('fullscreen'); + } + + private _tooltipValuePreviewFn(): string { + return formatValue(22, 0, '°C', false); + } + + private _tooltipDatePreviewFn(): string { + const dateFormat: DateFormatSettings = this.timeSeriesChartWidgetConfigForm.get('tooltipDateFormat').value; + const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat); + processor.update(Date.now()); + return processor.formatted; + } + + protected readonly ValueType = ValueType; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html index 8b233f0b09..1c4767b963 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html @@ -158,6 +158,21 @@
widget-config.decimals-suffix
+
+ + + + + {{ timeSeriesChartSeriesTypeIcons.get(keyRowFormGroup.get('timeSeriesType').value) }} + + + + {{ timeSeriesChartSeriesTypeIcons.get(type) }} + {{ timeSeriesChartSeriesTypeTranslations.get(type) | translate }} + + + +
1; } @@ -256,7 +264,8 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC } addKey() { - const dataKey = this.callbacks.generateDataKey('', null, this.datakeySettingsSchema); + const dataKey = this.callbacks.generateDataKey('', null, this.datakeySettingsSchema, + false, this.dataKeySettingsFunction); dataKey.label = ''; dataKey.decimals = 0; if (this.hasAdditionalLatestDataKeys) { diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html index fe6c347649..37c85e173d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html @@ -197,6 +197,7 @@ [dashboard]="dashboard" [aliasController]="aliasController" [widget]="widget" + [widgetConfig]="widgetConfig" formControlName="settings">
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.ts index b120a60809..9ebe8821cf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.ts @@ -40,7 +40,7 @@ import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; import { MatDialog } from '@angular/material/dialog'; import { EntityService } from '@core/http/entity.service'; -import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { Observable, of } from 'rxjs'; import { map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators'; @@ -51,8 +51,10 @@ import { WidgetService } from '@core/http/widget.service'; import { Dashboard } from '@shared/models/dashboard.models'; import { IAliasController } from '@core/api/widget-api.models'; import { aggregationTranslations, AggregationType, ComparisonDuration } from '@shared/models/time/time.models'; -import { genNextLabel } from '@core/utils'; +import { genNextLabel, isDefinedAndNotNull } from '@core/utils'; import { coerceBoolean } from '@shared/decorators/coercion'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { WidgetComponentService } from '@home/components/widget/widget-component.service'; @Component({ selector: 'tb-data-key-config', @@ -155,6 +157,8 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con modelValue: DataKey; + widgetConfig: WidgetConfigComponentData; + private propagateChange = null; public dataKeyFormGroup: UntypedFormGroup; @@ -180,12 +184,31 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con private dialog: MatDialog, private translate: TranslateService, private widgetService: WidgetService, + private widgetComponentService: WidgetComponentService, private fb: UntypedFormBuilder) { super(store); this.functionScopeVariables = this.widgetService.getWidgetScopeVariables(); } ngOnInit(): void { + + const widgetInfo = this.widgetComponentService.getInstantWidgetInfo(this.widget); + const typeParameters = widgetInfo.typeParameters; + const dataKeySettingsFunction: DataKeySettingsFunction = typeParameters?.dataKeySettingsFunction; + + this.widgetConfig = { + widgetName: widgetInfo.widgetName, + config: this.widget.config, + widgetType: this.widget.type, + typeParameters, + dataKeySettingsFunction, + settingsDirective: widgetInfo.settingsDirective, + dataKeySettingsDirective: widgetInfo.dataKeySettingsDirective, + latestDataKeySettingsDirective: widgetInfo.latestDataKeySettingsDirective, + hasBasicMode: isDefinedAndNotNull(widgetInfo.hasBasicMode) ? widgetInfo.hasBasicMode : false, + basicModeDirective: widgetInfo.basicModeDirective + } as WidgetConfigComponentData; + this.alarmKeys = []; for (const name of Object.keys(alarmFields)) { this.alarmKeys.push({ diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.models.ts b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.models.ts index 893631f57b..b0e80973f0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.models.ts @@ -18,8 +18,11 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKey, JsonSettingsSchema } from '@shared/models/widget.models'; import { Observable } from 'rxjs'; +export type DataKeySettingsFunction = (key: DataKey, isLatestDataKey: boolean) => any; + export interface DataKeysCallbacks { - generateDataKey: (chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema) => DataKey; + generateDataKey: (chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema, + isLatestDataKey: boolean, dataKeySettingsFunction: DataKeySettingsFunction) => DataKey; fetchEntityKeys: (entityAliasId: string, types: Array) => Observable>; fetchEntityKeysForDevice: (deviceId: string, types: Array) => Observable>; } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts index 800c831b67..8c2df629de 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts @@ -52,7 +52,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKey, DatasourceType, JsonSettingsSchema, Widget, widgetType } from '@shared/models/widget.models'; import { IAliasController } from '@core/api/widget-api.models'; -import { DataKeysCallbacks } from './data-keys.component.models'; +import { DataKeysCallbacks, DataKeySettingsFunction } from './data-keys.component.models'; import { alarmFields } from '@shared/models/alarm.models'; import { UtilsService } from '@core/services/utils.service'; import { ErrorStateMatcher } from '@angular/material/core'; @@ -139,6 +139,10 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange @Input() optDataKeys: boolean; + @Input() + @coerceBoolean() + latestDataKeys = false; + @Input() @coerceBoolean() simpleDataKeysLabel = false; @@ -149,6 +153,9 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange @Input() datakeySettingsSchema: JsonSettingsSchema; + @Input() + datakeySettingsFunction: DataKeySettingsFunction; + @Input() dataKeySettingsDirective: string; @@ -361,7 +368,8 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange if (this.widgetType === widgetType.alarm) { this.keys = this.utils.getDefaultAlarmDataKeys(); } else if (this.isCountDatasource) { - this.keys = [this.callbacks.generateDataKey('count', DataKeyType.count, this.datakeySettingsSchema)]; + this.keys = [this.callbacks.generateDataKey('count', DataKeyType.count, this.datakeySettingsSchema, + this.latestDataKeys, this.datakeySettingsFunction)]; } else { this.keys = []; } @@ -447,7 +455,8 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange } private addFromChipValue(chip: DataKey) { - const key = this.callbacks.generateDataKey(chip.name, chip.type, this.datakeySettingsSchema); + const key = this.callbacks.generateDataKey(chip.name, chip.type, this.datakeySettingsSchema, this.latestDataKeys, + this.datakeySettingsFunction); this.addKey(key); } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html b/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html index b72f9f0e5d..13a52a295b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html @@ -72,6 +72,7 @@ [aliasController]="aliasController" [datakeySettingsSchema]="dataKeySettingsSchema" [dataKeySettingsDirective]="dataKeySettingsDirective" + [datakeySettingsFunction]="dataKeySettingsFunction" [dashboard]="dashboard" [widget]="widget" [callbacks]="dataKeysCallbacks" @@ -82,10 +83,12 @@ { - const dataKey = this.constructDataKey(configData, key); + const dataKey = this.constructDataKey(configData, key, false); dataKeys.push(dataKey); }); } if (latestKeys && latestKeys.length) { latestDataKeys.length = 0; latestKeys.forEach(key => { - const dataKey = this.constructDataKey(configData, key); + const dataKey = this.constructDataKey(configData, key, true); latestDataKeys.push(dataKey); }); } } - protected constructDataKey(configData: WidgetConfigComponentData, key: DataKey): DataKey { + protected constructDataKey(configData: WidgetConfigComponentData, key: DataKey, isLatestKey: boolean): DataKey { const dataKey = - this.widgetConfigComponent.widgetConfigCallbacks.generateDataKey(key.name, key.type, configData.dataKeySettingsSchema); + this.widgetConfigComponent.widgetConfigCallbacks.generateDataKey(key.name, key.type, + configData.dataKeySettingsSchema, isLatestKey, configData.dataKeySettingsFunction); if (key.label) { dataKey.label = key.label; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts index ae7c3c5eee..3edce958f0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts @@ -148,13 +148,14 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit if (this.ctx.defaultSubscription.firstDatasource?.dataKeys?.length) { this.lineChartDataKey = this.ctx.defaultSubscription.firstDatasource?.dataKeys[0]; this.lineChartDataKey.settings = { - showPointLabel: false, type: TimeSeriesChartSeriesType.line, lineSettings: { - smooth: false, showLine: true, + step: false, + smooth: false, lineWidth: 2, - showPoints: false + showPoints: false, + showPointLabel: false } } as TimeSeriesChartKeySettings; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts index b59098de17..f5a305a247 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts @@ -148,13 +148,14 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD if (this.ctx.defaultSubscription.firstDatasource?.dataKeys?.length) { this.lineChartDataKey = this.ctx.defaultSubscription.firstDatasource?.dataKeys[0]; this.lineChartDataKey.settings = { - showPointLabel: false, type: TimeSeriesChartSeriesType.line, lineSettings: { - smooth: true, showLine: true, + step: false, + smooth: true, lineWidth: 2, - showPoints: false + showPoints: false, + showPointLabel: false } } as TimeSeriesChartKeySettings; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts index 7564bd9cab..06e8e98fd0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts @@ -44,7 +44,7 @@ import { } from 'echarts/charts'; import { LabelLayout } from 'echarts/features'; import { CanvasRenderer, SVGRenderer } from 'echarts/renderers'; -import { DataEntry, DataKey, DataSet } from '@shared/models/widget.models'; +import { DataEntry, DataKey, DataSet, LegendDirection } from '@shared/models/widget.models'; import { calculateAggIntervalWithWidgetTimeWindow, IntervalMath, @@ -304,6 +304,13 @@ export enum EChartsTooltipTrigger { axis = 'axis' } +export const tooltipTriggerTranslationMap = new Map( + [ + [ EChartsTooltipTrigger.point, 'tooltip.trigger-point' ], + [ EChartsTooltipTrigger.axis, 'tooltip.trigger-axis' ] + ] +); + export interface EChartsTooltipWidgetSettings { showTooltip: boolean; tooltipTrigger?: EChartsTooltipTrigger; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index ef8b432e46..3965f69000 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -40,11 +40,42 @@ import { import { DataKey } from '@shared/models/widget.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { TbColorScheme } from '@shared/models/color.models'; +import { DoughnutLayout } from '@home/components/widget/lib/chart/doughnut-widget.models'; -export enum PointLabelPosition { - top = 'top', - bottom = 'bottom' -} +const timeSeriesChartColorScheme: TbColorScheme = { + 'threshold.line': { + light: 'rgba(0, 0, 0, 0.76)', + dark: '#eee' + }, + 'threshold.label': { + light: 'rgba(0, 0, 0, 0.76)', + dark: '#eee' + }, + 'axis.line': { + light: 'rgba(0, 0, 0, 0.54)', + dark: '#B9B8CE' + }, + 'axis.label': { + light: 'rgba(0, 0, 0, 0.54)', + dark: '#B9B8CE' + }, + 'axis.ticks': { + light: 'rgba(0, 0, 0, 0.54)', + dark: '#B9B8CE' + }, + 'axis.tickLabel': { + light: 'rgba(0, 0, 0, 0.54)', + dark: '#B9B8CE' + }, + 'axis.splitLine': { + light: 'rgba(0, 0, 0, 0.12)', + dark: '#484753' + }, + 'series.label': { + light: 'rgba(0, 0, 0, 0.76)', + dark: '#eee' + } +}; export enum AxisPosition { left = 'left', @@ -86,19 +117,63 @@ export enum ThresholdLabelPosition { insideEndBottom = 'insideEndBottom' } +export enum TimeSeriesChartThresholdType { + constant = 'constant', + latestKey = 'latestKey', + entity = 'entity' +} + +export enum SeriesFillType { + none = 'none', + opacity = 'opacity', + gradient = 'gradient' +} + +export enum SeriesLabelPosition { + top = 'top', + bottom = 'bottom' +} + +export enum LineSeriesStepType { + start = 'start', + middle = 'middle', + end = 'end' +} + +export enum TimeSeriesChartSeriesType { + line = 'line', + bar = 'bar' +} + +export const timeSeriesChartSeriesTypes = Object.keys(TimeSeriesChartSeriesType) as TimeSeriesChartSeriesType[]; + +export const timeSeriesChartSeriesTypeTranslations = new Map( + [ + [TimeSeriesChartSeriesType.line, 'widgets.time-series-chart.series.type-line'], + [TimeSeriesChartSeriesType.bar, 'widgets.time-series-chart.series.type-bar'] + ] +); + +export const timeSeriesChartSeriesTypeIcons = new Map( + [ + [TimeSeriesChartSeriesType.line, 'mdi:chart-line'], + [TimeSeriesChartSeriesType.bar, 'mdi:chart-bar'] + ] +); + export interface TimeSeriesChartAxisSettings { show: boolean; - position: AxisPosition; label?: string; labelFont?: Font; labelColor?: string; - showLine: boolean; - lineColor: string; - showTicks: boolean; - ticksColor: string; + position: AxisPosition; showTickLabels: boolean; tickLabelFont: Font; tickLabelColor: string; + showTicks: boolean; + ticksColor: string; + showLine: boolean; + lineColor: string; showSplitLines: boolean; splitLinesColor: string; } @@ -109,24 +184,19 @@ export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSetting intervalCalculator?: string; } -export enum TimeSeriesChartThresholdType { - constant = 'constant', - latestKey = 'latestKey', - entity = 'entity' -} - export interface TimeSeriesChartThreshold { type: TimeSeriesChartThresholdType; value?: number; - latestKeyName?: string; + latestKey?: string; + latestKeyType?: DataKeyType.attribute | DataKeyType.timeseries; entityAlias?: string; - entityKeyType?: DataKeyType.attribute | DataKeyType.timeseries; entityKey?: string; + entityKeyType?: DataKeyType.attribute | DataKeyType.timeseries; units?: string; decimals?: number; - lineWidth: number; - lineType: TimeSeriesChartLineType; lineColor: string; + lineType: TimeSeriesChartLineType; + lineWidth: number; startSymbol: TimeSeriesChartShape; startSymbolSize: number; endSymbol: TimeSeriesChartShape; @@ -137,48 +207,13 @@ export interface TimeSeriesChartThreshold { labelColor: string; } -const timeSeriesChartColorScheme: TbColorScheme = { - 'threshold.line': { - light: 'rgba(0, 0, 0, 0.76)', - dark: '#eee' - }, - 'threshold.label': { - light: 'rgba(0, 0, 0, 0.76)', - dark: '#eee' - }, - 'axis.line': { - light: 'rgba(0, 0, 0, 0.54)', - dark: '#B9B8CE' - }, - 'axis.label': { - light: 'rgba(0, 0, 0, 0.54)', - dark: '#B9B8CE' - }, - 'axis.ticks': { - light: 'rgba(0, 0, 0, 0.54)', - dark: '#B9B8CE' - }, - 'axis.tickLabel': { - light: 'rgba(0, 0, 0, 0.54)', - dark: '#B9B8CE' - }, - 'axis.splitLine': { - light: 'rgba(0, 0, 0, 0.12)', - dark: '#484753' - }, - 'series.pointLabel': { - light: 'rgba(0, 0, 0, 0.76)', - dark: '#eee' - } -}; - export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold = { type: TimeSeriesChartThresholdType.constant, units: '', decimals: 0, - lineWidth: 1, - lineType: TimeSeriesChartLineType.solid, lineColor: timeSeriesChartColorScheme['threshold.line'].light, + lineType: TimeSeriesChartLineType.solid, + lineWidth: 1, startSymbol: TimeSeriesChartShape.none, startSymbolSize: 5, endSymbol: TimeSeriesChartShape.arrow, @@ -197,22 +232,21 @@ export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold = }; export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings { + thresholds: TimeSeriesChartThreshold[]; darkMode: boolean; dataZoom: boolean; stack: boolean; - thresholds: TimeSeriesChartThreshold[]; - xAxis: TimeSeriesChartAxisSettings; yAxis: TimeSeriesChartYAxisSettings; + xAxis: TimeSeriesChartAxisSettings; } export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { + thresholds: [], darkMode: false, dataZoom: true, stack: false, - thresholds: [], - xAxis: { + yAxis: { show: true, - position: AxisPosition.bottom, label: '', labelFont: { family: 'Roboto', @@ -223,26 +257,26 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { lineHeight: '1' }, labelColor: timeSeriesChartColorScheme['axis.label'].light, - showLine: true, - lineColor: timeSeriesChartColorScheme['axis.line'].light, - showTicks: true, - ticksColor: timeSeriesChartColorScheme['axis.ticks'].light, + position: AxisPosition.left, showTickLabels: true, tickLabelFont: { family: 'Roboto', - size: 10, + size: 12, sizeUnit: 'px', style: 'normal', weight: '400', lineHeight: '1' }, tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light, + showTicks: true, + ticksColor: timeSeriesChartColorScheme['axis.ticks'].light, + showLine: true, + lineColor: timeSeriesChartColorScheme['axis.line'].light, showSplitLines: true, splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light }, - yAxis: { + xAxis: { show: true, - position: AxisPosition.left, label: '', labelFont: { family: 'Roboto', @@ -253,20 +287,21 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { lineHeight: '1' }, labelColor: timeSeriesChartColorScheme['axis.label'].light, - showLine: true, - lineColor: timeSeriesChartColorScheme['axis.line'].light, - showTicks: true, - ticksColor: timeSeriesChartColorScheme['axis.ticks'].light, + position: AxisPosition.bottom, showTickLabels: true, tickLabelFont: { family: 'Roboto', - size: 12, + size: 10, sizeUnit: 'px', style: 'normal', weight: '400', lineHeight: '1' }, tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light, + showTicks: true, + ticksColor: timeSeriesChartColorScheme['axis.ticks'].light, + showLine: true, + lineColor: timeSeriesChartColorScheme['axis.line'].light, showSplitLines: true, splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light }, @@ -282,7 +317,6 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { }, tooltipValueColor: 'rgba(0, 0, 0, 0.76)', tooltipShowDate: true, - tooltipDateInterval: true, tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss'), tooltipDateFont: { family: 'Roboto', @@ -293,16 +327,11 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { lineHeight: '16px' }, tooltipDateColor: 'rgba(0, 0, 0, 0.76)', + tooltipDateInterval: true, tooltipBackgroundColor: 'rgba(255, 255, 255, 0.76)', tooltipBackgroundBlur: 4 }; -export enum SeriesFillType { - none = 'none', - opacity = 'opacity', - gradient = 'gradient' -} - export interface SeriesFillSettings { type: SeriesFillType; opacity: number; @@ -313,36 +342,36 @@ export interface SeriesFillSettings { } export interface LineSeriesSettings { - step: false | 'start' | 'end' | 'middle'; - smooth: boolean; showLine: boolean; - lineWidth: number; + step: boolean; + stepType: LineSeriesStepType; + smooth: boolean; lineType: TimeSeriesChartLineType; - fillAreaSettings: SeriesFillSettings; + lineWidth: number; showPoints: boolean; + showPointLabel: boolean; + pointLabelPosition: SeriesLabelPosition; + pointLabelFont: Font; + pointLabelColor: string; pointShape: TimeSeriesChartShape; pointSize: number; + fillAreaSettings: SeriesFillSettings; } export interface BarSeriesSettings { showBorder: boolean; borderWidth: number; borderRadius: number; + showLabel: boolean; + labelPosition: SeriesLabelPosition; + labelFont: Font; + labelColor: string; backgroundSettings: SeriesFillSettings; } -export enum TimeSeriesChartSeriesType { - line = 'line', - bar = 'bar' -} - export interface TimeSeriesChartKeySettings { - dataHiddenByDefault: boolean; showInLegend: boolean; - showPointLabel: boolean; - pointLabelPosition: PointLabelPosition; - pointLabelFont: Font; - pointLabelColor: string; + dataHiddenByDefault: boolean; type: TimeSeriesChartSeriesType; lineSettings: LineSeriesSettings; barSettings: BarSeriesSettings; @@ -351,24 +380,28 @@ export interface TimeSeriesChartKeySettings { export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = { showInLegend: true, dataHiddenByDefault: false, - showPointLabel: false, - pointLabelPosition: PointLabelPosition.top, - pointLabelFont: { - family: 'Roboto', - size: 11, - sizeUnit: 'px', - style: 'normal', - weight: '400', - lineHeight: '1' - }, - pointLabelColor: timeSeriesChartColorScheme['series.pointLabel'].light, type: TimeSeriesChartSeriesType.line, lineSettings: { + showLine: true, step: false, + stepType: LineSeriesStepType.start, smooth: false, - showLine: true, - lineWidth: 2, lineType: TimeSeriesChartLineType.solid, + lineWidth: 2, + showPoints: false, + showPointLabel: false, + pointLabelPosition: SeriesLabelPosition.top, + pointLabelFont: { + family: 'Roboto', + size: 11, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '1' + }, + pointLabelColor: timeSeriesChartColorScheme['series.label'].light, + pointShape: TimeSeriesChartShape.emptyCircle, + pointSize: 4, fillAreaSettings: { type: SeriesFillType.none, opacity: 0.4, @@ -376,15 +409,23 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = { start: 100, end: 0 } - }, - showPoints: false, - pointShape: TimeSeriesChartShape.emptyCircle, - pointSize: 4 + } }, barSettings: { showBorder: false, borderWidth: 2, borderRadius: 0, + showLabel: false, + labelPosition: SeriesLabelPosition.top, + labelFont: { + family: 'Roboto', + size: 11, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '1' + }, + labelColor: timeSeriesChartColorScheme['series.label'].light, backgroundSettings: { type: SeriesFillType.none, opacity: 0.4, @@ -707,21 +748,23 @@ export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChart } for (const item of dataItems) { if (item.dataKey.settings.type === TimeSeriesChartSeriesType.line) { + const lineSettings = item.dataKey.settings as LineSeriesSettings; if (item.option.label?.show) { - item.option.label.rich.value.color = prepareChartThemeColor(item.dataKey.settings.pointLabelColor, darkMode, 'series.pointLabel'); + item.option.label.rich.value.color = prepareChartThemeColor(lineSettings.pointLabelColor, darkMode, 'series.label'); } if (Array.isArray(options.series)) { const series = options.series.find(s => s.id === item.id); if (series) { if (series.label?.show) { - series.label.rich.value.color = prepareChartThemeColor(item.dataKey.settings.pointLabelColor, darkMode, 'series.pointLabel'); + series.label.rich.value.color = prepareChartThemeColor(lineSettings.pointLabelColor, darkMode, 'series.label'); } } } } else { if (item.barRenderContext?.labelOption?.show) { - item.barRenderContext.labelOption.rich.value.color = prepareChartThemeColor(item.dataKey.settings.pointLabelColor, - darkMode, 'series.pointLabel'); + const barSettings = item.dataKey.settings as BarSeriesSettings; + item.barRenderContext.labelOption.rich.value.color = prepareChartThemeColor(barSettings.labelColor, + darkMode, 'series.label'); } } } @@ -747,21 +790,6 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem, const dataKey = item.dataKey; const settings: TimeSeriesChartKeySettings = dataKey.settings; const seriesColor = item.dataKey.color; - let pointLabelStyle: ComponentStyle = {}; - if (settings.showPointLabel) { - pointLabelStyle = createChartTextStyle(settings.pointLabelFont, settings.pointLabelColor, darkMode, 'series.pointLabel'); - } - const label: SeriesLabelOption = { - show: settings.showPointLabel, - position: settings.pointLabelPosition, - formatter: (params): string => { - const value = formatValue(params.value[1], item.decimals, item.units, false); - return `{value|${value}}`; - }, - rich: { - value: pointLabelStyle - } - }; seriesOption = { id: item.id, dataGroupId: item.id, @@ -788,8 +816,9 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem, const lineSettings = settings.lineSettings; const lineSeriesOption = seriesOption as LineSeriesOption; lineSeriesOption.type = 'line'; - lineSeriesOption.label = label; - lineSeriesOption.step = lineSettings.step; + lineSeriesOption.label = createSeriesLabelOption(item, lineSettings.showPointLabel, + lineSettings.pointLabelFont, lineSettings.pointLabelColor, lineSettings.pointLabelPosition, darkMode); + lineSeriesOption.step = lineSettings.step ? lineSettings.stepType : false; lineSeriesOption.smooth = lineSettings.smooth; lineSeriesOption.lineStyle = { width: lineSettings.showLine ? lineSettings.lineWidth : 0, @@ -825,7 +854,8 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem, barVisualSettings.color = createLinearOpacityGradient(seriesColor, barSettings.backgroundSettings.gradient); } item.barRenderContext.visualSettings = barVisualSettings; - item.barRenderContext.labelOption = label; + item.barRenderContext.labelOption = createSeriesLabelOption(item, barSettings.showLabel, + barSettings.labelFont, barSettings.labelColor, barSettings.labelPosition, darkMode); barSeriesOption.renderItem = (params, api) => renderTimeSeriesBar(params, api, item.barRenderContext); } @@ -834,6 +864,26 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem, return seriesOption; }; +const createSeriesLabelOption = (item: TimeSeriesChartDataItem, show: boolean, + labelFont: Font, labelColor: string, position: SeriesLabelPosition, + darkMode: boolean): SeriesLabelOption => { + let labelStyle: ComponentStyle = {}; + if (show) { + labelStyle = createChartTextStyle(labelFont, labelColor, darkMode, 'series.label'); + } + return { + show, + position, + formatter: (params): string => { + const value = formatValue(params.value[1], item.decimals, item.units, false); + return `{value|${value}}`; + }, + rich: { + value: labelStyle + } + }; +}; + const createChartTextStyle = (font: Font, color: string, darkMode: boolean, colorKey?: string): ComponentStyle => { const style = textStyle(font); delete style.lineHeight; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 48c2dfaf57..c36327fec6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -21,6 +21,7 @@ import { createTimeSeriesXAxisOption, createTimeSeriesYAxis, generateChartData, + parseThresholdData, SeriesLabelPosition, TimeSeriesChartDataItem, timeSeriesChartDefaultSettings, timeSeriesChartKeyDefaultSettings, @@ -30,8 +31,6 @@ import { TimeSeriesChartThresholdItem, TimeSeriesChartThresholdType, TimeSeriesChartYAxis, - parseThresholdData, - PointLabelPosition, updateDarkMode } from '@home/components/widget/lib/chart/time-series-chart.models'; import { ResizeObserver } from '@juggle/resize-observer'; @@ -60,9 +59,20 @@ import { BehaviorSubject } from 'rxjs'; import { AggregationType } from '@shared/models/time/time.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { WidgetSubscriptionOptions } from '@core/api/widget-api.models'; +import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; export class TbTimeSeriesChart { + public static dataKeySettings(): DataKeySettingsFunction { + return (key, isLatestDataKey) => { + if (!isLatestDataKey) { + return mergeDeep({} as TimeSeriesChartKeySettings, + timeSeriesChartKeyDefaultSettings); + } + return null; + }; + } + private readonly shapeResize$: ResizeObserver; private dataItems: TimeSeriesChartDataItem[] = []; @@ -247,7 +257,10 @@ export class TbTimeSeriesChart { for (const dataKey of dataKeys) { const keySettings = mergeDeep({} as TimeSeriesChartKeySettings, timeSeriesChartKeyDefaultSettings, dataKey.settings); - if (keySettings.showPointLabel && keySettings.pointLabelPosition === PointLabelPosition.top) { + if ((keySettings.type === TimeSeriesChartSeriesType.line && keySettings.lineSettings.showPointLabel && + keySettings.lineSettings.pointLabelPosition === SeriesLabelPosition.top) || + (keySettings.type === TimeSeriesChartSeriesType.bar && keySettings.barSettings.showLabel && + keySettings.barSettings.labelPosition === SeriesLabelPosition.top)) { this.topPointLabels = true; } dataKey.settings = keySettings; @@ -280,8 +293,9 @@ export class TbTimeSeriesChart { if (this.ctx.datasources.length) { for (const datasource of this.ctx.datasources) { latestDataKey = datasource.latestDataKeys?.find(d => - (d.type === DataKeyType.function && d.label === threshold.latestKeyName) || - (d.type !== DataKeyType.function && d.name === threshold.latestKeyName)); + (d.type === DataKeyType.function && d.label === threshold.latestKey) || + (d.type !== DataKeyType.function && d.name === threshold.latestKey && + d.type === threshold.latestKeyType)); if (latestDataKey) { break; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts index a760d1ece1..a46e6a8427 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts @@ -298,6 +298,6 @@ export class MapWidgetController implements MapWidgetInterface { } } -export let TbMapWidgetV2: MapWidgetStaticInterface = MapWidgetController; +export const TbMapWidgetV2: MapWidgetStaticInterface = MapWidgetController; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html new file mode 100644 index 0000000000..b1d5d3d302 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html @@ -0,0 +1,36 @@ + + +
+
widgets.time-series-chart.series.legend-settings
+ +
+ {{ 'widgets.time-series-chart.series.show-in-legend' | translate }} +
+
+ +
+ {{ 'widgets.time-series-chart.series.hidden-by-default' | translate }} +
+
+
+
+
widgets.time-series-chart.series.series-type
+ TODO: +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts new file mode 100644 index 0000000000..8f266fa9b6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts @@ -0,0 +1,82 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { mergeDeep } from '@core/utils'; +import { + timeSeriesChartKeyDefaultSettings, + TimeSeriesChartKeySettings +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; + +@Component({ + selector: 'tb-time-series-chart-key-settings', + templateUrl: './time-series-chart-key-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent { + + timeSeriesChartKeySettingsForm: UntypedFormGroup; + + constructor(protected store: Store, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.timeSeriesChartKeySettingsForm; + } + + protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) { + const params = widgetConfig.typeParameters as any; + // const timeSeriesChartType = params.timeSeriesChartType; + } + + protected defaultSettings(): WidgetSettings { + return mergeDeep({} as TimeSeriesChartKeySettings, + timeSeriesChartKeyDefaultSettings); + } + + protected onSettingsSet(settings: WidgetSettings) { + const seriesSettings = settings as TimeSeriesChartKeySettings; + this.timeSeriesChartKeySettingsForm = this.fb.group({ + showInLegend: [seriesSettings.showInLegend, []], + dataHiddenByDefault: [seriesSettings.dataHiddenByDefault, []], + type: [seriesSettings.type, []], + lineSettings: [settings.lineSettings, []], + barSettings: [settings.barSettings, []] + }); + } + + protected validatorTriggers(): string[] { + return ['showInLegend']; + } + + protected updateValidators(_emitEvent: boolean) { + const showInLegend: boolean = this.timeSeriesChartKeySettingsForm.get('showInLegend').value; + if (showInLegend) { + this.timeSeriesChartKeySettingsForm.get('dataHiddenByDefault').enable(); + } else { + this.timeSeriesChartKeySettingsForm.get('dataHiddenByDefault').patchValue(false, {emitEvent: false}); + this.timeSeriesChartKeySettingsForm.get('dataHiddenByDefault').disable(); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.html index 44ab1d0bad..3dd5e08860 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.html @@ -16,7 +16,7 @@ --> -
+
{{ 'legend.direction' | translate }}
@@ -31,8 +31,9 @@ + [disabled]="!hideDirection && + legendConfigForm.get('direction').value === legendDirection.row && + (pos === legendPosition.left || pos === legendPosition.right)"> {{ legendPositionTranslations.get(legendPosition[pos]) | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.ts index cf7b3d4e29..8b065247f3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.ts @@ -25,6 +25,7 @@ import { legendPositionTranslationMap } from '@shared/models/widget.models'; import { Subscription } from 'rxjs'; +import { coerceBoolean } from '@shared/decorators/coercion'; // @dynamic @Component({ @@ -43,6 +44,10 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc @Input() disabled: boolean; + @Input() + @coerceBoolean() + hideDirection = false; + legendConfigForm: UntypedFormGroup; legendDirection = LegendDirection; legendDirections = Object.keys(LegendDirection); @@ -60,15 +65,17 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc ngOnInit(): void { this.legendConfigForm = this.fb.group({ - direction: [null, []], position: [null, []], showValues: [[], []], sortDataKeys: [null, []] }); - this.legendSettingsFormDirectionChanges$ = this.legendConfigForm.get('direction').valueChanges + if (!this.hideDirection) { + this.legendConfigForm.addControl('direction', this.fb.control([null, []])); + this.legendSettingsFormDirectionChanges$ = this.legendConfigForm.get('direction').valueChanges .subscribe((direction: LegendDirection) => { this.onDirectionChanged(direction); }); + } this.legendSettingsFormChanges$ = this.legendConfigForm.valueChanges.subscribe( () => this.legendConfigUpdated() ); @@ -114,23 +121,30 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc writeValue(legendConfig: LegendConfig): void { if (legendConfig) { - this.legendConfigForm.patchValue({ - direction: legendConfig.direction, + const value: any = { position: legendConfig.position, showValues: this.getShowValues(legendConfig), sortDataKeys: isDefined(legendConfig.sortDataKeys) ? legendConfig.sortDataKeys : false - }, {emitEvent: false}); + }; + if (!this.hideDirection) { + value.direction = legendConfig.direction; + } + this.legendConfigForm.patchValue(value, {emitEvent: false}); + } + if (!this.hideDirection) { + this.onDirectionChanged(legendConfig?.direction); } - this.onDirectionChanged(legendConfig.direction); } private legendConfigUpdated() { const configValue = this.legendConfigForm.value; const legendConfig: Partial = { - direction: configValue.direction, position: configValue.position, sortDataKeys: configValue.sortDataKeys }; + if (!this.hideDirection) { + legendConfig.direction = configValue.direction; + } this.setShowValues(configValue.showValues, legendConfig); this.propagateChange(legendConfig); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index 2943b4cc09..4183a06db2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -330,6 +330,9 @@ import { import { ToggleButtonWidgetSettingsComponent } from '@home/components/widget/lib/settings/button/toggle-button-widget-settings.component'; +import { + TimeSeriesChartKeySettingsComponent +} from '@home/components/widget/lib/settings/chart/time-series-chart-key-settings.component'; @NgModule({ declarations: [ @@ -448,7 +451,8 @@ import { CommandButtonWidgetSettingsComponent, PowerButtonWidgetSettingsComponent, SliderWidgetSettingsComponent, - ToggleButtonWidgetSettingsComponent + ToggleButtonWidgetSettingsComponent, + TimeSeriesChartKeySettingsComponent ], imports: [ CommonModule, @@ -572,7 +576,8 @@ import { CommandButtonWidgetSettingsComponent, PowerButtonWidgetSettingsComponent, SliderWidgetSettingsComponent, - ToggleButtonWidgetSettingsComponent + ToggleButtonWidgetSettingsComponent, + TimeSeriesChartKeySettingsComponent ] }) export class WidgetSettingsModule { @@ -663,5 +668,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type { + (window as any).TbTimeSeriesChart = mod.TbTimeSeriesChart; + })) + ); widgetModulesTasks.push(from(import('@home/components/widget/lib/analogue-compass')).pipe( tap((mod) => { (window as any).TbAnalogueCompass = mod.TbAnalogueCompass; @@ -577,6 +583,9 @@ export class WidgetComponentService { if (!isFunction(result.typeParameters.defaultLatestDataKeysFunction)) { result.typeParameters.defaultLatestDataKeysFunction = null; } + if (!isFunction(result.typeParameters.dataKeySettingsFunction)) { + result.typeParameters.dataKeySettingsFunction = null; + } if (isUndefined(result.typeParameters.displayRpcMessageToast)) { result.typeParameters.displayRpcMessageToast = true; } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index 49e4e2ea59..5ace8ac616 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -84,6 +84,7 @@ import { coerceBoolean } from '@shared/decorators/coercion'; import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/basic/basic-widget-config.module'; import { TimewindowConfigData } from '@home/components/widget/config/timewindow-config-panel.component'; import Timeout = NodeJS.Timeout; +import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; const emptySettingsSchema: JsonSchema = { type: 'object', @@ -734,7 +735,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe } } - public generateDataKey(chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema): DataKey { + public generateDataKey(chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema, + isLatestDataKey: boolean, dataKeySettingsFunction: DataKeySettingsFunction): DataKey { if (isObject(chip)) { (chip as DataKey)._hash = Math.random(); return chip; @@ -767,6 +769,11 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe } if (datakeySettingsSchema && isDefined(datakeySettingsSchema.schema)) { result.settings = this.utils.generateObjectFromJsonSchema(datakeySettingsSchema.schema); + } else if (dataKeySettingsFunction) { + const settings = dataKeySettingsFunction(result, isLatestDataKey); + if (settings) { + result.settings = settings; + } } return result; } diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 2ea34377d6..26e5fd4057 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -102,6 +102,7 @@ import { ImagePipe, MillisecondsToTimeStringPipe, TelemetrySubscriber } from '@a import { UserId } from '@shared/models/id/user-id'; import { UserSettingsService } from '@core/http/user-settings.service'; import { DynamicComponentModule } from '@core/services/dynamic-component-factory.service'; +import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; export interface IWidgetAction { name: string; @@ -549,6 +550,7 @@ export interface WidgetConfigComponentData { settingsSchema: JsonSettingsSchema; dataKeySettingsSchema: JsonSettingsSchema; latestDataKeySettingsSchema: JsonSettingsSchema; + dataKeySettingsFunction: DataKeySettingsFunction; settingsDirective: string; dataKeySettingsDirective: string; latestDataKeySettingsDirective: string; diff --git a/ui-ngx/src/app/shared/models/widget-settings.models.ts b/ui-ngx/src/app/shared/models/widget-settings.models.ts index 8e60c0d9cb..1e2e0d2415 100644 --- a/ui-ngx/src/app/shared/models/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -348,7 +348,7 @@ export const customDateFormat = (format: string): DateFormatSettings => ({ custom: true }); -export const dateFormats = ['MMM dd yyyy HH:mm', 'dd MMM yyyy HH:mm', 'yyyy MMM dd HH:mm', +export const dateFormats = ['MMM dd yyyy HH:mm', 'dd MMM yyyy HH:mm', 'dd MMM yyyy HH:mm:ss', 'yyyy MMM dd HH:mm', 'MM/dd/yyyy HH:mm', 'dd/MM/yyyy HH:mm', 'yyyy/MM/dd HH:mm:ss', 'yyyy-MM-dd HH:mm:ss', 'yyyy-MM-dd HH:mm:ss.SSS'] .map(f => simpleDateFormat(f)).concat([lastUpdateAgoDateFormat(), customDateFormat('EEE, MMMM dd, yyyy')]); diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 5f4ca8f7be..a164d8d3ec 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -43,6 +43,7 @@ import { WidgetConfigComponentData } from '@home/models/widget-component.models' import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { HasTenantId } from '@shared/models/entity.models'; +import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; export enum widgetType { timeseries = 'timeseries', @@ -184,6 +185,7 @@ export interface WidgetTypeParameters { hideDataSettings?: boolean; defaultDataKeysFunction?: (configComponent: any, configData: any) => DataKey[]; defaultLatestDataKeysFunction?: (configComponent: any, configData: any) => DataKey[]; + dataKeySettingsFunction?: DataKeySettingsFunction; displayRpcMessageToast?: boolean; } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index eacf783c53..a36847b334 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4334,8 +4334,13 @@ "preview": "Preview" }, "tooltip": { + "trigger": "Trigger", + "trigger-point": "Point", + "trigger-axis": "Axis", "value": "Value", "date": "Date", + "show-date-time-interval": "Show date time interval", + "show-date-time-interval-hint": "Show date time interval according to the data aggregation.", "background-color": "Background color", "background-blur": "Background blur" }, @@ -6590,6 +6595,24 @@ "table-tabs": "Table tabs", "show-cell-actions-menu-mobile": "Show cell actions dropdown menu in mobile mode" }, + "time-series-chart": { + "chart": "Chart", + "data-zoom": "Data zoom", + "stack-mode": "Stack mode", + "stack-mode-hint": "Stacks series on the chart. The series with the same unit would be put on top of each other.", + "axes": "Axes", + "series": { + "legend-settings": "Legend settings", + "show-in-legend": "Show in legend", + "show-in-legend-hint": "Show series name and data in legend.", + "hidden-by-default": "Hidden by default", + "hidden-by-default-hint": "Make series hidden in legend by default.", + "series-type": "Series type", + "type": "Type", + "type-line": "Line", + "type-bar": "Bar" + } + }, "wind-speed-direction": { "layout": "Layout", "layout-default": "Default", diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 524a4e86ac..c3e2599bd9 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -467,6 +467,17 @@ } } } + &.fixed-height { + .mat-mdc-form-field-infix { + max-height: 40px; + .mat-mdc-select { + max-height: 24px; + .mat-mdc-select-trigger { + max-height: 24px; + } + } + } + } } .tb-form-table { @@ -670,4 +681,14 @@ } } } + + .mat-mdc-option { + &.flex { + .mdc-list-item__primary-text { + display: flex; + align-items: center; + justify-content: flex-start; + } + } + } } From fc06a77943dacfd23c68a942acaaeed660f0d7e7 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 4 Mar 2024 16:56:29 +0200 Subject: [PATCH 151/209] UI: Implement time-series chart key settings form. --- .../lib/chart/time-series-chart.models.ts | 55 ++++++ .../widget/lib/chart/time-series-chart.ts | 3 +- ...e-series-chart-bar-settings.component.html | 65 ++++++ ...ime-series-chart-bar-settings.component.ts | 148 ++++++++++++++ ...-series-chart-fill-settings.component.html | 53 +++++ ...me-series-chart-fill-settings.component.ts | 143 ++++++++++++++ ...e-series-chart-key-settings.component.html | 14 +- ...ime-series-chart-key-settings.component.ts | 26 ++- ...-series-chart-line-settings.component.html | 111 +++++++++++ ...me-series-chart-line-settings.component.ts | 187 ++++++++++++++++++ .../lib/settings/widget-settings.module.ts | 19 +- .../assets/locale/locale.constant-en_US.json | 52 ++++- 12 files changed, 864 insertions(+), 12 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index 3965f69000..c9209b06b4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -96,12 +96,38 @@ export enum TimeSeriesChartShape { none = 'none' } +export const timeSeriesChartShapes = Object.keys(TimeSeriesChartShape) as TimeSeriesChartShape[]; + +export const timeSeriesChartShapeTranslations = new Map( + [ + [TimeSeriesChartShape.emptyCircle, 'widgets.time-series-chart.shape-empty-circle'], + [TimeSeriesChartShape.circle, 'widgets.time-series-chart.shape-circle'], + [TimeSeriesChartShape.rect, 'widgets.time-series-chart.shape-rect'], + [TimeSeriesChartShape.roundRect, 'widgets.time-series-chart.shape-round-rect'], + [TimeSeriesChartShape.triangle, 'widgets.time-series-chart.shape-triangle'], + [TimeSeriesChartShape.diamond, 'widgets.time-series-chart.shape-diamond'], + [TimeSeriesChartShape.pin, 'widgets.time-series-chart.shape-pin'], + [TimeSeriesChartShape.arrow, 'widgets.time-series-chart.shape-arrow'], + [TimeSeriesChartShape.none, 'widgets.time-series-chart.shape-none'] + ] +); + export enum TimeSeriesChartLineType { solid = 'solid', dashed = 'dashed', dotted = 'dotted' } +export const timeSeriesLineTypes = Object.keys(TimeSeriesChartLineType) as TimeSeriesChartLineType[]; + +export const timeSeriesLineTypeTranslations = new Map( + [ + [TimeSeriesChartLineType.solid, 'widgets.time-series-chart.line-type-solid'], + [TimeSeriesChartLineType.dashed, 'widgets.time-series-chart.line-type-dashed'], + [TimeSeriesChartLineType.dotted, 'widgets.time-series-chart.line-type-dotted'] + ] +); + export enum ThresholdLabelPosition { start = 'start', middle = 'middle', @@ -129,17 +155,46 @@ export enum SeriesFillType { gradient = 'gradient' } +export const seriesFillTypes = Object.keys(SeriesFillType) as SeriesFillType[]; + +export const seriesFillTypeTranslations = new Map( + [ + [SeriesFillType.none, 'widgets.time-series-chart.series.fill-type-none'], + [SeriesFillType.opacity, 'widgets.time-series-chart.series.fill-type-opacity'], + [SeriesFillType.gradient, 'widgets.time-series-chart.series.fill-type-gradient'] + ] +); + export enum SeriesLabelPosition { top = 'top', bottom = 'bottom' } +export const seriesLabelPositions = Object.keys(SeriesLabelPosition) as SeriesLabelPosition[]; + +export const seriesLabelPositionTranslations = new Map( + [ + [SeriesLabelPosition.top, 'widgets.time-series-chart.series.label-position-top'], + [SeriesLabelPosition.bottom, 'widgets.time-series-chart.series.label-position-bottom'] + ] +); + export enum LineSeriesStepType { start = 'start', middle = 'middle', end = 'end' } +export const lineSeriesStepTypes = Object.keys(LineSeriesStepType) as LineSeriesStepType[]; + +export const lineSeriesStepTypeTranslations = new Map( + [ + [LineSeriesStepType.start, 'widgets.time-series-chart.series.line.step-type-start'], + [LineSeriesStepType.middle, 'widgets.time-series-chart.series.line.step-type-middle'], + [LineSeriesStepType.end, 'widgets.time-series-chart.series.line.step-type-end'] + ] +); + export enum TimeSeriesChartSeriesType { line = 'line', bar = 'bar' diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index c36327fec6..08761fbe36 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -259,8 +259,7 @@ export class TbTimeSeriesChart { timeSeriesChartKeyDefaultSettings, dataKey.settings); if ((keySettings.type === TimeSeriesChartSeriesType.line && keySettings.lineSettings.showPointLabel && keySettings.lineSettings.pointLabelPosition === SeriesLabelPosition.top) || - (keySettings.type === TimeSeriesChartSeriesType.bar && keySettings.barSettings.showLabel && - keySettings.barSettings.labelPosition === SeriesLabelPosition.top)) { + (keySettings.type === TimeSeriesChartSeriesType.bar && keySettings.barSettings.showLabel)) { this.topPointLabels = true; } dataKey.settings = keySettings; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html new file mode 100644 index 0000000000..a62157dba5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html @@ -0,0 +1,65 @@ + + +
+ + {{ 'widgets.time-series-chart.series.bar.show-border' | translate }} + +
+
+
widgets.time-series-chart.series.bar.border-width
+ + + +
+
+
widgets.time-series-chart.series.bar.border-radius
+ + + +
+
+ +
+ {{ 'widgets.time-series-chart.series.bar.label' | translate }} +
+
+
+ + + + {{ seriesLabelPositionTranslations.get(position) | translate }} + + + + + + + +
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.ts new file mode 100644 index 0000000000..b42cec925a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.ts @@ -0,0 +1,148 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + Validators +} from '@angular/forms'; +import { + BarSeriesSettings, + seriesLabelPositions, + seriesLabelPositionTranslations +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { merge } from 'rxjs'; +import { formatValue, isDefinedAndNotNull } from '@core/utils'; +import { DataKeyConfigComponent } from '@home/components/widget/config/data-key-config.component'; + +@Component({ + selector: 'tb-time-series-chart-bar-settings', + templateUrl: './time-series-chart-bar-settings.component.html', + styleUrls: ['./../widget-settings.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeSeriesChartBarSettingsComponent), + multi: true + } + ] +}) +export class TimeSeriesChartBarSettingsComponent implements OnInit, ControlValueAccessor { + + seriesLabelPositions = seriesLabelPositions; + + seriesLabelPositionTranslations = seriesLabelPositionTranslations; + + labelPreviewFn = this._labelPreviewFn.bind(this); + + @Input() + disabled: boolean; + + private modelValue: BarSeriesSettings; + + private propagateChange = null; + + public barSettingsFormGroup: UntypedFormGroup; + + constructor(protected store: Store, + private dataKeyConfigComponent: DataKeyConfigComponent, + private fb: UntypedFormBuilder) { + } + + ngOnInit(): void { + this.barSettingsFormGroup = this.fb.group({ + showBorder: [null, []], + borderWidth: [null, [Validators.min(0)]], + borderRadius: [null, [Validators.min(0)]], + showLabel: [null, []], + labelPosition: [null, []], + labelFont: [null, []], + labelColor: [null, []], + backgroundSettings: [null, []] + }); + this.barSettingsFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + merge(this.barSettingsFormGroup.get('showBorder').valueChanges, + this.barSettingsFormGroup.get('showLabel').valueChanges) + .subscribe(() => { + this.updateValidators(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.barSettingsFormGroup.disable({emitEvent: false}); + } else { + this.barSettingsFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + writeValue(value: BarSeriesSettings): void { + this.modelValue = value; + this.barSettingsFormGroup.patchValue( + value, {emitEvent: false} + ); + this.updateValidators(); + } + + private updateValidators() { + const showBorder: boolean = this.barSettingsFormGroup.get('showBorder').value; + const showLabel: boolean = this.barSettingsFormGroup.get('showLabel').value; + if (showBorder) { + this.barSettingsFormGroup.get('borderWidth').enable({emitEvent: false}); + } else { + this.barSettingsFormGroup.get('borderWidth').disable({emitEvent: false}); + } + if (showLabel) { + this.barSettingsFormGroup.get('labelPosition').enable({emitEvent: false}); + this.barSettingsFormGroup.get('labelFont').enable({emitEvent: false}); + this.barSettingsFormGroup.get('labelColor').enable({emitEvent: false}); + } else { + this.barSettingsFormGroup.get('labelPosition').disable({emitEvent: false}); + this.barSettingsFormGroup.get('labelFont').disable({emitEvent: false}); + this.barSettingsFormGroup.get('labelColor').disable({emitEvent: false}); + } + } + + private updateModel() { + this.modelValue = this.barSettingsFormGroup.getRawValue(); + this.propagateChange(this.modelValue); + } + + private _labelPreviewFn(): string { + const dataKey = this.dataKeyConfigComponent.modelValue; + const widgetConfig = this.dataKeyConfigComponent.widgetConfig; + const units = dataKey.units && dataKey.units.length ? dataKey.units : widgetConfig.config.units; + const decimals = isDefinedAndNotNull(dataKey.decimals) ? dataKey.decimals : + (isDefinedAndNotNull(widgetConfig.config.decimals) ? widgetConfig.config.decimals : 2); + return formatValue(22, decimals, units, false); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.html new file mode 100644 index 0000000000..934f985483 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.html @@ -0,0 +1,53 @@ + + +
+
+
{{ title | translate }}
+ + {{ seriesFillTypeTranslationMap.get(type) | translate }} + +
+ +
+
widgets.time-series-chart.series.opacity
+ + + +
+
+ +
+
widgets.time-series-chart.series.gradient-stops
+
+
widgets.time-series-chart.series.gradient-start
+ + + +
widgets.time-series-chart.series.gradient-end
+ + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.ts new file mode 100644 index 0000000000..44cc870f5b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.ts @@ -0,0 +1,143 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + Validators +} from '@angular/forms'; +import { + SeriesFillSettings, + SeriesFillType, + seriesFillTypes, + seriesFillTypeTranslations +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; + +@Component({ + selector: 'tb-time-series-chart-fill-settings', + templateUrl: './time-series-chart-fill-settings.component.html', + styleUrls: ['./../widget-settings.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeSeriesChartFillSettingsComponent), + multi: true + } + ] +}) +export class TimeSeriesChartFillSettingsComponent implements OnInit, ControlValueAccessor { + + seriesFillTypes = seriesFillTypes; + + seriesFillTypeTranslationMap: Map = new Map([]); + + SeriesFillType = SeriesFillType; + + @Input() + disabled: boolean; + + @Input() + title = 'widgets.time-series-chart.series.fill'; + + @Input() + fillNoneTitle = 'widgets.time-series-chart.series.fill-type-none'; + + private modelValue: SeriesFillSettings; + + private propagateChange = null; + + public fillSettingsFormGroup: UntypedFormGroup; + + constructor(protected store: Store, + private fb: UntypedFormBuilder) { + } + + ngOnInit(): void { + this.fillSettingsFormGroup = this.fb.group({ + type: [null, []], + opacity: [null, [Validators.min(0), Validators.max(100)]], + gradient: this.fb.group({ + start: [null, [Validators.min(0), Validators.max(100)]], + end: [null, [Validators.min(0), Validators.max(100)]] + }) + }); + this.fillSettingsFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + this.fillSettingsFormGroup.get('type').valueChanges.subscribe(() => { + this.updateValidators(); + }); + for (const type of seriesFillTypes) { + let translation: string; + if (type === SeriesFillType.none) { + translation = this.fillNoneTitle; + } else { + translation = seriesFillTypeTranslations.get(type); + } + this.seriesFillTypeTranslationMap.set(type, translation); + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.fillSettingsFormGroup.disable({emitEvent: false}); + } else { + this.fillSettingsFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + writeValue(value: SeriesFillSettings): void { + this.modelValue = value; + this.fillSettingsFormGroup.patchValue( + value, {emitEvent: false} + ); + this.updateValidators(); + } + + private updateValidators() { + const type: SeriesFillType = this.fillSettingsFormGroup.get('type').value; + if (type === SeriesFillType.none) { + this.fillSettingsFormGroup.get('opacity').disable({emitEvent: false}); + this.fillSettingsFormGroup.get('gradient').disable({emitEvent: false}); + } else if (type === SeriesFillType.opacity) { + this.fillSettingsFormGroup.get('opacity').enable({emitEvent: false}); + this.fillSettingsFormGroup.get('gradient').disable({emitEvent: false}); + } else if (type === SeriesFillType.gradient) { + this.fillSettingsFormGroup.get('opacity').disable({emitEvent: false}); + this.fillSettingsFormGroup.get('gradient').enable({emitEvent: false}); + } + } + + private updateModel() { + const value: SeriesFillSettings = this.fillSettingsFormGroup.getRawValue(); + this.modelValue = value; + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html index b1d5d3d302..313894d7df 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html @@ -30,7 +30,17 @@
-
widgets.time-series-chart.series.series-type
- TODO: +
+
widgets.time-series-chart.series.series-type
+ + {{ timeSeriesChartSeriesTypeTranslations.get(type) | translate }} + +
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts index 8f266fa9b6..24fb200b0b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts @@ -22,7 +22,10 @@ import { AppState } from '@core/core.state'; import { mergeDeep } from '@core/utils'; import { timeSeriesChartKeyDefaultSettings, - TimeSeriesChartKeySettings + TimeSeriesChartKeySettings, + TimeSeriesChartSeriesType, + timeSeriesChartSeriesTypes, + timeSeriesChartSeriesTypeTranslations } from '@home/components/widget/lib/chart/time-series-chart.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; @@ -33,6 +36,12 @@ import { WidgetConfigComponentData } from '@home/models/widget-component.models' }) export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent { + TimeSeriesChartSeriesType = TimeSeriesChartSeriesType; + + timeSeriesChartSeriesTypes = timeSeriesChartSeriesTypes; + + timeSeriesChartSeriesTypeTranslations = timeSeriesChartSeriesTypeTranslations; + timeSeriesChartKeySettingsForm: UntypedFormGroup; constructor(protected store: Store, @@ -60,23 +69,30 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent showInLegend: [seriesSettings.showInLegend, []], dataHiddenByDefault: [seriesSettings.dataHiddenByDefault, []], type: [seriesSettings.type, []], - lineSettings: [settings.lineSettings, []], - barSettings: [settings.barSettings, []] + lineSettings: [seriesSettings.lineSettings, []], + barSettings: [seriesSettings.barSettings, []] }); } protected validatorTriggers(): string[] { - return ['showInLegend']; + return ['showInLegend', 'type']; } protected updateValidators(_emitEvent: boolean) { const showInLegend: boolean = this.timeSeriesChartKeySettingsForm.get('showInLegend').value; + const type: TimeSeriesChartSeriesType = this.timeSeriesChartKeySettingsForm.get('type').value; if (showInLegend) { this.timeSeriesChartKeySettingsForm.get('dataHiddenByDefault').enable(); } else { this.timeSeriesChartKeySettingsForm.get('dataHiddenByDefault').patchValue(false, {emitEvent: false}); this.timeSeriesChartKeySettingsForm.get('dataHiddenByDefault').disable(); } + if (type === TimeSeriesChartSeriesType.line) { + this.timeSeriesChartKeySettingsForm.get('lineSettings').enable(); + this.timeSeriesChartKeySettingsForm.get('barSettings').disable(); + } else if (type === TimeSeriesChartSeriesType.bar) { + this.timeSeriesChartKeySettingsForm.get('lineSettings').disable(); + this.timeSeriesChartKeySettingsForm.get('barSettings').enable(); + } } - } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html new file mode 100644 index 0000000000..298997c4f9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html @@ -0,0 +1,111 @@ + + +
+
widgets.time-series-chart.series.line.line
+
+ + {{ 'widgets.time-series-chart.series.line.show-line' | translate }} + +
+
+ + {{ 'widgets.time-series-chart.series.line.step-line' | translate }} + + + + + {{ lineSeriesStepTypeTranslations.get(stepType) | translate }} + + + +
+
+ + {{ 'widgets.time-series-chart.series.line.smooth-line' | translate }} + +
+
+
widgets.time-series-chart.line-type
+ + + + {{ timeSeriesLineTypeTranslations.get(lineType) | translate }} + + + +
+
+
widgets.time-series-chart.line-width
+ + + +
+
+
+
widgets.time-series-chart.series.point.points
+
+ + {{ 'widgets.time-series-chart.series.point.show-points' | translate }} + +
+
+ +
+ {{ 'widgets.time-series-chart.series.point.point-label' | translate }} +
+
+
+ + + + {{ seriesLabelPositionTranslations.get(position) | translate }} + + + + + + + +
+
+
+
widgets.time-series-chart.series.point.point-shape
+ + + + {{ timeSeriesChartShapeTranslations.get(shape) | translate }} + + + +
+
+
widgets.time-series-chart.series.point.point-size
+ + + +
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts new file mode 100644 index 0000000000..46a75edc08 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts @@ -0,0 +1,187 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + Validators +} from '@angular/forms'; +import { + LineSeriesSettings, + lineSeriesStepTypes, + lineSeriesStepTypeTranslations, + seriesLabelPositions, + seriesLabelPositionTranslations, + timeSeriesChartShapes, + timeSeriesChartShapeTranslations, + timeSeriesLineTypes, + timeSeriesLineTypeTranslations +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { merge } from 'rxjs'; +import { formatValue, isDefinedAndNotNull } from '@core/utils'; +import { DataKeyConfigComponent } from '@home/components/widget/config/data-key-config.component'; + +@Component({ + selector: 'tb-time-series-chart-line-settings', + templateUrl: './time-series-chart-line-settings.component.html', + styleUrls: ['./../widget-settings.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeSeriesChartLineSettingsComponent), + multi: true + } + ] +}) +export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValueAccessor { + + lineSeriesStepTypes = lineSeriesStepTypes; + + lineSeriesStepTypeTranslations = lineSeriesStepTypeTranslations; + + timeSeriesLineTypes = timeSeriesLineTypes; + + timeSeriesLineTypeTranslations = timeSeriesLineTypeTranslations; + + seriesLabelPositions = seriesLabelPositions; + + seriesLabelPositionTranslations = seriesLabelPositionTranslations; + + timeSeriesChartShapes = timeSeriesChartShapes; + + timeSeriesChartShapeTranslations = timeSeriesChartShapeTranslations; + + pointLabelPreviewFn = this._pointLabelPreviewFn.bind(this); + + @Input() + disabled: boolean; + + private modelValue: LineSeriesSettings; + + private propagateChange = null; + + public lineSettingsFormGroup: UntypedFormGroup; + + constructor(protected store: Store, + private dataKeyConfigComponent: DataKeyConfigComponent, + private fb: UntypedFormBuilder) { + } + + ngOnInit(): void { + this.lineSettingsFormGroup = this.fb.group({ + showLine: [null, []], + step: [null, []], + stepType: [null, []], + smooth: [null, []], + lineType: [null, []], + lineWidth: [null, [Validators.min(0)]], + showPoints: [null, []], + showPointLabel: [null, []], + pointLabelPosition: [null, []], + pointLabelFont: [null, []], + pointLabelColor: [null, []], + pointShape: [null, []], + pointSize: [null, [Validators.min(0)]], + fillAreaSettings: [null, []] + }); + this.lineSettingsFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + merge(this.lineSettingsFormGroup.get('showLine').valueChanges, + this.lineSettingsFormGroup.get('step').valueChanges, + this.lineSettingsFormGroup.get('showPointLabel').valueChanges) + .subscribe(() => { + this.updateValidators(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.lineSettingsFormGroup.disable({emitEvent: false}); + } else { + this.lineSettingsFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + writeValue(value: LineSeriesSettings): void { + this.modelValue = value; + this.lineSettingsFormGroup.patchValue( + value, {emitEvent: false} + ); + this.updateValidators(); + } + + private updateValidators() { + const showLine: boolean = this.lineSettingsFormGroup.get('showLine').value; + const step: boolean = this.lineSettingsFormGroup.get('step').value; + const showPointLabel: boolean = this.lineSettingsFormGroup.get('showPointLabel').value; + if (showLine) { + this.lineSettingsFormGroup.get('step').enable({emitEvent: false}); + if (step) { + this.lineSettingsFormGroup.get('stepType').enable({emitEvent: false}); + this.lineSettingsFormGroup.get('smooth').disable({emitEvent: false}); + } else { + this.lineSettingsFormGroup.get('stepType').disable({emitEvent: false}); + this.lineSettingsFormGroup.get('smooth').enable({emitEvent: false}); + } + this.lineSettingsFormGroup.get('lineType').enable({emitEvent: false}); + this.lineSettingsFormGroup.get('lineWidth').enable({emitEvent: false}); + } else { + this.lineSettingsFormGroup.get('step').disable({emitEvent: false}); + this.lineSettingsFormGroup.get('stepType').disable({emitEvent: false}); + this.lineSettingsFormGroup.get('smooth').disable({emitEvent: false}); + this.lineSettingsFormGroup.get('lineType').disable({emitEvent: false}); + this.lineSettingsFormGroup.get('lineWidth').disable({emitEvent: false}); + } + if (showPointLabel) { + this.lineSettingsFormGroup.get('pointLabelPosition').enable({emitEvent: false}); + this.lineSettingsFormGroup.get('pointLabelFont').enable({emitEvent: false}); + this.lineSettingsFormGroup.get('pointLabelColor').enable({emitEvent: false}); + } else { + this.lineSettingsFormGroup.get('pointLabelPosition').disable({emitEvent: false}); + this.lineSettingsFormGroup.get('pointLabelFont').disable({emitEvent: false}); + this.lineSettingsFormGroup.get('pointLabelColor').disable({emitEvent: false}); + } + } + + private updateModel() { + this.modelValue = this.lineSettingsFormGroup.getRawValue(); + this.propagateChange(this.modelValue); + } + + private _pointLabelPreviewFn(): string { + const dataKey = this.dataKeyConfigComponent.modelValue; + const widgetConfig = this.dataKeyConfigComponent.widgetConfig; + const units = dataKey.units && dataKey.units.length ? dataKey.units : widgetConfig.config.units; + const decimals = isDefinedAndNotNull(dataKey.decimals) ? dataKey.decimals : + (isDefinedAndNotNull(widgetConfig.config.decimals) ? widgetConfig.config.decimals : 2); + return formatValue(22, decimals, units, false); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index 4183a06db2..6cc766f2ab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -333,6 +333,15 @@ import { import { TimeSeriesChartKeySettingsComponent } from '@home/components/widget/lib/settings/chart/time-series-chart-key-settings.component'; +import { + TimeSeriesChartLineSettingsComponent +} from '@home/components/widget/lib/settings/chart/time-series-chart-line-settings.component'; +import { + TimeSeriesChartFillSettingsComponent +} from '@home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component'; +import { + TimeSeriesChartBarSettingsComponent +} from '@home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component'; @NgModule({ declarations: [ @@ -452,7 +461,10 @@ import { PowerButtonWidgetSettingsComponent, SliderWidgetSettingsComponent, ToggleButtonWidgetSettingsComponent, - TimeSeriesChartKeySettingsComponent + TimeSeriesChartKeySettingsComponent, + TimeSeriesChartLineSettingsComponent, + TimeSeriesChartBarSettingsComponent, + TimeSeriesChartFillSettingsComponent ], imports: [ CommonModule, @@ -577,7 +589,10 @@ import { PowerButtonWidgetSettingsComponent, SliderWidgetSettingsComponent, ToggleButtonWidgetSettingsComponent, - TimeSeriesChartKeySettingsComponent + TimeSeriesChartKeySettingsComponent, + TimeSeriesChartLineSettingsComponent, + TimeSeriesChartBarSettingsComponent, + TimeSeriesChartFillSettingsComponent ] }) export class WidgetSettingsModule { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index a36847b334..1e3370ab84 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6601,6 +6601,20 @@ "stack-mode": "Stack mode", "stack-mode-hint": "Stacks series on the chart. The series with the same unit would be put on top of each other.", "axes": "Axes", + "line-type": "Line type", + "line-type-solid": "Solid", + "line-type-dashed": "Dashed", + "line-type-dotted": "Dotted", + "line-width": "Line width", + "shape-empty-circle": "Empty circle", + "shape-circle": "Circle", + "shape-rect": "Rectangle", + "shape-round-rect": "Rounded rectangle", + "shape-triangle": "Triangle", + "shape-diamond": "Diamond", + "shape-pin": "Pin", + "shape-arrow": "Arrow", + "shape-none": "None", "series": { "legend-settings": "Legend settings", "show-in-legend": "Show in legend", @@ -6610,7 +6624,43 @@ "series-type": "Series type", "type": "Type", "type-line": "Line", - "type-bar": "Bar" + "type-bar": "Bar", + "label-position-top": "Top", + "label-position-bottom": "Bottom", + "fill": "Fill", + "background": "Background", + "fill-type-none": "None", + "fill-type-solid": "Solid", + "fill-type-opacity": "Opacity", + "fill-type-gradient": "Gradient", + "opacity": "Opacity", + "gradient-stops": "Gradient stops", + "gradient-start": "start", + "gradient-end": "end", + "line": { + "line": "Line", + "show-line": "Show line", + "step-line": "Step line", + "step-type-start": "Start", + "step-type-middle": "Middle", + "step-type-end": "End", + "smooth-line": "Smooth line" + }, + "point": { + "points": "Points", + "show-points": "Show points", + "point-label": "Point label", + "point-label-hint": "Display label with value over the series point.", + "point-shape": "Point shape", + "point-size": "Point size" + }, + "bar": { + "show-border": "Show border", + "border-width": "Border width", + "border-radius": "Border radius", + "label": "Label", + "label-hint": "Display label with value over the bar." + } } }, "wind-speed-direction": { From 0c09de5dc19fe3d974708fe2cbb940125cd531dc Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 4 Mar 2024 18:14:23 +0200 Subject: [PATCH 152/209] UI: Implement time series chart axes settings. --- ...e-series-chart-basic-config.component.html | 16 +- .../lib/chart/time-series-chart.models.ts | 11 ++ ...-series-chart-axis-settings.component.html | 122 ++++++++++++ ...me-series-chart-axis-settings.component.ts | 179 ++++++++++++++++++ .../common/widget-settings-common.module.ts | 9 +- .../assets/locale/locale.constant-en_US.json | 21 ++ ui-ngx/src/form.scss | 3 + 7 files changed, 352 insertions(+), 9 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html index 26f0f222d9..4fd7de467f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html @@ -100,13 +100,15 @@
-
widgets.time-series-chart.axes
-
- TODO: Y Axis -
-
- TODO: X Axis -
+
widgets.time-series-chart.axis.axes
+ + + +
( + [ + [AxisPosition.left, 'widgets.time-series-chart.axis.position-left'], + [AxisPosition.right, 'widgets.time-series-chart.axis.position-right'], + [AxisPosition.top, 'widgets.time-series-chart.axis.position-top'], + [AxisPosition.bottom, 'widgets.time-series-chart.axis.position-bottom'] + ] +); + export enum TimeSeriesChartShape { emptyCircle = 'emptyCircle', circle = 'circle', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html new file mode 100644 index 0000000000..8a77a92b19 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html @@ -0,0 +1,122 @@ + + +
+ + + + + {{ axisTitle | translate }} + + + + +
+
widgets.time-series-chart.axis.label
+
+ + + + + + + +
+
+
+
widgets.time-series-chart.axis.position
+ + + + {{ timeSeriesAxisPositionTranslations.get(position) | translate }} + + + +
+
+ +
widgets.time-series-chart.axis.tick-labels
+
+
+ + + + +
+
+
+ + {{ 'widgets.time-series-chart.axis.show-ticks' | translate }} + + + +
+
+ + {{ 'widgets.time-series-chart.axis.show-line' | translate }} + + + +
+
+ +
+ {{ 'widgets.time-series-chart.axis.show-split-lines' | translate }} +
+
+ + +
+
+
+
+
+
widgets.time-series-chart.axis.scale
+
+
widgets.time-series-chart.axis.scale-min
+ + + +
widgets.time-series-chart.axis.scale-max
+ + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts new file mode 100644 index 0000000000..2ae330c276 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts @@ -0,0 +1,179 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { + AxisPosition, + timeSeriesAxisPositionTranslations, + TimeSeriesChartAxisSettings, + TimeSeriesChartYAxisSettings +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { merge } from 'rxjs'; + +@Component({ + selector: 'tb-time-series-chart-axis-settings', + templateUrl: './time-series-chart-axis-settings.component.html', + styleUrls: ['./../../widget-settings.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeSeriesChartAxisSettingsComponent), + multi: true + } + ] +}) +export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValueAccessor { + + settingsExpanded = false; + + axisTitle: string; + + axisPositions: AxisPosition[]; + + timeSeriesAxisPositionTranslations = timeSeriesAxisPositionTranslations; + + @Input() + disabled: boolean; + + @Input() + axisType: 'xAxis' | 'yAxis' = 'xAxis'; + + private modelValue: TimeSeriesChartAxisSettings | TimeSeriesChartYAxisSettings; + + private propagateChange = null; + + public axisSettingsFormGroup: UntypedFormGroup; + + constructor(protected store: Store, + private fb: UntypedFormBuilder) { + } + + ngOnInit(): void { + + this.axisTitle = this.axisType === 'xAxis' ? 'widgets.time-series-chart.axis.x-axis' : 'widgets.time-series-chart.axis.y-axis'; + + this.axisPositions = this.axisType === 'xAxis' ? [AxisPosition.top, AxisPosition.bottom] : + [AxisPosition.left, AxisPosition.right]; + + this.axisSettingsFormGroup = this.fb.group({ + show: [null, []], + label: [null, []], + labelFont: [null, []], + labelColor: [null, []], + position: [null, []], + showTickLabels: [null, []], + tickLabelFont: [null, []], + tickLabelColor: [null, []], + showTicks: [null, []], + ticksColor: [null, []], + showLine: [null, []], + lineColor: [null, []], + showSplitLines: [null, []], + splitLinesColor: [null, []] + }); + if (this.axisType === 'yAxis') { + this.axisSettingsFormGroup.addControl('min', this.fb.control(null, [])); + this.axisSettingsFormGroup.addControl('max', this.fb.control(null, [])); + } + this.axisSettingsFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + merge(this.axisSettingsFormGroup.get('show').valueChanges, + this.axisSettingsFormGroup.get('showTickLabels').valueChanges, + this.axisSettingsFormGroup.get('showTicks').valueChanges, + this.axisSettingsFormGroup.get('showLine').valueChanges, + this.axisSettingsFormGroup.get('showSplitLines').valueChanges) + .subscribe(() => { + this.updateValidators(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.axisSettingsFormGroup.disable({emitEvent: false}); + } else { + this.axisSettingsFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + writeValue(value: TimeSeriesChartAxisSettings | TimeSeriesChartYAxisSettings): void { + this.modelValue = value; + this.axisSettingsFormGroup.patchValue( + value, {emitEvent: false} + ); + this.updateValidators(); + this.axisSettingsFormGroup.get('show').valueChanges.subscribe((show) => { + this.settingsExpanded = show; + }); + } + + private updateValidators() { + const show: boolean = this.axisSettingsFormGroup.get('show').value; + const showTickLabels: boolean = this.axisSettingsFormGroup.get('showTickLabels').value; + const showTicks: boolean = this.axisSettingsFormGroup.get('showTicks').value; + const showLine: boolean = this.axisSettingsFormGroup.get('showLine').value; + const showSplitLines: boolean = this.axisSettingsFormGroup.get('showSplitLines').value; + if (show) { + this.axisSettingsFormGroup.enable({emitEvent: false}); + if (showTickLabels) { + this.axisSettingsFormGroup.get('tickLabelFont').enable({emitEvent: false}); + this.axisSettingsFormGroup.get('tickLabelColor').enable({emitEvent: false}); + } else { + this.axisSettingsFormGroup.get('tickLabelFont').disable({emitEvent: false}); + this.axisSettingsFormGroup.get('tickLabelColor').disable({emitEvent: false}); + } + if (showTicks) { + this.axisSettingsFormGroup.get('ticksColor').enable({emitEvent: false}); + } else { + this.axisSettingsFormGroup.get('ticksColor').disable({emitEvent: false}); + } + if (showLine) { + this.axisSettingsFormGroup.get('lineColor').enable({emitEvent: false}); + } else { + this.axisSettingsFormGroup.get('lineColor').disable({emitEvent: false}); + } + if (showSplitLines) { + this.axisSettingsFormGroup.get('splitLinesColor').enable({emitEvent: false}); + } else { + this.axisSettingsFormGroup.get('splitLinesColor').disable({emitEvent: false}); + } + } else { + this.axisSettingsFormGroup.disable({emitEvent: false}); + this.axisSettingsFormGroup.get('show').enable({emitEvent: false}); + if (this.axisType === 'yAxis') { + this.axisSettingsFormGroup.get('min').enable({emitEvent: false}); + this.axisSettingsFormGroup.get('max').enable({emitEvent: false}); + } + } + } + + private updateModel() { + this.modelValue = this.axisSettingsFormGroup.getRawValue(); + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index e39c079f75..a7922dfdc4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -92,6 +92,9 @@ import { import { WidgetButtonCustomStylePanelComponent } from '@home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component'; +import { + TimeSeriesChartAxisSettingsComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component'; @NgModule({ declarations: [ @@ -127,7 +130,8 @@ import { WidgetActionSettingsPanelComponent, WidgetButtonAppearanceComponent, WidgetButtonCustomStyleComponent, - WidgetButtonCustomStylePanelComponent + WidgetButtonCustomStylePanelComponent, + TimeSeriesChartAxisSettingsComponent ], imports: [ CommonModule, @@ -167,7 +171,8 @@ import { WidgetActionSettingsPanelComponent, WidgetButtonAppearanceComponent, WidgetButtonCustomStyleComponent, - WidgetButtonCustomStylePanelComponent + WidgetButtonCustomStylePanelComponent, + TimeSeriesChartAxisSettingsComponent ], providers: [ ColorSettingsComponentService, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 1e3370ab84..4a05e1c3c1 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6615,6 +6615,27 @@ "shape-pin": "Pin", "shape-arrow": "Arrow", "shape-none": "None", + "axis": { + "axes": "Axes", + "x-axis": "X axis", + "y-axis": "Y axis", + "label": "Label", + "position": "Position", + "position-left": "Left", + "position-right": "Right", + "position-top": "Top", + "position-bottom": "Bottom", + "tick-labels": "Tick labels", + "show-ticks": "Show ticks", + "show-line": "Show line", + "show-split-lines": "Show split lines", + "show-split-lines-x-axis-hint": "If enabled, the vertical lines on the chart will be shown.", + "show-split-lines-y-axis-hint": "If enabled, the horizontal lines on the chart will be shown.", + "scale": "Scale", + "scale-min": "min", + "scale-max": "max", + "scale-auto": "Auto" + }, "series": { "legend-settings": "Legend settings", "show-in-legend": "Show in legend", diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index c3e2599bd9..dfe6667afd 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -55,6 +55,9 @@ &.no-padding-bottom { padding-bottom: 0; } + &.no-padding-top { + padding-top: 0; + } &.no-padding { padding: 0; } From 4e759dd4251ed1d003d8509243368b2a7bb89a46 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 4 Mar 2024 13:14:29 +0100 Subject: [PATCH 153/209] fixed lwm2m uplink executor lock --- .../lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java index f8ff5c70a0..174d7d4f59 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java @@ -480,7 +480,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl CountDownLatch latch = new CountDownLatch(targetIds.size()); targetIds.forEach(versionedId -> sendReadRequest(lwM2MClient, versionedId, new TbLwM2MLatchCallback<>(latch, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId)))); - latch.await(); + latch.await(config.getTimeout(), TimeUnit.MILLISECONDS); } catch (InterruptedException e) { log.error("[{}] Failed to await Read requests!", lwM2MClient.getEndpoint(), e); } catch (Exception e) { From ad99379f9025a9a1efe1d3323e93f09a26260335 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 4 Mar 2024 16:19:35 +0100 Subject: [PATCH 154/209] fixed possible lock in sendObserveRequests --- .../lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java index 174d7d4f59..8ba6815e30 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java @@ -498,7 +498,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl targetIds.forEach(targetId -> sendObserveRequest(lwM2MClient, targetId, new TbLwM2MLatchCallback<>(latch, new TbLwM2MObserveCallback(this, logService, lwM2MClient, targetId)))); - latch.await(); + latch.await(config.getTimeout(), TimeUnit.MILLISECONDS); } catch (InterruptedException e) { log.error("[{}] Failed to await Observe requests!", lwM2MClient.getEndpoint(), e); } catch (Exception e) { From b91c1ba288dd5ce22a1cd837f34a36752f82928e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 5 Mar 2024 19:26:04 +0200 Subject: [PATCH 155/209] UI: Implement thresholds config for Time series chart. --- ...e-series-chart-basic-config.component.html | 10 +- .../basic/common/data-key-row.component.html | 146 +------ .../basic/common/data-key-row.component.scss | 26 +- .../basic/common/data-key-row.component.ts | 235 +---------- .../lib/chart/time-series-chart.models.ts | 95 ++++- ...e-series-chart-bar-settings.component.html | 2 + ...-series-chart-line-settings.component.html | 2 + ...-series-chart-axis-settings.component.html | 4 + ...-series-chart-threshold-row.component.html | 107 +++++ ...-series-chart-threshold-row.component.scss | 64 +++ ...me-series-chart-threshold-row.component.ts | 261 ++++++++++++ ...rt-threshold-settings-panel.component.html | 121 ++++++ ...rt-threshold-settings-panel.component.scss | 47 +++ ...hart-threshold-settings-panel.component.ts | 139 ++++++ ...ries-chart-thresholds-panel.component.html | 47 +++ ...ries-chart-thresholds-panel.component.scss | 47 +++ ...series-chart-thresholds-panel.component.ts | 213 ++++++++++ .../common/data-key-input.component.html | 152 +++++++ .../common/data-key-input.component.scss | 46 ++ .../common/data-key-input.component.ts | 398 ++++++++++++++++++ .../common/entity-alias-input.component.html | 49 +++ .../common/entity-alias-input.component.scss | 20 + .../common/entity-alias-input.component.ts | 156 +++++++ .../common/widget-settings-common.module.ts | 25 +- .../assets/locale/locale.constant-en_US.json | 31 ++ 25 files changed, 2049 insertions(+), 394 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/entity-alias-input.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/entity-alias-input.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/entity-alias-input.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html index 4fd7de467f..14cf0c7e11 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html @@ -39,9 +39,13 @@ [entityAliasId]="datasource?.entityAliasId" formControlName="series"> -
-
TODO: Thresholds
-
+ +
widget-config.appearance
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html index 1c4767b963..3ef6891808 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html @@ -22,123 +22,19 @@ {{ 'datakey.latest' | translate }} - - - -
-
-
- - notifications - - - timeline - -
-
- - - -
-
- - -
-
- -
- - - - - notifications - - - timeline - - - - - -
-
- entity.no-keys-found -
- - - {{ translate.get('entity.no-key-matching', - {key: truncate.transform(keySearchText, true, 6, '...')}) | async }} - - - entity.create-new-key - - - {{'entity.create-new-key' | translate }} - notifications - - - timeline - - -
-
-
-
+ + @@ -193,19 +89,3 @@
- - - f() - - - - - - - - {{ modelValue?.aggregationType }}({{ modelValue?.name }}) - - - {{modelValue?.name}} - - diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss index 198fa64518..be91ff173c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss @@ -16,37 +16,13 @@ @import '../../../../../../../../scss/constants'; .tb-data-key-row { - .mat-mdc-form-field.tb-inline-field.tb-key-field { - .mat-mdc-text-field-wrapper:not(.mdc-text-field--outlined) { - padding-left: 8px; - padding-right: 0; - .mat-mdc-form-field-infix { - padding-top: 0; - padding-bottom: 6px; - .mdc-evolution-chip-set .mdc-evolution-chip { - margin: 0; - } - input.mat-mdc-chip-input { - height: 32px; - margin-left: 0; - } - } - } - .mat-mdc-chip.mat-mdc-standard-chip.tb-datakey-chip { - .tb-attribute-chip { - .tb-chip-labels { - background: transparent; - } - } - } - } .tb-source-field { width: 108px; min-width: 108px; } - .tb-key-field { + .tb-data-key-input { flex: 1; min-width: 150px; @media #{$mat-gt-sm} { diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts index d862a51566..b2710db346 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts @@ -17,15 +17,11 @@ import { ChangeDetectorRef, Component, - ElementRef, EventEmitter, forwardRef, Input, - OnChanges, OnInit, Output, - SimpleChanges, - ViewChild, ViewEncapsulation } from '@angular/core'; import { @@ -49,15 +45,8 @@ import { widgetType } from '@shared/models/widget.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; -import { AggregationType } from '@shared/models/time/time.models'; -import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; -import { MatChipGrid, MatChipInputEvent } from '@angular/material/chips'; import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; -import { MatAutocomplete, MatAutocompleteTrigger } from '@angular/material/autocomplete'; -import { Observable, of } from 'rxjs'; -import { filter, map, mergeMap, publishReplay, refCount, share, tap } from 'rxjs/operators'; -import { TranslateService } from '@ngx-translate/core'; -import { TruncatePipe } from '@shared/pipe/truncate.pipe'; +import { merge } from 'rxjs'; import { DataKeyConfigDialogComponent, DataKeyConfigDialogData @@ -66,8 +55,6 @@ import { deepClone } from '@core/utils'; import { Dashboard } from '@shared/models/dashboard.models'; import { IAliasController } from '@core/api/widget-api.models'; import { coerceBoolean } from '@shared/decorators/coercion'; -import { alarmFields } from '@shared/models/alarm.models'; -import { UtilsService } from '@core/services/utils.service'; import { TimeSeriesChartKeySettings, TimeSeriesChartSeriesType, @@ -101,22 +88,12 @@ export const dataKeyRowValidator = (control: AbstractControl): ValidationErrors ], encapsulation: ViewEncapsulation.None }) -export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChanges { - - dataKeyTypes = DataKeyType; - widgetTypes = widgetType; +export class DataKeyRowComponent implements ControlValueAccessor, OnInit { timeSeriesChartSeriesTypes = timeSeriesChartSeriesTypes; timeSeriesChartSeriesTypeTranslations = timeSeriesChartSeriesTypeTranslations; timeSeriesChartSeriesTypeIcons = timeSeriesChartSeriesTypeIcons; - separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON]; - - @ViewChild('keyInput') keyInput: ElementRef; - @ViewChild('keyAutocomplete') matAutocomplete: MatAutocomplete; - @ViewChild(MatAutocompleteTrigger) autocomplete: MatAutocompleteTrigger; - @ViewChild('chipList') chipList: MatChipGrid; - @Input() disabled: boolean; @@ -181,23 +158,13 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan @Output() keyRemoved = new EventEmitter(); - keysFormControl: UntypedFormControl; - keyFormControl: UntypedFormControl; keyRowFormGroup: UntypedFormGroup; modelValue: DataKey; - filteredKeys: Observable>; - - keySearchText = ''; - - alarmKeys: Array; - functionTypeKeys: Array; - - private latestKeySearchTextResult: Array = null; - private keyFetchObservable$: Observable> = null; + generateDataKey = this._generateDataKey.bind(this); get widgetType(): widgetType { return this.widgetConfigComponent.widgetType; @@ -256,29 +223,11 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan constructor(private fb: UntypedFormBuilder, private dialog: MatDialog, private cd: ChangeDetectorRef, - public translate: TranslateService, - public truncate: TruncatePipe, - private utils: UtilsService, private widgetConfigComponent: WidgetConfigComponent) { } ngOnInit() { - this.alarmKeys = []; - for (const name of Object.keys(alarmFields)) { - this.alarmKeys.push({ - name, - type: DataKeyType.alarm - }); - } - this.functionTypeKeys = []; - for (const type of this.utils.getPredefinedFunctionsList()) { - this.functionTypeKeys.push({ - name: type, - type: DataKeyType.function - }); - } - this.keyFormControl = this.fb.control(''); - this.keysFormControl = this.fb.control([], this.required ? [Validators.required] : []); + this.keyFormControl = this.fb.control(null, this.required ? [Validators.required] : []); this.keyRowFormGroup = this.fb.group({ label: [null, []], color: [null, []], @@ -287,59 +236,13 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan }); if (this.hasAdditionalLatestDataKeys) { this.keyRowFormGroup.addControl('latest', this.fb.control(false)); - this.keyRowFormGroup.valueChanges.subscribe( - () => this.clearKeySearchCache() - ); } if (this.showTimeSeriesType) { this.keyRowFormGroup.addControl('timeSeriesType', this.fb.control(null)); } - this.keyRowFormGroup.valueChanges.subscribe( + merge(this.keyFormControl.valueChanges, this.keyRowFormGroup.valueChanges).subscribe( () => this.updateModel() ); - this.filteredKeys = this.keyFormControl.valueChanges - .pipe( - tap((value: string | DataKey) => { - if (value && typeof value !== 'string') { - this.addKeyFromChipValue(value); - } else if (value === null) { - this.clearKeyChip(this.keyInput.nativeElement.value); - } - }), - filter((value) => typeof value === 'string'), - map((value) => value ? (typeof value === 'string' ? value : value.name) : ''), - mergeMap(name => this.fetchKeys(name) ), - share() - ); - } - - private reset() { - if (this.keyInput) { - this.keyInput.nativeElement.value = ''; - } - this.keyFormControl.patchValue('', {emitEvent: false}); - this.latestKeySearchTextResult = null; - } - - ngOnChanges(changes: SimpleChanges): void { - for (const propName of Object.keys(changes)) { - const change = changes[propName]; - if (!change.firstChange && change.currentValue !== change.previousValue) { - if (['deviceId', 'entityAliasId'].includes(propName)) { - this.clearKeySearchCache(); - } else if (['datasourceType'].includes(propName)) { - if ([DatasourceType.device, DatasourceType.entity].includes(change.previousValue) && - [DatasourceType.device, DatasourceType.entity].includes(change.currentValue)) { - this.clearKeySearchCache(); - } else { - this.clearKeySearchCache(); - setTimeout(() => { - this.reset(); - }, 1); - } - } - } - } } registerOnChange(fn: any): void { @@ -352,10 +255,10 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (isDisabled) { - this.keysFormControl.disable({emitEvent: false}); + this.keyFormControl.disable({emitEvent: false}); this.keyRowFormGroup.disable({emitEvent: false}); } else { - this.keysFormControl.enable({emitEvent: false}); + this.keyFormControl.enable({emitEvent: false}); this.keyRowFormGroup.enable({emitEvent: false}); } } @@ -382,36 +285,10 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan timeSeriesType }, {emitEvent: false}); } - this.keysFormControl.patchValue(this.modelValue ? [this.modelValue] : [], {emitEvent: false}); + this.keyFormControl.patchValue(deepClone(this.modelValue), {emitEvent: false}); this.cd.markForCheck(); } - dataKeyHasAggregation(): boolean { - return this.widgetConfigComponent.widgetType === widgetType.latest && this.modelValue?.type === DataKeyType.timeseries - && this.modelValue?.aggregationType && this.modelValue?.aggregationType !== AggregationType.NONE; - } - - dataKeyHasPostprocessing(): boolean { - return !!this.modelValue?.postFuncBody; - } - - displayKeyFn(key?: DataKey): string | undefined { - return key ? key.name : undefined; - } - - createKey(name: string, dataKeyType: DataKeyType = this.dataKeyType) { - this.addKeyFromChipValue({name: name ? name.trim() : '', type: dataKeyType}); - } - - addKey(event: MatChipInputEvent): void { - const value = event.value; - if ((value || '').trim() && this.dataKeyType) { - this.addKeyFromChipValue({name: value.trim(), type: this.dataKeyType}); - } else { - this.clearKeyChip(); - } - } - editKey(advanced = false) { this.dialog.open(DataKeyConfigDialogComponent, { @@ -451,104 +328,20 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan }); } - removeKey() { - this.modelValue = null; - this.updateModel(); - this.clearKeyChip(); - } - - textIsNotEmpty(text: string): boolean { - return text && text.length > 0; - } - - clearKeyChip(value: string = '', focus = true) { - this.autocomplete.closePanel(); - this.keyInput.nativeElement.value = value; - this.keyFormControl.patchValue(value, {emitEvent: focus}); - if (focus) { - setTimeout(() => { - this.keyInput.nativeElement.blur(); - this.keyInput.nativeElement.focus(); - }, 0); - } - } - - onKeyInputFocus() { - if (!this.modelValue?.type) { - this.keyFormControl.updateValueAndValidity({onlySelf: true, emitEvent: true}); - } - } - - private fetchKeys(searchText?: string): Observable> { - if (this.keySearchText !== searchText || this.latestKeySearchTextResult === null) { - this.keySearchText = searchText; - const dataKeyFilter = this.createDataKeyFilter(this.keySearchText); - return this.getKeys().pipe( - map(name => name.filter(dataKeyFilter)), - tap(res => this.latestKeySearchTextResult = res) - ); - } - return of(this.latestKeySearchTextResult); - } - - private getKeys(): Observable> { - if (this.keyFetchObservable$ === null) { - let fetchObservable: Observable>; - if (this.datasourceType === DatasourceType.function) { - const targetKeysList = this.widgetType === widgetType.alarm ? this.alarmKeys : this.functionTypeKeys; - fetchObservable = of(targetKeysList); - } else if (this.datasourceType === DatasourceType.entity && this.entityAliasId || - this.datasourceType === DatasourceType.device && this.deviceId) { - const dataKeyTypes = [DataKeyType.timeseries]; - if (this.isLatestDataKeys || this.widgetType === widgetType.latest || this.widgetType === widgetType.alarm) { - dataKeyTypes.push(DataKeyType.attribute); - dataKeyTypes.push(DataKeyType.entityField); - if (this.widgetType === widgetType.alarm) { - dataKeyTypes.push(DataKeyType.alarm); - } - } - if (this.datasourceType === DatasourceType.device) { - fetchObservable = this.callbacks.fetchEntityKeysForDevice(this.deviceId, dataKeyTypes); - } else { - fetchObservable = this.callbacks.fetchEntityKeys(this.entityAliasId, dataKeyTypes); - } - } else { - fetchObservable = of([]); - } - this.keyFetchObservable$ = fetchObservable.pipe( - publishReplay(1), - refCount() - ); - } - return this.keyFetchObservable$; - } - - private createDataKeyFilter(query: string): (key: DataKey) => boolean { - const lowercaseQuery = query.toLowerCase(); - return key => key.name.toLowerCase().startsWith(lowercaseQuery); - } - - private addKeyFromChipValue(chip: DataKey) { - this.modelValue = this.callbacks.generateDataKey(chip.name, chip.type, this.dataKeySettingsSchema, this.isLatestDataKeys, + private _generateDataKey(key: DataKey): DataKey { + key = this.callbacks.generateDataKey(key.name, key.type, this.dataKeySettingsSchema, this.isLatestDataKeys, this.dataKeySettingsFunction); if (!this.keyRowFormGroup.get('label').value) { - this.keyRowFormGroup.get('label').patchValue(this.modelValue.label, {emitEvent: false}); + this.keyRowFormGroup.get('label').patchValue(key.label, {emitEvent: false}); } if (this.showTimeSeriesType) { - this.keyRowFormGroup.get('timeSeriesType').patchValue(this.modelValue.settings?.type, {emitEvent: false}); + this.keyRowFormGroup.get('timeSeriesType').patchValue(key.settings?.type, {emitEvent: false}); } - this.updateModel(); - this.clearKeyChip('', false); - } - - private clearKeySearchCache() { - this.keySearchText = ''; - this.keyFetchObservable$ = null; - this.latestKeySearchTextResult = null; + return key; } private updateModel() { - this.keysFormControl.patchValue(this.modelValue ? [this.modelValue] : [], {emitEvent: false}); + this.modelValue = this.keyFormControl.value; if (this.modelValue !== null) { const value: DataKey = this.keyRowFormGroup.value; this.modelValue = {...this.modelValue, ...value}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index 6b2e3a5c6b..dfcd1aaced 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -25,7 +25,7 @@ import { import { ComponentStyle, Font, simpleDateFormat, textStyle } from '@shared/models/widget-settings.models'; import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared'; import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts'; -import { formatValue, isDefinedAndNotNull, parseFunction } from '@core/utils'; +import { formatValue, isDefinedAndNotNull, isUndefinedOrNull, parseFunction } from '@core/utils'; import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; import tinycolor from 'tinycolor2'; import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D'; @@ -40,7 +40,8 @@ import { import { DataKey } from '@shared/models/widget.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { TbColorScheme } from '@shared/models/color.models'; -import { DoughnutLayout } from '@home/components/widget/lib/chart/doughnut-widget.models'; +import { AbstractControl, ValidationErrors } from '@angular/forms'; +import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel'; const timeSeriesChartColorScheme: TbColorScheme = { 'threshold.line': { @@ -154,12 +155,41 @@ export enum ThresholdLabelPosition { insideEndBottom = 'insideEndBottom' } +export const timeSeriesThresholdLabelPositions = Object.keys(ThresholdLabelPosition) as ThresholdLabelPosition[]; + +export const timeSeriesThresholdLabelPositionTranslations = new Map( + [ + [ThresholdLabelPosition.start, 'widgets.time-series-chart.threshold.label-position-start'], + [ThresholdLabelPosition.middle, 'widgets.time-series-chart.threshold.label-position-middle'], + [ThresholdLabelPosition.end, 'widgets.time-series-chart.threshold.label-position-end'], + [ThresholdLabelPosition.insideStart, 'widgets.time-series-chart.threshold.label-position-inside-start'], + [ThresholdLabelPosition.insideStartTop, 'widgets.time-series-chart.threshold.label-position-inside-start-top'], + [ThresholdLabelPosition.insideStartBottom, 'widgets.time-series-chart.threshold.label-position-inside-start-bottom'], + [ThresholdLabelPosition.insideMiddle, 'widgets.time-series-chart.threshold.label-position-inside-middle'], + [ThresholdLabelPosition.insideMiddleTop, 'widgets.time-series-chart.threshold.label-position-inside-middle-top'], + [ThresholdLabelPosition.insideMiddleBottom, 'widgets.time-series-chart.threshold.label-position-inside-middle-bottom'], + [ThresholdLabelPosition.insideEnd, 'widgets.time-series-chart.threshold.label-position-inside-end'], + [ThresholdLabelPosition.insideEndTop, 'widgets.time-series-chart.threshold.label-position-inside-end-top'], + [ThresholdLabelPosition.insideEndBottom, 'widgets.time-series-chart.threshold.label-position-inside-end-bottom'] + ] +); + export enum TimeSeriesChartThresholdType { constant = 'constant', latestKey = 'latestKey', entity = 'entity' } +export const timeSeriesThresholdTypes = Object.keys(TimeSeriesChartThresholdType) as TimeSeriesChartThresholdType[]; + +export const timeSeriesThresholdTypeTranslations = new Map( + [ + [TimeSeriesChartThresholdType.constant, 'widgets.time-series-chart.threshold.type-constant'], + [TimeSeriesChartThresholdType.latestKey, 'widgets.time-series-chart.threshold.type-latest-key'], + [TimeSeriesChartThresholdType.entity, 'widgets.time-series-chart.threshold.type-entity'] + ] +); + export enum SeriesFillType { none = 'none', opacity = 'opacity', @@ -273,6 +303,40 @@ export interface TimeSeriesChartThreshold { labelColor: string; } +export const timeSeriesChartThresholdValid = (threshold: TimeSeriesChartThreshold): boolean => { + if (!threshold.type) { + return false; + } + switch (threshold.type) { + case TimeSeriesChartThresholdType.constant: + if (isUndefinedOrNull(threshold.value)) { + return false; + } + break; + case TimeSeriesChartThresholdType.latestKey: + if (!threshold.latestKey || !threshold.latestKeyType) { + return false; + } + break; + case TimeSeriesChartThresholdType.entity: + if (!threshold.entityAlias || !threshold.entityKey || !threshold.entityKeyType) { + return false; + } + break; + } + return true; +}; + +export const timeSeriesChartThresholdValidator = (control: AbstractControl): ValidationErrors | null => { + const threshold: TimeSeriesChartThreshold = control.value; + if (!timeSeriesChartThresholdValid(threshold)) { + return { + threshold: true + }; + } + return null; +}; + export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold = { type: TimeSeriesChartThresholdType.constant, units: '', @@ -709,8 +773,6 @@ const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[], }, markLine: { animation: true, - symbol: [item.settings.startSymbol, item.settings.endSymbol], - symbolSize: [item.settings.startSymbolSize, item.settings.endSymbolSize], lineStyle: { width: item.settings.lineWidth, color: prepareChartThemeColor(item.settings.lineColor, darkMode, 'threshold.line'), @@ -737,14 +799,10 @@ const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[], seriesOption.markLine.data = []; if (Array.isArray(item.value)) { for (const val of item.value) { - seriesOption.markLine.data.push({ - yAxis: val - }); + seriesOption.markLine.data.push(createThresholdData(val, item)); } } else { - seriesOption.markLine.data.push({ - yAxis: item.value - }); + seriesOption.markLine.data.push(createThresholdData(item.value, item)); } series.push(seriesOption); } @@ -752,6 +810,23 @@ const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[], return series; }; +const createThresholdData = (val: string | number, item: TimeSeriesChartThresholdItem): MarkLine2DDataItemOption => [ + { + xAxis: 'min', + yAxis: val, + value: val, + symbol: item.settings.startSymbol, + symbolSize: item.settings.startSymbolSize + }, + { + xAxis: 'max', + yAxis: val, + value: val, + symbol: item.settings.endSymbol, + symbolSize: item.settings.endSymbolSize + } + ]; + const generateChartSeries = (dataItems: TimeSeriesChartDataItem[], timeInterval: Interval, stack: boolean, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html index a62157dba5..447784091e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html @@ -49,6 +49,8 @@ +
+
+ + + + {{ timeSeriesThresholdTypeTranslations.get(type) | translate }} + + + + + +
+
+ + + + warning + + + + + + +
+
+ + +
+
+ + +
+
+ + + +
+
+
+ +
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.scss new file mode 100644 index 0000000000..2a7dafc5f1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.scss @@ -0,0 +1,64 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.tb-time-series-threshold-row { + .tb-threshold-source-field { + flex: 1; + min-width: 200px; + display: flex; + align-items: center; + flex-direction: row; + gap: 12px; + } + + .tb-threshold-type-field { + flex: 1; + } + + .tb-entity-alias-input { + flex: 1; + } + + .tb-threshold-key-value-field { + flex: 1; + min-width: 150px; + .tb-inline-field, .tb-data-key-input { + flex: 1; + } + } + + .tb-threshold-key-value-field, .tb-color-field, .tb-units-field, .tb-decimals-field { + display: flex; + flex-direction: row; + place-content: center; + align-items: center; + } + + .tb-units-field { + width: 80px; + min-width: 80px; + } + + .tb-color-field { + width: 40px; + min-width: 40px; + } + + .tb-decimals-field { + width: 60px; + min-width: 60px; + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.ts new file mode 100644 index 0000000000..e93969f795 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.ts @@ -0,0 +1,261 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectorRef, + Component, + EventEmitter, + forwardRef, + Input, + OnInit, + Output, + Renderer2, + ViewContainerRef, + ViewEncapsulation +} from '@angular/core'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validators +} from '@angular/forms'; +import { + TimeSeriesChartThreshold, + TimeSeriesChartThresholdType, + timeSeriesThresholdTypes, + timeSeriesThresholdTypeTranslations +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { + TimeSeriesChartThresholdsPanelComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component'; +import { IAliasController } from '@core/api/widget-api.models'; +import { DataKey, Datasource, DatasourceType, WidgetConfig } from '@shared/models/widget.models'; +import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { deepClone } from '@core/utils'; +import { + TimeSeriesChartThresholdSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component'; + +@Component({ + selector: 'tb-time-series-chart-threshold-row', + templateUrl: './time-series-chart-threshold-row.component.html', + styleUrls: ['./time-series-chart-threshold-row.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeSeriesChartThresholdRowComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class TimeSeriesChartThresholdRowComponent implements ControlValueAccessor, OnInit { + + DataKeyType = DataKeyType; + + DatasourceType = DatasourceType; + + TimeSeriesChartThresholdType = TimeSeriesChartThresholdType; + + timeSeriesThresholdTypes = timeSeriesThresholdTypes; + + timeSeriesThresholdTypeTranslations = timeSeriesThresholdTypeTranslations; + + get aliasController(): IAliasController { + return this.thresholdsPanel.aliasController; + } + + get dataKeyCallbacks(): DataKeysCallbacks { + return this.thresholdsPanel.dataKeyCallbacks; + } + + get datasource(): Datasource { + return this.thresholdsPanel.datasource; + } + + get widgetConfig(): WidgetConfig { + return this.thresholdsPanel.widgetConfig; + } + + @Input() + disabled: boolean; + + @Output() + thresholdRemoved = new EventEmitter(); + + thresholdFormGroup: UntypedFormGroup; + + modelValue: TimeSeriesChartThreshold; + + latestKeyFormControl: UntypedFormControl; + + entityKeyFormControl: UntypedFormControl; + + private propagateChange = (_val: any) => {}; + + constructor(private fb: UntypedFormBuilder, + private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef, + private thresholdsPanel: TimeSeriesChartThresholdsPanelComponent, + private cd: ChangeDetectorRef) { + } + + ngOnInit() { + this.thresholdFormGroup = this.fb.group({ + type: [null, []], + value: [null, [Validators.required]], + entityAlias: [null, [Validators.required]], + lineColor: [null, []], + units: [null, []], + decimals: [null, []] + }); + this.latestKeyFormControl = this.fb.control(null, [Validators.required]); + this.entityKeyFormControl = this.fb.control(null, [Validators.required]); + this.thresholdFormGroup.valueChanges.subscribe( + () => this.updateModel() + ); + this.latestKeyFormControl.valueChanges.subscribe( + () => this.updateModel() + ); + this.entityKeyFormControl.valueChanges.subscribe( + () => this.updateModel() + ); + this.thresholdFormGroup.get('type').valueChanges.subscribe(() => { + this.updateValidators(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.thresholdFormGroup.disable({emitEvent: false}); + this.latestKeyFormControl.disable({emitEvent: false}); + this.entityKeyFormControl.disable({emitEvent: false}); + } else { + this.thresholdFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + writeValue(value: TimeSeriesChartThreshold): void { + this.modelValue = value; + this.thresholdFormGroup.patchValue( + { + type: value.type, + value: value.value, + entityAlias: value.entityAlias, + lineColor: value.lineColor, + units: value.units, + decimals: value.decimals, + }, {emitEvent: false} + ); + if (value.type === TimeSeriesChartThresholdType.latestKey) { + this.latestKeyFormControl.patchValue({ + type: value.latestKeyType, + name: value.latestKey + }, {emitEvent: false}); + } else if (value.type === TimeSeriesChartThresholdType.entity) { + this.entityKeyFormControl.patchValue({ + type: value.entityKeyType, + name: value.entityKey + }, {emitEvent: false}); + } + this.updateValidators(); + this.cd.markForCheck(); + } + + editThreshold($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + thresholdSettings: deepClone(this.modelValue), + widgetConfig: this.widgetConfig + }; + const thresholdSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, TimeSeriesChartThresholdSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null, + ctx, + {}, + {}, {}, true); + thresholdSettingsPanelPopover.tbComponentRef.instance.popover = thresholdSettingsPanelPopover; + thresholdSettingsPanelPopover.tbComponentRef.instance.thresholdSettingsApplied.subscribe((thresholdSettings) => { + thresholdSettingsPanelPopover.hide(); + this.modelValue = {...this.modelValue, ...thresholdSettings}; + this.thresholdFormGroup.patchValue( + {lineColor: this.modelValue.lineColor}, + {emitEvent: false}); + this.propagateChange(this.modelValue); + }); + } + } + + private updateValidators() { + const type: TimeSeriesChartThresholdType = this.thresholdFormGroup.get('type').value; + if (type === TimeSeriesChartThresholdType.constant) { + this.thresholdFormGroup.get('value').enable({emitEvent: false}); + this.thresholdFormGroup.get('entityAlias').disable({emitEvent: false}); + this.latestKeyFormControl.disable({emitEvent: false}); + this.entityKeyFormControl.disable({emitEvent: false}); + } else if (type === TimeSeriesChartThresholdType.latestKey) { + this.thresholdFormGroup.get('value').disable({emitEvent: false}); + this.thresholdFormGroup.get('entityAlias').disable({emitEvent: false}); + this.latestKeyFormControl.enable({emitEvent: false}); + this.entityKeyFormControl.disable({emitEvent: false}); + } else if (type === TimeSeriesChartThresholdType.entity) { + this.thresholdFormGroup.get('value').disable({emitEvent: false}); + this.thresholdFormGroup.get('entityAlias').enable({emitEvent: false}); + this.latestKeyFormControl.disable({emitEvent: false}); + this.entityKeyFormControl.enable({emitEvent: false}); + } + } + + private updateModel() { + const value = this.thresholdFormGroup.value; + this.modelValue.type = value.type; + this.modelValue.value = value.value; + this.modelValue.entityAlias = value.entityAlias; + this.modelValue.lineColor = value.lineColor; + this.modelValue.units = value.units; + this.modelValue.decimals = value.decimals; + if (value.type === TimeSeriesChartThresholdType.latestKey) { + const latestKey: DataKey = this.latestKeyFormControl.value; + this.modelValue.latestKey = latestKey?.name; + this.modelValue.latestKeyType = (latestKey?.type as any); + } else if (value.type === TimeSeriesChartThresholdType.entity) { + const entityKey: DataKey = this.entityKeyFormControl.value; + this.modelValue.entityKey = entityKey?.name; + this.modelValue.entityKeyType = (entityKey?.type as any); + } + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.html new file mode 100644 index 0000000000..db4cc27bc9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.html @@ -0,0 +1,121 @@ + +
+
{{ 'widgets.time-series-chart.threshold.threshold-settings' | translate }}
+
+
+
widgets.time-series-chart.threshold.line-appearance
+
+
widgets.time-series-chart.threshold.line-color
+ + +
+
+
widgets.time-series-chart.line-type
+ + + + {{ timeSeriesLineTypeTranslations.get(lineType) | translate }} + + + +
+
+
widgets.time-series-chart.line-width
+ + + +
+
+
widgets.time-series-chart.threshold.start-symbol
+
+ + + + {{ timeSeriesChartShapeTranslations.get(shape) | translate }} + + + +
widgets.time-series-chart.threshold.symbol-size
+ + + +
+
+
+
widgets.time-series-chart.threshold.end-symbol
+
+ + + + {{ timeSeriesChartShapeTranslations.get(shape) | translate }} + + + +
widgets.time-series-chart.threshold.symbol-size
+ + + +
+
+
+ + {{ 'widgets.time-series-chart.threshold.label' | translate }} + +
+ + + + {{ timeSeriesThresholdLabelPositionTranslations.get(position) | translate }} + + + + + + + +
+
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.scss new file mode 100644 index 0000000000..260b199854 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.scss @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../../../../../../scss/constants'; + +.tb-threshold-settings-panel { + width: 530px; + display: flex; + flex-direction: column; + gap: 16px; + @media #{$mat-lt-md} { + width: 90vw; + } + .tb-threshold-settings-panel-content { + display: flex; + flex-direction: column; + gap: 16px; + overflow: auto; + } + .tb-threshold-settings-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-threshold-settings-panel-buttons { + height: 40px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts new file mode 100644 index 0000000000..a93a1c0181 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts @@ -0,0 +1,139 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { + TimeSeriesChartShape, + timeSeriesChartShapes, + timeSeriesChartShapeTranslations, + TimeSeriesChartThreshold, + timeSeriesLineTypes, + timeSeriesLineTypeTranslations, + timeSeriesThresholdLabelPositions, + timeSeriesThresholdLabelPositionTranslations +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { merge } from 'rxjs'; +import { WidgetConfig } from '@shared/models/widget.models'; +import { formatValue, isDefinedAndNotNull } from '@core/utils'; + +@Component({ + selector: 'tb-time-series-chart-threshold-settings-panel', + templateUrl: './time-series-chart-threshold-settings-panel.component.html', + providers: [], + styleUrls: ['./time-series-chart-threshold-settings-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit { + + timeSeriesLineTypes = timeSeriesLineTypes; + + timeSeriesLineTypeTranslations = timeSeriesLineTypeTranslations; + + timeSeriesChartShapes = timeSeriesChartShapes; + + timeSeriesChartShapeTranslations = timeSeriesChartShapeTranslations; + + timeSeriesThresholdLabelPositions = timeSeriesThresholdLabelPositions; + + timeSeriesThresholdLabelPositionTranslations = timeSeriesThresholdLabelPositionTranslations; + + labelPreviewFn = this._labelPreviewFn.bind(this); + + @Input() + thresholdSettings: Partial; + + @Input() + widgetConfig: WidgetConfig; + + @Input() + popover: TbPopoverComponent; + + @Output() + thresholdSettingsApplied = new EventEmitter>(); + + thresholdSettingsFormGroup: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + } + + ngOnInit(): void { + this.thresholdSettingsFormGroup = this.fb.group( + { + lineColor: [this.thresholdSettings.lineColor, []], + lineType: [this.thresholdSettings.lineType, []], + lineWidth: [this.thresholdSettings.lineWidth, [Validators.min(0)]], + startSymbol: [this.thresholdSettings.startSymbol, []], + startSymbolSize: [this.thresholdSettings.startSymbolSize, [Validators.min(0)]], + endSymbol: [this.thresholdSettings.endSymbol, []], + endSymbolSize: [this.thresholdSettings.endSymbolSize, [Validators.min(0)]], + showLabel: [this.thresholdSettings.showLabel, []], + labelPosition: [this.thresholdSettings.labelPosition, []], + labelFont: [this.thresholdSettings.labelFont, []], + labelColor: [this.thresholdSettings.labelColor, []] + } + ); + merge(this.thresholdSettingsFormGroup.get('showLabel').valueChanges, + this.thresholdSettingsFormGroup.get('startSymbol').valueChanges, + this.thresholdSettingsFormGroup.get('endSymbol').valueChanges).subscribe(() => { + this.updateValidators(); + }); + this.updateValidators(); + } + + cancel() { + this.popover?.hide(); + } + + applyThresholdSettings() { + const thresholdSettings = this.thresholdSettingsFormGroup.getRawValue(); + this.thresholdSettingsApplied.emit(thresholdSettings); + } + + private updateValidators() { + const showLabel: boolean = this.thresholdSettingsFormGroup.get('showLabel').value; + const startSymbol: TimeSeriesChartShape = this.thresholdSettingsFormGroup.get('startSymbol').value; + const endSymbol: TimeSeriesChartShape = this.thresholdSettingsFormGroup.get('endSymbol').value; + if (showLabel) { + this.thresholdSettingsFormGroup.get('labelPosition').enable({emitEvent: false}); + this.thresholdSettingsFormGroup.get('labelFont').enable({emitEvent: false}); + this.thresholdSettingsFormGroup.get('labelColor').enable({emitEvent: false}); + } else { + this.thresholdSettingsFormGroup.get('labelPosition').disable({emitEvent: false}); + this.thresholdSettingsFormGroup.get('labelFont').disable({emitEvent: false}); + this.thresholdSettingsFormGroup.get('labelColor').disable({emitEvent: false}); + } + if (startSymbol === TimeSeriesChartShape.none) { + this.thresholdSettingsFormGroup.get('startSymbolSize').disable({emitEvent: false}); + } else { + this.thresholdSettingsFormGroup.get('startSymbolSize').enable({emitEvent: false}); + } + if (endSymbol === TimeSeriesChartShape.none) { + this.thresholdSettingsFormGroup.get('endSymbolSize').disable({emitEvent: false}); + } else { + this.thresholdSettingsFormGroup.get('endSymbolSize').enable({emitEvent: false}); + } + } + + private _labelPreviewFn(): string { + const units = this.thresholdSettings.units && this.thresholdSettings.units.length ? + this.thresholdSettings.units : this.widgetConfig.units; + const decimals = isDefinedAndNotNull(this.thresholdSettings.decimals) ? this.thresholdSettings.decimals : + (isDefinedAndNotNull(this.widgetConfig.decimals) ? this.widgetConfig.decimals : 2); + return formatValue(22, decimals, units, false); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html new file mode 100644 index 0000000000..811106fc7b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html @@ -0,0 +1,47 @@ + +
+
{{ 'widgets.time-series-chart.threshold.thresholds' | translate }}
+
+
+
widgets.time-series-chart.threshold.source
+
widgets.time-series-chart.threshold.key-value
+
widgets.color.color
+
widget-config.units-short
+
widget-config.decimals-short
+
+
+
+
+ + +
+
+
+
+ +
+
+ + {{ 'widgets.time-series-chart.threshold.no-thresholds' | translate }} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.scss new file mode 100644 index 0000000000..d1f1ec6eed --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.scss @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.tb-time-series-thresholds-panel { + .tb-form-table-header-cell { + &.tb-threshold-source-header { + flex: 1; + min-width: 200px; + } + &.tb-threshold-key-value-header { + flex: 1; + min-width: 150px; + } + + &.tb-units-header { + width: 80px; + min-width: 80px; + } + + &.tb-color-header { + width: 40px; + min-width: 40px; + } + + &.tb-decimals-header { + width: 60px; + min-width: 60px; + } + + &.tb-actions-header { + width: 80px; + min-width: 80px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.ts new file mode 100644 index 0000000000..40db98ba4b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.ts @@ -0,0 +1,213 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validator +} from '@angular/forms'; +import { + TimeSeriesChartThreshold, + timeSeriesChartThresholdDefaultSettings, + TimeSeriesChartThresholdType, + timeSeriesChartThresholdValid, + timeSeriesChartThresholdValidator +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { mergeDeep } from '@core/utils'; +import { IAliasController } from '@core/api/widget-api.models'; +import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { DataKey, Datasource, WidgetConfig } from '@shared/models/widget.models'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; + +@Component({ + selector: 'tb-time-series-chart-thresholds-panel', + templateUrl: './time-series-chart-thresholds-panel.component.html', + styleUrls: ['./time-series-chart-thresholds-panel.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeSeriesChartThresholdsPanelComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => TimeSeriesChartThresholdsPanelComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class TimeSeriesChartThresholdsPanelComponent implements ControlValueAccessor, OnInit, Validator { + + @Input() + disabled: boolean; + + @Input() + aliasController: IAliasController; + + @Input() + dataKeyCallbacks: DataKeysCallbacks; + + @Input() + datasource: Datasource; + + @Input() + widgetConfig: WidgetConfig; + + thresholdsFormGroup: UntypedFormGroup; + + private propagateChange = (_val: any) => {}; + + constructor(private fb: UntypedFormBuilder) { + } + + ngOnInit() { + this.thresholdsFormGroup = this.fb.group({ + thresholds: [this.fb.array([]), []] + }); + this.thresholdsFormGroup.valueChanges.subscribe( + () => { + let thresholds: TimeSeriesChartThreshold[] = this.thresholdsFormGroup.get('thresholds').value; + if (thresholds) { + thresholds = thresholds.filter(t => timeSeriesChartThresholdValid(t)); + } + this.updateLatestDataKeys(thresholds); + this.propagateChange(thresholds); + } + ); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.thresholdsFormGroup.disable({emitEvent: false}); + } else { + this.thresholdsFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: TimeSeriesChartThreshold[] | undefined): void { + const thresholds = this.checkLatestDataKeys(value || []); + this.thresholdsFormGroup.setControl('thresholds', this.prepareThresholdsFormArray(thresholds), {emitEvent: false}); + } + + public validate(c: UntypedFormControl) { + const valid = this.thresholdsFormGroup.valid; + return valid ? null : { + thresholds: { + valid: false, + }, + }; + } + + thresholdsFormArray(): UntypedFormArray { + return this.thresholdsFormGroup.get('thresholds') as UntypedFormArray; + } + + trackByThreshold(index: number, thresholdControl: AbstractControl): any { + return thresholdControl; + } + + removeThreshold(index: number) { + (this.thresholdsFormGroup.get('thresholds') as UntypedFormArray).removeAt(index); + } + + addThreshold() { + const threshold = mergeDeep({} as TimeSeriesChartThreshold, + timeSeriesChartThresholdDefaultSettings); + const thresholdsArray = this.thresholdsFormGroup.get('thresholds') as UntypedFormArray; + const thresholdControl = this.fb.control(threshold, [timeSeriesChartThresholdValidator]); + thresholdsArray.push(thresholdControl); + } + + private prepareThresholdsFormArray(thresholds: TimeSeriesChartThreshold[] | undefined): UntypedFormArray { + const thresholdsControls: Array = []; + if (thresholds) { + thresholds.forEach((threshold) => { + thresholdsControls.push(this.fb.control(threshold, [timeSeriesChartThresholdValidator])); + }); + } + return this.fb.array(thresholdsControls); + } + + private checkLatestDataKeys(thresholds: TimeSeriesChartThreshold[]): TimeSeriesChartThreshold[] { + const result: TimeSeriesChartThreshold[] = []; + const latestKeys = this.datasource?.latestDataKeys || []; + for (const threshold of thresholds) { + if (threshold.type === TimeSeriesChartThresholdType.latestKey) { + const found = latestKeys.find(k => this.isThresholdKey(k, threshold)); + if (found) { + result.push(threshold); + } + } else { + result.push(threshold); + } + } + return result; + } + + private updateLatestDataKeys(thresholds: TimeSeriesChartThreshold[]) { + if (this.datasource) { + let latestKeys = this.datasource.latestDataKeys; + if (!latestKeys) { + latestKeys = []; + this.datasource.latestDataKeys = latestKeys; + } + const existingThresholdKeys = latestKeys.filter(k => k.settings?.__thresholdKey === true); + const foundThresholdKeys: DataKey[] = []; + for (const threshold of thresholds) { + if (threshold.type === TimeSeriesChartThresholdType.latestKey) { + const found = existingThresholdKeys.find(k => this.isThresholdKey(k, threshold)); + if (!found) { + const newKey = this.dataKeyCallbacks.generateDataKey(threshold.latestKey, threshold.latestKeyType, + null, true, null); + newKey.settings.__thresholdKey = true; + latestKeys.push(newKey); + } else if (foundThresholdKeys.indexOf(found) === -1) { + foundThresholdKeys.push(found); + } + } + } + const toRemove = existingThresholdKeys.filter(k => foundThresholdKeys.indexOf(k) === -1); + for (const key of toRemove) { + const index = latestKeys.indexOf(key); + if (index > -1) { + latestKeys.splice(index, 1); + } + } + } + } + + private isThresholdKey(d: DataKey, threshold: TimeSeriesChartThreshold): boolean { + return (d.type === DataKeyType.function && d.label === threshold.latestKey) || + (d.type !== DataKeyType.function && d.name === threshold.latestKey && + d.type === threshold.latestKeyType); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.html new file mode 100644 index 0000000000..072c637641 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.html @@ -0,0 +1,152 @@ + + + + +
+
+
+ + notifications + + + timeline + +
+
+ + + +
+
+ + +
+
+ +
+ + + + + notifications + + + timeline + + + + + +
+
+ entity.no-keys-found +
+ + + {{ translate.get('entity.no-key-matching', + {key: truncate.transform(keySearchText, true, 6, '...')}) | async }} + + + entity.create-new-key + + + {{'entity.create-new-key' | translate }} + notifications + + + timeline + + +
+
+
+
+ + + f() + + + + + + + + {{ modelValue?.aggregationType }}({{ modelValue?.name }}) + + + {{modelValue?.name}} + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss new file mode 100644 index 0000000000..6d443d0057 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.tb-data-key-input { + .mat-mdc-form-field.tb-inline-field.tb-key-field { + width: 100%; + .mat-mdc-text-field-wrapper:not(.mdc-text-field--outlined) { + padding-left: 8px; + padding-right: 0; + + .mat-mdc-form-field-infix { + padding-top: 0; + padding-bottom: 6px; + + .mdc-evolution-chip-set .mdc-evolution-chip { + margin: 0; + } + + input.mat-mdc-chip-input { + height: 32px; + margin-left: 0; + } + } + } + + .mat-mdc-chip.mat-mdc-standard-chip.tb-datakey-chip { + .tb-attribute-chip { + .tb-chip-labels { + background: transparent; + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.ts new file mode 100644 index 0000000000..04908bdd7b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.ts @@ -0,0 +1,398 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + forwardRef, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormControl, + Validators +} from '@angular/forms'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; +import { MatAutocomplete, MatAutocompleteTrigger } from '@angular/material/autocomplete'; +import { MatChipGrid, MatChipInputEvent } from '@angular/material/chips'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { DataKey, DatasourceType, widgetType } from '@shared/models/widget.models'; +import { Observable, of } from 'rxjs'; +import { MatDialog } from '@angular/material/dialog'; +import { TranslateService } from '@ngx-translate/core'; +import { TruncatePipe } from '@shared/pipe/truncate.pipe'; +import { UtilsService } from '@core/services/utils.service'; +import { alarmFields } from '@shared/models/alarm.models'; +import { filter, map, mergeMap, publishReplay, refCount, share, tap } from 'rxjs/operators'; +import { AggregationType } from '@shared/models/time/time.models'; +import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { IAliasController } from '@core/api/widget-api.models'; + +@Component({ + selector: 'tb-data-key-input', + templateUrl: './data-key-input.component.html', + styleUrls: ['./data-key-input.component.scss', '../../../config/data-keys.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DataKeyInputComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class DataKeyInputComponent implements ControlValueAccessor, OnInit, OnChanges { + + @HostBinding('class') + hostClass = 'tb-data-key-input'; + + DataKeyType = DataKeyType; + + separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON]; + + @ViewChild('keyInput') keyInput: ElementRef; + @ViewChild('keyAutocomplete') matAutocomplete: MatAutocomplete; + @ViewChild(MatAutocompleteTrigger) autocomplete: MatAutocompleteTrigger; + @ViewChild('chipList') chipList: MatChipGrid; + + @Input() + disabled: boolean; + + @Input() + @coerceBoolean() + required = false; + + @Input() + @coerceBoolean() + isLatestDataKeys = false; + + @Input() + @coerceBoolean() + editable = true; + + @Input() + datasourceType: DatasourceType; + + @Input() + entityAliasId: string; + + @Input() + entityAlias: string; + + @Input() + deviceId: string; + + @Input() + widgetType: widgetType; + + @Input() + callbacks: DataKeysCallbacks; + + @Input() + aliasController: IAliasController; + + @Input() + dataKeyType: DataKeyType; + + @Input() + dataKeyTypes: DataKeyType[]; + + @Input() + generateKey: (key: DataKey) => DataKey = (key) => key; + + @Output() + keyEdit = new EventEmitter(); + + keysFormControl: UntypedFormControl; + + keyFormControl: UntypedFormControl; + + modelValue: DataKey; + + filteredKeys: Observable>; + + keySearchText = ''; + + alarmKeys: Array; + functionTypeKeys: Array; + + allowedDataKeyTypes: DataKeyType[] = []; + + private latestKeySearchTextResult: Array = null; + private keyFetchObservable$: Observable> = null; + + get isEntityDatasource(): boolean { + return [DatasourceType.device, DatasourceType.entity].includes(this.datasourceType); + } + + private propagateChange = (_val: any) => {}; + + constructor(private fb: UntypedFormBuilder, + private dialog: MatDialog, + private cd: ChangeDetectorRef, + public translate: TranslateService, + public truncate: TruncatePipe, + private utils: UtilsService) { + } + + ngOnInit() { + this.alarmKeys = []; + for (const name of Object.keys(alarmFields)) { + this.alarmKeys.push({ + name, + type: DataKeyType.alarm + }); + } + this.functionTypeKeys = []; + for (const type of this.utils.getPredefinedFunctionsList()) { + this.functionTypeKeys.push({ + name: type, + type: DataKeyType.function + }); + } + this.keyFormControl = this.fb.control(''); + this.keysFormControl = this.fb.control([], this.required ? [Validators.required] : []); + this.filteredKeys = this.keyFormControl.valueChanges + .pipe( + tap((value: string | DataKey) => { + if (value && typeof value !== 'string') { + this.addKeyFromChipValue(value); + } else if (value === null) { + this.clearKeyChip(this.keyInput.nativeElement.value); + } + }), + filter((value) => typeof value === 'string'), + map((value) => value ? (typeof value === 'string' ? value : value.name) : ''), + mergeMap(name => this.fetchKeys(name) ), + share() + ); + this.updateAllowedDataKeys(); + } + + private updateAllowedDataKeys() { + this.allowedDataKeyTypes.length = 0; + if (this.dataKeyTypes?.length) { + this.allowedDataKeyTypes = this.allowedDataKeyTypes.concat(this.dataKeyTypes); + } else { + this.allowedDataKeyTypes = [DataKeyType.timeseries]; + if (this.isLatestDataKeys || this.widgetType === widgetType.latest || this.widgetType === widgetType.alarm) { + this.allowedDataKeyTypes.push(DataKeyType.attribute); + this.allowedDataKeyTypes.push(DataKeyType.entityField); + if (this.widgetType === widgetType.alarm) { + this.allowedDataKeyTypes.push(DataKeyType.alarm); + } + } + } + } + + private reset() { + if (this.keyInput) { + this.keyInput.nativeElement.value = ''; + } + this.keyFormControl.patchValue('', {emitEvent: false}); + this.latestKeySearchTextResult = null; + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (['deviceId', 'entityAliasId', 'entityAlias', 'isLatestDataKeys'].includes(propName)) { + this.clearKeySearchCache(); + if (propName === 'isLatestDataKeys') { + this.updateAllowedDataKeys(); + if (!this.isLatestDataKeys) { + if (this.widgetType === widgetType.timeseries && + this.modelValue?.type && + this.modelValue.type !== DataKeyType.timeseries) { + setTimeout(() => { + this.modelValue = null; + this.updateModel(); + this.clearKeyChip('', false); + }, 1); + } + } + } + } else if (['datasourceType'].includes(propName)) { + if ([DatasourceType.device, DatasourceType.entity].includes(change.previousValue) && + [DatasourceType.device, DatasourceType.entity].includes(change.currentValue)) { + this.clearKeySearchCache(); + } else { + this.clearKeySearchCache(); + setTimeout(() => { + this.reset(); + }, 1); + } + } + } + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.keysFormControl.disable({emitEvent: false}); + } else { + this.keysFormControl.enable({emitEvent: false}); + } + } + + writeValue(value: DataKey): void { + this.modelValue = (value?.name && value?.type) ? value : null; + this.keysFormControl.patchValue(this.modelValue ? [this.modelValue] : [], {emitEvent: false}); + this.cd.markForCheck(); + } + + dataKeyHasAggregation(): boolean { + return this.widgetType === widgetType.latest && this.modelValue?.type === DataKeyType.timeseries + && this.modelValue?.aggregationType && this.modelValue?.aggregationType !== AggregationType.NONE; + } + + dataKeyHasPostprocessing(): boolean { + return !!this.modelValue?.postFuncBody; + } + + displayKeyFn(key?: DataKey): string | undefined { + return key ? key.name : undefined; + } + + createKey(name: string, dataKeyType: DataKeyType = this.dataKeyType) { + this.addKeyFromChipValue({name: name ? name.trim() : '', type: dataKeyType}); + } + + addKey(event: MatChipInputEvent): void { + const value = event.value; + if ((value || '').trim() && this.dataKeyType) { + this.addKeyFromChipValue({name: value.trim(), type: this.dataKeyType}); + } else { + this.clearKeyChip(); + } + } + + editKey() { + this.keyEdit.emit(this.modelValue); + } + + removeKey() { + this.modelValue = null; + this.updateModel(); + this.clearKeyChip(); + } + + textIsNotEmpty(text: string): boolean { + return text && text.length > 0; + } + + clearKeyChip(value: string = '', focus = true) { + this.autocomplete.closePanel(); + this.keyInput.nativeElement.value = value; + this.keyFormControl.patchValue(value, {emitEvent: focus}); + if (focus) { + setTimeout(() => { + this.keyInput.nativeElement.blur(); + this.keyInput.nativeElement.focus(); + }, 0); + } + } + + onKeyInputFocus() { + if (!this.modelValue?.type) { + this.keyFormControl.updateValueAndValidity({onlySelf: true, emitEvent: true}); + } + } + + private fetchKeys(searchText?: string): Observable> { + if (this.keySearchText !== searchText || this.latestKeySearchTextResult === null) { + this.keySearchText = searchText; + const dataKeyFilter = this.createDataKeyFilter(this.keySearchText); + return this.getKeys().pipe( + map(name => name.filter(dataKeyFilter)), + tap(res => this.latestKeySearchTextResult = res) + ); + } + return of(this.latestKeySearchTextResult); + } + + private getKeys(): Observable> { + if (this.keyFetchObservable$ === null) { + let fetchObservable: Observable>; + if (this.datasourceType === DatasourceType.function) { + const targetKeysList = this.widgetType === widgetType.alarm ? this.alarmKeys : this.functionTypeKeys; + fetchObservable = of(targetKeysList); + } else if (this.datasourceType === DatasourceType.entity && (this.entityAliasId || this.entityAlias) || + this.datasourceType === DatasourceType.device && this.deviceId) { + if (this.datasourceType === DatasourceType.device) { + fetchObservable = this.callbacks.fetchEntityKeysForDevice(this.deviceId, this.allowedDataKeyTypes); + } else { + let entityAliasId = this.entityAliasId; + if (!entityAliasId && this.entityAlias && this.aliasController) { + entityAliasId = this.aliasController.getEntityAliasId(this.entityAlias); + } + fetchObservable = entityAliasId ? this.callbacks.fetchEntityKeys(entityAliasId, this.allowedDataKeyTypes) : of([]); + } + } else { + fetchObservable = of([]); + } + this.keyFetchObservable$ = fetchObservable.pipe( + publishReplay(1), + refCount() + ); + } + return this.keyFetchObservable$; + } + + private createDataKeyFilter(query: string): (key: DataKey) => boolean { + const lowercaseQuery = query.toLowerCase(); + return key => key.name.toLowerCase().startsWith(lowercaseQuery); + } + + private addKeyFromChipValue(chip: DataKey) { + this.modelValue = this.generateKey(chip); + this.updateModel(); + this.clearKeyChip('', false); + } + + private clearKeySearchCache() { + this.keySearchText = ''; + this.keyFetchObservable$ = null; + this.latestKeySearchTextResult = null; + } + + private updateModel() { + this.keysFormControl.patchValue(this.modelValue ? [this.modelValue] : [], {emitEvent: false}); + this.propagateChange(this.modelValue); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/entity-alias-input.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/entity-alias-input.component.html new file mode 100644 index 0000000000..78d0533865 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/entity-alias-input.component.html @@ -0,0 +1,49 @@ + + + + + warning + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/entity-alias-input.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/entity-alias-input.component.scss new file mode 100644 index 0000000000..baccbf2ab7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/entity-alias-input.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.tb-entity-alias-input { + .mat-mdc-form-field.tb-inline-field { + width: 100%; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/entity-alias-input.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/entity-alias-input.component.ts new file mode 100644 index 0000000000..dd6d19f14e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/entity-alias-input.component.ts @@ -0,0 +1,156 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectorRef, + Component, + ElementRef, + forwardRef, + HostBinding, + Input, + OnInit, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormControl, + Validators +} from '@angular/forms'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { IAliasController } from '@core/api/widget-api.models'; +import { map, mergeMap } from 'rxjs/operators'; +import { Observable, of } from 'rxjs'; +import { TimeSeriesChartThresholdType } from '@home/components/widget/lib/chart/time-series-chart.models'; + +@Component({ + selector: 'tb-entity-alias-input', + templateUrl: './entity-alias-input.component.html', + styleUrls: ['./entity-alias-input.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EntityAliasInputComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class EntityAliasInputComponent implements ControlValueAccessor, OnInit { + + @HostBinding('class') + hostClass = 'tb-entity-alias-input'; + + @ViewChild('entityAliasInput') entityAliasInput: ElementRef; + + @Input() + disabled: boolean; + + @Input() + @coerceBoolean() + required = false; + + @Input() + aliasController: IAliasController; + + entityAliasFormControl: UntypedFormControl; + + filteredEntityAliases: Observable>; + aliasSearchText = ''; + + private entityAliasList: Array = []; + private entityAliasDirty = false; + + private propagateChange = (_val: any) => {}; + + constructor(private fb: UntypedFormBuilder, + private cd: ChangeDetectorRef) { + } + + ngOnInit() { + this.entityAliasFormControl = this.fb.control(null, this.required ? [Validators.required] : []); + this.entityAliasFormControl.valueChanges.subscribe( + () => this.updateModel() + ); + + this.filteredEntityAliases = this.entityAliasFormControl.valueChanges + .pipe( + map(value => value ? value : ''), + mergeMap(name => this.fetchEntityAliases(name) ) + ); + + if (this.aliasController) { + const entityAliases = this.aliasController.getEntityAliases(); + for (const aliasId of Object.keys(entityAliases)) { + this.entityAliasList.push(entityAliases[aliasId].alias); + } + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.entityAliasFormControl.disable({emitEvent: false}); + } else { + this.entityAliasFormControl.enable({emitEvent: false}); + } + } + + writeValue(value: string): void { + this.entityAliasFormControl.patchValue(value, {emitEvent: false}); + this.entityAliasDirty = true; + } + + onEntityAliasFocus() { + if (this.entityAliasDirty) { + this.entityAliasFormControl.updateValueAndValidity({onlySelf: true}); + this.entityAliasDirty = false; + } + } + + clearEntityAlias() { + this.entityAliasFormControl.patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.entityAliasInput.nativeElement.blur(); + this.entityAliasInput.nativeElement.focus(); + }, 0); + } + + private fetchEntityAliases(searchText?: string): Observable> { + this.aliasSearchText = searchText; + let result = this.entityAliasList; + if (searchText && searchText.length) { + result = this.entityAliasList.filter((entityAlias) => entityAlias.toLowerCase().includes(searchText.toLowerCase())); + } + return of(result); + } + + private updateModel() { + const value = this.entityAliasFormControl.value; + this.propagateChange(value); + } + + protected readonly TimeSeriesChartThresholdType = TimeSeriesChartThresholdType; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index a7922dfdc4..e741314315 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -95,6 +95,17 @@ import { import { TimeSeriesChartAxisSettingsComponent } from '@home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component'; +import { + TimeSeriesChartThresholdsPanelComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component'; +import { + TimeSeriesChartThresholdRowComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component'; +import { DataKeyInputComponent } from '@home/components/widget/lib/settings/common/data-key-input.component'; +import { EntityAliasInputComponent } from '@home/components/widget/lib/settings/common/entity-alias-input.component'; +import { + TimeSeriesChartThresholdSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component'; @NgModule({ declarations: [ @@ -131,7 +142,12 @@ import { WidgetButtonAppearanceComponent, WidgetButtonCustomStyleComponent, WidgetButtonCustomStylePanelComponent, - TimeSeriesChartAxisSettingsComponent + TimeSeriesChartAxisSettingsComponent, + TimeSeriesChartThresholdsPanelComponent, + TimeSeriesChartThresholdRowComponent, + TimeSeriesChartThresholdSettingsPanelComponent, + DataKeyInputComponent, + EntityAliasInputComponent ], imports: [ CommonModule, @@ -172,7 +188,12 @@ import { WidgetButtonAppearanceComponent, WidgetButtonCustomStyleComponent, WidgetButtonCustomStylePanelComponent, - TimeSeriesChartAxisSettingsComponent + TimeSeriesChartAxisSettingsComponent, + TimeSeriesChartThresholdsPanelComponent, + TimeSeriesChartThresholdRowComponent, + TimeSeriesChartThresholdSettingsPanelComponent, + DataKeyInputComponent, + EntityAliasInputComponent ], providers: [ ColorSettingsComponentService, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 4a05e1c3c1..29fbafe83c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6615,6 +6615,37 @@ "shape-pin": "Pin", "shape-arrow": "Arrow", "shape-none": "None", + "threshold": { + "thresholds": "Thresholds", + "source": "Source", + "key-value": "Key / Value", + "no-thresholds": "No thresholds configured", + "add-threshold": "Add threshold", + "type-constant": "Constant", + "type-latest-key": "Key", + "type-entity": "Entity", + "threshold-settings": "Threshold settings", + "remove-threshold": "Remove threshold", + "threshold-value-required": "Threshold value is required.", + "line-appearance": "Line appearance", + "line-color": "Line color", + "start-symbol": "Start symbol", + "end-symbol": "End symbol", + "symbol-size": "size", + "label": "Label", + "label-position-start": "Start", + "label-position-middle": "Middle", + "label-position-end": "End", + "label-position-inside-start": "Inside start", + "label-position-inside-start-top": "Inside start top", + "label-position-inside-start-bottom": "Inside start bottom", + "label-position-inside-middle": "Inside middle", + "label-position-inside-middle-top": "Inside middle top", + "label-position-inside-middle-bottom": "Inside middle bottom", + "label-position-inside-end": "Inside end", + "label-position-inside-end-top": "Inside end top", + "label-position-inside-end-bottom": "Inside end bottom" + }, "axis": { "axes": "Axes", "x-axis": "X axis", From 270a023c5ecfb8722e64a9adb39533478a8ce91e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 6 Mar 2024 13:56:57 +0100 Subject: [PATCH 156/209] fixed deserialization lwm2m client --- .../server/store/util/LwM2MClientSerDes.java | 2 +- .../lwm2m/server/client/LwM2mClientTest.java | 0 .../store/util/LwM2MClientSerDesTest.java | 151 ++++++++++++++++++ .../store/util/LwM2MClientSerDesTest.java | 101 ------------ .../transport/lwm2m/src/test/resources/15.xml | 26 +++ .../transport/lwm2m/src/test/resources/17.xml | 26 +++ 6 files changed, 204 insertions(+), 102 deletions(-) rename common/transport/lwm2m/src/test/{ => java}/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java (100%) create mode 100644 common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java delete mode 100644 common/transport/lwm2m/src/test/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java create mode 100644 common/transport/lwm2m/src/test/resources/15.xml create mode 100644 common/transport/lwm2m/src/test/resources/17.xml diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java index 88663bae71..51fcfef97f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java @@ -146,7 +146,7 @@ public class LwM2MClientSerDes { if (multiInstances) { Map instances = new HashMap<>(); o.get("instances").asObject().forEach(entry -> { - instances.put(Integer.valueOf(entry.getName()), parseValue(type, entry.getValue())); + instances.put(Integer.valueOf(entry.getName()), parseValue(type, entry.getValue().asObject().get("value"))); }); return LwM2mMultipleResource.newResource(id, instances, type); } else { diff --git a/common/transport/lwm2m/src/test/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java similarity index 100% rename from common/transport/lwm2m/src/test/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java rename to common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java new file mode 100644 index 0000000000..8cdd7b5147 --- /dev/null +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java @@ -0,0 +1,151 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server.store.util; + +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; +import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.server.registration.Registration; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.device.data.PowerMode; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.transport.TransportResourceCache; +import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; +import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; +import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; + +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class LwM2MClientSerDesTest { + + @Test + public void serializeDeserialize() throws Exception { + LwM2mClient client = new LwM2mClient("nodeId", "testEndpoint"); + + TransportDeviceInfo tdi = new TransportDeviceInfo(); + tdi.setPowerMode(PowerMode.PSM); + tdi.setPsmActivityTimer(10000L); + tdi.setPagingTransmissionWindow(2000L); + tdi.setEdrxCycle(3000L); + tdi.setTenantId(TenantId.fromUUID(UUID.randomUUID())); + tdi.setCustomerId(new CustomerId(UUID.randomUUID())); + tdi.setDeviceId(new DeviceId(UUID.randomUUID())); + tdi.setDeviceProfileId(new DeviceProfileId(UUID.randomUUID())); + tdi.setDeviceName("testDevice"); + tdi.setDeviceType("testType"); + ValidateDeviceCredentialsResponse credentialsResponse = ValidateDeviceCredentialsResponse.builder() + .deviceInfo(tdi) + .build(); + + client.init(credentialsResponse, UUID.randomUUID()); + + Registration registration = + new Registration.Builder("test", "testEndpoint", Identity + .unsecure(new InetSocketAddress(1000))) + .supportedContentFormats() + .supportedObjects(Map.of(15, "1.0", 17, "1.0")) + .objectLinks(new Link[]{new Link("/")}) + .build(); + + client.setRegistration(registration); + client.setState(LwM2MClientState.REGISTERED); + client.getSharedAttributes().put("key1", TransportProtos.TsKvProto.newBuilder().setTs(0).setKv(TransportProtos.KeyValueProto.newBuilder().setStringV("test").build()).build()); + client.getSharedAttributes().put("key2", TransportProtos.TsKvProto.newBuilder().setTs(1).setKv(TransportProtos.KeyValueProto.newBuilder().setDoubleV(1.02).build()).build()); + + TransportResourceCache resourceCache = mock(TransportResourceCache.class); + LwM2mTransportContext context = mock(LwM2mTransportContext.class); + LwM2mClientContext clientContext = mock(LwM2mClientContext.class); + + var provider = new LwM2mVersionedModelProvider(clientContext, new LwM2mTransportServerHelper(context), context); + + TbResource resource15 = new TbResource(); + resource15.setData(Files.readAllBytes(Path.of(this.getClass().getClassLoader().getResource("15.xml").toURI()))); + TbResource resource17 = new TbResource(); + resource17.setData(Files.readAllBytes(Path.of(this.getClass().getClassLoader().getResource("17.xml").toURI()))); + + when(resourceCache.get(any(), any(), eq("15_1.0"))).thenReturn(Optional.of(resource15)); + when(resourceCache.get(any(), any(), eq("17_1.0"))).thenReturn(Optional.of(resource17)); + when(context.getTransportResourceCache()).thenReturn(resourceCache); + when(clientContext.getClientByEndpoint(any())).thenReturn(client); + + LwM2mResource singleResource = LwM2mSingleResource.newStringResource(15, "testValue"); + LwM2mResource multipleResource = LwM2mMultipleResource.newStringResource(17, Map.of(0, "testValue", 1, "testValue")); + client.saveResourceValue("/15_1.0/0/0", singleResource, provider, WriteRequest.Mode.UPDATE); + client.saveResourceValue("/17_1.0/0/0", multipleResource, provider, WriteRequest.Mode.UPDATE); + + byte[] bytes = LwM2MClientSerDes.serialize(client); + Assert.assertNotNull(bytes); + + LwM2mClient desClient = LwM2MClientSerDes.deserialize(bytes); + + assertEquals(client.getNodeId(), desClient.getNodeId()); + assertEquals(client.getEndpoint(), desClient.getEndpoint()); + assertEquals(client.getSharedAttributes(), desClient.getSharedAttributes()); + assertEquals(client.getKeyTsLatestMap(), desClient.getKeyTsLatestMap()); + assertEquals(client.getTenantId(), desClient.getTenantId()); + assertEquals(client.getProfileId(), desClient.getProfileId()); + assertEquals(client.getDeviceId(), desClient.getDeviceId()); + assertEquals(client.getState(), desClient.getState()); + assertEquals(client.getSession(), desClient.getSession()); + assertEquals(client.getPowerMode(), desClient.getPowerMode()); + assertEquals(client.getPsmActivityTimer(), desClient.getPsmActivityTimer()); + assertEquals(client.getPagingTransmissionWindow(), desClient.getPagingTransmissionWindow()); + assertEquals(client.getEdrxCycle(), desClient.getEdrxCycle()); + assertEquals(client.getRegistration(), desClient.getRegistration()); + assertEquals(client.isAsleep(), desClient.isAsleep()); + assertEquals(client.getLastUplinkTime(), desClient.getLastUplinkTime()); + assertEquals(client.getSleepTask(), desClient.getSleepTask()); + assertEquals(client.getClientSupportContentFormats(), desClient.getClientSupportContentFormats()); + assertEquals(client.getDefaultContentFormat(), desClient.getDefaultContentFormat()); + assertEquals(client.getRetryAttempts().get(), desClient.getRetryAttempts().get()); + assertEquals(client.getLastSentRpcId(), desClient.getLastSentRpcId()); + + Map expectedResources = client.getResources(); + Map actualResources = desClient.getResources(); + assertNotNull(actualResources); + assertEquals(expectedResources.size(), actualResources.size()); + expectedResources.forEach((key, value) -> assertEquals(value.toString(), actualResources.get(key).toString())); + } + +} \ No newline at end of file diff --git a/common/transport/lwm2m/src/test/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java b/common/transport/lwm2m/src/test/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java deleted file mode 100644 index 79e1f99c67..0000000000 --- a/common/transport/lwm2m/src/test/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.lwm2m.server.store.util; - -import org.eclipse.leshan.core.link.Link; -import org.eclipse.leshan.core.request.Identity; -import org.eclipse.leshan.server.registration.Registration; -import org.junit.Assert; -import org.junit.Test; -import org.thingsboard.server.common.data.device.data.PowerMode; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; -import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; - -import java.net.InetSocketAddress; -import java.util.UUID; - -public class LwM2MClientSerDesTest { - - @Test - public void serializeDeserialize() { - LwM2mClient client = new LwM2mClient("nodeId", "testEndpoint"); - - TransportDeviceInfo tdi = new TransportDeviceInfo(); - tdi.setPowerMode(PowerMode.PSM); - tdi.setPsmActivityTimer(10000L); - tdi.setPagingTransmissionWindow(2000L); - tdi.setEdrxCycle(3000L); - tdi.setTenantId(TenantId.fromUUID(UUID.randomUUID())); - tdi.setCustomerId(new CustomerId(UUID.randomUUID())); - tdi.setDeviceId(new DeviceId(UUID.randomUUID())); - tdi.setDeviceProfileId(new DeviceProfileId(UUID.randomUUID())); - tdi.setDeviceName("testDevice"); - tdi.setDeviceType("testType"); - ValidateDeviceCredentialsResponse credentialsResponse = ValidateDeviceCredentialsResponse.builder() - .deviceInfo(tdi) - .build(); - - client.init(credentialsResponse, UUID.randomUUID()); - - Registration registration = - new Registration.Builder("test", "testEndpoint", Identity - .unsecure(new InetSocketAddress(1000))) - .supportedContentFormats() - .objectLinks(new Link[]{new Link("/")}) - .build(); - - client.setRegistration(registration); - client.setState(LwM2MClientState.REGISTERED); - - client.getSharedAttributes().put("key1", TransportProtos.TsKvProto.newBuilder().setTs(0).setKv(TransportProtos.KeyValueProto.newBuilder().setStringV("test").build()).build()); - client.getSharedAttributes().put("key2", TransportProtos.TsKvProto.newBuilder().setTs(1).setKv(TransportProtos.KeyValueProto.newBuilder().setDoubleV(1.02).build()).build()); - - byte[] bytes = LwM2MClientSerDes.serialize(client); - Assert.assertNotNull(bytes); - - LwM2mClient desClient = LwM2MClientSerDes.deserialize(bytes); - - Assert.assertEquals(client.getNodeId(), desClient.getNodeId()); - Assert.assertEquals(client.getEndpoint(), desClient.getEndpoint()); - Assert.assertEquals(client.getResources(), desClient.getResources()); - Assert.assertEquals(client.getSharedAttributes(), desClient.getSharedAttributes()); - Assert.assertEquals(client.getKeyTsLatestMap(), desClient.getKeyTsLatestMap()); - Assert.assertEquals(client.getTenantId(), desClient.getTenantId()); - Assert.assertEquals(client.getProfileId(), desClient.getProfileId()); - Assert.assertEquals(client.getDeviceId(), desClient.getDeviceId()); - Assert.assertEquals(client.getState(), desClient.getState()); - Assert.assertEquals(client.getSession(), desClient.getSession()); - Assert.assertEquals(client.getPowerMode(), desClient.getPowerMode()); - Assert.assertEquals(client.getPsmActivityTimer(), desClient.getPsmActivityTimer()); - Assert.assertEquals(client.getPagingTransmissionWindow(), desClient.getPagingTransmissionWindow()); - Assert.assertEquals(client.getEdrxCycle(), desClient.getEdrxCycle()); - Assert.assertEquals(client.getRegistration(), desClient.getRegistration()); - Assert.assertEquals(client.isAsleep(), desClient.isAsleep()); - Assert.assertEquals(client.getLastUplinkTime(), desClient.getLastUplinkTime()); - Assert.assertEquals(client.getSleepTask(), desClient.getSleepTask()); - Assert.assertEquals(client.getClientSupportContentFormats(), desClient.getClientSupportContentFormats()); - Assert.assertEquals(client.getDefaultContentFormat(), desClient.getDefaultContentFormat()); - Assert.assertEquals(client.getRetryAttempts().get(), desClient.getRetryAttempts().get()); - Assert.assertEquals(client.getLastSentRpcId(), desClient.getLastSentRpcId()); - } -} \ No newline at end of file diff --git a/common/transport/lwm2m/src/test/resources/15.xml b/common/transport/lwm2m/src/test/resources/15.xml new file mode 100644 index 0000000000..11c018b3ac --- /dev/null +++ b/common/transport/lwm2m/src/test/resources/15.xml @@ -0,0 +1,26 @@ + + + + Test 15 + + 15 + urn:oma:lwm2m:oma:15:1.0 + 1.0 + 1.0 + Multiple + Optional + + + Test + R + Single + Mandatory + String + + + + + + + + diff --git a/common/transport/lwm2m/src/test/resources/17.xml b/common/transport/lwm2m/src/test/resources/17.xml new file mode 100644 index 0000000000..8417c66fb4 --- /dev/null +++ b/common/transport/lwm2m/src/test/resources/17.xml @@ -0,0 +1,26 @@ + + + + Test 17 + + 17 + urn:oma:lwm2m:oma:17:1.0 + 1.0 + 1.0 + Multiple + Mandatory + + + Test + R + Multiple + Optional + String + + + + + + + + From cd206c0f8095a7122b13c2723ca2c9a0865fa1eb Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 6 Mar 2024 19:50:56 +0200 Subject: [PATCH 157/209] UI: Implement line / bar / point charts. --- .../json/system/widget_bundles/charts.json | 5 +- .../json/system/widget_types/bar_chart.json | 32 ++++ .../json/system/widget_types/line_chart.json | 32 ++++ .../json/system/widget_types/point_chart.json | 32 ++++ .../widget_types/time_series_chart.json | 8 +- .../widget_types/timeseries_bar_chart.json | 5 +- .../widget_types/timeseries_line_chart.json | 5 +- ui-ngx/src/app/core/api/data-aggregator.ts | 17 +- ...rt-with-labels-basic-config.component.html | 7 + ...hart-with-labels-basic-config.component.ts | 6 + .../range-chart-basic-config.component.html | 7 + .../range-chart-basic-config.component.ts | 6 + ...e-series-chart-basic-config.component.html | 2 +- ...ime-series-chart-basic-config.component.ts | 18 +- .../basic/common/data-key-row.component.scss | 8 +- .../basic/common/data-key-row.component.ts | 1 + .../common/data-keys-panel.component.html | 2 +- .../common/data-keys-panel.component.scss | 107 ++++++----- .../config/data-key-config.component.html | 1 + .../config/widget-settings.component.ts | 12 +- .../lib/chart/time-series-chart-bar.models.ts | 9 +- .../lib/chart/time-series-chart.models.ts | 15 ++ .../widget/lib/chart/time-series-chart.ts | 21 ++- ...with-labels-widget-settings.component.html | 7 + ...t-with-labels-widget-settings.component.ts | 7 +- ...range-chart-widget-settings.component.html | 7 + .../range-chart-widget-settings.component.ts | 5 + ...e-series-chart-key-settings.component.html | 6 +- ...ime-series-chart-key-settings.component.ts | 14 +- ...-series-chart-line-settings.component.html | 89 ++++----- ...me-series-chart-line-settings.component.ts | 7 +- ...eries-chart-widget-settings.component.html | 159 ++++++++++++++++ ...-series-chart-widget-settings.component.ts | 174 ++++++++++++++++++ ...-series-chart-threshold-row.component.html | 3 + ...-series-chart-threshold-row.component.scss | 41 ++++- ...me-series-chart-threshold-row.component.ts | 6 +- ...rt-threshold-settings-panel.component.html | 68 ++++--- ...rt-threshold-settings-panel.component.scss | 2 + ...hart-threshold-settings-panel.component.ts | 9 +- ...ries-chart-thresholds-panel.component.html | 3 +- ...ries-chart-thresholds-panel.component.scss | 31 +++- .../common/data-key-input.component.html | 12 +- .../common/data-key-input.component.ts | 3 + .../lib/settings/widget-settings.module.ts | 12 +- .../widget/widget-config.component.html | 1 + .../src/app/shared/models/time/time.models.ts | 9 +- ui-ngx/src/app/shared/models/widget.models.ts | 9 +- .../assets/locale/locale.constant-en_US.json | 6 + 48 files changed, 870 insertions(+), 178 deletions(-) create mode 100644 application/src/main/data/json/system/widget_types/bar_chart.json create mode 100644 application/src/main/data/json/system/widget_types/line_chart.json create mode 100644 application/src/main/data/json/system/widget_types/point_chart.json create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json index 5421cfb607..979b241e67 100644 --- a/application/src/main/data/json/system/widget_bundles/charts.json +++ b/application/src/main/data/json/system/widget_bundles/charts.json @@ -2,13 +2,16 @@ "widgetsBundle": { "alias": "charts", "title": "Charts", - "image": "tb-image:Y2hhcnRzX3N5c3RlbV9idW5kbGVfaW1hZ2UucG5n:IkNoYXJ0cyIgc3lzdGVtIGJ1bmRsZSBpbWFnZQ==;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAC91BMVEX////K5vz/wQchlvP0QzZgfYtMr1C20ujl5eUAAADi4uL/ySX9/v7/9+Dg4OA8o/TK2eT+6Ob1Wk7q9f7x8fHw8PHy8/L5+vn09PTT2t/p6ekznvRfs/b+wxHLy8v8/Pz/6KL///3+8/JRsVX6+/vq9uqTw+rn5+diuWVNq/bk5OTY2NjNzc2+vr6lzqfqop3u1Yr/wgzD4/w2oPSs2Pv4+Pnj4+S83/v8xBj/wgr0+v5itfdGqPX/1lv29vZUrvXHx8fH5fx2vvgunPTO5OvQ4+Oryd35xSG23fuRy/qAw/n39/jt7e3U4NK3t7eqqqprufdasfY/pPXL5PHu7u7r6+vf3t3/8srW3sjCwsK6urqxsbGw2vum1fuGxvn//fj/+urtzVf3/P/i8v6g0vvL5vqa0PqMyfns7Ozm5ubl1IL/333p0nLtz1/zyj/7/f/S6v17wfjM5fR4uevS0tJtiJX/3HLq0Wrxy0b1yTj/zDD/xhn9/v/u9/7/+PgpmvT//PPS4dj8w77/663c2qnm03yXzvpwu/dmt/fv7+/m6+2uz+ra4eX93tzV1dX/7LOurq6tra3g15j/4of/2WfvzFH1V0v/0kj0STz3yDH5xyjZ7f2gyerR4t7/9tzb3Nv/9dT61tPY3b/Z3Lrb27KMoavf2J92j5v/5JPrz2P2ZFn/xx++4fvr7/Dg6Ou+ztD8zcquvcT/8MKarbakpKSCmqT5n5iTz5Zmgo/5kIn3cWfpx0z1T0P/zz7U6/3P4+bO3ObO19vF0tq80dm6x82jtLz/7rni1YyJy4zc7/3O6Pz+7uz94d/Y7tm437qx3LPIz7P6sauo1ar/5pv6opuRkZHi1o97xH7aynr4gHdpvGzhymZbtl7zxC36wRC30ui30uTM1Njz69TM6M3F5cbC5MPDz7/p3bt/q52huI64wIf4gXjfzG67umTVvUDk8+Tm5N3a185yss2TwMjCz8T7ta/7s610qaWOr5PSy5H4hn33fnX3eG83NCs7AAAOvklEQVR42tzaSYyLYRzH8Z9SaV6P19a+7+u1VKmUWhJLK7baDpY2StKZCSNmwsFMHJDgYM84WGIZIUgkloMQYt8FsUessUWIiy0ODk4ccDDzvG2fvn3eeVvK+758LxKHJ/30eZ63/+kM/p+ICGVhHA5r9Gj0m7wQv1LlHiRrt8BZSae7kiSS+IWk55WhLXAWRIY4uW/fLaiHTFBsZdO9k5OOgRApJAiCCqm+78LTNZVQwoIQTcgoIlJfL/abPBpEEPYIf6kwiioeEqMpGbToQpLqGknjasWoj6D4BNhYUBVDMpqK+MSw8i9A4kLB1+kvExPE4ZBYOORHEUmiShwMkbmXZ0ZZ51QIUcUg8lPWb4hh2TAYlBDjjoTEREnvOnrs1JlFbre7Izo0b96p95qJQ5ZDlz+sOhCSEAhYwWunGgw0Ckk3YmazZfrzJTsM4hdSYB29pSkYhDVo0q5csCg5CiKLEWTbsN/NYhDW8LvIRjwJB0EiYpDtRobBQ1hTryCbqv5VyKpufKthnCISpIvfYgADCGssu/gpz9+EtHbxtYJhceboeMBtDmF1moBMXo8jIHLWQU5wCAbhGzsruyeqAyDBcj+0lP1uMwhf79lIV1ZjO4RkPwmOHHCbQ/gGZe+8R7Ib4lGgtX6RuxCEb9yY7PsRtBfiVZmjMIRvZbPsTbMVIovcuTKF8I2bz94SGyGZAxE84y4Owjcoc+PDEfsgUhm0TrmLhfD1ngWaX7QNQjKfIDfdxUP4xkKrxmcXJKSkL/r5kiDNh0BLJPZAiJj+94y7NEin5eyk2gFRFdCOuUuA0BY0uSVXO/ONLR1iuCGxRSVA0s0HzZuA1rTInul0+bbN+dr8YUiZBNoJd+mQztASQSt/LiWRtAoighZfVAIkW7Pc0xqsnC7Vo94iSCTENqR0yPDM9yp0Qyqfd7VuR2plNEYWlQJhzdZd900Nd6QcRBBuGM0Cwq/00QAyXhDC+SfrmvvPQOaAts4HliU7Ele5b35KgXSeARqJWg1RY0hHNtw6UBJk5fDBy9lGWw0RQVtVrQ0pJ8zHeFPFMO7qWQkRQOvmWtKNs/CQQgqWlLASwh6+I+n/P9UsR26e+QXIuClMwSJRayFeH2itXFqHD7bmLGaQTmOHzAMtRKBLtBaiRrQr4mIt3do6oFn2F4CMGNxhHpt0FOgSfhsS6G5QIYhA0FiFi8Ysu6lFub7/fFOQUUP0ByqmQleU/C6kXQu+9gUg7K7n12XuXmqJpS16yKgJwwpcCqgxOyBbXQYNWKxZ4m9Onc+FrB04CwD87849MPnZKSXZAVnsMm7otopA+hdX5zXIlGZUEXx37nXLlg9NID6vHZBtrqbbVlGlWdZj2Hx6ueX3dxoUDZ0zgUgpOyAjXaaNPFmFTPKHOz2oogBEqXEgpKEdt1c1Kh6+oIp0d0wgcdWZEJerNQDygikK7UjCaXck3UGgbgXkHrmQJ2Z3ZJ0dkLmFj1YAG3deAB7kQj6YQNZJdkAOFnJ0WYUVh1q0qAP650B6mkASiqUQEbTNhSAVwIWG9S61A3mVdbxCTsFa6ArJlkI8fjS2t4BjLlBHF7wYgJSFPOI/N1gCLIXUKNrA2cXUsSSAdpe0FZ8B79OOHhJy8sjQJVoJYQ/Jy6aQavgvZpY8DtwxeGZBhK0Qf5gbtvg2A2ezS27vDj8dUHrptkD2QJccshLC3rjbZgMKsK8F6yXwufFgPTCZ2oGEZDEkrN32qqYvyeEqdG+fu+hX4BGdfM1OlkCA2OnToX7TR1sD8fkKDSm7ge+6RXduBHnxiPstJA+TUn02JZG0AsIuyWaT0eRxC32HVsAPfSIB+K/GlUqyBfVWQNhLqFpqMprkL3sBeUkq9IUiAOTTHimpbgHxeq8bQbzGbTSAbPe+NYJ4vRIypbzcuMWNJlx10EXKidHDN9a3r6D0jYP4fIYQn3H7jCA+Q4jP50UmIoBW3cVkNNGljSq5qRL0KWVgWXS0EPU3Pcov1kYTvosBsGJh5BUO2gBRVG5LuNGE7xn/9wasoAAbIBBJU7ekGoH0aMK3jzkiyKs2bgOEfQasasWPJs+MDJlRRSss8X88DDsgbEtOmowmfC9B8/iQXyhmE0QJQWsxP5qY9AUAiXqRnyzAJgiiMmhVl/WjySdjARtVEFXAJcjFQnr24HtVCiQoQmv3UpPRhMVGFQIuSUXRkJZ8vUqBoMwHrYrMM/hyAPe50cR0VGEPYxshEGVorR6qfRNfDdz7VMCxvQ58Qgx2QvwiyUgGuBraisaOHzJhXDq7AnypGtgKgVKLrITOvLRA3cWmduPsPRgUEWAzBGoC6SoOL61Gto1n2/PPq5d1VTAqWE5sh6B2HdK1Pgld9x9/O7QzuxU/zh5fAeP85TLsh/xk545xGgaCAIr+Eo1GUzn2apsVkoUlF0Bjl1wASsgZnKOk4AZIKDfIPaDiBtyEDhdZkLPVRPKrd4uv3WKqYaz52/XH6+F4PHzmG+apy0OIWqDMfN9FCNpXlEv2gJMQ6CZKNdbgJ4S4U4rU1uIphGANBWKv+Aoh2abgW1XgLQSmMx9FozR4DKEdO2WxYDX4DIFgUcnJn/QbArXFtHTBgOsQCCKV8o+hs0nBfQjos437RI6GznYDcBEhQNr01k+hZaaP+87sZWAxFfm+OfUm77envmR7lyHb+wyRnuVSeLoy+TXGamC1Wq1WPxoFtAfR4rpyDpE6ugxDHZSIy4EBw1AHfaAYcQDHCCemLMcQEULij3pkUAiNeoR8odq04eERE2ljz2HgkZOBNrHmxkY09wgHBtCmppBZsXMxOzu7mvMBqhuvTc8Y6Q4K9GAEgULNfOI15u8zKU6vISpGtHUdSmhfIQq3G6cxQkGgF1Eaa43S2m3qg6yLNWWsiPGIhLa2ro4OjT1iIh3QzQgDRkEmBDQKm3ta8ztrdnilGDEysjsG2CQT4REdHm1dGsfIbHtNE0Z2RjgwkZ6NW2NyoWY9v0xhmwdUMUijl/Ep4jM7J608YhUnHQd2DwKkB1nh0lhg7BRrhFAJ1dgmHTPgxe8afhlzqHsQIMAaV5Eg3YasDq7RxDhuYD0i3M6fBncPAjhqtmHVaOIci6QKWaO5ZkDSAHkE0N7ZhTQVhnF879NFRcSCPoiOZxREMLKLbdHO2sbcV/uAlXOQm87VdHMwp+kMt5lR4lZCYmBJaFqhIklR0K2UQZDUVRBEEH1c9UVFX1DdtffMebZ5ds5ZlJT0v5j4+Pzf9/zeZ+/zvuzCdZ3f2SY1GuSIBQRZCT2LMVQ1goqAIHmZ7ujig6zx+nTpzkl3HVYQ1KhYW2g8dFKqZLIWGg1V59lnrP8jIA3WkNOoKLMHrrI9D6M23aF8426nU464QJCNkOPELfm9xKsm+n4XyO61Da16pbfuREhNEM62xuILy0iv8+WN1aA5qUeI21inCIhEq2+SQ9ozPZe24FpYK41Om5ynIq1r+UBslU6pRmxcdowQa3ROdaUh2/4FgKCr4hERo1sKuwCjtcrecwQyknX63377HMKF5wZpIKR93CCNGltj3U7rKZQrbhB3YpxZX+M9UVZywiZoBVzvAbSrXz3sOGeZwxnyh5Mf8RA1+z0Vkh0Lb7994vYy3S6ui+cpYs9yLPzKiDP0lQKYvp4NtYv75oYaMW7jMDJ6+RSefb+9YvvzdkLcvuPHu+kMzqfMN0tI0lpQkXqdAek1Po6KXHs9rUpMCFhFJmS2QIIEMuyaC/VWzr1FxV5OY1YDJAw5fAq7QmO3ZkvknkxN0Q8lkeCKFIJ09arRjOtUVV0RkAM92meA1TImHMQ0BC1RczOAJZkJyTUB3HYNGiUSAGLyA8SiCDW25Z02XHvk4N237+MWUD198noNC8iWGx1HMMMj84Al/eOBQJBoCwyZ8D5pSZscdMhKINFutU6OBIC4h4FKMiFukOPVPU3aThnQolT4dfjs5ku5IFuqO64AwIc3ZrqyYQpg0CEEJDgIFtqDgpMUqGImnBVQtEp79UzWneRUbDzKMlYwrIKWUSQIpOPcKsjI8uHdwNgMcj0Ix+k30BHt5eoDdNKlCM7pPLNHKs8WPEUCNHfzg8SAmgc2RWRADbzAx/UyO8rKMRun144cHDAXjGWOA6SCSBiICvdmbdPmahexc94Q/PJGe4Ue/VzT3TOdALCqqVrkFStzVjFCgmpqlAfkMZBuxMgRB3g6hpA+gGhFxyPDuJvGH4UTkFYi5Q4yY01QYBlHSCDIw9OZfxpzngjkGtSVXRc2n8UIuDbNN9I5rTQpM6w5JgNZZIYLZABUE/mRiWEAfwbfPOknAYCaSt7J/bU5acpsrgjA4AwSDJK9eYtteQa9LkQ3gNNNN7WnD6ygM04ULn93MwCZMhUFSapgsjAWfUy3YjdTAmzMK5AqPutY3p0A2SxCpYLsltoLDMrcJpz21Et9LMM6/ABU2MU+0xgJj1m2DW7FMmZTFBi7Z+P4jxYKht2oZJBDZZULDF5CngPS1dvLPiyNkjKxzOSgIMbeWN0JGI4lXcUe0TURswD4Tah0EINTv9AQUNxjQHxSfbFhx1pwVUyFM41awB9kB0HBbp52F3wwgVDpICGNnM0QqjqaBdkpVnIMOzYIQEbMuSF8oMejLI8oOCQMpEJS0T//cVCdWMluMOjqMyDPCS/3sG6M8sTMBFz0gf7nQTb09+8ol2AQfJ8dKXbx1JWtwLpNbOO96r6cBpA9yd5xX0yD5TpL1u8MbccgtfvnP6BrJeqKksulNtzSNO1ClszhV4Fs7rSfAqr7ty4//x5pUDRyGJRir6hLfVLgTI7mOZQIUG60yCAaH6fBS9y36/SCZ8IoqqkUyMbRYoPYeAwBY9XVUmYajeHzbBItOgivwWYtcabRCPkI/YUgvxAyoSUCgv6D/AdZEiB79y4REI9niYAsmYrQWvkPS5SvlSJW/SNZS1F4q/CrZoeApNr15fv5szySDQKmlJSXe0oD8QjKr60VkLR9fW0Nf9bGw+US/qxNnq2SP1KRGgFJF9d5BKTt6z+8jz+rQlJzmCflJ6e3vqMkTdRjAAAAAElFTkSuQmCC", + "image": "tb-image:Y2hhcnRzLnN2Zw==:IkNoYXJ0cyIgc3lzdGVtIGJ1bmRsZSBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF80MTk4XzExMDQ5MykiPgo8cGF0aCBkPSJNMTkuMjQ2MSAyMC45OTY5TDQuOTU0NDggNTQuMzI1NEwwIDYzLjVWNzZIOTZWNjAuOTU2MVY0NS4zMzMzTDg4Ljg5ODcgNjkuMDQ2MUw4NC45NDA3IDU0LjIxNTNMODMuMjIzMyA1OC41NzczTDc1Ljg4MjQgNzQuOTM3NkM3MC45ODg5IDU3LjE0MzMgNjYuOTExMSA1MC41MDM2IDYxLjU4NTEgMzMuMDIwOUw1Ni4wODk3IDU5LjEzMDlINDkuMDIyMUw0Ny42NTcgMTYuNzAyTDQ0LjkwNzYgMEwzMS44MjUgNDEuNjcwOEwyOC43MTQxIDQwLjM2MjdMMTkuMjQ2MSAyMC45OTY5WiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzQxOThfMTEwNDkzKSIvPgo8cGF0aCBkPSJNMCA2My42NDg3TDUuMDAxMDkgNTMuNDg4TDE5LjQyNzEgMjAuMzk4OEwyOC45ODQyIDQwLjYyNTVMMzIuMTI0NCA0MS45MjQzTDQ0Ljg2OTYgMUw0Ny43MzkxIDE2LjE3MDNDNDguMjc3MiAzMi42MjA5IDQ4LjQxODQgNDIuNTQ5NCA0OC45NTY1IDU5SDU2TDYxLjU2NTIgMzIuMTIzM0w3NS45MTMgNzRMODUuMDE4NSA1My40NDEzTDg4Ljk1NjUgNjguMDE3Nkw5NiA0NC43MTk0IiBzdHJva2U9InVybCgjcGFpbnQxX2xpbmVhcl80MTk4XzExMDQ5MykiIHN0cm9rZS13aWR0aD0iMC43NzY1ODIiLz4KPC9nPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDFfNDE5OF8xMTA0OTMpIj4KPHBhdGggZD0iTTAgMEw5NiAxLjYwODcyZS0wNSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiIHN0cm9rZS13aWR0aD0iMC4yNTQxNyIvPgo8cGF0aCBkPSJNMCAxOUw5NiAxOSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiIHN0cm9rZS13aWR0aD0iMC4yNTQxNyIvPgo8cGF0aCBkPSJNMCAzOEw5NiAzOCIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiIHN0cm9rZS13aWR0aD0iMC4yNTQxNyIvPgo8cGF0aCBkPSJNMCA1N0w5NiA1NyIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiIHN0cm9rZS13aWR0aD0iMC4yNTQxNyIvPgo8cGF0aCBkPSJNMCA3Nkw5NiA3NiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiIHN0cm9rZS13aWR0aD0iMC4yNTQxNyIvPgo8L2c+CjxyZWN0IHg9IjEwNCIgd2lkdGg9Ijk2IiBoZWlnaHQ9Ijc2IiByeD0iMS4xMzQzMyIgZmlsbD0id2hpdGUiLz4KPG1hc2sgaWQ9InBhdGgtOS1vdXRzaWRlLTFfNDE5OF8xMTA0OTMiIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiIHg9IjE1Mi4wMTgiIHk9IjMuMjUiIHdpZHRoPSIzNSIgaGVpZ2h0PSI1MSIgZmlsbD0iYmxhY2siPgo8cmVjdCBmaWxsPSJ3aGl0ZSIgeD0iMTUyLjAxOCIgeT0iMy4yNSIgd2lkdGg9IjM1IiBoZWlnaHQ9IjUxIi8+CjxwYXRoIGQ9Ik0xNTMuMzQxIDcuMjYyNDlDMTUzLjM5IDUuODg2MDYgMTU0LjU0NiA0Ljc5OTU1IDE1NS45MTUgNC45NTA2MUMxNjAuNjM1IDUuNDcxNDMgMTY1LjE5OCA2Ljk5ODQgMTY5LjI5MyA5LjQzOTU4QzE3NC4xMDQgMTIuMzA3MyAxNzguMTEzIDE2LjM0MzggMTgwLjk0NyAyMS4xNzQ0QzE4My43ODEgMjYuMDA1IDE4NS4zNDkgMzEuNDczMiAxODUuNTA1IDM3LjA3MTZDMTg1LjYzOCA0MS44MzczIDE4NC43NDUgNDYuNTY1OSAxODIuODk3IDUwLjkzOThDMTgyLjM2MSA1Mi4yMDg1IDE4MC44NDggNTIuNjg4MiAxNzkuNjIzIDUyLjA1ODZDMTc4LjM5OCA1MS40MjkxIDE3Ny45MjcgNDkuOTI5IDE3OC40NDYgNDguNjUzM0MxNzkuOTE5IDQ1LjAzMjUgMTgwLjYyOSA0MS4xMzY1IDE4MC41MiAzNy4yMTA5QzE4MC4zODcgMzIuNDUyMiAxNzkuMDU0IDI3LjgwNDIgMTc2LjY0NSAyMy42OTgyQzE3NC4yMzYgMTkuNTkyMiAxNzAuODI5IDE2LjE2MTIgMTY2Ljc0IDEzLjcyMzZDMTYzLjM2NiAxMS43MTI4IDE1OS42MTkgMTAuNDMxNyAxNTUuNzQgOS45NTE1MkMxNTQuMzczIDkuNzgyMzQgMTUzLjI5MyA4LjYzODkxIDE1My4zNDEgNy4yNjI0OVoiLz4KPC9tYXNrPgo8cGF0aCBkPSJNMTUzLjM0MSA3LjI2MjQ5QzE1My4zOSA1Ljg4NjA2IDE1NC41NDYgNC43OTk1NSAxNTUuOTE1IDQuOTUwNjFDMTYwLjYzNSA1LjQ3MTQzIDE2NS4xOTggNi45OTg0IDE2OS4yOTMgOS40Mzk1OEMxNzQuMTA0IDEyLjMwNzMgMTc4LjExMyAxNi4zNDM4IDE4MC45NDcgMjEuMTc0NEMxODMuNzgxIDI2LjAwNSAxODUuMzQ5IDMxLjQ3MzIgMTg1LjUwNSAzNy4wNzE2QzE4NS42MzggNDEuODM3MyAxODQuNzQ1IDQ2LjU2NTkgMTgyLjg5NyA1MC45Mzk4QzE4Mi4zNjEgNTIuMjA4NSAxODAuODQ4IDUyLjY4ODIgMTc5LjYyMyA1Mi4wNTg2QzE3OC4zOTggNTEuNDI5MSAxNzcuOTI3IDQ5LjkyOSAxNzguNDQ2IDQ4LjY1MzNDMTc5LjkxOSA0NS4wMzI1IDE4MC42MjkgNDEuMTM2NSAxODAuNTIgMzcuMjEwOUMxODAuMzg3IDMyLjQ1MjIgMTc5LjA1NCAyNy44MDQyIDE3Ni42NDUgMjMuNjk4MkMxNzQuMjM2IDE5LjU5MjIgMTcwLjgyOSAxNi4xNjEyIDE2Ni43NCAxMy43MjM2QzE2My4zNjYgMTEuNzEyOCAxNTkuNjE5IDEwLjQzMTcgMTU1Ljc0IDkuOTUxNTJDMTU0LjM3MyA5Ljc4MjM0IDE1My4yOTMgOC42Mzg5MSAxNTMuMzQxIDcuMjYyNDlaIiBmaWxsPSIjRkY0RDVBIi8+CjxwYXRoIGQ9Ik0xNTMuMzQxIDcuMjYyNDlDMTUzLjM5IDUuODg2MDYgMTU0LjU0NiA0Ljc5OTU1IDE1NS45MTUgNC45NTA2MUMxNjAuNjM1IDUuNDcxNDMgMTY1LjE5OCA2Ljk5ODQgMTY5LjI5MyA5LjQzOTU4QzE3NC4xMDQgMTIuMzA3MyAxNzguMTEzIDE2LjM0MzggMTgwLjk0NyAyMS4xNzQ0QzE4My43ODEgMjYuMDA1IDE4NS4zNDkgMzEuNDczMiAxODUuNTA1IDM3LjA3MTZDMTg1LjYzOCA0MS44MzczIDE4NC43NDUgNDYuNTY1OSAxODIuODk3IDUwLjkzOThDMTgyLjM2MSA1Mi4yMDg1IDE4MC44NDggNTIuNjg4MiAxNzkuNjIzIDUyLjA1ODZDMTc4LjM5OCA1MS40MjkxIDE3Ny45MjcgNDkuOTI5IDE3OC40NDYgNDguNjUzM0MxNzkuOTE5IDQ1LjAzMjUgMTgwLjYyOSA0MS4xMzY1IDE4MC41MiAzNy4yMTA5QzE4MC4zODcgMzIuNDUyMiAxNzkuMDU0IDI3LjgwNDIgMTc2LjY0NSAyMy42OTgyQzE3NC4yMzYgMTkuNTkyMiAxNzAuODI5IDE2LjE2MTIgMTY2Ljc0IDEzLjcyMzZDMTYzLjM2NiAxMS43MTI4IDE1OS42MTkgMTAuNDMxNyAxNTUuNzQgOS45NTE1MkMxNTQuMzczIDkuNzgyMzQgMTUzLjI5MyA4LjYzODkxIDE1My4zNDEgNy4yNjI0OVoiIGZpbGw9IndoaXRlIiBmaWxsLW9wYWNpdHk9IjAuMSIvPgo8cGF0aCBkPSJNMTUzLjM0MSA3LjI2MjQ5QzE1My4zOSA1Ljg4NjA2IDE1NC41NDYgNC43OTk1NSAxNTUuOTE1IDQuOTUwNjFDMTYwLjYzNSA1LjQ3MTQzIDE2NS4xOTggNi45OTg0IDE2OS4yOTMgOS40Mzk1OEMxNzQuMTA0IDEyLjMwNzMgMTc4LjExMyAxNi4zNDM4IDE4MC45NDcgMjEuMTc0NEMxODMuNzgxIDI2LjAwNSAxODUuMzQ5IDMxLjQ3MzIgMTg1LjUwNSAzNy4wNzE2QzE4NS42MzggNDEuODM3MyAxODQuNzQ1IDQ2LjU2NTkgMTgyLjg5NyA1MC45Mzk4QzE4Mi4zNjEgNTIuMjA4NSAxODAuODQ4IDUyLjY4ODIgMTc5LjYyMyA1Mi4wNTg2QzE3OC4zOTggNTEuNDI5MSAxNzcuOTI3IDQ5LjkyOSAxNzguNDQ2IDQ4LjY1MzNDMTc5LjkxOSA0NS4wMzI1IDE4MC42MjkgNDEuMTM2NSAxODAuNTIgMzcuMjEwOUMxODAuMzg3IDMyLjQ1MjIgMTc5LjA1NCAyNy44MDQyIDE3Ni42NDUgMjMuNjk4MkMxNzQuMjM2IDE5LjU5MjIgMTcwLjgyOSAxNi4xNjEyIDE2Ni43NCAxMy43MjM2QzE2My4zNjYgMTEuNzEyOCAxNTkuNjE5IDEwLjQzMTcgMTU1Ljc0IDkuOTUxNTJDMTU0LjM3MyA5Ljc4MjM0IDE1My4yOTMgOC42Mzg5MSAxNTMuMzQxIDcuMjYyNDlaIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjEuOTMzNzEiIG1hc2s9InVybCgjcGF0aC05LW91dHNpZGUtMV80MTk4XzExMDQ5MykiLz4KPHBhdGggZD0iTTE1MS4xOTUgNy4yNjI0OUMxNTEuMTQ3IDUuODg2MDYgMTQ5Ljk5IDQuNzk5NTUgMTQ4LjYyMSA0Ljk1MDYyQzE0My43MjcgNS40OTA2NyAxMzkuMDAzIDcuMTEyMzcgMTM0Ljc5NiA5LjcxMDUzQzEyOS44ODEgMTIuNzQ2NSAxMjUuODQxIDE3LjAxMDkgMTIzLjA3NSAyMi4wODM1QzEyMC4zMDkgMjcuMTU2MSAxMTguOTEzIDMyLjg2MTggMTE5LjAyNCAzOC42MzgzQzExOS4xMzUgNDQuNDE0OCAxMjAuNzQ5IDUwLjA2MjcgMTIzLjcwOCA1NS4wMjU0QzEyNi42NjYgNTkuOTg4MSAxMzAuODY2IDY0LjA5NDMgMTM1Ljg5NSA2Ni45MzkzQzE0MC45MjQgNjkuNzg0MyAxNDYuNjA3IDcxLjI3IDE1Mi4zODQgNzEuMjQ5OEMxNTguMTYyIDcxLjIyOTYgMTYzLjgzNCA2OS43MDQ0IDE2OC44NDMgNjYuODI0M0MxNzMuMTI5IDY0LjM1OTYgMTc2LjgwMiA2MC45NzU0IDE3OS42MDUgNTYuOTI3M0MxODAuMzg5IDU1Ljc5NDkgMTc5Ljk4NCA1NC4yNjA3IDE3OC43OTYgNTMuNTYzN0MxNzcuNjA4IDUyLjg2NjggMTc2LjA4OSA1My4yNzI0IDE3NS4yOSA1NC4zOTQzQzE3Mi45MzYgNTcuNjk5OSAxNjkuODkxIDYwLjQ2ODEgMTY2LjM1NyA2Mi41MDA3QzE2Mi4wOTkgNjQuOTQ4NyAxNTcuMjc4IDY2LjI0NTIgMTUyLjM2NyA2Ni4yNjIzQzE0Ny40NTYgNjYuMjc5NSAxNDIuNjI1IDY1LjAxNjcgMTM4LjM1MSA2Mi41OTg0QzEzNC4wNzcgNjAuMTgwMiAxMzAuNTA2IDU2LjY4OTkgMTI3Ljk5MiA1Mi40NzE2QzEyNS40NzcgNDguMjUzMyAxMjQuMTA1IDQzLjQ1MjYgMTI0LjAxMSAzOC41NDI2QzEyMy45MTYgMzMuNjMyNSAxMjUuMTAzIDI4Ljc4MjcgMTI3LjQ1NCAyNC40NzFDMTI5LjgwNSAyMC4xNTkzIDEzMy4yMzkgMTYuNTM0NSAxMzcuNDE3IDEzLjk1MzlDMTQwLjg4NiAxMS44MTEzIDE0NC43NjkgMTAuNDUgMTQ4Ljc5NiA5Ljk1MTUxQzE1MC4xNjMgOS43ODIzNCAxNTEuMjQzIDguNjM4OTEgMTUxLjE5NSA3LjI2MjQ5WiIgZmlsbD0iIzRDQUY1MCIvPgo8cGF0aCBkPSJNMTQ3LjEwNiAyOS4yMzQ0VjMzLjVIMTQ2LjU0OVYyOS4yMzQ0SDE0Ny4xMDZaTTE0OC40NzcgMjkuMjM0NFYyOS42OTczSDE0NS4xODFWMjkuMjM0NEgxNDguNDc3Wk0xNDguNTkxIDMxLjk1MDJWMzEuODgyOEMxNDguNTkxIDMxLjY1NDMgMTQ4LjYyNSAzMS40NDI0IDE0OC42OTEgMzEuMjQ3MUMxNDguNzU3IDMxLjA0OTggMTQ4Ljg1MyAzMC44Nzg5IDE0OC45NzggMzAuNzM0NEMxNDkuMTAzIDMwLjU4NzkgMTQ5LjI1NCAzMC40NzQ2IDE0OS40MzIgMzAuMzk0NUMxNDkuNjEgMzAuMzEyNSAxNDkuODA5IDMwLjI3MTUgMTUwLjAzIDMwLjI3MTVDMTUwLjI1MiAzMC4yNzE1IDE1MC40NTMgMzAuMzEyNSAxNTAuNjMgMzAuMzk0NUMxNTAuODEgMzAuNDc0NiAxNTAuOTYyIDMwLjU4NzkgMTUxLjA4NyAzMC43MzQ0QzE1MS4yMTQgMzAuODc4OSAxNTEuMzExIDMxLjA0OTggMTUxLjM3NyAzMS4yNDcxQzE1MS40NDQgMzEuNDQyNCAxNTEuNDc3IDMxLjY1NDMgMTUxLjQ3NyAzMS44ODI4VjMxLjk1MDJDMTUxLjQ3NyAzMi4xNzg3IDE1MS40NDQgMzIuMzkwNiAxNTEuMzc3IDMyLjU4NTlDMTUxLjMxMSAzMi43ODEyIDE1MS4yMTQgMzIuOTUyMSAxNTEuMDg3IDMzLjA5ODZDMTUwLjk2MiAzMy4yNDMyIDE1MC44MTEgMzMuMzU2NCAxNTAuNjMzIDMzLjQzODVDMTUwLjQ1OCAzMy41MTg2IDE1MC4yNTggMzMuNTU4NiAxNTAuMDM2IDMzLjU1ODZDMTQ5LjgxMyAzMy41NTg2IDE0OS42MTMgMzMuNTE4NiAxNDkuNDM1IDMzLjQzODVDMTQ5LjI1NyAzMy4zNTY0IDE0OS4xMDUgMzMuMjQzMiAxNDguOTc4IDMzLjA5ODZDMTQ4Ljg1MyAzMi45NTIxIDE0OC43NTcgMzIuNzgxMiAxNDguNjkxIDMyLjU4NTlDMTQ4LjYyNSAzMi4zOTA2IDE0OC41OTEgMzIuMTc4NyAxNDguNTkxIDMxLjk1MDJaTTE0OS4xMzMgMzEuODgyOFYzMS45NTAyQzE0OS4xMzMgMzIuMTA4NCAxNDkuMTUyIDMyLjI1NzggMTQ5LjE4OSAzMi4zOTg0QzE0OS4yMjYgMzIuNTM3MSAxNDkuMjgyIDMyLjY2MDIgMTQ5LjM1NiAzMi43Njc2QzE0OS40MzIgMzIuODc1IDE0OS41MjcgMzIuOTYgMTQ5LjY0IDMzLjAyMjVDMTQ5Ljc1MyAzMy4wODMgMTQ5Ljg4NSAzMy4xMTMzIDE1MC4wMzYgMzMuMTEzM0MxNTAuMTg0IDMzLjExMzMgMTUwLjMxNCAzMy4wODMgMTUwLjQyNSAzMy4wMjI1QzE1MC41MzkgMzIuOTYgMTUwLjYzMiAzMi44NzUgMTUwLjcwNyAzMi43Njc2QzE1MC43ODEgMzIuNjYwMiAxNTAuODM2IDMyLjUzNzEgMTUwLjg3NCAzMi4zOTg0QzE1MC45MTMgMzIuMjU3OCAxNTAuOTMyIDMyLjEwODQgMTUwLjkzMiAzMS45NTAyVjMxLjg4MjhDMTUwLjkzMiAzMS43MjY2IDE1MC45MTMgMzEuNTc5MSAxNTAuODc0IDMxLjQ0MDRDMTUwLjgzNiAzMS4yOTk4IDE1MC43OCAzMS4xNzU4IDE1MC43MDQgMzEuMDY4NEMxNTAuNjI5IDMwLjk1OSAxNTAuNTM2IDMwLjg3MyAxNTAuNDIyIDMwLjgxMDVDMTUwLjMxMSAzMC43NDggMTUwLjE4IDMwLjcxNjggMTUwLjAzIDMwLjcxNjhDMTQ5Ljg4MSAzMC43MTY4IDE0OS43NSAzMC43NDggMTQ5LjYzNyAzMC44MTA1QzE0OS41MjYgMzAuODczIDE0OS40MzIgMzAuOTU5IDE0OS4zNTYgMzEuMDY4NEMxNDkuMjgyIDMxLjE3NTggMTQ5LjIyNiAzMS4yOTk4IDE0OS4xODkgMzEuNDQwNEMxNDkuMTUyIDMxLjU3OTEgMTQ5LjEzMyAzMS43MjY2IDE0OS4xMzMgMzEuODgyOFpNMTUzLjQ4NCAzMC4zMzAxVjMwLjc0NjFIMTUxLjc3VjMwLjMzMDFIMTUzLjQ4NFpNMTUyLjM1IDI5LjU1OTZIMTUyLjg5MlYzMi43MTQ4QzE1Mi44OTIgMzIuODIyMyAxNTIuOTA5IDMyLjkwMzMgMTUyLjk0MiAzMi45NThDMTUyLjk3NSAzMy4wMTI3IDE1My4wMTggMzMuMDQ4OCAxNTMuMDcxIDMzLjA2NjRDMTUzLjEyNCAzMy4wODQgMTUzLjE4IDMzLjA5MjggMTUzLjI0MSAzMy4wOTI4QzE1My4yODYgMzMuMDkyOCAxNTMuMzMzIDMzLjA4ODkgMTUzLjM4MSAzMy4wODExQzE1My40MzIgMzMuMDcxMyAxNTMuNDcgMzMuMDYzNSAxNTMuNDk2IDMzLjA1NzZMMTUzLjQ5OSAzMy41QzE1My40NTYgMzMuNTEzNyAxNTMuMzk5IDMzLjUyNjQgMTUzLjMyOSAzMy41MzgxQzE1My4yNiAzMy41NTE4IDE1My4xNzcgMzMuNTU4NiAxNTMuMDggMzMuNTU4NkMxNTIuOTQ3IDMzLjU1ODYgMTUyLjgyNSAzMy41MzIyIDE1Mi43MTMgMzMuNDc5NUMxNTIuNjAyIDMzLjQyNjggMTUyLjUxMyAzMy4zMzg5IDE1Mi40NDcgMzMuMjE1OEMxNTIuMzgyIDMzLjA5MDggMTUyLjM1IDMyLjkyMjkgMTUyLjM1IDMyLjcxMTlWMjkuNTU5NlpNMTU1Ljk4OSAzMi45NThWMzEuMzI2MkMxNTUuOTg5IDMxLjIwMTIgMTU1Ljk2MyAzMS4wOTI4IDE1NS45MTMgMzEuMDAxQzE1NS44NjQgMzAuOTA3MiAxNTUuNzkgMzAuODM1IDE1NS42OSAzMC43ODQyQzE1NS41OSAzMC43MzM0IDE1NS40NjcgMzAuNzA4IDE1NS4zMjEgMzAuNzA4QzE1NS4xODQgMzAuNzA4IDE1NS4wNjQgMzAuNzMxNCAxNTQuOTYgMzAuNzc4M0MxNTQuODU5IDMwLjgyNTIgMTU0Ljc3OSAzMC44ODY3IDE1NC43MiAzMC45NjI5QzE1NC42NjQgMzEuMDM5MSAxNTQuNjM1IDMxLjEyMTEgMTU0LjYzNSAzMS4yMDlIMTU0LjA5M0MxNTQuMDkzIDMxLjA5NTcgMTU0LjEyMyAzMC45ODM0IDE1NC4xODEgMzAuODcyMUMxNTQuMjQgMzAuNzYwNyAxNTQuMzI0IDMwLjY2MDIgMTU0LjQzMyAzMC41NzAzQzE1NC41NDQgMzAuNDc4NSAxNTQuNjc3IDMwLjQwNjIgMTU0LjgzMiAzMC4zNTM1QzE1NC45ODggMzAuMjk4OCAxNTUuMTYyIDMwLjI3MTUgMTU1LjM1MyAzMC4yNzE1QzE1NS41ODMgMzAuMjcxNSAxNTUuNzg3IDMwLjMxMDUgMTU1Ljk2MiAzMC4zODg3QzE1Ni4xNCAzMC40NjY4IDE1Ni4yNzkgMzAuNTg1IDE1Ni4zNzggMzAuNzQzMkMxNTYuNDggMzAuODk5NCAxNTYuNTMxIDMxLjA5NTcgMTU2LjUzMSAzMS4zMzJWMzIuODA4NkMxNTYuNTMxIDMyLjkxNDEgMTU2LjU0IDMzLjAyNjQgMTU2LjU1NyAzMy4xNDU1QzE1Ni41NzcgMzMuMjY0NiAxNTYuNjA1IDMzLjM2NzIgMTU2LjY0MiAzMy40NTMxVjMzLjVIMTU2LjA3N0MxNTYuMDQ5IDMzLjQzNzUgMTU2LjAyOCAzMy4zNTQ1IDE1Ni4wMTIgMzMuMjUxQzE1NS45OTcgMzMuMTQ1NSAxNTUuOTg5IDMzLjA0NzkgMTU1Ljk4OSAzMi45NThaTTE1Ni4wODMgMzEuNTc4MUwxNTYuMDg4IDMxLjk1OUgxNTUuNTQxQzE1NS4zODYgMzEuOTU5IDE1NS4yNDkgMzEuOTcxNyAxNTUuMTI3IDMxLjk5NzFDMTU1LjAwNiAzMi4wMjA1IDE1NC45MDUgMzIuMDU2NiAxNTQuODIzIDMyLjEwNTVDMTU0Ljc0MSAzMi4xNTQzIDE1NC42NzggMzIuMjE1OCAxNTQuNjM1IDMyLjI5QzE1NC41OTIgMzIuMzYyMyAxNTQuNTcxIDMyLjQ0NzMgMTU0LjU3MSAzMi41NDQ5QzE1NC41NzEgMzIuNjQ0NSAxNTQuNTkzIDMyLjczNTQgMTU0LjYzOCAzMi44MTc0QzE1NC42ODMgMzIuODk5NCAxNTQuNzUgMzIuOTY0OCAxNTQuODQgMzMuMDEzN0MxNTQuOTMyIDMzLjA2MDUgMTU1LjA0NCAzMy4wODQgMTU1LjE3NyAzMy4wODRDMTU1LjM0MyAzMy4wODQgMTU1LjQ5IDMzLjA0ODggMTU1LjYxNyAzMi45Nzg1QzE1NS43NDQgMzIuOTA4MiAxNTUuODQ0IDMyLjgyMjMgMTU1LjkxOCAzMi43MjA3QzE1NS45OTUgMzIuNjE5MSAxNTYuMDM2IDMyLjUyMDUgMTU2LjA0MiAzMi40MjQ4TDE1Ni4yNzMgMzIuNjg1NUMxNTYuMjU5IDMyLjc2NzYgMTU2LjIyMiAzMi44NTg0IDE1Ni4xNjIgMzIuOTU4QzE1Ni4xMDEgMzMuMDU3NiAxNTYuMDIgMzMuMTUzMyAxNTUuOTE4IDMzLjI0NTFDMTU1LjgxOSAzMy4zMzUgMTU1LjcgMzMuNDEwMiAxNTUuNTYxIDMzLjQ3MDdDMTU1LjQyNCAzMy41MjkzIDE1NS4yNyAzMy41NTg2IDE1NS4wOTggMzMuNTU4NkMxNTQuODgzIDMzLjU1ODYgMTU0LjY5NSAzMy41MTY2IDE1NC41MzMgMzMuNDMyNkMxNTQuMzczIDMzLjM0ODYgMTU0LjI0OCAzMy4yMzYzIDE1NC4xNTggMzMuMDk1N0MxNTQuMDcgMzIuOTUzMSAxNTQuMDI2IDMyLjc5MzkgMTU0LjAyNiAzMi42MTgyQzE1NC4wMjYgMzIuNDQ4MiAxNTQuMDU5IDMyLjI5ODggMTU0LjEyNSAzMi4xNjk5QzE1NC4xOTIgMzIuMDM5MSAxNTQuMjg4IDMxLjkzMDcgMTU0LjQxMyAzMS44NDQ3QzE1NC41MzggMzEuNzU2OCAxNTQuNjg4IDMxLjY5MDQgMTU0Ljg2NCAzMS42NDU1QzE1NS4wNCAzMS42MDA2IDE1NS4yMzYgMzEuNTc4MSAxNTUuNDUzIDMxLjU3ODFIMTU2LjA4M1pNMTU3Ljk3MiAyOVYzMy41SDE1Ny40MjdWMjlIMTU3Ljk3MloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuMzgiLz4KPHBhdGggZD0iTTE0NS43MzIgMzYuOTM5NVY0NS41SDE0NC4zMlYzOC42MTUyTDE0Mi4yMjggMzkuMzI0MlYzOC4xNTgyTDE0NS41NjIgMzYuOTM5NUgxNDUuNzMyWk0xNTQuMjc1IDQzLjE3MzhDMTU0LjI3NSA0My43MDUxIDE1NC4xNTIgNDQuMTUyMyAxNTMuOTA2IDQ0LjUxNTZDMTUzLjY2IDQ0Ljg3ODkgMTUzLjMyNCA0NS4xNTQzIDE1Mi44OTggNDUuMzQxOEMxNTIuNDc2IDQ1LjUyNTQgMTUyIDQ1LjYxNzIgMTUxLjQ2OCA0NS42MTcyQzE1MC45MzcgNDUuNjE3MiAxNTAuNDU4IDQ1LjUyNTQgMTUwLjAzMyA0NS4zNDE4QzE0OS42MDcgNDUuMTU0MyAxNDkuMjcxIDQ0Ljg3ODkgMTQ5LjAyNSA0NC41MTU2QzE0OC43NzkgNDQuMTUyMyAxNDguNjU2IDQzLjcwNTEgMTQ4LjY1NiA0My4xNzM4QzE0OC42NTYgNDIuODIyMyAxNDguNzI0IDQyLjUwMzkgMTQ4Ljg2MSA0Mi4yMTg4QzE0OC45OTggNDEuOTI5NyAxNDkuMTkxIDQxLjY4MTYgMTQ5LjQ0MSA0MS40NzQ2QzE0OS42OTUgNDEuMjYzNyAxNDkuOTkyIDQxLjEwMTYgMTUwLjMzMiA0MC45ODgzQzE1MC42NzUgNDAuODc1IDE1MS4wNSA0MC44MTg0IDE1MS40NTcgNDAuODE4NEMxNTEuOTk2IDQwLjgxODQgMTUyLjQ3OCA0MC45MTggMTUyLjkwNCA0MS4xMTcyQzE1My4zMyA0MS4zMTY0IDE1My42NjQgNDEuNTkxOCAxNTMuOTA2IDQxLjk0MzRDMTU0LjE1MiA0Mi4yOTQ5IDE1NC4yNzUgNDIuNzA1MSAxNTQuMjc1IDQzLjE3MzhaTTE1Mi44NTcgNDMuMTAzNUMxNTIuODU3IDQyLjgxODQgMTUyLjc5OCA0Mi41Njg0IDE1Mi42ODEgNDIuMzUzNUMxNTIuNTY0IDQyLjEzODcgMTUyLjQgNDEuOTcyNyAxNTIuMTg5IDQxLjg1NTVDMTUxLjk3OCA0MS43MzgzIDE1MS43MzQgNDEuNjc5NyAxNTEuNDU3IDQxLjY3OTdDMTUxLjE3NSA0MS42Nzk3IDE1MC45MzEgNDEuNzM4MyAxNTAuNzI0IDQxLjg1NTVDMTUwLjUxNyA0MS45NzI3IDE1MC4zNTUgNDIuMTM4NyAxNTAuMjM4IDQyLjM1MzVDMTUwLjEyNSA0Mi41Njg0IDE1MC4wNjggNDIuODE4NCAxNTAuMDY4IDQzLjEwMzVDMTUwLjA2OCA0My4zOTI2IDE1MC4xMjUgNDMuNjQyNiAxNTAuMjM4IDQzLjg1MzVDMTUwLjM1MSA0NC4wNjA1IDE1MC41MTMgNDQuMjE4OCAxNTAuNzI0IDQ0LjMyODFDMTUwLjkzNSA0NC40Mzc1IDE1MS4xODMgNDQuNDkyMiAxNTEuNDY4IDQ0LjQ5MjJDMTUxLjc1MyA0NC40OTIyIDE1MiA0NC40Mzc1IDE1Mi4yMDcgNDQuMzI4MUMxNTIuNDE0IDQ0LjIxODggMTUyLjU3NCA0NC4wNjA1IDE1Mi42ODcgNDMuODUzNUMxNTIuOCA0My42NDI2IDE1Mi44NTcgNDMuMzkyNiAxNTIuODU3IDQzLjEwMzVaTTE1NC4wODIgMzkuMjEyOUMxNTQuMDgyIDM5LjYzODcgMTUzLjk2OCA0MC4wMTc2IDE1My43NDIgNDAuMzQ5NkMxNTMuNTE5IDQwLjY4MTYgMTUzLjIxIDQwLjk0MzQgMTUyLjgxNiA0MS4xMzQ4QzE1Mi40MjEgNDEuMzIyMyAxNTEuOTcyIDQxLjQxNiAxNTEuNDY4IDQxLjQxNkMxNTAuOTYgNDEuNDE2IDE1MC41MDcgNDEuMzIyMyAxNTAuMTA5IDQxLjEzNDhDMTQ5LjcxNCA0MC45NDM0IDE0OS40MDQgNDAuNjgxNiAxNDkuMTc3IDQwLjM0OTZDMTQ4Ljk1NSA0MC4wMTc2IDE0OC44NDMgMzkuNjM4NyAxNDguODQzIDM5LjIxMjlDMTQ4Ljg0MyAzOC43MDUxIDE0OC45NTUgMzguMjc3MyAxNDkuMTc3IDM3LjkyOTdDMTQ5LjQwNCAzNy41NzgxIDE0OS43MTQgMzcuMzEwNSAxNTAuMTA5IDM3LjEyN0MxNTAuNTAzIDM2Ljk0MzQgMTUwLjk1NSAzNi44NTE2IDE1MS40NjIgMzYuODUxNkMxNTEuOTcgMzYuODUxNiAxNTIuNDIxIDM2Ljk0MzQgMTUyLjgxNiAzNy4xMjdDMTUzLjIxIDM3LjMxMDUgMTUzLjUxOSAzNy41NzgxIDE1My43NDIgMzcuOTI5N0MxNTMuOTY4IDM4LjI3NzMgMTU0LjA4MiAzOC43MDUxIDE1NC4wODIgMzkuMjEyOVpNMTUyLjY2OSAzOS4yNTk4QzE1Mi42NjkgMzkuMDA1OSAxNTIuNjE5IDM4Ljc4MzIgMTUyLjUxNyAzOC41OTE4QzE1Mi40MTkgMzguMzk2NSAxNTIuMjgxIDM4LjI0NDEgMTUyLjEwMSAzOC4xMzQ4QzE1MS45MjEgMzguMDI1NCAxNTEuNzA4IDM3Ljk3MDcgMTUxLjQ2MiAzNy45NzA3QzE1MS4yMTYgMzcuOTcwNyAxNTEuMDAzIDM4LjAyMzQgMTUwLjgyNCAzOC4xMjg5QzE1MC42NDQgMzguMjM0NCAxNTAuNTA1IDM4LjM4MjggMTUwLjQwOCAzOC41NzQyQzE1MC4zMSAzOC43NjU2IDE1MC4yNjEgMzguOTk0MSAxNTAuMjYxIDM5LjI1OThDMTUwLjI2MSAzOS41MjE1IDE1MC4zMSAzOS43NSAxNTAuNDA4IDM5Ljk0NTNDMTUwLjUwNSA0MC4xMzY3IDE1MC42NDQgNDAuMjg3MSAxNTAuODI0IDQwLjM5NjVDMTUxLjAwNyA0MC41MDU5IDE1MS4yMjIgNDAuNTYwNSAxNTEuNDY4IDQwLjU2MDVDMTUxLjcxNCA0MC41NjA1IDE1MS45MjcgNDAuNTA1OSAxNTIuMTA3IDQwLjM5NjVDMTUyLjI4NyA0MC4yODcxIDE1Mi40MjUgNDAuMTM2NyAxNTIuNTIzIDM5Ljk0NTNDMTUyLjYyMSAzOS43NSAxNTIuNjY5IDM5LjUyMTUgMTUyLjY2OSAzOS4yNTk4Wk0xNTcuMTc1IDQwLjU5NTdIMTU4LjAxOUMxNTguMzQ3IDQwLjU5NTcgMTU4LjYxOSA0MC41MzkxIDE1OC44MzMgNDAuNDI1OEMxNTkuMDUyIDQwLjMxMjUgMTU5LjIxNCA0MC4xNTYyIDE1OS4zMiAzOS45NTdDMTU5LjQyNSAzOS43NTc4IDE1OS40NzggMzkuNTI5MyAxNTkuNDc4IDM5LjI3MTVDMTU5LjQ3OCAzOS4wMDIgMTU5LjQyOSAzOC43NzE1IDE1OS4zMzIgMzguNTgwMUMxNTkuMjM4IDM4LjM4NDggMTU5LjA5MyAzOC4yMzQ0IDE1OC44OTggMzguMTI4OUMxNTguNzA3IDM4LjAyMzQgMTU4LjQ2MiAzNy45NzA3IDE1OC4xNjYgMzcuOTcwN0MxNTcuOTE2IDM3Ljk3MDcgMTU3LjY4OSAzOC4wMjE1IDE1Ny40ODYgMzguMTIzQzE1Ny4yODcgMzguMjIwNyAxNTcuMTI4IDM4LjM2MTMgMTU3LjAxMSAzOC41NDQ5QzE1Ni44OTQgMzguNzI0NiAxNTYuODM1IDM4LjkzOTUgMTU2LjgzNSAzOS4xODk1SDE1NS40MTdDMTU1LjQxNyAzOC43MzYzIDE1NS41MzcgMzguMzM0IDE1NS43NzUgMzcuOTgyNEMxNTYuMDEzIDM3LjYzMDkgMTU2LjMzNyAzNy4zNTU1IDE1Ni43NDggMzcuMTU2MkMxNTcuMTYyIDM2Ljk1MzEgMTU3LjYyNiAzNi44NTE2IDE1OC4xNDIgMzYuODUxNkMxNTguNjkzIDM2Ljg1MTYgMTU5LjE3MyAzNi45NDM0IDE1OS41ODMgMzcuMTI3QzE1OS45OTggMzcuMzA2NiAxNjAuMzIgMzcuNTc2MiAxNjAuNTUgMzcuOTM1NUMxNjAuNzgxIDM4LjI5NDkgMTYwLjg5NiAzOC43NDAyIDE2MC44OTYgMzkuMjcxNUMxNjAuODk2IDM5LjUxMzcgMTYwLjgzOSAzOS43NTk4IDE2MC43MjYgNDAuMDA5OEMxNjAuNjEzIDQwLjI1OTggMTYwLjQ0NSA0MC40ODgzIDE2MC4yMjIgNDAuNjk1M0MxNjAgNDAuODk4NCAxNTkuNzIyIDQxLjA2NDUgMTU5LjM5IDQxLjE5MzRDMTU5LjA1OCA0MS4zMTg0IDE1OC42NzMgNDEuMzgwOSAxNTguMjM2IDQxLjM4MDlIMTU3LjE3NVY0MC41OTU3Wk0xNTcuMTc1IDQxLjY5NzNWNDAuOTIzOEgxNTguMjM2QzE1OC43MzYgNDAuOTIzOCAxNTkuMTYyIDQwLjk4MjQgMTU5LjUxMyA0MS4wOTk2QzE1OS44NjkgNDEuMjE2OCAxNjAuMTU4IDQxLjM3ODkgMTYwLjM4IDQxLjU4NTlDMTYwLjYwMyA0MS43ODkxIDE2MC43NjUgNDIuMDIxNSAxNjAuODY3IDQyLjI4MzJDMTYwLjk3MiA0Mi41NDQ5IDE2MS4wMjUgNDIuODIyMyAxNjEuMDI1IDQzLjExNTJDMTYxLjAyNSA0My41MTM3IDE2MC45NTMgNDMuODY5MSAxNjAuODA4IDQ0LjE4MTZDMTYwLjY2NyA0NC40OTAyIDE2MC40NjYgNDQuNzUyIDE2MC4yMDUgNDQuOTY2OEMxNTkuOTQzIDQ1LjE4MTYgMTU5LjYzNiA0NS4zNDM4IDE1OS4yODUgNDUuNDUzMUMxNTguOTM3IDQ1LjU2MjUgMTU4LjU1OCA0NS42MTcyIDE1OC4xNDggNDUuNjE3MkMxNTcuNzgxIDQ1LjYxNzIgMTU3LjQyOSA0NS41NjY0IDE1Ny4wOTMgNDUuNDY0OEMxNTYuNzU3IDQ1LjM2MzMgMTU2LjQ1NyA0NS4yMTI5IDE1Ni4xOTEgNDUuMDEzN0MxNTUuOTI1IDQ0LjgxMDUgMTU1LjcxNCA0NC41NTg2IDE1NS41NTggNDQuMjU3OEMxNTUuNDA2IDQzLjk1MzEgMTU1LjMzIDQzLjYwMTYgMTU1LjMzIDQzLjIwMzFIMTU2Ljc0MkMxNTYuNzQyIDQzLjQ1NyAxNTYuOCA0My42ODE2IDE1Ni45MTcgNDMuODc3QzE1Ny4wMzkgNDQuMDY4NCAxNTcuMjA3IDQ0LjIxODggMTU3LjQyMSA0NC4zMjgxQzE1Ny42NCA0NC40Mzc1IDE1Ny44OSA0NC40OTIyIDE1OC4xNzEgNDQuNDkyMkMxNTguNDY4IDQ0LjQ5MjIgMTU4LjcyNCA0NC40Mzk1IDE1OC45MzkgNDQuMzM0QzE1OS4xNTQgNDQuMjI4NSAxNTkuMzE4IDQ0LjA3MjMgMTU5LjQzMSA0My44NjUyQzE1OS41NDggNDMuNjU4MiAxNTkuNjA3IDQzLjQwODIgMTU5LjYwNyA0My4xMTUyQzE1OS42MDcgNDIuNzgzMiAxNTkuNTQyIDQyLjUxMzcgMTU5LjQxNCA0Mi4zMDY2QzE1OS4yODUgNDIuMDk5NiAxNTkuMTAxIDQxLjk0NzMgMTU4Ljg2MyA0MS44NDk2QzE1OC42MjUgNDEuNzQ4IDE1OC4zNDMgNDEuNjk3MyAxNTguMDE5IDQxLjY5NzNIMTU3LjE3NVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuODciLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAyXzQxOThfMTEwNDkzKSI+CjxwYXRoIGQ9Ik0yIDg4TDE5NyA4OCIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiIHN0cm9rZS13aWR0aD0iMC41Ii8+CjxwYXRoIGQ9Ik0yIDEwNkwxOTcgMTA2IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIgc3Ryb2tlLXdpZHRoPSIwLjUiLz4KPHBhdGggZD0iTTIgMTI0TDE5NyAxMjQiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIiBzdHJva2Utd2lkdGg9IjAuNSIvPgo8cGF0aCBkPSJNMiAxNDJMMTk3IDE0MiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiIHN0cm9rZS13aWR0aD0iMC41Ii8+CjxwYXRoIGQ9Ik0yIDE2MEwxOTcgMTYwIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIvPgo8cGF0aCBkPSJNMTAzLjY5NSA5MUMxMDMuNjk1IDg5Ljg5NTQgMTA0LjU5IDg5IDEwNS42OTUgODlIMTE3LjY5NUMxMTguNzk5IDg5IDExOS42OTUgODkuODk1NCAxMTkuNjk1IDkxVjE2MEgxMDMuNjk1VjkxWiIgZmlsbD0iIzQxOTZGNiIvPgo8cGF0aCBkPSJNMTM2IDEyNEMxMzYgMTIyLjg5NSAxMzYuODk1IDEyMiAxMzggMTIySDE1MEMxNTEuMTA1IDEyMiAxNTIgMTIyLjg5NSAxNTIgMTI0VjE2MEgxMzZWMTI0WiIgZmlsbD0iIzQxOTZGNiIvPgo8cGF0aCBkPSJNNy42OTQ4MiAxNDNDNy42OTQ4MiAxNDEuODk1IDguNTkwMjUgMTQxIDkuNjk0ODIgMTQxSDIxLjY5NDhDMjIuNzk5NCAxNDEgMjMuNjk0OCAxNDEuODk1IDIzLjY5NDggMTQzVjE2MEg3LjY5NDgyVjE0M1oiIGZpbGw9IiM0MTk2RjYiLz4KPHBhdGggZD0iTTM5LjY5NDggMTA3QzM5LjY5NDggMTA1Ljg5NSA0MC41OTAzIDEwNSA0MS42OTQ4IDEwNUg1My42OTQ4QzU0Ljc5OTQgMTA1IDU1LjY5NDggMTA1Ljg5NSA1NS42OTQ4IDEwN1YxNjBIMzkuNjk0OFYxMDdaIiBmaWxsPSIjNDE5NkY2Ii8+CjxwYXRoIGQ9Ik03MiAxMzJDNzIgMTMwLjg5NSA3Mi44OTU0IDEzMCA3NCAxMzBIODZDODcuMTA0NiAxMzAgODggMTMwLjg5NSA4OCAxMzJWMTYwSDcyVjEzMloiIGZpbGw9IiM0MTk2RjYiLz4KPHBhdGggZD0iTTE2Ny42OTUgMTUwQzE2Ny42OTUgMTQ4Ljg5NSAxNjguNTkgMTQ4IDE2OS42OTUgMTQ4SDE4MS42OTVDMTgyLjc5OSAxNDggMTgzLjY5NSAxNDguODk1IDE4My42OTUgMTUwVjE2MEgxNjcuNjk1VjE1MFoiIGZpbGw9IiM0MTk2RjYiLz4KPHBhdGggZD0iTTQgMTA5TDE4LjUyMTEgMTAzLjAzMUMxOC41OTMzIDEwMy4wMDEgMTguNjY4NyAxMDIuOTggMTguNzQ1NyAxMDIuOTY4TDU5LjU3NDcgOTYuNTM2NUM1OS42NjE1IDk2LjUyMjggNTkuNzQ5OCA5Ni41MjA3IDU5LjgzNzMgOTYuNTMwMUw5OS4wOTMzIDEwMC43NTVMMTM5LjA0NCA5OS43MTMxTDE4MS4xNDIgOTUuODQwMkMxODEuMjEgOTUuODMzOSAxODEuMjc4IDk1LjgyMDYgMTgxLjM0NCA5NS44MDA1TDE5NyA5MSIgc3Ryb2tlPSIjRkZDMTA3IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8Y2lyY2xlIGN4PSI1OSIgY3k9Ijk3IiByPSIxLjUiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNGRkMxMDciLz4KPGNpcmNsZSBjeD0iMTguNjk0OCIgY3k9IjEwMy4xNjEiIHI9IjEuNSIgZmlsbD0id2hpdGUiIHN0cm9rZT0iI0ZGQzEwNyIvPgo8Y2lyY2xlIGN4PSIzLjY5NDgyIiBjeT0iMTA5LjE2MSIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjRkZDMTA3Ii8+CjxjaXJjbGUgY3g9IjEwMCIgY3k9IjEwMSIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjRkZDMTA3Ii8+CjxjaXJjbGUgY3g9IjE0MyIgY3k9Ijk5IiByPSIxLjUiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNGRkMxMDciLz4KPGNpcmNsZSBjeD0iMTgwLjY5NSIgY3k9Ijk2LjE2MTEiIHI9IjEuNSIgZmlsbD0id2hpdGUiIHN0cm9rZT0iI0ZGQzEwNyIvPgo8Y2lyY2xlIGN4PSIxOTYiIGN5PSI5MSIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjRkZDMTA3Ii8+CjxwYXRoIGQ9Ik00IDEzMkwxOC42MzkzIDExOS44MTRMNjAuMzA1IDEyNy42TDEwMC4yODIgMTE2TDE0MC4yNTggMTE2TDE3Ny45ODIgMTIyLjE4NEwxOTYgMTI3LjYiIHN0cm9rZT0iIzRDQUY1MCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4KPGNpcmNsZSBjeD0iNTkiIGN5PSIxMjgiIHI9IjEuNSIgZmlsbD0id2hpdGUiIHN0cm9rZT0iIzRDQUY1MCIvPgo8Y2lyY2xlIGN4PSIxOSIgY3k9IjEyMCIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjNENBRjUwIi8+CjxjaXJjbGUgY3g9IjEwMCIgY3k9IjExNiIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjNENBRjUwIi8+CjxjaXJjbGUgY3g9IjE3OC42OTUiIGN5PSIxMjIiIHI9IjEuNSIgZmlsbD0id2hpdGUiIHN0cm9rZT0iIzRDQUY1MCIvPgo8Y2lyY2xlIGN4PSIxOTYiIGN5PSIxMjgiIHI9IjEuNSIgZmlsbD0id2hpdGUiIHN0cm9rZT0iIzRDQUY1MCIvPgo8Y2lyY2xlIGN4PSIzLjY5NDgyIiBjeT0iMTMxLjE2MSIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjNENBRjUwIi8+CjxjaXJjbGUgY3g9IjE0MyIgY3k9IjExNiIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjNENBRjUwIi8+CjwvZz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl80MTk4XzExMDQ5MyIgeDE9IjQ1LjY5ODYiIHkxPSI4NC43NDkzIiB4Mj0iNDUuNjk4NiIgeTI9Ii0xOS45Mzg4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIG9mZnNldD0iMC4xNzMzODkiIHN0b3AtY29sb3I9IiM2REM3NzAiLz4KPHN0b3Agb2Zmc2V0PSIwLjI2Mjk3OSIgc3RvcC1jb2xvcj0iIzZEQzc3MCIvPgo8c3RvcCBvZmZzZXQ9IjAuMjY3OTY5IiBzdG9wLWNvbG9yPSIjRkZDMTA3Ii8+CjxzdG9wIG9mZnNldD0iMC40NDYxNzgiIHN0b3AtY29sb3I9IiNGRkMxMDciLz4KPHN0b3Agb2Zmc2V0PSIwLjQ0ODMxNiIgc3RvcC1jb2xvcj0iI0ZEOEYzQyIvPgo8c3RvcCBvZmZzZXQ9IjAuNjI0Mzg3IiBzdG9wLWNvbG9yPSIjRkQ4RjNDIi8+CjxzdG9wIG9mZnNldD0iMC42MzA4MDMiIHN0b3AtY29sb3I9IiNGMjczN0MiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDFfbGluZWFyXzQxOThfMTEwNDkzIiB4MT0iNDUuMjc5NyIgeTE9Ijc0LjY2MzUiIHgyPSI0NS4yMjYxIiB5Mj0iLTIwLjI0MzEiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agb2Zmc2V0PSIwLjE4MjI1NCIgc3RvcC1jb2xvcj0iIzNGQTcxQSIvPgo8c3RvcCBvZmZzZXQ9IjAuMTkwMTE4IiBzdG9wLWNvbG9yPSIjRkZBNjAwIi8+CjxzdG9wIG9mZnNldD0iMC4zODI3NjEiIHN0b3AtY29sb3I9IiNGRkE2MDAiLz4KPHN0b3Agb2Zmc2V0PSIwLjM4ODI2OCIgc3RvcC1jb2xvcj0iI0Y1NkUwOCIvPgo8c3RvcCBvZmZzZXQ9IjAuNTgxNjk1IiBzdG9wLWNvbG9yPSIjRjU2RTA4Ii8+CjxzdG9wIG9mZnNldD0iMC41ODc5ODUiIHN0b3AtY29sb3I9IiNGRjRENUEiLz4KPC9saW5lYXJHcmFkaWVudD4KPGNsaXBQYXRoIGlkPSJjbGlwMF80MTk4XzExMDQ5MyI+CjxyZWN0IHdpZHRoPSI5NiIgaGVpZ2h0PSI3NiIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPGNsaXBQYXRoIGlkPSJjbGlwMV80MTk4XzExMDQ5MyI+CjxyZWN0IHdpZHRoPSI5NiIgaGVpZ2h0PSI3NiIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPGNsaXBQYXRoIGlkPSJjbGlwMl80MTk4XzExMDQ5MyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iNzYiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDg0KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=", "description": "Display time series data using customizable line and bar charts. Use various pie charts to display the latest values.", "order": 1000, "name": "Charts" }, "widgetTypeFqns": [ "time_series_chart", + "line_chart", + "bar_chart", + "point_chart", "charts.basic_timeseries", "charts.state_chart", "range_chart", diff --git a/application/src/main/data/json/system/widget_types/bar_chart.json b/application/src/main/data/json/system/widget_types/bar_chart.json new file mode 100644 index 0000000000..c857922eea --- /dev/null +++ b/application/src/main/data/json/system/widget_types/bar_chart.json @@ -0,0 +1,32 @@ +{ + "fqn": "bar_chart", + "name": "Bar chart", + "deprecated": false, + "image": "tb-image:Y2hhcnRfKDIpLnN2Zw==:IkJhciBjaGFydCIgc3lzdGVtIHdpZGdldCBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF80MTgzXzkwODc3KSI+CjxwYXRoIGQ9Ik0yLjg0NzY2IDEuMjgxMjVWN0gyLjEyNVYyLjE4MzU5TDAuNjY3OTY5IDIuNzE0ODRWMi4wNjI1TDIuNzM0MzggMS4yODEyNUgyLjg0NzY2Wk04LjY3NTE3IDMuNzAzMTJWNC41NzAzMUM4LjY3NTE3IDUuMDM2NDYgOC42MzM1MSA1LjQyOTY5IDguNTUwMTcgNS43NUM4LjQ2Njg0IDYuMDcwMzEgOC4zNDcwNSA2LjMyODEyIDguMTkwOCA2LjUyMzQ0QzguMDM0NTUgNi43MTg3NSA3Ljg0NTc1IDYuODYwNjggNy42MjQzOSA2Ljk0OTIyQzcuNDA1NjQgNy4wMzUxNiA3LjE1ODI1IDcuMDc4MTIgNi44ODIyIDcuMDc4MTJDNi42NjM0NSA3LjA3ODEyIDYuNDYxNjMgNy4wNTA3OCA2LjI3NjczIDYuOTk2MDlDNi4wOTE4NCA2Ljk0MTQxIDUuOTI1MTcgNi44NTQxNyA1Ljc3NjczIDYuNzM0MzhDNS42MzA5IDYuNjExOTggNS41MDU5IDYuNDUzMTIgNS40MDE3MyA2LjI1NzgxQzUuMjk3NTcgNi4wNjI1IDUuMjE4MTQgNS44MjU1MiA1LjE2MzQ1IDUuNTQ2ODhDNS4xMDg3NyA1LjI2ODIzIDUuMDgxNDIgNC45NDI3MSA1LjA4MTQyIDQuNTcwMzFWMy43MDMxMkM1LjA4MTQyIDMuMjM2OTggNS4xMjMwOSAyLjg0NjM1IDUuMjA2NDIgMi41MzEyNUM1LjI5MjM2IDIuMjE2MTUgNS40MTM0NSAxLjk2MzU0IDUuNTY5NyAxLjc3MzQ0QzUuNzI1OTUgMS41ODA3MyA1LjkxMzQ1IDEuNDQyNzEgNi4xMzIyIDEuMzU5MzhDNi4zNTM1NiAxLjI3NjA0IDYuNjAwOTUgMS4yMzQzOCA2Ljg3NDM5IDEuMjM0MzhDNy4wOTU3NSAxLjIzNDM4IDcuMjk4ODcgMS4yNjE3MiA3LjQ4Mzc3IDEuMzE2NDFDNy42NzEyNyAxLjM2ODQ5IDcuODM3OTMgMS40NTMxMiA3Ljk4Mzc3IDEuNTcwMzFDOC4xMjk2IDEuNjg0OSA4LjI1MzMgMS44Mzg1NCA4LjM1NDg2IDIuMDMxMjVDOC40NTkwMyAyLjIyMTM1IDguNTM4NDUgMi40NTQ0MyA4LjU5MzE0IDIuNzMwNDdDOC42NDc4MyAzLjAwNjUxIDguNjc1MTcgMy4zMzA3MyA4LjY3NTE3IDMuNzAzMTJaTTcuOTQ4NjEgNC42ODc1VjMuNTgyMDNDNy45NDg2MSAzLjMyNjgyIDcuOTMyOTggMy4xMDI4NiA3LjkwMTczIDIuOTEwMTZDNy44NzMwOSAyLjcxNDg0IDcuODMwMTIgMi41NDgxOCA3Ljc3MjgzIDIuNDEwMTZDNy43MTU1NCAyLjI3MjE0IDcuNjQyNjIgMi4xNjAxNiA3LjU1NDA4IDIuMDc0MjJDNy40NjgxNCAxLjk4ODI4IDcuMzY3ODggMS45MjU3OCA3LjI1MzMgMS44ODY3MkM3LjE0MTMyIDEuODQ1MDUgNy4wMTUwMiAxLjgyNDIyIDYuODc0MzkgMS44MjQyMkM2LjcwMjUyIDEuODI0MjIgNi41NTAxNyAxLjg1Njc3IDYuNDE3MzYgMS45MjE4OEM2LjI4NDU1IDEuOTg0MzggNi4xNzI1NyAyLjA4NDY0IDYuMDgxNDIgMi4yMjI2NkM1Ljk5Mjg4IDIuMzYwNjggNS45MjUxNyAyLjU0MTY3IDUuODc4MyAyLjc2NTYyQzUuODMxNDIgMi45ODk1OCA1LjgwNzk4IDMuMjYxNzIgNS44MDc5OCAzLjU4MjAzVjQuNjg3NUM1LjgwNzk4IDQuOTQyNzEgNS44MjIzMSA1LjE2Nzk3IDUuODUwOTUgNS4zNjMyOEM1Ljg4MjIgNS41NTg1OSA1LjkyNzc4IDUuNzI3ODYgNS45ODc2NyA1Ljg3MTA5QzYuMDQ3NTcgNi4wMTE3MiA2LjEyMDQ4IDYuMTI3NiA2LjIwNjQyIDYuMjE4NzVDNi4yOTIzNiA2LjMwOTkgNi4zOTEzMiA2LjM3NzYgNi41MDMzIDYuNDIxODhDNi42MTc4OCA2LjQ2MzU0IDYuNzQ0MTggNi40ODQzOCA2Ljg4MjIgNi40ODQzOEM3LjA1OTI5IDYuNDg0MzggNy4yMTQyMyA2LjQ1MDUyIDcuMzQ3MDUgNi4zODI4MUM3LjQ3OTg2IDYuMzE1MSA3LjU5MDU0IDYuMjA5NjQgNy42NzkwOCA2LjA2NjQxQzcuNzcwMjIgNS45MjA1NyA3LjgzNzkzIDUuNzM0MzggNy44ODIyIDUuNTA3ODFDNy45MjY0NyA1LjI3ODY1IDcuOTQ4NjEgNS4wMDUyMSA3Ljk0ODYxIDQuNjg3NVpNMTMuMzA3NCAzLjcwMzEyVjQuNTcwMzFDMTMuMzA3NCA1LjAzNjQ2IDEzLjI2NTcgNS40Mjk2OSAxMy4xODI0IDUuNzVDMTMuMDk5IDYuMDcwMzEgMTIuOTc5MyA2LjMyODEyIDEyLjgyMyA2LjUyMzQ0QzEyLjY2NjggNi43MTg3NSAxMi40Nzc5IDYuODYwNjggMTIuMjU2NiA2Ljk0OTIyQzEyLjAzNzggNy4wMzUxNiAxMS43OTA0IDcuMDc4MTIgMTEuNTE0NCA3LjA3ODEyQzExLjI5NTcgNy4wNzgxMiAxMS4wOTM4IDcuMDUwNzggMTAuOTA4OSA2Ljk5NjA5QzEwLjcyNCA2Ljk0MTQxIDEwLjU1NzQgNi44NTQxNyAxMC40MDg5IDYuNzM0MzhDMTAuMjYzMSA2LjYxMTk4IDEwLjEzODEgNi40NTMxMiAxMC4wMzM5IDYuMjU3ODFDOS45Mjk3NyA2LjA2MjUgOS44NTAzNCA1LjgyNTUyIDkuNzk1NjYgNS41NDY4OEM5Ljc0MDk3IDUuMjY4MjMgOS43MTM2MyA0Ljk0MjcxIDkuNzEzNjMgNC41NzAzMVYzLjcwMzEyQzkuNzEzNjMgMy4yMzY5OCA5Ljc1NTI5IDIuODQ2MzUgOS44Mzg2MyAyLjUzMTI1QzkuOTI0NTYgMi4yMTYxNSAxMC4wNDU3IDEuOTYzNTQgMTAuMjAxOSAxLjc3MzQ0QzEwLjM1ODIgMS41ODA3MyAxMC41NDU3IDEuNDQyNzEgMTAuNzY0NCAxLjM1OTM4QzEwLjk4NTggMS4yNzYwNCAxMS4yMzMyIDEuMjM0MzggMTEuNTA2NiAxLjIzNDM4QzExLjcyNzkgMS4yMzQzOCAxMS45MzExIDEuMjYxNzIgMTIuMTE2IDEuMzE2NDFDMTIuMzAzNSAxLjM2ODQ5IDEyLjQ3MDEgMS40NTMxMiAxMi42MTYgMS41NzAzMUMxMi43NjE4IDEuNjg0OSAxMi44ODU1IDEuODM4NTQgMTIuOTg3MSAyLjAzMTI1QzEzLjA5MTIgMi4yMjEzNSAxMy4xNzA3IDIuNDU0NDMgMTMuMjI1MyAyLjczMDQ3QzEzLjI4IDMuMDA2NTEgMTMuMzA3NCAzLjMzMDczIDEzLjMwNzQgMy43MDMxMlpNMTIuNTgwOCA0LjY4NzVWMy41ODIwM0MxMi41ODA4IDMuMzI2ODIgMTIuNTY1MiAzLjEwMjg2IDEyLjUzMzkgMi45MTAxNkMxMi41MDUzIDIuNzE0ODQgMTIuNDYyMyAyLjU0ODE4IDEyLjQwNSAyLjQxMDE2QzEyLjM0NzcgMi4yNzIxNCAxMi4yNzQ4IDIuMTYwMTYgMTIuMTg2MyAyLjA3NDIyQzEyLjEwMDMgMS45ODgyOCAxMi4wMDAxIDEuOTI1NzggMTEuODg1NSAxLjg4NjcyQzExLjc3MzUgMS44NDUwNSAxMS42NDcyIDEuODI0MjIgMTEuNTA2NiAxLjgyNDIyQzExLjMzNDcgMS44MjQyMiAxMS4xODI0IDEuODU2NzcgMTEuMDQ5NiAxLjkyMTg4QzEwLjkxNjggMS45ODQzOCAxMC44MDQ4IDIuMDg0NjQgMTAuNzEzNiAyLjIyMjY2QzEwLjYyNTEgMi4zNjA2OCAxMC41NTc0IDIuNTQxNjcgMTAuNTEwNSAyLjc2NTYyQzEwLjQ2MzYgMi45ODk1OCAxMC40NDAyIDMuMjYxNzIgMTAuNDQwMiAzLjU4MjAzVjQuNjg3NUMxMC40NDAyIDQuOTQyNzEgMTAuNDU0NSA1LjE2Nzk3IDEwLjQ4MzIgNS4zNjMyOEMxMC41MTQ0IDUuNTU4NTkgMTAuNTYgNS43Mjc4NiAxMC42MTk5IDUuODcxMDlDMTAuNjc5OCA2LjAxMTcyIDEwLjc1MjcgNi4xMjc2IDEwLjgzODYgNi4yMTg3NUMxMC45MjQ2IDYuMzA5OSAxMS4wMjM1IDYuMzc3NiAxMS4xMzU1IDYuNDIxODhDMTEuMjUwMSA2LjQ2MzU0IDExLjM3NjQgNi40ODQzOCAxMS41MTQ0IDYuNDg0MzhDMTEuNjkxNSA2LjQ4NDM4IDExLjg0NjQgNi40NTA1MiAxMS45NzkzIDYuMzgyODFDMTIuMTEyMSA2LjMxNTEgMTIuMjIyNyA2LjIwOTY0IDEyLjMxMTMgNi4wNjY0MUMxMi40MDI0IDUuOTIwNTcgMTIuNDcwMSA1LjczNDM4IDEyLjUxNDQgNS41MDc4MUMxMi41NTg3IDUuMjc4NjUgMTIuNTgwOCA1LjAwNTIxIDEyLjU4MDggNC42ODc1Wk0xNC4zMDY4IDIuNzA3MDNWMi40MDYyNUMxNC4zMDY4IDIuMTkwMSAxNC4zNTM2IDEuOTkzNDkgMTQuNDQ3NCAxLjgxNjQxQzE0LjU0MTEgMS42MzkzMiAxNC42NzUzIDEuNDk3NCAxNC44NDk3IDEuMzkwNjJDMTUuMDI0MiAxLjI4Mzg1IDE1LjIzMTIgMS4yMzA0NyAxNS40NzA4IDEuMjMwNDdDMTUuNzE1NiAxLjIzMDQ3IDE1LjkyNCAxLjI4Mzg1IDE2LjA5NTggMS4zOTA2MkMxNi4yNzAzIDEuNDk3NCAxNi40MDQ0IDEuNjM5MzIgMTYuNDk4MiAxLjgxNjQxQzE2LjU5MTkgMS45OTM0OSAxNi42Mzg4IDIuMTkwMSAxNi42Mzg4IDIuNDA2MjVWMi43MDcwM0MxNi42Mzg4IDIuOTE3OTcgMTYuNTkxOSAzLjExMTk4IDE2LjQ5ODIgMy4yODkwNkMxNi40MDcgMy40NjYxNSAxNi4yNzQyIDMuNjA4MDcgMTYuMDk5NyAzLjcxNDg0QzE1LjkyNzkgMy44MjE2MSAxNS43MjA4IDMuODc1IDE1LjQ3ODYgMy44NzVDMTUuMjM2NSAzLjg3NSAxNS4wMjY4IDMuODIxNjEgMTQuODQ5NyAzLjcxNDg0QzE0LjY3NTMgMy42MDgwNyAxNC41NDExIDMuNDY2MTUgMTQuNDQ3NCAzLjI4OTA2QzE0LjM1MzYgMy4xMTE5OCAxNC4zMDY4IDIuOTE3OTcgMTQuMzA2OCAyLjcwNzAzWk0xNC44NDk3IDIuNDA2MjVWMi43MDcwM0MxNC44NDk3IDIuODI2ODIgMTQuODcxOSAyLjk0MDEgMTQuOTE2MSAzLjA0Njg4QzE0Ljk2MyAzLjE1MzY1IDE1LjAzMzMgMy4yNDA4OSAxNS4xMjcxIDMuMzA4NTlDMTUuMjIwOCAzLjM3MzcgMTUuMzM4IDMuNDA2MjUgMTUuNDc4NiAzLjQwNjI1QzE1LjYxOTMgMy40MDYyNSAxNS43MzUyIDMuMzczNyAxNS44MjYzIDMuMzA4NTlDMTUuOTE3NCAzLjI0MDg5IDE1Ljk4NTIgMy4xNTM2NSAxNi4wMjk0IDMuMDQ2ODhDMTYuMDczNyAyLjk0MDEgMTYuMDk1OCAyLjgyNjgyIDE2LjA5NTggMi43MDcwM1YyLjQwNjI1QzE2LjA5NTggMi4yODM4NSAxNi4wNzI0IDIuMTY5MjcgMTYuMDI1NSAyLjA2MjVDMTUuOTgxMiAxLjk1MzEyIDE1LjkxMjIgMS44NjU4OSAxNS44MTg1IDEuODAwNzhDMTUuNzI3MyAxLjczMzA3IDE1LjYxMTUgMS42OTkyMiAxNS40NzA4IDEuNjk5MjJDMTUuMzMyOCAxLjY5OTIyIDE1LjIxNjkgMS43MzMwNyAxNS4xMjMyIDEuODAwNzhDMTUuMDMyIDEuODY1ODkgMTQuOTYzIDEuOTUzMTIgMTQuOTE2MSAyLjA2MjVDMTQuODcxOSAyLjE2OTI3IDE0Ljg0OTcgMi4yODM4NSAxNC44NDk3IDIuNDA2MjVaTTE3LjA3NjMgNS45MTAxNlY1LjYwNTQ3QzE3LjA3NjMgNS4zOTE5MyAxNy4xMjMyIDUuMTk2NjEgMTcuMjE2OSA1LjAxOTUzQzE3LjMxMDcgNC44NDI0NSAxNy40NDQ4IDQuNzAwNTIgMTcuNjE5MyA0LjU5Mzc1QzE3Ljc5MzcgNC40ODY5OCAxOC4wMDA4IDQuNDMzNTkgMTguMjQwNCA0LjQzMzU5QzE4LjQ4NTIgNC40MzM1OSAxOC42OTM1IDQuNDg2OTggMTguODY1NCA0LjU5Mzc1QzE5LjAzOTggNC43MDA1MiAxOS4xNzQgNC44NDI0NSAxOS4yNjc3IDUuMDE5NTNDMTkuMzYxNSA1LjE5NjYxIDE5LjQwODMgNS4zOTE5MyAxOS40MDgzIDUuNjA1NDdWNS45MTAxNkMxOS40MDgzIDYuMTIzNyAxOS4zNjE1IDYuMzE5MDEgMTkuMjY3NyA2LjQ5NjA5QzE5LjE3NjYgNi42NzMxOCAxOS4wNDM3IDYuODE1MSAxOC44NjkzIDYuOTIxODhDMTguNjk3NCA3LjAyODY1IDE4LjQ5MDQgNy4wODIwMyAxOC4yNDgyIDcuMDgyMDNDMTguMDA2IDcuMDgyMDMgMTcuNzk3NyA3LjAyODY1IDE3LjYyMzIgNi45MjE4OEMxNy40NDg3IDYuODE1MSAxNy4zMTMzIDYuNjczMTggMTcuMjE2OSA2LjQ5NjA5QzE3LjEyMzIgNi4zMTkwMSAxNy4wNzYzIDYuMTIzNyAxNy4wNzYzIDUuOTEwMTZaTTE3LjYxOTMgNS42MDU0N1Y1LjkxMDE2QzE3LjYxOTMgNi4wMjk5NSAxNy42NDE0IDYuMTQ0NTMgMTcuNjg1NyA2LjI1MzkxQzE3LjczMjUgNi4zNjA2OCAxNy44MDI5IDYuNDQ3OTIgMTcuODk2NiA2LjUxNTYyQzE3Ljk5MDQgNi41ODA3MyAxOC4xMDc1IDYuNjEzMjggMTguMjQ4MiA2LjYxMzI4QzE4LjM4ODggNi42MTMyOCAxOC41MDQ3IDYuNTgwNzMgMTguNTk1OCA2LjUxNTYyQzE4LjY4OTYgNi40NDc5MiAxOC43NTg2IDYuMzYwNjggMTguODAyOSA2LjI1MzkxQzE4Ljg0NzEgNi4xNDcxNCAxOC44NjkzIDYuMDMyNTUgMTguODY5MyA1LjkxMDE2VjUuNjA1NDdDMTguODY5MyA1LjQ4MzA3IDE4Ljg0NTggNS4zNjg0OSAxOC43OTkgNS4yNjE3MkMxOC43NTQ3IDUuMTU0OTUgMTguNjg1NyA1LjA2OTAxIDE4LjU5MTkgNS4wMDM5MUMxOC41MDA4IDQuOTM2MiAxOC4zODM2IDQuOTAyMzQgMTguMjQwNCA0LjkwMjM0QzE4LjEwMjMgNC45MDIzNCAxNy45ODY1IDQuOTM2MiAxNy44OTI3IDUuMDAzOTFDMTcuODAxNiA1LjA2OTAxIDE3LjczMjUgNS4xNTQ5NSAxNy42ODU3IDUuMjYxNzJDMTcuNjQxNCA1LjM2ODQ5IDE3LjYxOTMgNS40ODMwNyAxNy42MTkzIDUuNjA1NDdaTTE4LjQyIDIuMTIxMDlMMTUuNjQyNyA2LjU2NjQxTDE1LjIzNjUgNi4zMDg1OUwxOC4wMTM4IDEuODYzMjhMMTguNDIgMi4xMjEwOVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTguMDU4NTkgMzMuNjY3N0M4LjA1ODU5IDM0LjAxNDEgNy45Nzc4NiAzNC4zMDgzIDcuODE2NDEgMzQuNTUwNUM3LjY1NzU1IDM0Ljc5MDEgNy40NDE0MSAzNC45NzI0IDcuMTY3OTcgMzUuMDk3NEM2Ljg5NzE0IDM1LjIyMjQgNi41OTExNSAzNS4yODQ5IDYuMjUgMzUuMjg0OUM1LjkwODg1IDM1LjI4NDkgNS42MDE1NiAzNS4yMjI0IDUuMzI4MTIgMzUuMDk3NEM1LjA1NDY5IDM0Ljk3MjQgNC44Mzg1NCAzNC43OTAxIDQuNjc5NjkgMzQuNTUwNUM0LjUyMDgzIDM0LjMwODMgNC40NDE0MSAzNC4wMTQxIDQuNDQxNDEgMzMuNjY3N0M0LjQ0MTQxIDMzLjQ0MTIgNC40ODQzOCAzMy4yMzQxIDQuNTcwMzEgMzMuMDQ2NkM0LjY1ODg1IDMyLjg1NjUgNC43ODI1NSAzMi42OTEyIDQuOTQxNDEgMzIuNTUwNUM1LjEwMjg2IDMyLjQwOTkgNS4yOTI5NyAzMi4zMDE4IDUuNTExNzIgMzIuMjI2M0M1LjczMzA3IDMyLjE0ODIgNS45NzY1NiAzMi4xMDkxIDYuMjQyMTkgMzIuMTA5MUM2LjU5MTE1IDMyLjEwOTEgNi45MDIzNCAzMi4xNzY4IDcuMTc1NzggMzIuMzEyM0M3LjQ0OTIyIDMyLjQ0NTEgNy42NjQwNiAzMi42Mjg3IDcuODIwMzEgMzIuODYzQzcuOTc5MTcgMzMuMDk3NCA4LjA1ODU5IDMzLjM2NTYgOC4wNTg1OSAzMy42Njc3Wk03LjMzMjAzIDMzLjY1MjFDNy4zMzIwMyAzMy40NDEyIDcuMjg2NDYgMzMuMjU1IDcuMTk1MzEgMzMuMDkzNUM3LjEwNDE3IDMyLjkyOTQgNi45NzY1NiAzMi44MDE4IDYuODEyNSAzMi43MTA3QzYuNjQ4NDQgMzIuNjE5NSA2LjQ1ODMzIDMyLjU3NCA2LjI0MjE5IDMyLjU3NEM2LjAyMDgzIDMyLjU3NCA1LjgyOTQzIDMyLjYxOTUgNS42Njc5NyAzMi43MTA3QzUuNTA5MTEgMzIuODAxOCA1LjM4NTQyIDMyLjkyOTQgNS4yOTY4OCAzMy4wOTM1QzUuMjA4MzMgMzMuMjU1IDUuMTY0MDYgMzMuNDQxMiA1LjE2NDA2IDMzLjY1MjFDNS4xNjQwNiAzMy44NzA4IDUuMjA3MDMgMzQuMDU4MyA1LjI5Mjk3IDM0LjIxNDZDNS4zODE1MSAzNC4zNjgyIDUuNTA2NTEgMzQuNDg2NyA1LjY2Nzk3IDM0LjU3MDFDNS44MzIwMyAzNC42NTA4IDYuMDI2MDQgMzQuNjkxMiA2LjI1IDM0LjY5MTJDNi40NzM5NiAzNC42OTEyIDYuNjY2NjcgMzQuNjUwOCA2LjgyODEyIDM0LjU3MDFDNi45ODk1OCAzNC40ODY3IDcuMTEzMjggMzQuMzY4MiA3LjE5OTIyIDM0LjIxNDZDNy4yODc3NiAzNC4wNTgzIDcuMzMyMDMgMzMuODcwOCA3LjMzMjAzIDMzLjY1MjFaTTcuOTI1NzggMzAuOTk5OEM3LjkyNTc4IDMxLjI3NTggNy44NTI4NiAzMS41MjQ1IDcuNzA3MDMgMzEuNzQ1OEM3LjU2MTIgMzEuOTY3MiA3LjM2MTk4IDMyLjE0MTcgNy4xMDkzOCAzMi4yNjkzQzYuODU2NzcgMzIuMzk2OSA2LjU3MDMxIDMyLjQ2MDcgNi4yNSAzMi40NjA3QzUuOTI0NDggMzIuNDYwNyA1LjYzNDExIDMyLjM5NjkgNS4zNzg5MSAzMi4yNjkzQzUuMTI2MyAzMi4xNDE3IDQuOTI4MzkgMzEuOTY3MiA0Ljc4NTE2IDMxLjc0NThDNC42NDE5MyAzMS41MjQ1IDQuNTcwMzEgMzEuMjc1OCA0LjU3MDMxIDMwLjk5OThDNC41NzAzMSAzMC42NjkgNC42NDE5MyAzMC4zODc4IDQuNzg1MTYgMzAuMTU2QzQuOTMwOTkgMjkuOTI0MiA1LjEzMDIxIDI5Ljc0NzIgNS4zODI4MSAyOS42MjQ4QzUuNjM1NDIgMjkuNTAyNCA1LjkyMzE4IDI5LjQ0MTIgNi4yNDYwOSAyOS40NDEyQzYuNTcxNjEgMjkuNDQxMiA2Ljg2MDY4IDI5LjUwMjQgNy4xMTMyOCAyOS42MjQ4QzcuMzY1ODkgMjkuNzQ3MiA3LjU2MzggMjkuOTI0MiA3LjcwNzAzIDMwLjE1NkM3Ljg1Mjg2IDMwLjM4NzggNy45MjU3OCAzMC42NjkgNy45MjU3OCAzMC45OTk4Wk03LjIwMzEyIDMxLjAxMTVDNy4yMDMxMiAzMC44MjE0IDcuMTYyNzYgMzAuNjUzNCA3LjA4MjAzIDMwLjUwNzZDNy4wMDEzIDMwLjM2MTcgNi44ODkzMiAzMC4yNDcyIDYuNzQ2MDkgMzAuMTYzOEM2LjYwMjg2IDMwLjA3NzkgNi40MzYyIDMwLjAzNDkgNi4yNDYwOSAzMC4wMzQ5QzYuMDU1OTkgMzAuMDM0OSA1Ljg4OTMyIDMwLjA3NTMgNS43NDYwOSAzMC4xNTZDNS42MDU0NyAzMC4yMzQxIDUuNDk0NzkgMzAuMzQ2MSA1LjQxNDA2IDMwLjQ5MTlDNS4zMzU5NCAzMC42Mzc4IDUuMjk2ODggMzAuODExIDUuMjk2ODggMzEuMDExNUM1LjI5Njg4IDMxLjIwNjggNS4zMzU5NCAzMS4zNzc0IDUuNDE0MDYgMzEuNTIzMkM1LjQ5NDc5IDMxLjY2OSA1LjYwNjc3IDMxLjc4MjMgNS43NSAzMS44NjNDNS44OTMyMyAzMS45NDM4IDYuMDU5OSAzMS45ODQxIDYuMjUgMzEuOTg0MUM2LjQ0MDEgMzEuOTg0MSA2LjYwNTQ3IDMxLjk0MzggNi43NDYwOSAzMS44NjNDNi44ODkzMiAzMS43ODIzIDcuMDAxMyAzMS42NjkgNy4wODIwMyAzMS41MjMyQzcuMTYyNzYgMzEuMzc3NCA3LjIwMzEyIDMxLjIwNjggNy4yMDMxMiAzMS4wMTE1Wk0xMi42NzUyIDMxLjkwOTlWMzIuNzc3MUMxMi42NzUyIDMzLjI0MzIgMTIuNjMzNSAzMy42MzY1IDEyLjU1MDIgMzMuOTU2OEMxMi40NjY4IDM0LjI3NzEgMTIuMzQ3IDM0LjUzNDkgMTIuMTkwOCAzNC43MzAyQzEyLjAzNDUgMzQuOTI1NSAxMS44NDU3IDM1LjA2NzUgMTEuNjI0NCAzNS4xNTZDMTEuNDA1NiAzNS4yNDE5IDExLjE1ODIgMzUuMjg0OSAxMC44ODIyIDM1LjI4NDlDMTAuNjYzNSAzNS4yODQ5IDEwLjQ2MTYgMzUuMjU3NiAxMC4yNzY3IDM1LjIwMjlDMTAuMDkxOCAzNS4xNDgyIDkuOTI1MTcgMzUuMDYxIDkuNzc2NzMgMzQuOTQxMkM5LjYzMDkgMzQuODE4OCA5LjUwNTkgMzQuNjU5OSA5LjQwMTczIDM0LjQ2NDZDOS4yOTc1NyAzNC4yNjkzIDkuMjE4MTQgMzQuMDMyMyA5LjE2MzQ1IDMzLjc1MzdDOS4xMDg3NyAzMy40NzUgOS4wODE0MiAzMy4xNDk1IDkuMDgxNDIgMzIuNzc3MVYzMS45MDk5QzkuMDgxNDIgMzEuNDQzOCA5LjEyMzA5IDMxLjA1MzEgOS4yMDY0MiAzMC43MzhDOS4yOTIzNiAzMC40MjI5IDkuNDEzNDUgMzAuMTcwMyA5LjU2OTcgMjkuOTgwMkM5LjcyNTk1IDI5Ljc4NzUgOS45MTM0NSAyOS42NDk1IDEwLjEzMjIgMjkuNTY2MkMxMC4zNTM2IDI5LjQ4MjggMTAuNjAxIDI5LjQ0MTIgMTAuODc0NCAyOS40NDEyQzExLjA5NTcgMjkuNDQxMiAxMS4yOTg5IDI5LjQ2ODUgMTEuNDgzOCAyOS41MjMyQzExLjY3MTMgMjkuNTc1MyAxMS44Mzc5IDI5LjY1OTkgMTEuOTgzOCAyOS43NzcxQzEyLjEyOTYgMjkuODkxNyAxMi4yNTMzIDMwLjA0NTMgMTIuMzU0OSAzMC4yMzhDMTIuNDU5IDMwLjQyODEgMTIuNTM4NSAzMC42NjEyIDEyLjU5MzEgMzAuOTM3M0MxMi42NDc4IDMxLjIxMzMgMTIuNjc1MiAzMS41Mzc1IDEyLjY3NTIgMzEuOTA5OVpNMTEuOTQ4NiAzMi44OTQzVjMxLjc4ODhDMTEuOTQ4NiAzMS41MzM2IDExLjkzMyAzMS4zMDk3IDExLjkwMTcgMzEuMTE2OUMxMS44NzMxIDMwLjkyMTYgMTEuODMwMSAzMC43NTUgMTEuNzcyOCAzMC42MTY5QzExLjcxNTUgMzAuNDc4OSAxMS42NDI2IDMwLjM2NjkgMTEuNTU0MSAzMC4yODFDMTEuNDY4MSAzMC4xOTUxIDExLjM2NzkgMzAuMTMyNiAxMS4yNTMzIDMwLjA5MzVDMTEuMTQxMyAzMC4wNTE4IDExLjAxNSAzMC4wMzEgMTAuODc0NCAzMC4wMzFDMTAuNzAyNSAzMC4wMzEgMTAuNTUwMiAzMC4wNjM2IDEwLjQxNzQgMzAuMTI4N0MxMC4yODQ1IDMwLjE5MTIgMTAuMTcyNiAzMC4yOTE0IDEwLjA4MTQgMzAuNDI5NEM5Ljk5Mjg4IDMwLjU2NzUgOS45MjUxNyAzMC43NDg1IDkuODc4MyAzMC45NzI0QzkuODMxNDIgMzEuMTk2NCA5LjgwNzk4IDMxLjQ2ODUgOS44MDc5OCAzMS43ODg4VjMyLjg5NDNDOS44MDc5OCAzMy4xNDk1IDkuODIyMzEgMzMuMzc0OCA5Ljg1MDk1IDMzLjU3MDFDOS44ODIyIDMzLjc2NTQgOS45Mjc3OCAzMy45MzQ3IDkuOTg3NjcgMzQuMDc3OUMxMC4wNDc2IDM0LjIxODUgMTAuMTIwNSAzNC4zMzQ0IDEwLjIwNjQgMzQuNDI1NUMxMC4yOTI0IDM0LjUxNjcgMTAuMzkxMyAzNC41ODQ0IDEwLjUwMzMgMzQuNjI4N0MxMC42MTc5IDM0LjY3MDMgMTAuNzQ0MiAzNC42OTEyIDEwLjg4MjIgMzQuNjkxMkMxMS4wNTkzIDM0LjY5MTIgMTEuMjE0MiAzNC42NTczIDExLjM0NyAzNC41ODk2QzExLjQ3OTkgMzQuNTIxOSAxMS41OTA1IDM0LjQxNjQgMTEuNjc5MSAzNC4yNzMyQzExLjc3MDIgMzQuMTI3NCAxMS44Mzc5IDMzLjk0MTIgMTEuODgyMiAzMy43MTQ2QzExLjkyNjUgMzMuNDg1NCAxMS45NDg2IDMzLjIxMiAxMS45NDg2IDMyLjg5NDNaTTEzLjY3NDYgMzAuOTEzOFYzMC42MTNDMTMuNjc0NiAzMC4zOTY5IDEzLjcyMTQgMzAuMjAwMyAxMy44MTUyIDMwLjAyMzJDMTMuOTA4OSAyOS44NDYxIDE0LjA0MzEgMjkuNzA0MiAxNC4yMTc1IDI5LjU5NzRDMTQuMzkyIDI5LjQ5MDYgMTQuNTk5IDI5LjQzNzMgMTQuODM4NiAyOS40MzczQzE1LjA4MzQgMjkuNDM3MyAxNS4yOTE4IDI5LjQ5MDYgMTUuNDYzNiAyOS41OTc0QzE1LjYzODEgMjkuNzA0MiAxNS43NzIyIDI5Ljg0NjEgMTUuODY2IDMwLjAyMzJDMTUuOTU5NyAzMC4yMDAzIDE2LjAwNjYgMzAuMzk2OSAxNi4wMDY2IDMwLjYxM1YzMC45MTM4QzE2LjAwNjYgMzEuMTI0OCAxNS45NTk3IDMxLjMxODggMTUuODY2IDMxLjQ5NThDMTUuNzc0OCAzMS42NzI5IDE1LjY0MiAzMS44MTQ5IDE1LjQ2NzUgMzEuOTIxNkMxNS4yOTU3IDMyLjAyODQgMTUuMDg4NiAzMi4wODE4IDE0Ljg0NjQgMzIuMDgxOEMxNC42MDQzIDMyLjA4MTggMTQuMzk0NiAzMi4wMjg0IDE0LjIxNzUgMzEuOTIxNkMxNC4wNDMxIDMxLjgxNDkgMTMuOTA4OSAzMS42NzI5IDEzLjgxNTIgMzEuNDk1OEMxMy43MjE0IDMxLjMxODggMTMuNjc0NiAzMS4xMjQ4IDEzLjY3NDYgMzAuOTEzOFpNMTQuMjE3NSAzMC42MTNWMzAuOTEzOEMxNC4yMTc1IDMxLjAzMzYgMTQuMjM5NyAzMS4xNDY5IDE0LjI4MzkgMzEuMjUzN0MxNC4zMzA4IDMxLjM2MDQgMTQuNDAxMSAzMS40NDc3IDE0LjQ5NDkgMzEuNTE1NEMxNC41ODg2IDMxLjU4MDUgMTQuNzA1OCAzMS42MTMgMTQuODQ2NCAzMS42MTNDMTQuOTg3MSAzMS42MTMgMTUuMTAyOSAzMS41ODA1IDE1LjE5NDEgMzEuNTE1NEMxNS4yODUyIDMxLjQ0NzcgMTUuMzUyOSAzMS4zNjA0IDE1LjM5NzIgMzEuMjUzN0MxNS40NDE1IDMxLjE0NjkgMTUuNDYzNiAzMS4wMzM2IDE1LjQ2MzYgMzAuOTEzOFYzMC42MTNDMTUuNDYzNiAzMC40OTA2IDE1LjQ0MDIgMzAuMzc2MSAxNS4zOTMzIDMwLjI2OTNDMTUuMzQ5IDMwLjE1OTkgMTUuMjggMzAuMDcyNyAxNS4xODYzIDMwLjAwNzZDMTUuMDk1MSAyOS45Mzk5IDE0Ljk3OTMgMjkuOTA2IDE0LjgzODYgMjkuOTA2QzE0LjcwMDYgMjkuOTA2IDE0LjU4NDcgMjkuOTM5OSAxNC40OTEgMzAuMDA3NkMxNC4zOTk4IDMwLjA3MjcgMTQuMzMwOCAzMC4xNTk5IDE0LjI4MzkgMzAuMjY5M0MxNC4yMzk3IDMwLjM3NjEgMTQuMjE3NSAzMC40OTA2IDE0LjIxNzUgMzAuNjEzWk0xNi40NDQxIDM0LjExNjlWMzMuODEyM0MxNi40NDQxIDMzLjU5ODcgMTYuNDkxIDMzLjQwMzQgMTYuNTg0NyAzMy4yMjYzQzE2LjY3ODUgMzMuMDQ5MiAxNi44MTI2IDMyLjkwNzMgMTYuOTg3MSAzMi44MDA1QzE3LjE2MTUgMzIuNjkzOCAxNy4zNjg2IDMyLjY0MDQgMTcuNjA4MiAzMi42NDA0QzE3Ljg1MjkgMzIuNjQwNCAxOC4wNjEzIDMyLjY5MzggMTguMjMzMiAzMi44MDA1QzE4LjQwNzYgMzIuOTA3MyAxOC41NDE4IDMzLjA0OTIgMTguNjM1NSAzMy4yMjYzQzE4LjcyOTMgMzMuNDAzNCAxOC43NzYxIDMzLjU5ODcgMTguNzc2MSAzMy44MTIzVjM0LjExNjlDMTguNzc2MSAzNC4zMzA1IDE4LjcyOTMgMzQuNTI1OCAxOC42MzU1IDM0LjcwMjlDMTguNTQ0NCAzNC44OCAxOC40MTE1IDM1LjAyMTkgMTguMjM3MSAzNS4xMjg3QzE4LjA2NTIgMzUuMjM1NCAxNy44NTgyIDM1LjI4ODggMTcuNjE2IDM1LjI4ODhDMTcuMzczOCAzNS4yODg4IDE3LjE2NTQgMzUuMjM1NCAxNi45OTEgMzUuMTI4N0MxNi44MTY1IDM1LjAyMTkgMTYuNjgxMSAzNC44OCAxNi41ODQ3IDM0LjcwMjlDMTYuNDkxIDM0LjUyNTggMTYuNDQ0MSAzNC4zMzA1IDE2LjQ0NDEgMzQuMTE2OVpNMTYuOTg3MSAzMy44MTIzVjM0LjExNjlDMTYuOTg3MSAzNC4yMzY3IDE3LjAwOTIgMzQuMzUxMyAxNy4wNTM1IDM0LjQ2MDdDMTcuMTAwMyAzNC41Njc1IDE3LjE3MDcgMzQuNjU0NyAxNy4yNjQ0IDM0LjcyMjRDMTcuMzU4MiAzNC43ODc1IDE3LjQ3NTMgMzQuODIwMSAxNy42MTYgMzQuODIwMUMxNy43NTY2IDM0LjgyMDEgMTcuODcyNSAzNC43ODc1IDE3Ljk2MzYgMzQuNzIyNEMxOC4wNTc0IDM0LjY1NDcgMTguMTI2NCAzNC41Njc1IDE4LjE3MDcgMzQuNDYwN0MxOC4yMTQ5IDM0LjM1MzkgMTguMjM3MSAzNC4yMzkzIDE4LjIzNzEgMzQuMTE2OVYzMy44MTIzQzE4LjIzNzEgMzMuNjg5OSAxOC4yMTM2IDMzLjU3NTMgMTguMTY2OCAzMy40Njg1QzE4LjEyMjUgMzMuMzYxNyAxOC4wNTM1IDMzLjI3NTggMTcuOTU5NyAzMy4yMTA3QzE3Ljg2ODYgMzMuMTQzIDE3Ljc1MTQgMzMuMTA5MSAxNy42MDgyIDMzLjEwOTFDMTcuNDcwMSAzMy4xMDkxIDE3LjM1NDMgMzMuMTQzIDE3LjI2MDUgMzMuMjEwN0MxNy4xNjk0IDMzLjI3NTggMTcuMTAwMyAzMy4zNjE3IDE3LjA1MzUgMzMuNDY4NUMxNy4wMDkyIDMzLjU3NTMgMTYuOTg3MSAzMy42ODk5IDE2Ljk4NzEgMzMuODEyM1pNMTcuNzg3OCAzMC4zMjc5TDE1LjAxMDUgMzQuNzczMkwxNC42MDQzIDM0LjUxNTRMMTcuMzgxNiAzMC4wNzAxTDE3Ljc4NzggMzAuMzI3OVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTcuMjQ2MDkgNTcuNzE4M0g3LjMwODU5VjU4LjMzMTVINy4yNDYwOUM2Ljg2MzI4IDU4LjMzMTUgNi41NDI5NyA1OC4zOTQgNi4yODUxNiA1OC41MTlDNi4wMjczNCA1OC42NDE0IDUuODIyOTIgNTguODA2OCA1LjY3MTg4IDU5LjAxNTFDNS41MjA4MyA1OS4yMjA5IDUuNDExNDYgNTkuNDUyNiA1LjM0Mzc1IDU5LjcxMDRDNS4yNzg2NSA1OS45NjgzIDUuMjQ2MDkgNjAuMjMgNS4yNDYwOSA2MC40OTU2VjYxLjMzMTVDNS4yNDYwOSA2MS41ODQxIDUuMjc2MDQgNjEuODA4MSA1LjMzNTk0IDYyLjAwMzRDNS4zOTU4MyA2Mi4xOTYxIDUuNDc3ODYgNjIuMzU4OSA1LjU4MjAzIDYyLjQ5MTdDNS42ODYyIDYyLjYyNDUgNS44MDMzOSA2Mi43MjQ4IDUuOTMzNTkgNjIuNzkyNUM2LjA2NjQxIDYyLjg2MDIgNi4yMDQ0MyA2Mi44OTQgNi4zNDc2NiA2Mi44OTRDNi41MTQzMiA2Mi44OTQgNi42NjI3NiA2Mi44NjI4IDYuNzkyOTcgNjIuODAwM0M2LjkyMzE4IDYyLjczNTIgNy4wMzI1NSA2Mi42NDUzIDcuMTIxMDkgNjIuNTMwOEM3LjIxMjI0IDYyLjQxMzYgNy4yODEyNSA2Mi4yNzU2IDcuMzI4MTIgNjIuMTE2N0M3LjM3NSA2MS45NTc4IDcuMzk4NDQgNjEuNzgzNCA3LjM5ODQ0IDYxLjU5MzNDNy4zOTg0NCA2MS40MjQgNy4zNzc2IDYxLjI2MTIgNy4zMzU5NCA2MS4xMDVDNy4yOTQyNyA2MC45NDYxIDcuMjMwNDcgNjAuODA1NSA3LjE0NDUzIDYwLjY4MzFDNy4wNTg1OSA2MC41NTgxIDYuOTUwNTIgNjAuNDYwNCA2LjgyMDMxIDYwLjM5MDFDNi42OTI3MSA2MC4zMTcyIDYuNTQwMzYgNjAuMjgwOCA2LjM2MzI4IDYwLjI4MDhDNi4xNjI3NiA2MC4yODA4IDUuOTc1MjYgNjAuMzMwMiA1LjgwMDc4IDYwLjQyOTJDNS42Mjg5MSA2MC41MjU2IDUuNDg2OTggNjAuNjUzMiA1LjM3NSA2MC44MTJDNS4yNjU2MiA2MC45NjgzIDUuMjAzMTIgNjEuMTM4OCA1LjE4NzUgNjEuMzIzN0w0LjgwNDY5IDYxLjMxOThDNC44NDExNSA2MS4wMjgyIDQuOTA4ODUgNjAuNzc5NSA1LjAwNzgxIDYwLjU3MzdDNS4xMDkzOCA2MC4zNjU0IDUuMjM0MzggNjAuMTk2MSA1LjM4MjgxIDYwLjA2NTlDNS41MzM4NSA1OS45MzMxIDUuNzAxODIgNTkuODM2OCA1Ljg4NjcyIDU5Ljc3NjlDNi4wNzQyMiA1OS43MTQ0IDYuMjcyMTQgNTkuNjgzMSA2LjQ4MDQ3IDU5LjY4MzFDNi43NjQzMiA1OS42ODMxIDcuMDA5MTEgNTkuNzM2NSA3LjIxNDg0IDU5Ljg0MzNDNy40MjA1NyA1OS45NSA3LjU4OTg0IDYwLjA5MzMgNy43MjI2NiA2MC4yNzI5QzcuODU1NDcgNjAuNDUgNy45NTMxMiA2MC42NTA2IDguMDE1NjIgNjAuODc0NUM4LjA4MDczIDYxLjA5NTkgOC4xMTMyOCA2MS4zMjM3IDguMTEzMjggNjEuNTU4MUM4LjExMzI4IDYxLjgyNjMgOC4wNzU1MiA2Mi4wNzc2IDggNjIuMzEyQzcuOTI0NDggNjIuNTQ2NCA3LjgxMTIgNjIuNzUyMSA3LjY2MDE2IDYyLjkyOTJDNy41MTE3MiA2My4xMDYzIDcuMzI4MTIgNjMuMjQ0MyA3LjEwOTM4IDYzLjM0MzNDNi44OTA2MiA2My40NDIyIDYuNjM2NzIgNjMuNDkxNyA2LjM0NzY2IDYzLjQ5MTdDNi4wNDAzNiA2My40OTE3IDUuNzcyMTQgNjMuNDI5MiA1LjU0Mjk3IDYzLjMwNDJDNS4zMTM4IDYzLjE3NjYgNS4xMjM3IDYzLjAwNzMgNC45NzI2NiA2Mi43OTY0QzQuODIxNjEgNjIuNTg1NCA0LjcwODMzIDYyLjM1MTEgNC42MzI4MSA2Mi4wOTMzQzQuNTU3MjkgNjEuODM1NCA0LjUxOTUzIDYxLjU3MzcgNC41MTk1MyA2MS4zMDgxVjYwLjk2ODNDNC41MTk1MyA2MC41NjcyIDQuNTU5OSA2MC4xNzQgNC42NDA2MiA1OS43ODg2QzQuNzIxMzUgNTkuNDAzMiA0Ljg2MDY4IDU5LjA1NDIgNS4wNTg1OSA1OC43NDE3QzUuMjU5MTEgNTguNDI5MiA1LjUzNjQ2IDU4LjE4MDUgNS44OTA2MiA1Ny45OTU2QzYuMjQ0NzkgNTcuODEwNyA2LjY5NjYxIDU3LjcxODMgNy4yNDYwOSA1Ny43MTgzWk0xMi42NzUyIDYwLjExNjdWNjAuOTgzOUMxMi42NzUyIDYxLjQ1IDEyLjYzMzUgNjEuODQzMyAxMi41NTAyIDYyLjE2MzZDMTIuNDY2OCA2Mi40ODM5IDEyLjM0NyA2Mi43NDE3IDEyLjE5MDggNjIuOTM3QzEyLjAzNDUgNjMuMTMyMyAxMS44NDU3IDYzLjI3NDMgMTEuNjI0NCA2My4zNjI4QzExLjQwNTYgNjMuNDQ4NyAxMS4xNTgyIDYzLjQ5MTcgMTAuODgyMiA2My40OTE3QzEwLjY2MzUgNjMuNDkxNyAxMC40NjE2IDYzLjQ2NDQgMTAuMjc2NyA2My40MDk3QzEwLjA5MTggNjMuMzU1IDkuOTI1MTcgNjMuMjY3NyA5Ljc3NjczIDYzLjE0NzlDOS42MzA5IDYzLjAyNTYgOS41MDU5IDYyLjg2NjcgOS40MDE3MyA2Mi42NzE0QzkuMjk3NTcgNjIuNDc2MSA5LjIxODE0IDYyLjIzOTEgOS4xNjM0NSA2MS45NjA0QzkuMTA4NzcgNjEuNjgxOCA5LjA4MTQyIDYxLjM1NjMgOS4wODE0MiA2MC45ODM5VjYwLjExNjdDOS4wODE0MiA1OS42NTA2IDkuMTIzMDkgNTkuMjU5OSA5LjIwNjQyIDU4Ljk0NDhDOS4yOTIzNiA1OC42Mjk3IDkuNDEzNDUgNTguMzc3MSA5LjU2OTcgNTguMTg3QzkuNzI1OTUgNTcuOTk0MyA5LjkxMzQ1IDU3Ljg1NjMgMTAuMTMyMiA1Ny43NzI5QzEwLjM1MzYgNTcuNjg5NiAxMC42MDEgNTcuNjQ3OSAxMC44NzQ0IDU3LjY0NzlDMTEuMDk1NyA1Ny42NDc5IDExLjI5ODkgNTcuNjc1MyAxMS40ODM4IDU3LjczQzExLjY3MTMgNTcuNzgyMSAxMS44Mzc5IDU3Ljg2NjcgMTEuOTgzOCA1Ny45ODM5QzEyLjEyOTYgNTguMDk4NSAxMi4yNTMzIDU4LjI1MjEgMTIuMzU0OSA1OC40NDQ4QzEyLjQ1OSA1OC42MzQ5IDEyLjUzODUgNTguODY4IDEyLjU5MzEgNTkuMTQ0QzEyLjY0NzggNTkuNDIwMSAxMi42NzUyIDU5Ljc0NDMgMTIuNjc1MiA2MC4xMTY3Wk0xMS45NDg2IDYxLjEwMTFWNTkuOTk1NkMxMS45NDg2IDU5Ljc0MDQgMTEuOTMzIDU5LjUxNjQgMTEuOTAxNyA1OS4zMjM3QzExLjg3MzEgNTkuMTI4NCAxMS44MzAxIDU4Ljk2MTggMTEuNzcyOCA1OC44MjM3QzExLjcxNTUgNTguNjg1NyAxMS42NDI2IDU4LjU3MzcgMTEuNTU0MSA1OC40ODc4QzExLjQ2ODEgNTguNDAxOSAxMS4zNjc5IDU4LjMzOTQgMTEuMjUzMyA1OC4zMDAzQzExLjE0MTMgNTguMjU4NiAxMS4wMTUgNTguMjM3OCAxMC44NzQ0IDU4LjIzNzhDMTAuNzAyNSA1OC4yMzc4IDEwLjU1MDIgNTguMjcwMyAxMC40MTc0IDU4LjMzNTRDMTAuMjg0NSA1OC4zOTc5IDEwLjE3MjYgNTguNDk4MiAxMC4wODE0IDU4LjYzNjJDOS45OTI4OCA1OC43NzQzIDkuOTI1MTcgNTguOTU1MiA5Ljg3ODMgNTkuMTc5MkM5LjgzMTQyIDU5LjQwMzIgOS44MDc5OCA1OS42NzUzIDkuODA3OTggNTkuOTk1NlY2MS4xMDExQzkuODA3OTggNjEuMzU2MyA5LjgyMjMxIDYxLjU4MTUgOS44NTA5NSA2MS43NzY5QzkuODgyMiA2MS45NzIyIDkuOTI3NzggNjIuMTQxNCA5Ljk4NzY3IDYyLjI4NDdDMTAuMDQ3NiA2Mi40MjUzIDEwLjEyMDUgNjIuNTQxMiAxMC4yMDY0IDYyLjYzMjNDMTAuMjkyNCA2Mi43MjM1IDEwLjM5MTMgNjIuNzkxMiAxMC41MDMzIDYyLjgzNTRDMTAuNjE3OSA2Mi44NzcxIDEwLjc0NDIgNjIuODk3OSAxMC44ODIyIDYyLjg5NzlDMTEuMDU5MyA2Mi44OTc5IDExLjIxNDIgNjIuODY0MSAxMS4zNDcgNjIuNzk2NEMxMS40Nzk5IDYyLjcyODcgMTEuNTkwNSA2Mi42MjMyIDExLjY3OTEgNjIuNDhDMTEuNzcwMiA2Mi4zMzQxIDExLjgzNzkgNjIuMTQ3OSAxMS44ODIyIDYxLjkyMTRDMTEuOTI2NSA2MS42OTIyIDExLjk0ODYgNjEuNDE4OCAxMS45NDg2IDYxLjEwMTFaTTEzLjY3NDYgNTkuMTIwNlY1OC44MTk4QzEzLjY3NDYgNTguNjAzNyAxMy43MjE0IDU4LjQwNzEgMTMuODE1MiA1OC4yM0MxMy45MDg5IDU4LjA1MjkgMTQuMDQzMSA1Ny45MTEgMTQuMjE3NSA1Ny44MDQyQzE0LjM5MiA1Ny42OTc0IDE0LjU5OSA1Ny42NDQgMTQuODM4NiA1Ny42NDRDMTUuMDgzNCA1Ny42NDQgMTUuMjkxOCA1Ny42OTc0IDE1LjQ2MzYgNTcuODA0MkMxNS42MzgxIDU3LjkxMSAxNS43NzIyIDU4LjA1MjkgMTUuODY2IDU4LjIzQzE1Ljk1OTcgNTguNDA3MSAxNi4wMDY2IDU4LjYwMzcgMTYuMDA2NiA1OC44MTk4VjU5LjEyMDZDMTYuMDA2NiA1OS4zMzE1IDE1Ljk1OTcgNTkuNTI1NiAxNS44NjYgNTkuNzAyNkMxNS43NzQ4IDU5Ljg3OTcgMTUuNjQyIDYwLjAyMTYgMTUuNDY3NSA2MC4xMjg0QzE1LjI5NTcgNjAuMjM1MiAxNS4wODg2IDYwLjI4ODYgMTQuODQ2NCA2MC4yODg2QzE0LjYwNDMgNjAuMjg4NiAxNC4zOTQ2IDYwLjIzNTIgMTQuMjE3NSA2MC4xMjg0QzE0LjA0MzEgNjAuMDIxNiAxMy45MDg5IDU5Ljg3OTcgMTMuODE1MiA1OS43MDI2QzEzLjcyMTQgNTkuNTI1NiAxMy42NzQ2IDU5LjMzMTUgMTMuNjc0NiA1OS4xMjA2Wk0xNC4yMTc1IDU4LjgxOThWNTkuMTIwNkMxNC4yMTc1IDU5LjI0MDQgMTQuMjM5NyA1OS4zNTM3IDE0LjI4MzkgNTkuNDYwNEMxNC4zMzA4IDU5LjU2NzIgMTQuNDAxMSA1OS42NTQ1IDE0LjQ5NDkgNTkuNzIyMkMxNC41ODg2IDU5Ljc4NzMgMTQuNzA1OCA1OS44MTk4IDE0Ljg0NjQgNTkuODE5OEMxNC45ODcxIDU5LjgxOTggMTUuMTAyOSA1OS43ODczIDE1LjE5NDEgNTkuNzIyMkMxNS4yODUyIDU5LjY1NDUgMTUuMzUyOSA1OS41NjcyIDE1LjM5NzIgNTkuNDYwNEMxNS40NDE1IDU5LjM1MzcgMTUuNDYzNiA1OS4yNDA0IDE1LjQ2MzYgNTkuMTIwNlY1OC44MTk4QzE1LjQ2MzYgNTguNjk3NCAxNS40NDAyIDU4LjU4MjggMTUuMzkzMyA1OC40NzYxQzE1LjM0OSA1OC4zNjY3IDE1LjI4IDU4LjI3OTUgMTUuMTg2MyA1OC4yMTQ0QzE1LjA5NTEgNTguMTQ2NiAxNC45NzkzIDU4LjExMjggMTQuODM4NiA1OC4xMTI4QzE0LjcwMDYgNTguMTEyOCAxNC41ODQ3IDU4LjE0NjYgMTQuNDkxIDU4LjIxNDRDMTQuMzk5OCA1OC4yNzk1IDE0LjMzMDggNTguMzY2NyAxNC4yODM5IDU4LjQ3NjFDMTQuMjM5NyA1OC41ODI4IDE0LjIxNzUgNTguNjk3NCAxNC4yMTc1IDU4LjgxOThaTTE2LjQ0NDEgNjIuMzIzN1Y2Mi4wMTlDMTYuNDQ0MSA2MS44MDU1IDE2LjQ5MSA2MS42MTAyIDE2LjU4NDcgNjEuNDMzMUMxNi42Nzg1IDYxLjI1NiAxNi44MTI2IDYxLjExNDEgMTYuOTg3MSA2MS4wMDczQzE3LjE2MTUgNjAuOTAwNiAxNy4zNjg2IDYwLjg0NzIgMTcuNjA4MiA2MC44NDcyQzE3Ljg1MjkgNjAuODQ3MiAxOC4wNjEzIDYwLjkwMDYgMTguMjMzMiA2MS4wMDczQzE4LjQwNzYgNjEuMTE0MSAxOC41NDE4IDYxLjI1NiAxOC42MzU1IDYxLjQzMzFDMTguNzI5MyA2MS42MTAyIDE4Ljc3NjEgNjEuODA1NSAxOC43NzYxIDYyLjAxOVY2Mi4zMjM3QzE4Ljc3NjEgNjIuNTM3MyAxOC43MjkzIDYyLjczMjYgMTguNjM1NSA2Mi45MDk3QzE4LjU0NDQgNjMuMDg2OCAxOC40MTE1IDYzLjIyODcgMTguMjM3MSA2My4zMzU0QzE4LjA2NTIgNjMuNDQyMiAxNy44NTgyIDYzLjQ5NTYgMTcuNjE2IDYzLjQ5NTZDMTcuMzczOCA2My40OTU2IDE3LjE2NTQgNjMuNDQyMiAxNi45OTEgNjMuMzM1NEMxNi44MTY1IDYzLjIyODcgMTYuNjgxMSA2My4wODY4IDE2LjU4NDcgNjIuOTA5N0MxNi40OTEgNjIuNzMyNiAxNi40NDQxIDYyLjUzNzMgMTYuNDQ0MSA2Mi4zMjM3Wk0xNi45ODcxIDYyLjAxOVY2Mi4zMjM3QzE2Ljk4NzEgNjIuNDQzNSAxNy4wMDkyIDYyLjU1ODEgMTcuMDUzNSA2Mi42Njc1QzE3LjEwMDMgNjIuNzc0MyAxNy4xNzA3IDYyLjg2MTUgMTcuMjY0NCA2Mi45MjkyQzE3LjM1ODIgNjIuOTk0MyAxNy40NzUzIDYzLjAyNjkgMTcuNjE2IDYzLjAyNjlDMTcuNzU2NiA2My4wMjY5IDE3Ljg3MjUgNjIuOTk0MyAxNy45NjM2IDYyLjkyOTJDMTguMDU3NCA2Mi44NjE1IDE4LjEyNjQgNjIuNzc0MyAxOC4xNzA3IDYyLjY2NzVDMTguMjE0OSA2Mi41NjA3IDE4LjIzNzEgNjIuNDQ2MSAxOC4yMzcxIDYyLjMyMzdWNjIuMDE5QzE4LjIzNzEgNjEuODk2NiAxOC4yMTM2IDYxLjc4MjEgMTguMTY2OCA2MS42NzUzQzE4LjEyMjUgNjEuNTY4NSAxOC4wNTM1IDYxLjQ4MjYgMTcuOTU5NyA2MS40MTc1QzE3Ljg2ODYgNjEuMzQ5OCAxNy43NTE0IDYxLjMxNTkgMTcuNjA4MiA2MS4zMTU5QzE3LjQ3MDEgNjEuMzE1OSAxNy4zNTQzIDYxLjM0OTggMTcuMjYwNSA2MS40MTc1QzE3LjE2OTQgNjEuNDgyNiAxNy4xMDAzIDYxLjU2ODUgMTcuMDUzNSA2MS42NzUzQzE3LjAwOTIgNjEuNzgyMSAxNi45ODcxIDYxLjg5NjYgMTYuOTg3MSA2Mi4wMTlaTTE3Ljc4NzggNTguNTM0N0wxNS4wMTA1IDYyLjk4TDE0LjYwNDMgNjIuNzIyMkwxNy4zODE2IDU4LjI3NjlMMTcuNzg3OCA1OC41MzQ3WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNOC4zMTY0MSA4OS43MDYzVjkwLjNINC4yMDcwM1Y4OS44NzQzTDYuNzUzOTEgODUuOTMyOUg3LjM0Mzc1TDYuNzEwOTQgODcuMDczNUw1LjAyNzM0IDg5LjcwNjNIOC4zMTY0MVpNNy41MjM0NCA4NS45MzI5VjkxLjYyMDRINi44MDA3OFY4NS45MzI5SDcuNTIzNDRaTTEyLjY3NTIgODguMzIzNVY4OS4xOTA3QzEyLjY3NTIgODkuNjU2OCAxMi42MzM1IDkwLjA1IDEyLjU1MDIgOTAuMzcwNEMxMi40NjY4IDkwLjY5MDcgMTIuMzQ3IDkwLjk0ODUgMTIuMTkwOCA5MS4xNDM4QzEyLjAzNDUgOTEuMzM5MSAxMS44NDU3IDkxLjQ4MSAxMS42MjQ0IDkxLjU2OTZDMTEuNDA1NiA5MS42NTU1IDExLjE1ODIgOTEuNjk4NSAxMC44ODIyIDkxLjY5ODVDMTAuNjYzNSA5MS42OTg1IDEwLjQ2MTYgOTEuNjcxMSAxMC4yNzY3IDkxLjYxNjVDMTAuMDkxOCA5MS41NjE4IDkuOTI1MTcgOTEuNDc0NSA5Ljc3NjczIDkxLjM1NDdDOS42MzA5IDkxLjIzMjMgOS41MDU5IDkxLjA3MzUgOS40MDE3MyA5MC44NzgyQzkuMjk3NTcgOTAuNjgyOSA5LjIxODE0IDkwLjQ0NTkgOS4xNjM0NSA5MC4xNjcyQzkuMTA4NzcgODkuODg4NiA5LjA4MTQyIDg5LjU2MzEgOS4wODE0MiA4OS4xOTA3Vjg4LjMyMzVDOS4wODE0MiA4Ny44NTczIDkuMTIzMDkgODcuNDY2NyA5LjIwNjQyIDg3LjE1MTZDOS4yOTIzNiA4Ni44MzY1IDkuNDEzNDUgODYuNTgzOSA5LjU2OTcgODYuMzkzOEM5LjcyNTk1IDg2LjIwMTEgOS45MTM0NSA4Ni4wNjMxIDEwLjEzMjIgODUuOTc5N0MxMC4zNTM2IDg1Ljg5NjQgMTAuNjAxIDg1Ljg1NDcgMTAuODc0NCA4NS44NTQ3QzExLjA5NTcgODUuODU0NyAxMS4yOTg5IDg1Ljg4MjEgMTEuNDgzOCA4NS45MzY4QzExLjY3MTMgODUuOTg4OSAxMS44Mzc5IDg2LjA3MzUgMTEuOTgzOCA4Ni4xOTA3QzEyLjEyOTYgODYuMzA1MyAxMi4yNTMzIDg2LjQ1ODkgMTIuMzU0OSA4Ni42NTE2QzEyLjQ1OSA4Ni44NDE3IDEyLjUzODUgODcuMDc0OCAxMi41OTMxIDg3LjM1MDhDMTIuNjQ3OCA4Ny42MjY5IDEyLjY3NTIgODcuOTUxMSAxMi42NzUyIDg4LjMyMzVaTTExLjk0ODYgODkuMzA3OVY4OC4yMDI0QzExLjk0ODYgODcuOTQ3MiAxMS45MzMgODcuNzIzMiAxMS45MDE3IDg3LjUzMDVDMTEuODczMSA4Ny4zMzUyIDExLjgzMDEgODcuMTY4NSAxMS43NzI4IDg3LjAzMDVDMTEuNzE1NSA4Ni44OTI1IDExLjY0MjYgODYuNzgwNSAxMS41NTQxIDg2LjY5NDZDMTEuNDY4MSA4Ni42MDg2IDExLjM2NzkgODYuNTQ2MSAxMS4yNTMzIDg2LjUwNzFDMTEuMTQxMyA4Ni40NjU0IDExLjAxNSA4Ni40NDQ2IDEwLjg3NDQgODYuNDQ0NkMxMC43MDI1IDg2LjQ0NDYgMTAuNTUwMiA4Ni40NzcxIDEwLjQxNzQgODYuNTQyMkMxMC4yODQ1IDg2LjYwNDcgMTAuMTcyNiA4Ni43MDUgMTAuMDgxNCA4Ni44NDNDOS45OTI4OCA4Ni45ODEgOS45MjUxNyA4Ny4xNjIgOS44NzgzIDg3LjM4NkM5LjgzMTQyIDg3LjYwOTkgOS44MDc5OCA4Ny44ODIxIDkuODA3OTggODguMjAyNFY4OS4zMDc5QzkuODA3OTggODkuNTYzMSA5LjgyMjMxIDg5Ljc4ODMgOS44NTA5NSA4OS45ODM2QzkuODgyMiA5MC4xNzkgOS45Mjc3OCA5MC4zNDgyIDkuOTg3NjcgOTAuNDkxNUMxMC4wNDc2IDkwLjYzMjEgMTAuMTIwNSA5MC43NDggMTAuMjA2NCA5MC44MzkxQzEwLjI5MjQgOTAuOTMwMyAxMC4zOTEzIDkwLjk5OCAxMC41MDMzIDkxLjA0MjJDMTAuNjE3OSA5MS4wODM5IDEwLjc0NDIgOTEuMTA0NyAxMC44ODIyIDkxLjEwNDdDMTEuMDU5MyA5MS4xMDQ3IDExLjIxNDIgOTEuMDcwOSAxMS4zNDcgOTEuMDAzMkMxMS40Nzk5IDkwLjkzNTUgMTEuNTkwNSA5MC44MyAxMS42NzkxIDkwLjY4NjhDMTEuNzcwMiA5MC41NDA5IDExLjgzNzkgOTAuMzU0NyAxMS44ODIyIDkwLjEyODJDMTEuOTI2NSA4OS44OTkgMTEuOTQ4NiA4OS42MjU2IDExLjk0ODYgODkuMzA3OVpNMTMuNjc0NiA4Ny4zMjc0Vjg3LjAyNjZDMTMuNjc0NiA4Ni44MTA1IDEzLjcyMTQgODYuNjEzOSAxMy44MTUyIDg2LjQzNjhDMTMuOTA4OSA4Ni4yNTk3IDE0LjA0MzEgODYuMTE3OCAxNC4yMTc1IDg2LjAxMUMxNC4zOTIgODUuOTA0MiAxNC41OTkgODUuODUwOCAxNC44Mzg2IDg1Ljg1MDhDMTUuMDgzNCA4NS44NTA4IDE1LjI5MTggODUuOTA0MiAxNS40NjM2IDg2LjAxMUMxNS42MzgxIDg2LjExNzggMTUuNzcyMiA4Ni4yNTk3IDE1Ljg2NiA4Ni40MzY4QzE1Ljk1OTcgODYuNjEzOSAxNi4wMDY2IDg2LjgxMDUgMTYuMDA2NiA4Ny4wMjY2Vjg3LjMyNzRDMTYuMDA2NiA4Ny41MzgzIDE1Ljk1OTcgODcuNzMyMyAxNS44NjYgODcuOTA5NEMxNS43NzQ4IDg4LjA4NjUgMTUuNjQyIDg4LjIyODQgMTUuNDY3NSA4OC4zMzUyQzE1LjI5NTcgODguNDQyIDE1LjA4ODYgODguNDk1NCAxNC44NDY0IDg4LjQ5NTRDMTQuNjA0MyA4OC40OTU0IDE0LjM5NDYgODguNDQyIDE0LjIxNzUgODguMzM1MkMxNC4wNDMxIDg4LjIyODQgMTMuOTA4OSA4OC4wODY1IDEzLjgxNTIgODcuOTA5NEMxMy43MjE0IDg3LjczMjMgMTMuNjc0NiA4Ny41MzgzIDEzLjY3NDYgODcuMzI3NFpNMTQuMjE3NSA4Ny4wMjY2Vjg3LjMyNzRDMTQuMjE3NSA4Ny40NDcyIDE0LjIzOTcgODcuNTYwNSAxNC4yODM5IDg3LjY2NzJDMTQuMzMwOCA4Ny43NzQgMTQuNDAxMSA4Ny44NjEyIDE0LjQ5NDkgODcuOTI5QzE0LjU4ODYgODcuOTk0MSAxNC43MDU4IDg4LjAyNjYgMTQuODQ2NCA4OC4wMjY2QzE0Ljk4NzEgODguMDI2NiAxNS4xMDI5IDg3Ljk5NDEgMTUuMTk0MSA4Ny45MjlDMTUuMjg1MiA4Ny44NjEyIDE1LjM1MjkgODcuNzc0IDE1LjM5NzIgODcuNjY3MkMxNS40NDE1IDg3LjU2MDUgMTUuNDYzNiA4Ny40NDcyIDE1LjQ2MzYgODcuMzI3NFY4Ny4wMjY2QzE1LjQ2MzYgODYuOTA0MiAxNS40NDAyIDg2Ljc4OTYgMTUuMzkzMyA4Ni42ODI5QzE1LjM0OSA4Ni41NzM1IDE1LjI4IDg2LjQ4NjIgMTUuMTg2MyA4Ni40MjExQzE1LjA5NTEgODYuMzUzNCAxNC45NzkzIDg2LjMxOTYgMTQuODM4NiA4Ni4zMTk2QzE0LjcwMDYgODYuMzE5NiAxNC41ODQ3IDg2LjM1MzQgMTQuNDkxIDg2LjQyMTFDMTQuMzk5OCA4Ni40ODYyIDE0LjMzMDggODYuNTczNSAxNC4yODM5IDg2LjY4MjlDMTQuMjM5NyA4Ni43ODk2IDE0LjIxNzUgODYuOTA0MiAxNC4yMTc1IDg3LjAyNjZaTTE2LjQ0NDEgOTAuNTMwNVY5MC4yMjU4QzE2LjQ0NDEgOTAuMDEyMyAxNi40OTEgODkuODE3IDE2LjU4NDcgODkuNjM5OUMxNi42Nzg1IDg5LjQ2MjggMTYuODEyNiA4OS4zMjA5IDE2Ljk4NzEgODkuMjE0MUMxNy4xNjE1IDg5LjEwNzMgMTcuMzY4NiA4OS4wNTQgMTcuNjA4MiA4OS4wNTRDMTcuODUyOSA4OS4wNTQgMTguMDYxMyA4OS4xMDczIDE4LjIzMzIgODkuMjE0MUMxOC40MDc2IDg5LjMyMDkgMTguNTQxOCA4OS40NjI4IDE4LjYzNTUgODkuNjM5OUMxOC43MjkzIDg5LjgxNyAxOC43NzYxIDkwLjAxMjMgMTguNzc2MSA5MC4yMjU4VjkwLjUzMDVDMTguNzc2MSA5MC43NDQxIDE4LjcyOTMgOTAuOTM5NCAxOC42MzU1IDkxLjExNjVDMTguNTQ0NCA5MS4yOTM1IDE4LjQxMTUgOTEuNDM1NSAxOC4yMzcxIDkxLjU0MjJDMTguMDY1MiA5MS42NDkgMTcuODU4MiA5MS43MDI0IDE3LjYxNiA5MS43MDI0QzE3LjM3MzggOTEuNzAyNCAxNy4xNjU0IDkxLjY0OSAxNi45OTEgOTEuNTQyMkMxNi44MTY1IDkxLjQzNTUgMTYuNjgxMSA5MS4yOTM1IDE2LjU4NDcgOTEuMTE2NUMxNi40OTEgOTAuOTM5NCAxNi40NDQxIDkwLjc0NDEgMTYuNDQ0MSA5MC41MzA1Wk0xNi45ODcxIDkwLjIyNThWOTAuNTMwNUMxNi45ODcxIDkwLjY1MDMgMTcuMDA5MiA5MC43NjQ5IDE3LjA1MzUgOTAuODc0M0MxNy4xMDAzIDkwLjk4MSAxNy4xNzA3IDkxLjA2ODMgMTcuMjY0NCA5MS4xMzZDMTcuMzU4MiA5MS4yMDExIDE3LjQ3NTMgOTEuMjMzNiAxNy42MTYgOTEuMjMzNkMxNy43NTY2IDkxLjIzMzYgMTcuODcyNSA5MS4yMDExIDE3Ljk2MzYgOTEuMTM2QzE4LjA1NzQgOTEuMDY4MyAxOC4xMjY0IDkwLjk4MSAxOC4xNzA3IDkwLjg3NDNDMTguMjE0OSA5MC43Njc1IDE4LjIzNzEgOTAuNjUyOSAxOC4yMzcxIDkwLjUzMDVWOTAuMjI1OEMxOC4yMzcxIDkwLjEwMzQgMTguMjEzNiA4OS45ODg5IDE4LjE2NjggODkuODgyMUMxOC4xMjI1IDg5Ljc3NTMgMTguMDUzNSA4OS42ODk0IDE3Ljk1OTcgODkuNjI0M0MxNy44Njg2IDg5LjU1NjYgMTcuNzUxNCA4OS41MjI3IDE3LjYwODIgODkuNTIyN0MxNy40NzAxIDg5LjUyMjcgMTcuMzU0MyA4OS41NTY2IDE3LjI2MDUgODkuNjI0M0MxNy4xNjk0IDg5LjY4OTQgMTcuMTAwMyA4OS43NzUzIDE3LjA1MzUgODkuODgyMUMxNy4wMDkyIDg5Ljk4ODkgMTYuOTg3MSA5MC4xMDM0IDE2Ljk4NzEgOTAuMjI1OFpNMTcuNzg3OCA4Ni43NDE1TDE1LjAxMDUgOTEuMTg2OEwxNC42MDQzIDkwLjkyOUwxNy4zODE2IDg2LjQ4MzZMMTcuNzg3OCA4Ni43NDE1WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNOC4xOTkyMiAxMTkuMjMzVjExOS44MjdINC40NzY1NlYxMTkuMzA4TDYuMzM5ODQgMTE3LjIzM0M2LjU2OTAxIDExNi45NzggNi43NDYwOSAxMTYuNzYyIDYuODcxMDkgMTE2LjU4NUM2Ljk5ODcgMTE2LjQwNSA3LjA4NzI0IDExNi4yNDUgNy4xMzY3MiAxMTYuMTA0QzcuMTg4OCAxMTUuOTYxIDcuMjE0ODQgMTE1LjgxNSA3LjIxNDg0IDExNS42NjdDNy4yMTQ4NCAxMTUuNDc5IDcuMTc1NzggMTE1LjMxIDcuMDk3NjYgMTE1LjE1OUM3LjAyMjE0IDExNS4wMDYgNi45MTAxNiAxMTQuODgzIDYuNzYxNzIgMTE0Ljc5MkM2LjYxMzI4IDExNC43MDEgNi40MzM1OSAxMTQuNjU1IDYuMjIyNjYgMTE0LjY1NUM1Ljk3MDA1IDExNC42NTUgNS43NTkxMSAxMTQuNzA1IDUuNTg5ODQgMTE0LjgwNEM1LjQyMzE4IDExNC45IDUuMjk4MTggMTE1LjAzNSA1LjIxNDg0IDExNS4yMUM1LjEzMTUxIDExNS4zODQgNS4wODk4NCAxMTUuNTg1IDUuMDg5ODQgMTE1LjgxMkg0LjM2NzE5QzQuMzY3MTkgMTE1LjQ5MSA0LjQzNzUgMTE1LjE5OCA0LjU3ODEyIDExNC45MzNDNC43MTg3NSAxMTQuNjY3IDQuOTI3MDggMTE0LjQ1NiA1LjIwMzEyIDExNC4zQzUuNDc5MTcgMTE0LjE0MSA1LjgxOTAxIDExNC4wNjIgNi4yMjI2NiAxMTQuMDYyQzYuNTgyMDMgMTE0LjA2MiA2Ljg4OTMyIDExNC4xMjUgNy4xNDQ1MyAxMTQuMjUzQzcuMzk5NzQgMTE0LjM3OCA3LjU5NTA1IDExNC41NTUgNy43MzA0NyAxMTQuNzg0QzcuODY4NDkgMTE1LjAxMSA3LjkzNzUgMTE1LjI3NiA3LjkzNzUgMTE1LjU4MUM3LjkzNzUgMTE1Ljc0OCA3LjkwODg1IDExNS45MTcgNy44NTE1NiAxMTYuMDg5QzcuNzk2ODggMTE2LjI1OCA3LjcyMDA1IDExNi40MjcgNy42MjEwOSAxMTYuNTk3QzcuNTI0NzQgMTE2Ljc2NiA3LjQxMTQ2IDExNi45MzMgNy4yODEyNSAxMTcuMDk3QzcuMTUzNjUgMTE3LjI2MSA3LjAxNjkzIDExNy40MjIgNi44NzEwOSAxMTcuNTgxTDUuMzQ3NjYgMTE5LjIzM0g4LjE5OTIyWk0xMi42NzUyIDExNi41M1YxMTcuMzk3QzEyLjY3NTIgMTE3Ljg2NCAxMi42MzM1IDExOC4yNTcgMTIuNTUwMiAxMTguNTc3QzEyLjQ2NjggMTE4Ljg5NyAxMi4zNDcgMTE5LjE1NSAxMi4xOTA4IDExOS4zNTFDMTIuMDM0NSAxMTkuNTQ2IDExLjg0NTcgMTE5LjY4OCAxMS42MjQ0IDExOS43NzZDMTEuNDA1NiAxMTkuODYyIDExLjE1ODIgMTE5LjkwNSAxMC44ODIyIDExOS45MDVDMTAuNjYzNSAxMTkuOTA1IDEwLjQ2MTYgMTE5Ljg3OCAxMC4yNzY3IDExOS44MjNDMTAuMDkxOCAxMTkuNzY5IDkuOTI1MTcgMTE5LjY4MSA5Ljc3NjczIDExOS41NjJDOS42MzA5IDExOS40MzkgOS41MDU5IDExOS4yOCA5LjQwMTczIDExOS4wODVDOS4yOTc1NyAxMTguODkgOS4yMTgxNCAxMTguNjUzIDkuMTYzNDUgMTE4LjM3NEM5LjEwODc3IDExOC4wOTUgOS4wODE0MiAxMTcuNzcgOS4wODE0MiAxMTcuMzk3VjExNi41M0M5LjA4MTQyIDExNi4wNjQgOS4xMjMwOSAxMTUuNjc0IDkuMjA2NDIgMTE1LjM1OEM5LjI5MjM2IDExNS4wNDMgOS40MTM0NSAxMTQuNzkxIDkuNTY5NyAxMTQuNjAxQzkuNzI1OTUgMTE0LjQwOCA5LjkxMzQ1IDExNC4yNyAxMC4xMzIyIDExNC4xODdDMTAuMzUzNiAxMTQuMTAzIDEwLjYwMSAxMTQuMDYyIDEwLjg3NDQgMTE0LjA2MkMxMS4wOTU3IDExNC4wNjIgMTEuMjk4OSAxMTQuMDg5IDExLjQ4MzggMTE0LjE0NEMxMS42NzEzIDExNC4xOTYgMTEuODM3OSAxMTQuMjggMTEuOTgzOCAxMTQuMzk3QzEyLjEyOTYgMTE0LjUxMiAxMi4yNTMzIDExNC42NjYgMTIuMzU0OSAxMTQuODU4QzEyLjQ1OSAxMTUuMDQ5IDEyLjUzODUgMTE1LjI4MiAxMi41OTMxIDExNS41NThDMTIuNjQ3OCAxMTUuODM0IDEyLjY3NTIgMTE2LjE1OCAxMi42NzUyIDExNi41M1pNMTEuOTQ4NiAxMTcuNTE1VjExNi40MDlDMTEuOTQ4NiAxMTYuMTU0IDExLjkzMyAxMTUuOTMgMTEuOTAxNyAxMTUuNzM3QzExLjg3MzEgMTE1LjU0MiAxMS44MzAxIDExNS4zNzUgMTEuNzcyOCAxMTUuMjM3QzExLjcxNTUgMTE1LjA5OSAxMS42NDI2IDExNC45ODcgMTEuNTU0MSAxMTQuOTAxQzExLjQ2ODEgMTE0LjgxNSAxMS4zNjc5IDExNC43NTMgMTEuMjUzMyAxMTQuNzE0QzExLjE0MTMgMTE0LjY3MiAxMS4wMTUgMTE0LjY1MSAxMC44NzQ0IDExNC42NTFDMTAuNzAyNSAxMTQuNjUxIDEwLjU1MDIgMTE0LjY4NCAxMC40MTc0IDExNC43NDlDMTAuMjg0NSAxMTQuODEyIDEwLjE3MjYgMTE0LjkxMiAxMC4wODE0IDExNS4wNUM5Ljk5Mjg4IDExNS4xODggOS45MjUxNyAxMTUuMzY5IDkuODc4MyAxMTUuNTkzQzkuODMxNDIgMTE1LjgxNyA5LjgwNzk4IDExNi4wODkgOS44MDc5OCAxMTYuNDA5VjExNy41MTVDOS44MDc5OCAxMTcuNzcgOS44MjIzMSAxMTcuOTk1IDkuODUwOTUgMTE4LjE5QzkuODgyMiAxMTguMzg2IDkuOTI3NzggMTE4LjU1NSA5Ljk4NzY3IDExOC42OThDMTAuMDQ3NiAxMTguODM5IDEwLjEyMDUgMTE4Ljk1NSAxMC4yMDY0IDExOS4wNDZDMTAuMjkyNCAxMTkuMTM3IDEwLjM5MTMgMTE5LjIwNSAxMC41MDMzIDExOS4yNDlDMTAuNjE3OSAxMTkuMjkxIDEwLjc0NDIgMTE5LjMxMiAxMC44ODIyIDExOS4zMTJDMTEuMDU5MyAxMTkuMzEyIDExLjIxNDIgMTE5LjI3OCAxMS4zNDcgMTE5LjIxQzExLjQ3OTkgMTE5LjE0MiAxMS41OTA1IDExOS4wMzcgMTEuNjc5MSAxMTguODk0QzExLjc3MDIgMTE4Ljc0OCAxMS44Mzc5IDExOC41NjIgMTEuODgyMiAxMTguMzM1QzExLjkyNjUgMTE4LjEwNiAxMS45NDg2IDExNy44MzIgMTEuOTQ4NiAxMTcuNTE1Wk0xMy42NzQ2IDExNS41MzRWMTE1LjIzM0MxMy42NzQ2IDExNS4wMTcgMTMuNzIxNCAxMTQuODIxIDEzLjgxNTIgMTE0LjY0NEMxMy45MDg5IDExNC40NjYgMTQuMDQzMSAxMTQuMzI1IDE0LjIxNzUgMTE0LjIxOEMxNC4zOTIgMTE0LjExMSAxNC41OTkgMTE0LjA1OCAxNC44Mzg2IDExNC4wNThDMTUuMDgzNCAxMTQuMDU4IDE1LjI5MTggMTE0LjExMSAxNS40NjM2IDExNC4yMThDMTUuNjM4MSAxMTQuMzI1IDE1Ljc3MjIgMTE0LjQ2NiAxNS44NjYgMTE0LjY0NEMxNS45NTk3IDExNC44MjEgMTYuMDA2NiAxMTUuMDE3IDE2LjAwNjYgMTE1LjIzM1YxMTUuNTM0QzE2LjAwNjYgMTE1Ljc0NSAxNS45NTk3IDExNS45MzkgMTUuODY2IDExNi4xMTZDMTUuNzc0OCAxMTYuMjkzIDE1LjY0MiAxMTYuNDM1IDE1LjQ2NzUgMTE2LjU0MkMxNS4yOTU3IDExNi42NDkgMTUuMDg4NiAxMTYuNzAyIDE0Ljg0NjQgMTE2LjcwMkMxNC42MDQzIDExNi43MDIgMTQuMzk0NiAxMTYuNjQ5IDE0LjIxNzUgMTE2LjU0MkMxNC4wNDMxIDExNi40MzUgMTMuOTA4OSAxMTYuMjkzIDEzLjgxNTIgMTE2LjExNkMxMy43MjE0IDExNS45MzkgMTMuNjc0NiAxMTUuNzQ1IDEzLjY3NDYgMTE1LjUzNFpNMTQuMjE3NSAxMTUuMjMzVjExNS41MzRDMTQuMjE3NSAxMTUuNjU0IDE0LjIzOTcgMTE1Ljc2NyAxNC4yODM5IDExNS44NzRDMTQuMzMwOCAxMTUuOTgxIDE0LjQwMTEgMTE2LjA2OCAxNC40OTQ5IDExNi4xMzZDMTQuNTg4NiAxMTYuMjAxIDE0LjcwNTggMTE2LjIzMyAxNC44NDY0IDExNi4yMzNDMTQuOTg3MSAxMTYuMjMzIDE1LjEwMjkgMTE2LjIwMSAxNS4xOTQxIDExNi4xMzZDMTUuMjg1MiAxMTYuMDY4IDE1LjM1MjkgMTE1Ljk4MSAxNS4zOTcyIDExNS44NzRDMTUuNDQxNSAxMTUuNzY3IDE1LjQ2MzYgMTE1LjY1NCAxNS40NjM2IDExNS41MzRWMTE1LjIzM0MxNS40NjM2IDExNS4xMTEgMTUuNDQwMiAxMTQuOTk2IDE1LjM5MzMgMTE0Ljg5QzE1LjM0OSAxMTQuNzggMTUuMjggMTE0LjY5MyAxNS4xODYzIDExNC42MjhDMTUuMDk1MSAxMTQuNTYgMTQuOTc5MyAxMTQuNTI2IDE0LjgzODYgMTE0LjUyNkMxNC43MDA2IDExNC41MjYgMTQuNTg0NyAxMTQuNTYgMTQuNDkxIDExNC42MjhDMTQuMzk5OCAxMTQuNjkzIDE0LjMzMDggMTE0Ljc4IDE0LjI4MzkgMTE0Ljg5QzE0LjIzOTcgMTE0Ljk5NiAxNC4yMTc1IDExNS4xMTEgMTQuMjE3NSAxMTUuMjMzWk0xNi40NDQxIDExOC43MzdWMTE4LjQzM0MxNi40NDQxIDExOC4yMTkgMTYuNDkxIDExOC4wMjQgMTYuNTg0NyAxMTcuODQ3QzE2LjY3ODUgMTE3LjY3IDE2LjgxMjYgMTE3LjUyOCAxNi45ODcxIDExNy40MjFDMTcuMTYxNSAxMTcuMzE0IDE3LjM2ODYgMTE3LjI2MSAxNy42MDgyIDExNy4yNjFDMTcuODUyOSAxMTcuMjYxIDE4LjA2MTMgMTE3LjMxNCAxOC4yMzMyIDExNy40MjFDMTguNDA3NiAxMTcuNTI4IDE4LjU0MTggMTE3LjY3IDE4LjYzNTUgMTE3Ljg0N0MxOC43MjkzIDExOC4wMjQgMTguNzc2MSAxMTguMjE5IDE4Ljc3NjEgMTE4LjQzM1YxMTguNzM3QzE4Ljc3NjEgMTE4Ljk1MSAxOC43MjkzIDExOS4xNDYgMTguNjM1NSAxMTkuMzIzQzE4LjU0NDQgMTE5LjUgMTguNDExNSAxMTkuNjQyIDE4LjIzNzEgMTE5Ljc0OUMxOC4wNjUyIDExOS44NTYgMTcuODU4MiAxMTkuOTA5IDE3LjYxNiAxMTkuOTA5QzE3LjM3MzggMTE5LjkwOSAxNy4xNjU0IDExOS44NTYgMTYuOTkxIDExOS43NDlDMTYuODE2NSAxMTkuNjQyIDE2LjY4MTEgMTE5LjUgMTYuNTg0NyAxMTkuMzIzQzE2LjQ5MSAxMTkuMTQ2IDE2LjQ0NDEgMTE4Ljk1MSAxNi40NDQxIDExOC43MzdaTTE2Ljk4NzEgMTE4LjQzM1YxMTguNzM3QzE2Ljk4NzEgMTE4Ljg1NyAxNy4wMDkyIDExOC45NzIgMTcuMDUzNSAxMTkuMDgxQzE3LjEwMDMgMTE5LjE4OCAxNy4xNzA3IDExOS4yNzUgMTcuMjY0NCAxMTkuMzQzQzE3LjM1ODIgMTE5LjQwOCAxNy40NzUzIDExOS40NCAxNy42MTYgMTE5LjQ0QzE3Ljc1NjYgMTE5LjQ0IDE3Ljg3MjUgMTE5LjQwOCAxNy45NjM2IDExOS4zNDNDMTguMDU3NCAxMTkuMjc1IDE4LjEyNjQgMTE5LjE4OCAxOC4xNzA3IDExOS4wODFDMTguMjE0OSAxMTguOTc0IDE4LjIzNzEgMTE4Ljg2IDE4LjIzNzEgMTE4LjczN1YxMTguNDMzQzE4LjIzNzEgMTE4LjMxIDE4LjIxMzYgMTE4LjE5NiAxOC4xNjY4IDExOC4wODlDMTguMTIyNSAxMTcuOTgyIDE4LjA1MzUgMTE3Ljg5NiAxNy45NTk3IDExNy44MzFDMTcuODY4NiAxMTcuNzYzIDE3Ljc1MTQgMTE3LjcyOSAxNy42MDgyIDExNy43MjlDMTcuNDcwMSAxMTcuNzI5IDE3LjM1NDMgMTE3Ljc2MyAxNy4yNjA1IDExNy44MzFDMTcuMTY5NCAxMTcuODk2IDE3LjEwMDMgMTE3Ljk4MiAxNy4wNTM1IDExOC4wODlDMTcuMDA5MiAxMTguMTk2IDE2Ljk4NzEgMTE4LjMxIDE2Ljk4NzEgMTE4LjQzM1pNMTcuNzg3OCAxMTQuOTQ4TDE1LjAxMDUgMTE5LjM5NEwxNC42MDQzIDExOS4xMzZMMTcuMzgxNiAxMTQuNjlMMTcuNzg3OCAxMTQuOTQ4WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNMTMuMDQzIDE0NC43MzdWMTQ1LjYwNEMxMy4wNDMgMTQ2LjA3IDEzLjAwMTMgMTQ2LjQ2NCAxMi45MTggMTQ2Ljc4NEMxMi44MzQ2IDE0Ny4xMDQgMTIuNzE0OCAxNDcuMzYyIDEyLjU1ODYgMTQ3LjU1N0MxMi40MDIzIDE0Ny43NTMgMTIuMjEzNSAxNDcuODk1IDExLjk5MjIgMTQ3Ljk4M0MxMS43NzM0IDE0OC4wNjkgMTEuNTI2IDE0OC4xMTIgMTEuMjUgMTQ4LjExMkMxMS4wMzEyIDE0OC4xMTIgMTAuODI5NCAxNDguMDg1IDEwLjY0NDUgMTQ4LjAzQzEwLjQ1OTYgMTQ3Ljk3NSAxMC4yOTMgMTQ3Ljg4OCAxMC4xNDQ1IDE0Ny43NjhDOS45OTg3IDE0Ny42NDYgOS44NzM3IDE0Ny40ODcgOS43Njk1MyAxNDcuMjkyQzkuNjY1MzYgMTQ3LjA5NiA5LjU4NTk0IDE0Ni44NTkgOS41MzEyNSAxNDYuNTgxQzkuNDc2NTYgMTQ2LjMwMiA5LjQ0OTIyIDE0NS45NzcgOS40NDkyMiAxNDUuNjA0VjE0NC43MzdDOS40NDkyMiAxNDQuMjcxIDkuNDkwODkgMTQzLjg4IDkuNTc0MjIgMTQzLjU2NUM5LjY2MDE2IDE0My4yNSA5Ljc4MTI1IDE0Mi45OTcgOS45Mzc1IDE0Mi44MDdDMTAuMDkzOCAxNDIuNjE1IDEwLjI4MTIgMTQyLjQ3NyAxMC41IDE0Mi4zOTNDMTAuNzIxNCAxNDIuMzEgMTAuOTY4OCAxNDIuMjY4IDExLjI0MjIgMTQyLjI2OEMxMS40NjM1IDE0Mi4yNjggMTEuNjY2NyAxNDIuMjk2IDExLjg1MTYgMTQyLjM1QzEyLjAzOTEgMTQyLjQwMiAxMi4yMDU3IDE0Mi40ODcgMTIuMzUxNiAxNDIuNjA0QzEyLjQ5NzQgMTQyLjcxOSAxMi42MjExIDE0Mi44NzIgMTIuNzIyNyAxNDMuMDY1QzEyLjgyNjggMTQzLjI1NSAxMi45MDYyIDE0My40ODggMTIuOTYwOSAxNDMuNzY0QzEzLjAxNTYgMTQ0LjA0IDEzLjA0MyAxNDQuMzY1IDEzLjA0MyAxNDQuNzM3Wk0xMi4zMTY0IDE0NS43MjFWMTQ0LjYxNkMxMi4zMTY0IDE0NC4zNjEgMTIuMzAwOCAxNDQuMTM3IDEyLjI2OTUgMTQzLjk0NEMxMi4yNDA5IDE0My43NDkgMTIuMTk3OSAxNDMuNTgyIDEyLjE0MDYgMTQzLjQ0NEMxMi4wODMzIDE0My4zMDYgMTIuMDEwNCAxNDMuMTk0IDExLjkyMTkgMTQzLjEwOEMxMS44MzU5IDE0My4wMjIgMTEuNzM1NyAxNDIuOTYgMTEuNjIxMSAxNDIuOTIxQzExLjUwOTEgMTQyLjg3OSAxMS4zODI4IDE0Mi44NTggMTEuMjQyMiAxNDIuODU4QzExLjA3MDMgMTQyLjg1OCAxMC45MTggMTQyLjg5MSAxMC43ODUyIDE0Mi45NTZDMTAuNjUyMyAxNDMuMDE4IDEwLjU0MDQgMTQzLjExOSAxMC40NDkyIDE0My4yNTdDMTAuMzYwNyAxNDMuMzk1IDEwLjI5MyAxNDMuNTc2IDEwLjI0NjEgMTQzLjhDMTAuMTk5MiAxNDQuMDI0IDEwLjE3NTggMTQ0LjI5NiAxMC4xNzU4IDE0NC42MTZWMTQ1LjcyMUMxMC4xNzU4IDE0NS45NzcgMTAuMTkwMSAxNDYuMjAyIDEwLjIxODggMTQ2LjM5N0MxMC4yNSAxNDYuNTkzIDEwLjI5NTYgMTQ2Ljc2MiAxMC4zNTU1IDE0Ni45MDVDMTAuNDE1NCAxNDcuMDQ2IDEwLjQ4ODMgMTQ3LjE2MiAxMC41NzQyIDE0Ny4yNTNDMTAuNjYwMiAxNDcuMzQ0IDEwLjc1OTEgMTQ3LjQxMiAxMC44NzExIDE0Ny40NTZDMTAuOTg1NyAxNDcuNDk3IDExLjExMiAxNDcuNTE4IDExLjI1IDE0Ny41MThDMTEuNDI3MSAxNDcuNTE4IDExLjU4MiAxNDcuNDg0IDExLjcxNDggMTQ3LjQxN0MxMS44NDc3IDE0Ny4zNDkgMTEuOTU4MyAxNDcuMjQ0IDEyLjA0NjkgMTQ3LjFDMTIuMTM4IDE0Ni45NTUgMTIuMjA1NyAxNDYuNzY4IDEyLjI1IDE0Ni41NDJDMTIuMjk0MyAxNDYuMzEzIDEyLjMxNjQgMTQ2LjAzOSAxMi4zMTY0IDE0NS43MjFaTTE0LjA0MjQgMTQzLjc0MVYxNDMuNDRDMTQuMDQyNCAxNDMuMjI0IDE0LjA4OTIgMTQzLjAyNyAxNC4xODMgMTQyLjg1QzE0LjI3NjcgMTQyLjY3MyAxNC40MTA4IDE0Mi41MzEgMTQuNTg1MyAxNDIuNDI1QzE0Ljc1OTggMTQyLjMxOCAxNC45NjY4IDE0Mi4yNjQgMTUuMjA2NCAxNDIuMjY0QzE1LjQ1MTIgMTQyLjI2NCAxNS42NTk1IDE0Mi4zMTggMTUuODMxNCAxNDIuNDI1QzE2LjAwNTkgMTQyLjUzMSAxNi4xNCAxNDIuNjczIDE2LjIzMzggMTQyLjg1QzE2LjMyNzUgMTQzLjAyNyAxNi4zNzQ0IDE0My4yMjQgMTYuMzc0NCAxNDMuNDRWMTQzLjc0MUMxNi4zNzQ0IDE0My45NTIgMTYuMzI3NSAxNDQuMTQ2IDE2LjIzMzggMTQ0LjMyM0MxNi4xNDI2IDE0NC41IDE2LjAwOTggMTQ0LjY0MiAxNS44MzUzIDE0NC43NDlDMTUuNjYzNSAxNDQuODU2IDE1LjQ1NjQgMTQ0LjkwOSAxNS4yMTQyIDE0NC45MDlDMTQuOTcyIDE0NC45MDkgMTQuNzYyNCAxNDQuODU2IDE0LjU4NTMgMTQ0Ljc0OUMxNC40MTA4IDE0NC42NDIgMTQuMjc2NyAxNDQuNSAxNC4xODMgMTQ0LjMyM0MxNC4wODkyIDE0NC4xNDYgMTQuMDQyNCAxNDMuOTUyIDE0LjA0MjQgMTQzLjc0MVpNMTQuNTg1MyAxNDMuNDRWMTQzLjc0MUMxNC41ODUzIDE0My44NjEgMTQuNjA3NSAxNDMuOTc0IDE0LjY1MTcgMTQ0LjA4MUMxNC42OTg2IDE0NC4xODggMTQuNzY4OSAxNDQuMjc1IDE0Ljg2MjcgMTQ0LjM0M0MxNC45NTY0IDE0NC40MDggMTUuMDczNiAxNDQuNDQgMTUuMjE0MiAxNDQuNDRDMTUuMzU0OSAxNDQuNDQgMTUuNDcwNyAxNDQuNDA4IDE1LjU2MTkgMTQ0LjM0M0MxNS42NTMgMTQ0LjI3NSAxNS43MjA3IDE0NC4xODggMTUuNzY1IDE0NC4wODFDMTUuODA5MyAxNDMuOTc0IDE1LjgzMTQgMTQzLjg2MSAxNS44MzE0IDE0My43NDFWMTQzLjQ0QzE1LjgzMTQgMTQzLjMxOCAxNS44MDggMTQzLjIwMyAxNS43NjExIDE0My4wOTZDMTUuNzE2OCAxNDIuOTg3IDE1LjY0NzggMTQyLjkgMTUuNTU0MSAxNDIuODM1QzE1LjQ2MjkgMTQyLjc2NyAxNS4zNDcgMTQyLjczMyAxNS4yMDY0IDE0Mi43MzNDMTUuMDY4NCAxNDIuNzMzIDE0Ljk1MjUgMTQyLjc2NyAxNC44NTg4IDE0Mi44MzVDMTQuNzY3NiAxNDIuOSAxNC42OTg2IDE0Mi45ODcgMTQuNjUxNyAxNDMuMDk2QzE0LjYwNzUgMTQzLjIwMyAxNC41ODUzIDE0My4zMTggMTQuNTg1MyAxNDMuNDRaTTE2LjgxMTkgMTQ2Ljk0NFYxNDYuNjM5QzE2LjgxMTkgMTQ2LjQyNiAxNi44NTg4IDE0Ni4yMzEgMTYuOTUyNSAxNDYuMDUzQzE3LjA0NjMgMTQ1Ljg3NiAxNy4xODA0IDE0NS43MzQgMTcuMzU0OSAxNDUuNjI4QzE3LjUyOTMgMTQ1LjUyMSAxNy43MzY0IDE0NS40NjggMTcuOTc2IDE0NS40NjhDMTguMjIwNyAxNDUuNDY4IDE4LjQyOTEgMTQ1LjUyMSAxOC42MDEgMTQ1LjYyOEMxOC43NzU0IDE0NS43MzQgMTguOTA5NSAxNDUuODc2IDE5LjAwMzMgMTQ2LjA1M0MxOS4wOTcgMTQ2LjIzMSAxOS4xNDM5IDE0Ni40MjYgMTkuMTQzOSAxNDYuNjM5VjE0Ni45NDRDMTkuMTQzOSAxNDcuMTU4IDE5LjA5NyAxNDcuMzUzIDE5LjAwMzMgMTQ3LjUzQzE4LjkxMjIgMTQ3LjcwNyAxOC43NzkzIDE0Ny44NDkgMTguNjA0OSAxNDcuOTU2QzE4LjQzMyAxNDguMDYzIDE4LjIyNiAxNDguMTE2IDE3Ljk4MzggMTQ4LjExNkMxNy43NDE2IDE0OC4xMTYgMTcuNTMzMiAxNDguMDYzIDE3LjM1ODggMTQ3Ljk1NkMxNy4xODQzIDE0Ny44NDkgMTcuMDQ4OSAxNDcuNzA3IDE2Ljk1MjUgMTQ3LjUzQzE2Ljg1ODggMTQ3LjM1MyAxNi44MTE5IDE0Ny4xNTggMTYuODExOSAxNDYuOTQ0Wk0xNy4zNTQ5IDE0Ni42MzlWMTQ2Ljk0NEMxNy4zNTQ5IDE0Ny4wNjQgMTcuMzc3IDE0Ny4xNzggMTcuNDIxMyAxNDcuMjg4QzE3LjQ2ODEgMTQ3LjM5NSAxNy41Mzg1IDE0Ny40ODIgMTcuNjMyMiAxNDcuNTVDMTcuNzI2IDE0Ny42MTUgMTcuODQzMSAxNDcuNjQ3IDE3Ljk4MzggMTQ3LjY0N0MxOC4xMjQ0IDE0Ny42NDcgMTguMjQwMyAxNDcuNjE1IDE4LjMzMTQgMTQ3LjU1QzE4LjQyNTIgMTQ3LjQ4MiAxOC40OTQyIDE0Ny4zOTUgMTguNTM4NSAxNDcuMjg4QzE4LjU4MjcgMTQ3LjE4MSAxOC42MDQ5IDE0Ny4wNjYgMTguNjA0OSAxNDYuOTQ0VjE0Ni42MzlDMTguNjA0OSAxNDYuNTE3IDE4LjU4MTQgMTQ2LjQwMiAxOC41MzQ1IDE0Ni4yOTZDMTguNDkwMyAxNDYuMTg5IDE4LjQyMTMgMTQ2LjEwMyAxOC4zMjc1IDE0Ni4wMzhDMTguMjM2NCAxNDUuOTcgMTguMTE5MiAxNDUuOTM2IDE3Ljk3NiAxNDUuOTM2QzE3LjgzNzkgMTQ1LjkzNiAxNy43MjIgMTQ1Ljk3IDE3LjYyODMgMTQ2LjAzOEMxNy41MzcyIDE0Ni4xMDMgMTcuNDY4MSAxNDYuMTg5IDE3LjQyMTMgMTQ2LjI5NkMxNy4zNzcgMTQ2LjQwMiAxNy4zNTQ5IDE0Ni41MTcgMTcuMzU0OSAxNDYuNjM5Wk0xOC4xNTU2IDE0My4xNTVMMTUuMzc4MyAxNDcuNkwxNC45NzIgMTQ3LjM0M0wxNy43NDk0IDE0Mi44OTdMMTguMTU1NiAxNDMuMTU1WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNMjUgNC4xNjExM0wyMDAgNC4xNjExNiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPHBhdGggZD0iTTI1IDMzLjE2MTFMMjAwIDMzLjE2MTIiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIi8+CjxwYXRoIGQ9Ik0yNSA2MS4xNjExTDIwMCA2MS4xNjEyIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIvPgo8cGF0aCBkPSJNMjUgODkuMTYxMUwyMDAgODkuMTYxMiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPHBhdGggZD0iTTI1IDExOC4xNjFMMjAwIDExOC4xNjEiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIi8+CjxwYXRoIGQ9Ik0zNiA2MkMzNiA2MC44OTU0IDM2Ljg5NTQgNjAgMzggNjBINTJDNTMuMTA0NiA2MCA1NCA2MC44OTU0IDU0IDYyVjE0NkgzNlY2MloiIGZpbGw9IiNGRkMxMDciLz4KPHBhdGggZD0iTTU4IDkyQzU4IDkwLjg5NTQgNTguODk1NCA5MCA2MCA5MEg3NEM3NS4xMDQ2IDkwIDc2IDkwLjg5NTQgNzYgOTJWMTQ2SDU4VjkyWiIgZmlsbD0iIzRDQUY1MCIvPgo8cGF0aCBkPSJNODAgNzlDODAgNzcuODk1NCA4MC44OTU0IDc3IDgyIDc3SDk2Qzk3LjEwNDYgNzcgOTggNzcuODk1NCA5OCA3OVYxNDZIODBWNzlaIiBmaWxsPSIjMjE5NkYzIi8+CjxwYXRoIGQ9Ik0xMjUgNzhDMTI1IDc2Ljg5NTQgMTI1Ljg5NSA3NiAxMjcgNzZIMTQxQzE0Mi4xMDUgNzYgMTQzIDc2Ljg5NTQgMTQzIDc4VjE0NkgxMjVWNzhaIiBmaWxsPSIjRkZDMTA3Ii8+CjxwYXRoIGQ9Ik0xNDcgNTFDMTQ3IDQ5Ljg5NTQgMTQ3Ljg5NSA0OSAxNDkgNDlIMTYzQzE2NC4xMDUgNDkgMTY1IDQ5Ljg5NTQgMTY1IDUxVjE0NkgxNDdWNTFaIiBmaWxsPSIjNENBRjUwIi8+CjxwYXRoIGQ9Ik0xNjkgMzZDMTY5IDM0Ljg5NTQgMTY5Ljg5NSAzNCAxNzEgMzRIMTg1QzE4Ni4xMDUgMzQgMTg3IDM0Ljg5NTQgMTg3IDM2VjE0NkgxNjlWMzZaIiBmaWxsPSIjMjE5NkYzIi8+CjxsaW5lIHgxPSIyMy4yIiB5MT0iMTQ1Ljk2MSIgeDI9IjIwMi44IiB5Mj0iMTQ1Ljk2MSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuNyIgc3Ryb2tlLXdpZHRoPSIwLjQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiLz4KPGxpbmUgeDE9IjY3IiB5MT0iMTQ4LjA3MiIgeDI9IjY3IiB5Mj0iMTQ3LjI1IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNNTkuMDU5MSAxNTIuMDI1VjE1Mi44OTNDNTkuMDU5MSAxNTMuMzU5IDU5LjAxNzQgMTUzLjc1MiA1OC45MzQxIDE1NC4wNzJDNTguODUwNyAxNTQuMzkzIDU4LjczMDkgMTU0LjY1IDU4LjU3NDcgMTU0Ljg0NkM1OC40MTg0IDE1NS4wNDEgNTguMjI5NiAxNTUuMTgzIDU4LjAwODMgMTU1LjI3MUM1Ny43ODk1IDE1NS4zNTcgNTcuNTQyMSAxNTUuNCA1Ny4yNjYxIDE1NS40QzU3LjA0NzMgMTU1LjQgNTYuODQ1NSAxNTUuMzczIDU2LjY2MDYgMTU1LjMxOEM1Ni40NzU3IDE1NS4yNjQgNTYuMzA5MSAxNTUuMTc2IDU2LjE2MDYgMTU1LjA1N0M1Ni4wMTQ4IDE1NC45MzQgNTUuODg5OCAxNTQuNzc1IDU1Ljc4NTYgMTU0LjU4QzU1LjY4MTUgMTU0LjM4NSA1NS42MDIgMTU0LjE0OCA1NS41NDczIDE1My44NjlDNTUuNDkyNyAxNTMuNTkgNTUuNDY1MyAxNTMuMjY1IDU1LjQ2NTMgMTUyLjg5M1YxNTIuMDI1QzU1LjQ2NTMgMTUxLjU1OSA1NS41MDcgMTUxLjE2OSA1NS41OTAzIDE1MC44NTRDNTUuNjc2MiAxNTAuNTM4IDU1Ljc5NzMgMTUwLjI4NiA1NS45NTM2IDE1MC4wOTZDNTYuMTA5OCAxNDkuOTAzIDU2LjI5NzMgMTQ5Ljc2NSA1Ni41MTYxIDE0OS42ODJDNTYuNzM3NCAxNDkuNTk4IDU2Ljk4NDggMTQ5LjU1NyA1Ny4yNTgzIDE0OS41NTdDNTcuNDc5NiAxNDkuNTU3IDU3LjY4MjggMTQ5LjU4NCA1Ny44Njc3IDE0OS42MzlDNTguMDU1MiAxNDkuNjkxIDU4LjIyMTggMTQ5Ljc3NSA1OC4zNjc3IDE0OS44OTNDNTguNTEzNSAxNTAuMDA3IDU4LjYzNzIgMTUwLjE2MSA1OC43Mzg3IDE1MC4zNTRDNTguODQyOSAxNTAuNTQ0IDU4LjkyMjMgMTUwLjc3NyA1OC45NzcgMTUxLjA1M0M1OS4wMzE3IDE1MS4zMjkgNTkuMDU5MSAxNTEuNjUzIDU5LjA1OTEgMTUyLjAyNVpNNTguMzMyNSAxNTMuMDFWMTUxLjkwNEM1OC4zMzI1IDE1MS42NDkgNTguMzE2OSAxNTEuNDI1IDU4LjI4NTYgMTUxLjIzMkM1OC4yNTcgMTUxLjAzNyA1OC4yMTQgMTUwLjg3IDU4LjE1NjcgMTUwLjczMkM1OC4wOTk0IDE1MC41OTQgNTguMDI2NSAxNTAuNDgyIDU3LjkzOCAxNTAuMzk2QzU3Ljg1MiAxNTAuMzExIDU3Ljc1MTggMTUwLjI0OCA1Ny42MzcyIDE1MC4yMDlDNTcuNTI1MiAxNTAuMTY3IDU3LjM5ODkgMTUwLjE0NiA1Ny4yNTgzIDE1MC4xNDZDNTcuMDg2NCAxNTAuMTQ2IDU2LjkzNDEgMTUwLjE3OSA1Ni44MDEyIDE1MC4yNDRDNTYuNjY4NCAxNTAuMzA3IDU2LjU1NjUgMTUwLjQwNyA1Ni40NjUzIDE1MC41NDVDNTYuMzc2OCAxNTAuNjgzIDU2LjMwOTEgMTUwLjg2NCA1Ni4yNjIyIDE1MS4wODhDNTYuMjE1MyAxNTEuMzEyIDU2LjE5MTkgMTUxLjU4NCA1Ni4xOTE5IDE1MS45MDRWMTUzLjAxQzU2LjE5MTkgMTUzLjI2NSA1Ni4yMDYyIDE1My40OSA1Ni4yMzQ4IDE1My42ODZDNTYuMjY2MSAxNTMuODgxIDU2LjMxMTcgMTU0LjA1IDU2LjM3MTYgMTU0LjE5M0M1Ni40MzE1IDE1NC4zMzQgNTYuNTA0NCAxNTQuNDUgNTYuNTkwMyAxNTQuNTQxQzU2LjY3NjIgMTU0LjYzMiA1Ni43NzUyIDE1NC43IDU2Ljg4NzIgMTU0Ljc0NEM1Ny4wMDE4IDE1NC43ODYgNTcuMTI4MSAxNTQuODA3IDU3LjI2NjEgMTU0LjgwN0M1Ny40NDMyIDE1NC44MDcgNTcuNTk4MSAxNTQuNzczIDU3LjczMDkgMTU0LjcwNUM1Ny44NjM3IDE1NC42MzcgNTcuOTc0NCAxNTQuNTMyIDU4LjA2MyAxNTQuMzg5QzU4LjE1NDEgMTU0LjI0MyA1OC4yMjE4IDE1NC4wNTcgNTguMjY2MSAxNTMuODNDNTguMzEwNCAxNTMuNjAxIDU4LjMzMjUgMTUzLjMyNyA1OC4zMzI1IDE1My4wMVpNNjIuNDQ2NCAxNDkuNjA0VjE1NS4zMjJINjEuNzIzN1YxNTAuNTA2TDYwLjI2NjcgMTUxLjAzN1YxNTAuMzg1TDYyLjMzMzEgMTQ5LjYwNEg2Mi40NDY0Wk02Ny42NjI0IDE0OS42MzVWMTU1LjMyMkg2Ni45MDg1VjE0OS42MzVINjcuNjYyNFpNNzAuMDQ1MiAxNTIuMTkzVjE1Mi44MTFINjcuNDk4M1YxNTIuMTkzSDcwLjA0NTJaTTcwLjQzMTkgMTQ5LjYzNVYxNTAuMjUySDY3LjQ5ODNWMTQ5LjYzNUg3MC40MzE5Wk03Mi45NzE2IDE1NS40QzcyLjY3NzMgMTU1LjQgNzIuNDEwNCAxNTUuMzUxIDcyLjE3MDggMTU1LjI1MkM3MS45MzM4IDE1NS4xNSA3MS43Mjk0IDE1NS4wMDggNzEuNTU3NSAxNTQuODI2QzcxLjM4ODMgMTU0LjY0NCA3MS4yNTgxIDE1NC40MjggNzEuMTY2OSAxNTQuMTc4QzcxLjA3NTggMTUzLjkyOCA3MS4wMzAyIDE1My42NTQgNzEuMDMwMiAxNTMuMzU3VjE1My4xOTNDNzEuMDMwMiAxNTIuODUgNzEuMDgxIDE1Mi41NDQgNzEuMTgyNSAxNTIuMjc1QzcxLjI4NDEgMTUyLjAwNSA3MS40MjIxIDE1MS43NzUgNzEuNTk2NiAxNTEuNTg4QzcxLjc3MTEgMTUxLjQgNzEuOTY5IDE1MS4yNTggNzIuMTkwMyAxNTEuMTYyQzcyLjQxMTcgMTUxLjA2NiA3Mi42NDA5IDE1MS4wMTggNzIuODc3OCAxNTEuMDE4QzczLjE3OTkgMTUxLjAxOCA3My40NDAzIDE1MS4wNyA3My42NTkxIDE1MS4xNzRDNzMuODgwNSAxNTEuMjc4IDc0LjA2MTQgMTUxLjQyNCA3NC4yMDIxIDE1MS42MTFDNzQuMzQyNyAxNTEuNzk2IDc0LjQ0NjkgMTUyLjAxNSA3NC41MTQ2IDE1Mi4yNjhDNzQuNTgyMyAxNTIuNTE4IDc0LjYxNjEgMTUyLjc5MSA3NC42MTYxIDE1My4wODhWMTUzLjQxMkg3MS40NTk5VjE1Mi44MjJINzMuODkzNVYxNTIuNzY4QzczLjg4MzEgMTUyLjU4IDczLjg0NCAxNTIuMzk4IDczLjc3NjMgMTUyLjIyMUM3My43MTEyIDE1Mi4wNDQgNzMuNjA3IDE1MS44OTggNzMuNDYzOCAxNTEuNzgzQzczLjMyMDYgMTUxLjY2OSA3My4xMjUyIDE1MS42MTEgNzIuODc3OCAxNTEuNjExQzcyLjcxMzggMTUxLjYxMSA3Mi41NjI3IDE1MS42NDYgNzIuNDI0NyAxNTEuNzE3QzcyLjI4NjcgMTUxLjc4NSA3Mi4xNjgyIDE1MS44ODYgNzIuMDY5MyAxNTIuMDIxQzcxLjk3MDMgMTUyLjE1NyA3MS44OTM1IDE1Mi4zMjIgNzEuODM4OCAxNTIuNTE4QzcxLjc4NDEgMTUyLjcxMyA3MS43NTY4IDE1Mi45MzggNzEuNzU2OCAxNTMuMTkzVjE1My4zNTdDNzEuNzU2OCAxNTMuNTU4IDcxLjc4NDEgMTUzLjc0NyA3MS44Mzg4IDE1My45MjRDNzEuODk2MSAxNTQuMDk4IDcxLjk3ODEgMTU0LjI1MiA3Mi4wODQ5IDE1NC4zODVDNzIuMTk0MyAxNTQuNTE4IDcyLjMyNTggMTU0LjYyMiA3Mi40Nzk0IDE1NC42OTdDNzIuNjM1NyAxNTQuNzczIDcyLjgxMjcgMTU0LjgxMSA3My4wMTA3IDE1NC44MTFDNzMuMjY1OSAxNTQuODExIDczLjQ4MiAxNTQuNzU4IDczLjY1OTEgMTU0LjY1NEM3My44MzYyIDE1NC41NSA3My45OTExIDE1NC40MTEgNzQuMTIzOSAxNTQuMjM2TDc0LjU2MTQgMTU0LjU4NEM3NC40NzAzIDE1NC43MjIgNzQuMzU0NCAxNTQuODU0IDc0LjIxMzggMTU0Ljk3OUM3NC4wNzMyIDE1NS4xMDQgNzMuOSAxNTUuMjA1IDczLjY5NDMgMTU1LjI4M0M3My40OTExIDE1NS4zNjEgNzMuMjUwMiAxNTUuNCA3Mi45NzE2IDE1NS40Wk03NS41Mzg2IDE0OS4zMjJINzYuMjY1MlYxNTQuNTAyTDc2LjIwMjcgMTU1LjMyMkg3NS41Mzg2VjE0OS4zMjJaTTc5LjEyMDYgMTUzLjE3NFYxNTMuMjU2Qzc5LjEyMDYgMTUzLjU2MyA3OS4wODQyIDE1My44NDggNzkuMDExMyAxNTQuMTExQzc4LjkzODMgMTU0LjM3MiA3OC44MzE2IDE1NC41OTggNzguNjkwOSAxNTQuNzkxQzc4LjU1MDMgMTU0Ljk4NCA3OC4zNzg0IDE1NS4xMzMgNzguMTc1MyAxNTUuMjRDNzcuOTcyMiAxNTUuMzQ3IDc3LjczOTEgMTU1LjQgNzcuNDc2MSAxNTUuNEM3Ny4yMDc5IDE1NS40IDc2Ljk3MjIgMTU1LjM1NSA3Ni43NjkxIDE1NS4yNjRDNzYuNTY4NSAxNTUuMTcgNzYuMzk5MyAxNTUuMDM2IDc2LjI2MTMgMTU0Ljg2MUM3Ni4xMjMyIDE1NC42ODcgNzYuMDEyNiAxNTQuNDc2IDc1LjkyOTIgMTU0LjIyOUM3NS44NDg1IDE1My45ODEgNzUuNzkyNSAxNTMuNzAyIDc1Ljc2MTMgMTUzLjM5M1YxNTMuMDMzQzc1Ljc5MjUgMTUyLjcyMSA3NS44NDg1IDE1Mi40NDEgNzUuOTI5MiAxNTIuMTkzQzc2LjAxMjYgMTUxLjk0NiA3Ni4xMjMyIDE1MS43MzUgNzYuMjYxMyAxNTEuNTYxQzc2LjM5OTMgMTUxLjM4MyA3Ni41Njg1IDE1MS4yNDkgNzYuNzY5MSAxNTEuMTU4Qzc2Ljk2OTYgMTUxLjA2NCA3Ny4yMDI3IDE1MS4wMTggNzcuNDY4MyAxNTEuMDE4Qzc3LjczMzkgMTUxLjAxOCA3Ny45Njk2IDE1MS4wNyA3OC4xNzUzIDE1MS4xNzRDNzguMzgxIDE1MS4yNzUgNzguNTUyOSAxNTEuNDIxIDc4LjY5MDkgMTUxLjYxMUM3OC44MzE2IDE1MS44MDEgNzguOTM4MyAxNTIuMDI5IDc5LjAxMTMgMTUyLjI5NUM3OS4wODQyIDE1Mi41NTggNzkuMTIwNiAxNTIuODUxIDc5LjEyMDYgMTUzLjE3NFpNNzguMzk0MSAxNTMuMjU2VjE1My4xNzRDNzguMzk0MSAxNTIuOTYzIDc4LjM3NDUgMTUyLjc2NSA3OC4zMzU1IDE1Mi41OEM3OC4yOTY0IDE1Mi4zOTMgNzguMjMzOSAxNTIuMjI5IDc4LjE0OCAxNTIuMDg4Qzc4LjA2MiAxNTEuOTQ1IDc3Ljk0ODggMTUxLjgzMyA3Ny44MDgxIDE1MS43NTJDNzcuNjY3NSAxNTEuNjY5IDc3LjQ5NDMgMTUxLjYyNyA3Ny4yODg2IDE1MS42MjdDNzcuMTA2MyAxNTEuNjI3IDc2Ljk0NzUgMTUxLjY1OCA3Ni44MTIgMTUxLjcyMUM3Ni42NzkyIDE1MS43ODMgNzYuNTY1OSAxNTEuODY4IDc2LjQ3MjIgMTUxLjk3NUM3Ni4zNzg0IDE1Mi4wNzkgNzYuMzAxNiAxNTIuMTk5IDc2LjI0MTcgMTUyLjMzNEM3Ni4xODQ0IDE1Mi40NjcgNzYuMTQxNSAxNTIuNjA1IDc2LjExMjggMTUyLjc0OFYxNTMuNjg5Qzc2LjE1NDUgMTUzLjg3MiA3Ni4yMjIyIDE1NC4wNDggNzYuMzE1OSAxNTQuMjE3Qzc2LjQxMjMgMTU0LjM4MyA3Ni41Mzk5IDE1NC41MiA3Ni42OTg4IDE1NC42MjdDNzYuODYwMiAxNTQuNzM0IDc3LjA1OTQgMTU0Ljc4NyA3Ny4yOTY0IDE1NC43ODdDNzcuNDkxNyAxNTQuNzg3IDc3LjY1ODQgMTU0Ljc0OCA3Ny43OTY0IDE1NC42N0M3Ny45MzcgMTU0LjU4OSA3OC4wNTAzIDE1NC40NzkgNzguMTM2MyAxNTQuMzM4Qzc4LjIyNDggMTU0LjE5NyA3OC4yODk5IDE1NC4wMzUgNzguMzMxNiAxNTMuODVDNzguMzczMiAxNTMuNjY1IDc4LjM5NDEgMTUzLjQ2NyA3OC4zOTQxIDE1My4yNTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMV80MTgzXzkwODc3KSI+CjxsaW5lIHgxPSIxNTUuNSIgeTE9IjE0Ny4wNzIiIHgyPSIxNTUuNSIgeTI9IjE0Ni4yNSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuNSIgc3Ryb2tlLXdpZHRoPSIwLjUiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiLz4KPHBhdGggZD0iTTE0Ny41NTkgMTUyLjAyNVYxNTIuODkyQzE0Ny41NTkgMTUzLjM1OCAxNDcuNTE3IDE1My43NTIgMTQ3LjQzNCAxNTQuMDcyQzE0Ny4zNTEgMTU0LjM5MiAxNDcuMjMxIDE1NC42NSAxNDcuMDc1IDE1NC44NDVDMTQ2LjkxOCAxNTUuMDQxIDE0Ni43MyAxNTUuMTgzIDE0Ni41MDggMTU1LjI3MUMxNDYuMjkgMTU1LjM1NyAxNDYuMDQyIDE1NS40IDE0NS43NjYgMTU1LjRDMTQ1LjU0NyAxNTUuNCAxNDUuMzQ2IDE1NS4zNzMgMTQ1LjE2MSAxNTUuMzE4QzE0NC45NzYgMTU1LjI2MyAxNDQuODA5IDE1NS4xNzYgMTQ0LjY2MSAxNTUuMDU2QzE0NC41MTUgMTU0LjkzNCAxNDQuMzkgMTU0Ljc3NSAxNDQuMjg2IDE1NC41OEMxNDQuMTgxIDE1NC4zODUgMTQ0LjEwMiAxNTQuMTQ4IDE0NC4wNDcgMTUzLjg2OUMxNDMuOTkzIDE1My41OSAxNDMuOTY1IDE1My4yNjUgMTQzLjk2NSAxNTIuODkyVjE1Mi4wMjVDMTQzLjk2NSAxNTEuNTU5IDE0NC4wMDcgMTUxLjE2OCAxNDQuMDkgMTUwLjg1M0MxNDQuMTc2IDE1MC41MzggMTQ0LjI5NyAxNTAuMjg2IDE0NC40NTQgMTUwLjA5NUMxNDQuNjEgMTQ5LjkwMyAxNDQuNzk3IDE0OS43NjUgMTQ1LjAxNiAxNDkuNjgxQzE0NS4yMzcgMTQ5LjU5OCAxNDUuNDg1IDE0OS41NTYgMTQ1Ljc1OCAxNDkuNTU2QzE0NS45OCAxNDkuNTU2IDE0Ni4xODMgMTQ5LjU4NCAxNDYuMzY4IDE0OS42MzhDMTQ2LjU1NSAxNDkuNjkxIDE0Ni43MjIgMTQ5Ljc3NSAxNDYuODY4IDE0OS44OTJDMTQ3LjAxMyAxNTAuMDA3IDE0Ny4xMzcgMTUwLjE2MSAxNDcuMjM5IDE1MC4zNTNDMTQ3LjM0MyAxNTAuNTQzIDE0Ny40MjIgMTUwLjc3NiAxNDcuNDc3IDE1MS4wNTJDMTQ3LjUzMiAxNTEuMzI5IDE0Ny41NTkgMTUxLjY1MyAxNDcuNTU5IDE1Mi4wMjVaTTE0Ni44MzIgMTUzLjAxVjE1MS45MDRDMTQ2LjgzMiAxNTEuNjQ5IDE0Ni44MTcgMTUxLjQyNSAxNDYuNzg2IDE1MS4yMzJDMTQ2Ljc1NyAxNTEuMDM3IDE0Ni43MTQgMTUwLjg3IDE0Ni42NTcgMTUwLjczMkMxNDYuNTk5IDE1MC41OTQgMTQ2LjUyNyAxNTAuNDgyIDE0Ni40MzggMTUwLjM5NkMxNDYuMzUyIDE1MC4zMSAxNDYuMjUyIDE1MC4yNDggMTQ2LjEzNyAxNTAuMjA5QzE0Ni4wMjUgMTUwLjE2NyAxNDUuODk5IDE1MC4xNDYgMTQ1Ljc1OCAxNTAuMTQ2QzE0NS41ODYgMTUwLjE0NiAxNDUuNDM0IDE1MC4xNzkgMTQ1LjMwMSAxNTAuMjQ0QzE0NS4xNjggMTUwLjMwNiAxNDUuMDU2IDE1MC40MDcgMTQ0Ljk2NSAxNTAuNTQ1QzE0NC44NzcgMTUwLjY4MyAxNDQuODA5IDE1MC44NjQgMTQ0Ljc2MiAxNTEuMDg4QzE0NC43MTUgMTUxLjMxMiAxNDQuNjkyIDE1MS41ODQgMTQ0LjY5MiAxNTEuOTA0VjE1My4wMUMxNDQuNjkyIDE1My4yNjUgMTQ0LjcwNiAxNTMuNDkgMTQ0LjczNSAxNTMuNjg1QzE0NC43NjYgMTUzLjg4MSAxNDQuODEyIDE1NC4wNSAxNDQuODcyIDE1NC4xOTNDMTQ0LjkzMSAxNTQuMzM0IDE0NS4wMDQgMTU0LjQ1IDE0NS4wOSAxNTQuNTQxQzE0NS4xNzYgMTU0LjYzMiAxNDUuMjc1IDE1NC43IDE0NS4zODcgMTU0Ljc0NEMxNDUuNTAyIDE1NC43ODYgMTQ1LjYyOCAxNTQuODA2IDE0NS43NjYgMTU0LjgwNkMxNDUuOTQzIDE1NC44MDYgMTQ2LjA5OCAxNTQuNzczIDE0Ni4yMzEgMTU0LjcwNUMxNDYuMzY0IDE1NC42MzcgMTQ2LjQ3NCAxNTQuNTMyIDE0Ni41NjMgMTU0LjM4OEMxNDYuNjU0IDE1NC4yNDMgMTQ2LjcyMiAxNTQuMDU2IDE0Ni43NjYgMTUzLjgzQzE0Ni44MSAxNTMuNjAxIDE0Ni44MzIgMTUzLjMyNyAxNDYuODMyIDE1My4wMVpNMTUyLjI5OCAxNTQuNzI4VjE1NS4zMjJIMTQ4LjU3NVYxNTQuODAyTDE1MC40MzkgMTUyLjcyOEMxNTAuNjY4IDE1Mi40NzMgMTUwLjg0NSAxNTIuMjU3IDE1MC45NyAxNTIuMDhDMTUxLjA5NyAxNTEuOSAxNTEuMTg2IDE1MS43NCAxNTEuMjM1IDE1MS41OTlDMTUxLjI4OCAxNTEuNDU2IDE1MS4zMTQgMTUxLjMxIDE1MS4zMTQgMTUxLjE2MkMxNTEuMzE0IDE1MC45NzQgMTUxLjI3NCAxNTAuODA1IDE1MS4xOTYgMTUwLjY1NEMxNTEuMTIxIDE1MC41IDE1MS4wMDkgMTUwLjM3OCAxNTAuODYgMTUwLjI4N0MxNTAuNzEyIDE1MC4xOTYgMTUwLjUzMiAxNTAuMTUgMTUwLjMyMSAxNTAuMTVDMTUwLjA2OSAxNTAuMTUgMTQ5Ljg1OCAxNTAuMiAxNDkuNjg5IDE1MC4yOTlDMTQ5LjUyMiAxNTAuMzk1IDE0OS4zOTcgMTUwLjUzIDE0OS4zMTQgMTUwLjcwNUMxNDkuMjMgMTUwLjg3OSAxNDkuMTg5IDE1MS4wOCAxNDkuMTg5IDE1MS4zMDZIMTQ4LjQ2NkMxNDguNDY2IDE1MC45ODYgMTQ4LjUzNiAxNTAuNjkzIDE0OC42NzcgMTUwLjQyN0MxNDguODE3IDE1MC4xNjIgMTQ5LjAyNiAxNDkuOTUxIDE0OS4zMDIgMTQ5Ljc5NUMxNDkuNTc4IDE0OS42MzYgMTQ5LjkxOCAxNDkuNTU2IDE1MC4zMjEgMTQ5LjU1NkMxNTAuNjgxIDE0OS41NTYgMTUwLjk4OCAxNDkuNjIgMTUxLjI0MyAxNDkuNzQ4QzE1MS40OTggMTQ5Ljg3MyAxNTEuNjk0IDE1MC4wNSAxNTEuODI5IDE1MC4yNzlDMTUxLjk2NyAxNTAuNTA2IDE1Mi4wMzYgMTUwLjc3MSAxNTIuMDM2IDE1MS4wNzZDMTUyLjAzNiAxNTEuMjQzIDE1Mi4wMDggMTUxLjQxMiAxNTEuOTUgMTUxLjU4NEMxNTEuODk2IDE1MS43NTMgMTUxLjgxOSAxNTEuOTIyIDE1MS43MiAxNTIuMDkyQzE1MS42MjMgMTUyLjI2MSAxNTEuNTEgMTUyLjQyNyAxNTEuMzggMTUyLjU5MkMxNTEuMjUyIDE1Mi43NTYgMTUxLjExNiAxNTIuOTE3IDE1MC45NyAxNTMuMDc2TDE0OS40NDYgMTU0LjcyOEgxNTIuMjk4Wk0xNTYuMTYyIDE0OS42MzVWMTU1LjMyMkgxNTUuNDA5VjE0OS42MzVIMTU2LjE2MlpNMTU4LjU0NSAxNTIuMTkzVjE1Mi44MUgxNTUuOTk4VjE1Mi4xOTNIMTU4LjU0NVpNMTU4LjkzMiAxNDkuNjM1VjE1MC4yNTJIMTU1Ljk5OFYxNDkuNjM1SDE1OC45MzJaTTE2MS40NzIgMTU1LjRDMTYxLjE3NyAxNTUuNCAxNjAuOTEgMTU1LjM1MSAxNjAuNjcxIDE1NS4yNTJDMTYwLjQzNCAxNTUuMTUgMTYwLjIyOSAxNTUuMDA4IDE2MC4wNTggMTU0LjgyNkMxNTkuODg4IDE1NC42NDQgMTU5Ljc1OCAxNTQuNDI3IDE1OS42NjcgMTU0LjE3N0MxNTkuNTc2IDE1My45MjcgMTU5LjUzIDE1My42NTQgMTU5LjUzIDE1My4zNTdWMTUzLjE5M0MxNTkuNTMgMTUyLjg0OSAxNTkuNTgxIDE1Mi41NDMgMTU5LjY4MyAxNTIuMjc1QzE1OS43ODQgMTUyLjAwNCAxNTkuOTIyIDE1MS43NzUgMTYwLjA5NyAxNTEuNTg4QzE2MC4yNzEgMTUxLjQgMTYwLjQ2OSAxNTEuMjU4IDE2MC42OSAxNTEuMTYyQzE2MC45MTIgMTUxLjA2NiAxNjEuMTQxIDE1MS4wMTcgMTYxLjM3OCAxNTEuMDE3QzE2MS42OCAxNTEuMDE3IDE2MS45NCAxNTEuMDY5IDE2Mi4xNTkgMTUxLjE3NEMxNjIuMzggMTUxLjI3OCAxNjIuNTYxIDE1MS40MjQgMTYyLjcwMiAxNTEuNjExQzE2Mi44NDMgMTUxLjc5NiAxNjIuOTQ3IDE1Mi4wMTUgMTYzLjAxNSAxNTIuMjY3QzE2My4wODIgMTUyLjUxNyAxNjMuMTE2IDE1Mi43OTEgMTYzLjExNiAxNTMuMDg4VjE1My40MTJIMTU5Ljk2VjE1Mi44MjJIMTYyLjM5M1YxNTIuNzY3QzE2Mi4zODMgMTUyLjU4IDE2Mi4zNDQgMTUyLjM5OCAxNjIuMjc2IDE1Mi4yMkMxNjIuMjExIDE1Mi4wNDMgMTYyLjEwNyAxNTEuODk4IDE2MS45NjQgMTUxLjc4M0MxNjEuODIxIDE1MS42NjggMTYxLjYyNSAxNTEuNjExIDE2MS4zNzggMTUxLjYxMUMxNjEuMjE0IDE1MS42MTEgMTYxLjA2MyAxNTEuNjQ2IDE2MC45MjUgMTUxLjcxN0MxNjAuNzg3IDE1MS43ODQgMTYwLjY2OCAxNTEuODg2IDE2MC41NjkgMTUyLjAyMUMxNjAuNDcgMTUyLjE1NyAxNjAuMzkzIDE1Mi4zMjIgMTYwLjMzOSAxNTIuNTE3QzE2MC4yODQgMTUyLjcxMyAxNjAuMjU3IDE1Mi45MzggMTYwLjI1NyAxNTMuMTkzVjE1My4zNTdDMTYwLjI1NyAxNTMuNTU4IDE2MC4yODQgMTUzLjc0NyAxNjAuMzM5IDE1My45MjRDMTYwLjM5NiAxNTQuMDk4IDE2MC40NzggMTU0LjI1MiAxNjAuNTg1IDE1NC4zODVDMTYwLjY5NCAxNTQuNTE3IDE2MC44MjYgMTU0LjYyMiAxNjAuOTc5IDE1NC42OTdDMTYxLjEzNiAxNTQuNzczIDE2MS4zMTMgMTU0LjgxIDE2MS41MTEgMTU0LjgxQzE2MS43NjYgMTU0LjgxIDE2MS45ODIgMTU0Ljc1OCAxNjIuMTU5IDE1NC42NTRDMTYyLjMzNiAxNTQuNTUgMTYyLjQ5MSAxNTQuNDExIDE2Mi42MjQgMTU0LjIzNkwxNjMuMDYxIDE1NC41ODRDMTYyLjk3IDE1NC43MjIgMTYyLjg1NCAxNTQuODUzIDE2Mi43MTQgMTU0Ljk3OEMxNjIuNTczIDE1NS4xMDMgMTYyLjQgMTU1LjIwNSAxNjIuMTk0IDE1NS4yODNDMTYxLjk5MSAxNTUuMzYxIDE2MS43NSAxNTUuNCAxNjEuNDcyIDE1NS40Wk0xNjQuMDM5IDE0OS4zMjJIMTY0Ljc2NVYxNTQuNTAyTDE2NC43MDMgMTU1LjMyMkgxNjQuMDM5VjE0OS4zMjJaTTE2Ny42MjEgMTUzLjE3NFYxNTMuMjU2QzE2Ny42MjEgMTUzLjU2MyAxNjcuNTg0IDE1My44NDggMTY3LjUxMSAxNTQuMTExQzE2Ny40MzggMTU0LjM3MiAxNjcuMzMyIDE1NC41OTggMTY3LjE5MSAxNTQuNzkxQzE2Ny4wNSAxNTQuOTgzIDE2Ni44NzggMTU1LjEzMyAxNjYuNjc1IDE1NS4yNEMxNjYuNDcyIDE1NS4zNDcgMTY2LjIzOSAxNTUuNCAxNjUuOTc2IDE1NS40QzE2NS43MDggMTU1LjQgMTY1LjQ3MiAxNTUuMzU1IDE2NS4yNjkgMTU1LjI2M0MxNjUuMDY5IDE1NS4xNyAxNjQuODk5IDE1NS4wMzYgMTY0Ljc2MSAxNTQuODYxQzE2NC42MjMgMTU0LjY4NyAxNjQuNTEzIDE1NC40NzYgMTY0LjQyOSAxNTQuMjI4QzE2NC4zNDggMTUzLjk4MSAxNjQuMjkzIDE1My43MDIgMTY0LjI2MSAxNTMuMzkyVjE1My4wMzNDMTY0LjI5MyAxNTIuNzIgMTY0LjM0OCAxNTIuNDQxIDE2NC40MjkgMTUyLjE5M0MxNjQuNTEzIDE1MS45NDYgMTY0LjYyMyAxNTEuNzM1IDE2NC43NjEgMTUxLjU2QzE2NC44OTkgMTUxLjM4MyAxNjUuMDY5IDE1MS4yNDkgMTY1LjI2OSAxNTEuMTU4QzE2NS40NyAxNTEuMDY0IDE2NS43MDMgMTUxLjAxNyAxNjUuOTY4IDE1MS4wMTdDMTY2LjIzNCAxNTEuMDE3IDE2Ni40NyAxNTEuMDY5IDE2Ni42NzUgMTUxLjE3NEMxNjYuODgxIDE1MS4yNzUgMTY3LjA1MyAxNTEuNDIxIDE2Ny4xOTEgMTUxLjYxMUMxNjcuMzMyIDE1MS44MDEgMTY3LjQzOCAxNTIuMDI5IDE2Ny41MTEgMTUyLjI5NUMxNjcuNTg0IDE1Mi41NTggMTY3LjYyMSAxNTIuODUxIDE2Ny42MjEgMTUzLjE3NFpNMTY2Ljg5NCAxNTMuMjU2VjE1My4xNzRDMTY2Ljg5NCAxNTIuOTYzIDE2Ni44NzUgMTUyLjc2NSAxNjYuODM1IDE1Mi41OEMxNjYuNzk2IDE1Mi4zOTIgMTY2LjczNCAxNTIuMjI4IDE2Ni42NDggMTUyLjA4OEMxNjYuNTYyIDE1MS45NDQgMTY2LjQ0OSAxNTEuODMyIDE2Ni4zMDggMTUxLjc1MkMxNjYuMTY4IDE1MS42NjggMTY1Ljk5NCAxNTEuNjI3IDE2NS43ODkgMTUxLjYyN0MxNjUuNjA2IDE1MS42MjcgMTY1LjQ0NyAxNTEuNjU4IDE2NS4zMTIgMTUxLjcyQzE2NS4xNzkgMTUxLjc4MyAxNjUuMDY2IDE1MS44NjggMTY0Ljk3MiAxNTEuOTc0QzE2NC44NzggMTUyLjA3OSAxNjQuODAyIDE1Mi4xOTggMTY0Ljc0MiAxNTIuMzM0QzE2NC42ODQgMTUyLjQ2NyAxNjQuNjQxIDE1Mi42MDUgMTY0LjYxMyAxNTIuNzQ4VjE1My42ODlDMTY0LjY1NCAxNTMuODcyIDE2NC43MjIgMTU0LjA0NyAxNjQuODE2IDE1NC4yMTdDMTY0LjkxMiAxNTQuMzgzIDE2NS4wNCAxNTQuNTIgMTY1LjE5OSAxNTQuNjI3QzE2NS4zNiAxNTQuNzMzIDE2NS41NTkgMTU0Ljc4NyAxNjUuNzk2IDE1NC43ODdDMTY1Ljk5MiAxNTQuNzg3IDE2Ni4xNTggMTU0Ljc0OCAxNjYuMjk2IDE1NC42N0MxNjYuNDM3IDE1NC41ODkgMTY2LjU1IDE1NC40NzggMTY2LjYzNiAxNTQuMzM4QzE2Ni43MjUgMTU0LjE5NyAxNjYuNzkgMTU0LjAzNCAxNjYuODMyIDE1My44NDlDMTY2Ljg3MyAxNTMuNjY0IDE2Ni44OTQgMTUzLjQ2NyAxNjYuODk0IDE1My4yNTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjwvZz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF80MTgzXzkwODc3Ij4KPHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIxNjAiIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjxjbGlwUGF0aCBpZD0iY2xpcDFfNDE4M185MDg3NyI+CjxyZWN0IHdpZHRoPSI4OC41IiBoZWlnaHQ9IjEwLjMyMiIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDExMS41IDE0NikiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K", + "description": "Displays changes to time-series data over time—for example, temperature or humidity readings.", + "descriptor": { + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n chartType: 'bar',\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings('bar'),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n", + "settingsSchema": "{}", + "dataKeySettingsSchema": "{}", + "latestDataKeySettingsSchema": "{}", + "settingsDirective": "tb-time-series-chart-widget-settings", + "dataKeySettingsDirective": "tb-time-series-chart-key-settings", + "latestDataKeySettingsDirective": "", + "hasBasicMode": true, + "basicModeDirective": "tb-time-series-chart-basic-config", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":\"dd MMM yyyy HH:mm:ss\",\"lastUpdateAgo\":false,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Bar chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + }, + "tags": [ + "chart", + "time series", + "time-series", + "bar", + "bar chart" + ] +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/line_chart.json b/application/src/main/data/json/system/widget_types/line_chart.json new file mode 100644 index 0000000000..5fff201c28 --- /dev/null +++ b/application/src/main/data/json/system/widget_types/line_chart.json @@ -0,0 +1,32 @@ +{ + "fqn": "line_chart", + "name": "Line chart", + "deprecated": false, + "image": "tb-image:Y2hhcnRfKDEpLnN2Zw==:IkxpbmUgY2hhcnQiIHN5c3RlbSB3aWRnZXQgaW1hZ2U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF80MTg0Xzk0NTI3KSI+CjxwYXRoIGQ9Ik0yLjg0NzY2IDEuMjgxMjVWN0gyLjEyNVYyLjE4MzU5TDAuNjY3OTY5IDIuNzE0ODRWMi4wNjI1TDIuNzM0MzggMS4yODEyNUgyLjg0NzY2Wk04LjY3NTE3IDMuNzAzMTJWNC41NzAzMUM4LjY3NTE3IDUuMDM2NDYgOC42MzM1MSA1LjQyOTY5IDguNTUwMTcgNS43NUM4LjQ2Njg0IDYuMDcwMzEgOC4zNDcwNSA2LjMyODEyIDguMTkwOCA2LjUyMzQ0QzguMDM0NTUgNi43MTg3NSA3Ljg0NTc1IDYuODYwNjggNy42MjQzOSA2Ljk0OTIyQzcuNDA1NjQgNy4wMzUxNiA3LjE1ODI1IDcuMDc4MTIgNi44ODIyIDcuMDc4MTJDNi42NjM0NSA3LjA3ODEyIDYuNDYxNjMgNy4wNTA3OCA2LjI3NjczIDYuOTk2MDlDNi4wOTE4NCA2Ljk0MTQxIDUuOTI1MTcgNi44NTQxNyA1Ljc3NjczIDYuNzM0MzhDNS42MzA5IDYuNjExOTggNS41MDU5IDYuNDUzMTIgNS40MDE3MyA2LjI1NzgxQzUuMjk3NTcgNi4wNjI1IDUuMjE4MTQgNS44MjU1MiA1LjE2MzQ1IDUuNTQ2ODhDNS4xMDg3NyA1LjI2ODIzIDUuMDgxNDIgNC45NDI3MSA1LjA4MTQyIDQuNTcwMzFWMy43MDMxMkM1LjA4MTQyIDMuMjM2OTggNS4xMjMwOSAyLjg0NjM1IDUuMjA2NDIgMi41MzEyNUM1LjI5MjM2IDIuMjE2MTUgNS40MTM0NSAxLjk2MzU0IDUuNTY5NyAxLjc3MzQ0QzUuNzI1OTUgMS41ODA3MyA1LjkxMzQ1IDEuNDQyNzEgNi4xMzIyIDEuMzU5MzhDNi4zNTM1NiAxLjI3NjA0IDYuNjAwOTUgMS4yMzQzOCA2Ljg3NDM5IDEuMjM0MzhDNy4wOTU3NSAxLjIzNDM4IDcuMjk4ODcgMS4yNjE3MiA3LjQ4Mzc3IDEuMzE2NDFDNy42NzEyNyAxLjM2ODQ5IDcuODM3OTMgMS40NTMxMiA3Ljk4Mzc3IDEuNTcwMzFDOC4xMjk2IDEuNjg0OSA4LjI1MzMgMS44Mzg1NCA4LjM1NDg2IDIuMDMxMjVDOC40NTkwMyAyLjIyMTM1IDguNTM4NDUgMi40NTQ0MyA4LjU5MzE0IDIuNzMwNDdDOC42NDc4MyAzLjAwNjUxIDguNjc1MTcgMy4zMzA3MyA4LjY3NTE3IDMuNzAzMTJaTTcuOTQ4NjEgNC42ODc1VjMuNTgyMDNDNy45NDg2MSAzLjMyNjgyIDcuOTMyOTggMy4xMDI4NiA3LjkwMTczIDIuOTEwMTZDNy44NzMwOSAyLjcxNDg0IDcuODMwMTIgMi41NDgxOCA3Ljc3MjgzIDIuNDEwMTZDNy43MTU1NCAyLjI3MjE0IDcuNjQyNjIgMi4xNjAxNiA3LjU1NDA4IDIuMDc0MjJDNy40NjgxNCAxLjk4ODI4IDcuMzY3ODggMS45MjU3OCA3LjI1MzMgMS44ODY3MkM3LjE0MTMyIDEuODQ1MDUgNy4wMTUwMiAxLjgyNDIyIDYuODc0MzkgMS44MjQyMkM2LjcwMjUyIDEuODI0MjIgNi41NTAxNyAxLjg1Njc3IDYuNDE3MzYgMS45MjE4OEM2LjI4NDU1IDEuOTg0MzggNi4xNzI1NyAyLjA4NDY0IDYuMDgxNDIgMi4yMjI2NkM1Ljk5Mjg4IDIuMzYwNjggNS45MjUxNyAyLjU0MTY3IDUuODc4MyAyLjc2NTYyQzUuODMxNDIgMi45ODk1OCA1LjgwNzk4IDMuMjYxNzIgNS44MDc5OCAzLjU4MjAzVjQuNjg3NUM1LjgwNzk4IDQuOTQyNzEgNS44MjIzMSA1LjE2Nzk3IDUuODUwOTUgNS4zNjMyOEM1Ljg4MjIgNS41NTg1OSA1LjkyNzc4IDUuNzI3ODYgNS45ODc2NyA1Ljg3MTA5QzYuMDQ3NTcgNi4wMTE3MiA2LjEyMDQ4IDYuMTI3NiA2LjIwNjQyIDYuMjE4NzVDNi4yOTIzNiA2LjMwOTkgNi4zOTEzMiA2LjM3NzYgNi41MDMzIDYuNDIxODhDNi42MTc4OCA2LjQ2MzU0IDYuNzQ0MTggNi40ODQzOCA2Ljg4MjIgNi40ODQzOEM3LjA1OTI5IDYuNDg0MzggNy4yMTQyMyA2LjQ1MDUyIDcuMzQ3MDUgNi4zODI4MUM3LjQ3OTg2IDYuMzE1MSA3LjU5MDU0IDYuMjA5NjQgNy42NzkwOCA2LjA2NjQxQzcuNzcwMjIgNS45MjA1NyA3LjgzNzkzIDUuNzM0MzggNy44ODIyIDUuNTA3ODFDNy45MjY0NyA1LjI3ODY1IDcuOTQ4NjEgNS4wMDUyMSA3Ljk0ODYxIDQuNjg3NVpNMTMuMzA3NCAzLjcwMzEyVjQuNTcwMzFDMTMuMzA3NCA1LjAzNjQ2IDEzLjI2NTcgNS40Mjk2OSAxMy4xODI0IDUuNzVDMTMuMDk5IDYuMDcwMzEgMTIuOTc5MyA2LjMyODEyIDEyLjgyMyA2LjUyMzQ0QzEyLjY2NjggNi43MTg3NSAxMi40Nzc5IDYuODYwNjggMTIuMjU2NiA2Ljk0OTIyQzEyLjAzNzggNy4wMzUxNiAxMS43OTA0IDcuMDc4MTIgMTEuNTE0NCA3LjA3ODEyQzExLjI5NTcgNy4wNzgxMiAxMS4wOTM4IDcuMDUwNzggMTAuOTA4OSA2Ljk5NjA5QzEwLjcyNCA2Ljk0MTQxIDEwLjU1NzQgNi44NTQxNyAxMC40MDg5IDYuNzM0MzhDMTAuMjYzMSA2LjYxMTk4IDEwLjEzODEgNi40NTMxMiAxMC4wMzM5IDYuMjU3ODFDOS45Mjk3NyA2LjA2MjUgOS44NTAzNCA1LjgyNTUyIDkuNzk1NjYgNS41NDY4OEM5Ljc0MDk3IDUuMjY4MjMgOS43MTM2MyA0Ljk0MjcxIDkuNzEzNjMgNC41NzAzMVYzLjcwMzEyQzkuNzEzNjMgMy4yMzY5OCA5Ljc1NTI5IDIuODQ2MzUgOS44Mzg2MyAyLjUzMTI1QzkuOTI0NTYgMi4yMTYxNSAxMC4wNDU3IDEuOTYzNTQgMTAuMjAxOSAxLjc3MzQ0QzEwLjM1ODIgMS41ODA3MyAxMC41NDU3IDEuNDQyNzEgMTAuNzY0NCAxLjM1OTM4QzEwLjk4NTggMS4yNzYwNCAxMS4yMzMyIDEuMjM0MzggMTEuNTA2NiAxLjIzNDM4QzExLjcyNzkgMS4yMzQzOCAxMS45MzExIDEuMjYxNzIgMTIuMTE2IDEuMzE2NDFDMTIuMzAzNSAxLjM2ODQ5IDEyLjQ3MDEgMS40NTMxMiAxMi42MTYgMS41NzAzMUMxMi43NjE4IDEuNjg0OSAxMi44ODU1IDEuODM4NTQgMTIuOTg3MSAyLjAzMTI1QzEzLjA5MTIgMi4yMjEzNSAxMy4xNzA3IDIuNDU0NDMgMTMuMjI1MyAyLjczMDQ3QzEzLjI4IDMuMDA2NTEgMTMuMzA3NCAzLjMzMDczIDEzLjMwNzQgMy43MDMxMlpNMTIuNTgwOCA0LjY4NzVWMy41ODIwM0MxMi41ODA4IDMuMzI2ODIgMTIuNTY1MiAzLjEwMjg2IDEyLjUzMzkgMi45MTAxNkMxMi41MDUzIDIuNzE0ODQgMTIuNDYyMyAyLjU0ODE4IDEyLjQwNSAyLjQxMDE2QzEyLjM0NzcgMi4yNzIxNCAxMi4yNzQ4IDIuMTYwMTYgMTIuMTg2MyAyLjA3NDIyQzEyLjEwMDMgMS45ODgyOCAxMi4wMDAxIDEuOTI1NzggMTEuODg1NSAxLjg4NjcyQzExLjc3MzUgMS44NDUwNSAxMS42NDcyIDEuODI0MjIgMTEuNTA2NiAxLjgyNDIyQzExLjMzNDcgMS44MjQyMiAxMS4xODI0IDEuODU2NzcgMTEuMDQ5NiAxLjkyMTg4QzEwLjkxNjggMS45ODQzOCAxMC44MDQ4IDIuMDg0NjQgMTAuNzEzNiAyLjIyMjY2QzEwLjYyNTEgMi4zNjA2OCAxMC41NTc0IDIuNTQxNjcgMTAuNTEwNSAyLjc2NTYyQzEwLjQ2MzYgMi45ODk1OCAxMC40NDAyIDMuMjYxNzIgMTAuNDQwMiAzLjU4MjAzVjQuNjg3NUMxMC40NDAyIDQuOTQyNzEgMTAuNDU0NSA1LjE2Nzk3IDEwLjQ4MzIgNS4zNjMyOEMxMC41MTQ0IDUuNTU4NTkgMTAuNTYgNS43Mjc4NiAxMC42MTk5IDUuODcxMDlDMTAuNjc5OCA2LjAxMTcyIDEwLjc1MjcgNi4xMjc2IDEwLjgzODYgNi4yMTg3NUMxMC45MjQ2IDYuMzA5OSAxMS4wMjM1IDYuMzc3NiAxMS4xMzU1IDYuNDIxODhDMTEuMjUwMSA2LjQ2MzU0IDExLjM3NjQgNi40ODQzOCAxMS41MTQ0IDYuNDg0MzhDMTEuNjkxNSA2LjQ4NDM4IDExLjg0NjQgNi40NTA1MiAxMS45NzkzIDYuMzgyODFDMTIuMTEyMSA2LjMxNTEgMTIuMjIyNyA2LjIwOTY0IDEyLjMxMTMgNi4wNjY0MUMxMi40MDI0IDUuOTIwNTcgMTIuNDcwMSA1LjczNDM4IDEyLjUxNDQgNS41MDc4MUMxMi41NTg3IDUuMjc4NjUgMTIuNTgwOCA1LjAwNTIxIDEyLjU4MDggNC42ODc1Wk0xNC4zMDY4IDIuNzA3MDNWMi40MDYyNUMxNC4zMDY4IDIuMTkwMSAxNC4zNTM2IDEuOTkzNDkgMTQuNDQ3NCAxLjgxNjQxQzE0LjU0MTEgMS42MzkzMiAxNC42NzUzIDEuNDk3NCAxNC44NDk3IDEuMzkwNjJDMTUuMDI0MiAxLjI4Mzg1IDE1LjIzMTIgMS4yMzA0NyAxNS40NzA4IDEuMjMwNDdDMTUuNzE1NiAxLjIzMDQ3IDE1LjkyNCAxLjI4Mzg1IDE2LjA5NTggMS4zOTA2MkMxNi4yNzAzIDEuNDk3NCAxNi40MDQ0IDEuNjM5MzIgMTYuNDk4MiAxLjgxNjQxQzE2LjU5MTkgMS45OTM0OSAxNi42Mzg4IDIuMTkwMSAxNi42Mzg4IDIuNDA2MjVWMi43MDcwM0MxNi42Mzg4IDIuOTE3OTcgMTYuNTkxOSAzLjExMTk4IDE2LjQ5ODIgMy4yODkwNkMxNi40MDcgMy40NjYxNSAxNi4yNzQyIDMuNjA4MDcgMTYuMDk5NyAzLjcxNDg0QzE1LjkyNzkgMy44MjE2MSAxNS43MjA4IDMuODc1IDE1LjQ3ODYgMy44NzVDMTUuMjM2NSAzLjg3NSAxNS4wMjY4IDMuODIxNjEgMTQuODQ5NyAzLjcxNDg0QzE0LjY3NTMgMy42MDgwNyAxNC41NDExIDMuNDY2MTUgMTQuNDQ3NCAzLjI4OTA2QzE0LjM1MzYgMy4xMTE5OCAxNC4zMDY4IDIuOTE3OTcgMTQuMzA2OCAyLjcwNzAzWk0xNC44NDk3IDIuNDA2MjVWMi43MDcwM0MxNC44NDk3IDIuODI2ODIgMTQuODcxOSAyLjk0MDEgMTQuOTE2MSAzLjA0Njg4QzE0Ljk2MyAzLjE1MzY1IDE1LjAzMzMgMy4yNDA4OSAxNS4xMjcxIDMuMzA4NTlDMTUuMjIwOCAzLjM3MzcgMTUuMzM4IDMuNDA2MjUgMTUuNDc4NiAzLjQwNjI1QzE1LjYxOTMgMy40MDYyNSAxNS43MzUyIDMuMzczNyAxNS44MjYzIDMuMzA4NTlDMTUuOTE3NCAzLjI0MDg5IDE1Ljk4NTIgMy4xNTM2NSAxNi4wMjk0IDMuMDQ2ODhDMTYuMDczNyAyLjk0MDEgMTYuMDk1OCAyLjgyNjgyIDE2LjA5NTggMi43MDcwM1YyLjQwNjI1QzE2LjA5NTggMi4yODM4NSAxNi4wNzI0IDIuMTY5MjcgMTYuMDI1NSAyLjA2MjVDMTUuOTgxMiAxLjk1MzEyIDE1LjkxMjIgMS44NjU4OSAxNS44MTg1IDEuODAwNzhDMTUuNzI3MyAxLjczMzA3IDE1LjYxMTUgMS42OTkyMiAxNS40NzA4IDEuNjk5MjJDMTUuMzMyOCAxLjY5OTIyIDE1LjIxNjkgMS43MzMwNyAxNS4xMjMyIDEuODAwNzhDMTUuMDMyIDEuODY1ODkgMTQuOTYzIDEuOTUzMTIgMTQuOTE2MSAyLjA2MjVDMTQuODcxOSAyLjE2OTI3IDE0Ljg0OTcgMi4yODM4NSAxNC44NDk3IDIuNDA2MjVaTTE3LjA3NjMgNS45MTAxNlY1LjYwNTQ3QzE3LjA3NjMgNS4zOTE5MyAxNy4xMjMyIDUuMTk2NjEgMTcuMjE2OSA1LjAxOTUzQzE3LjMxMDcgNC44NDI0NSAxNy40NDQ4IDQuNzAwNTIgMTcuNjE5MyA0LjU5Mzc1QzE3Ljc5MzcgNC40ODY5OCAxOC4wMDA4IDQuNDMzNTkgMTguMjQwNCA0LjQzMzU5QzE4LjQ4NTIgNC40MzM1OSAxOC42OTM1IDQuNDg2OTggMTguODY1NCA0LjU5Mzc1QzE5LjAzOTggNC43MDA1MiAxOS4xNzQgNC44NDI0NSAxOS4yNjc3IDUuMDE5NTNDMTkuMzYxNSA1LjE5NjYxIDE5LjQwODMgNS4zOTE5MyAxOS40MDgzIDUuNjA1NDdWNS45MTAxNkMxOS40MDgzIDYuMTIzNyAxOS4zNjE1IDYuMzE5MDEgMTkuMjY3NyA2LjQ5NjA5QzE5LjE3NjYgNi42NzMxOCAxOS4wNDM3IDYuODE1MSAxOC44NjkzIDYuOTIxODhDMTguNjk3NCA3LjAyODY1IDE4LjQ5MDQgNy4wODIwMyAxOC4yNDgyIDcuMDgyMDNDMTguMDA2IDcuMDgyMDMgMTcuNzk3NyA3LjAyODY1IDE3LjYyMzIgNi45MjE4OEMxNy40NDg3IDYuODE1MSAxNy4zMTMzIDYuNjczMTggMTcuMjE2OSA2LjQ5NjA5QzE3LjEyMzIgNi4zMTkwMSAxNy4wNzYzIDYuMTIzNyAxNy4wNzYzIDUuOTEwMTZaTTE3LjYxOTMgNS42MDU0N1Y1LjkxMDE2QzE3LjYxOTMgNi4wMjk5NSAxNy42NDE0IDYuMTQ0NTMgMTcuNjg1NyA2LjI1MzkxQzE3LjczMjUgNi4zNjA2OCAxNy44MDI5IDYuNDQ3OTIgMTcuODk2NiA2LjUxNTYyQzE3Ljk5MDQgNi41ODA3MyAxOC4xMDc1IDYuNjEzMjggMTguMjQ4MiA2LjYxMzI4QzE4LjM4ODggNi42MTMyOCAxOC41MDQ3IDYuNTgwNzMgMTguNTk1OCA2LjUxNTYyQzE4LjY4OTYgNi40NDc5MiAxOC43NTg2IDYuMzYwNjggMTguODAyOSA2LjI1MzkxQzE4Ljg0NzEgNi4xNDcxNCAxOC44NjkzIDYuMDMyNTUgMTguODY5MyA1LjkxMDE2VjUuNjA1NDdDMTguODY5MyA1LjQ4MzA3IDE4Ljg0NTggNS4zNjg0OSAxOC43OTkgNS4yNjE3MkMxOC43NTQ3IDUuMTU0OTUgMTguNjg1NyA1LjA2OTAxIDE4LjU5MTkgNS4wMDM5MUMxOC41MDA4IDQuOTM2MiAxOC4zODM2IDQuOTAyMzQgMTguMjQwNCA0LjkwMjM0QzE4LjEwMjMgNC45MDIzNCAxNy45ODY1IDQuOTM2MiAxNy44OTI3IDUuMDAzOTFDMTcuODAxNiA1LjA2OTAxIDE3LjczMjUgNS4xNTQ5NSAxNy42ODU3IDUuMjYxNzJDMTcuNjQxNCA1LjM2ODQ5IDE3LjYxOTMgNS40ODMwNyAxNy42MTkzIDUuNjA1NDdaTTE4LjQyIDIuMTIxMDlMMTUuNjQyNyA2LjU2NjQxTDE1LjIzNjUgNi4zMDg1OUwxOC4wMTM4IDEuODYzMjhMMTguNDIgMi4xMjEwOVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTguMDU4NTkgMzMuNjY3N0M4LjA1ODU5IDM0LjAxNDEgNy45Nzc4NiAzNC4zMDgzIDcuODE2NDEgMzQuNTUwNUM3LjY1NzU1IDM0Ljc5MDEgNy40NDE0MSAzNC45NzI0IDcuMTY3OTcgMzUuMDk3NEM2Ljg5NzE0IDM1LjIyMjQgNi41OTExNSAzNS4yODQ5IDYuMjUgMzUuMjg0OUM1LjkwODg1IDM1LjI4NDkgNS42MDE1NiAzNS4yMjI0IDUuMzI4MTIgMzUuMDk3NEM1LjA1NDY5IDM0Ljk3MjQgNC44Mzg1NCAzNC43OTAxIDQuNjc5NjkgMzQuNTUwNUM0LjUyMDgzIDM0LjMwODMgNC40NDE0MSAzNC4wMTQxIDQuNDQxNDEgMzMuNjY3N0M0LjQ0MTQxIDMzLjQ0MTIgNC40ODQzOCAzMy4yMzQxIDQuNTcwMzEgMzMuMDQ2NkM0LjY1ODg1IDMyLjg1NjUgNC43ODI1NSAzMi42OTEyIDQuOTQxNDEgMzIuNTUwNUM1LjEwMjg2IDMyLjQwOTkgNS4yOTI5NyAzMi4zMDE4IDUuNTExNzIgMzIuMjI2M0M1LjczMzA3IDMyLjE0ODIgNS45NzY1NiAzMi4xMDkxIDYuMjQyMTkgMzIuMTA5MUM2LjU5MTE1IDMyLjEwOTEgNi45MDIzNCAzMi4xNzY4IDcuMTc1NzggMzIuMzEyM0M3LjQ0OTIyIDMyLjQ0NTEgNy42NjQwNiAzMi42Mjg3IDcuODIwMzEgMzIuODYzQzcuOTc5MTcgMzMuMDk3NCA4LjA1ODU5IDMzLjM2NTYgOC4wNTg1OSAzMy42Njc3Wk03LjMzMjAzIDMzLjY1MjFDNy4zMzIwMyAzMy40NDEyIDcuMjg2NDYgMzMuMjU1IDcuMTk1MzEgMzMuMDkzNUM3LjEwNDE3IDMyLjkyOTQgNi45NzY1NiAzMi44MDE4IDYuODEyNSAzMi43MTA3QzYuNjQ4NDQgMzIuNjE5NSA2LjQ1ODMzIDMyLjU3NCA2LjI0MjE5IDMyLjU3NEM2LjAyMDgzIDMyLjU3NCA1LjgyOTQzIDMyLjYxOTUgNS42Njc5NyAzMi43MTA3QzUuNTA5MTEgMzIuODAxOCA1LjM4NTQyIDMyLjkyOTQgNS4yOTY4OCAzMy4wOTM1QzUuMjA4MzMgMzMuMjU1IDUuMTY0MDYgMzMuNDQxMiA1LjE2NDA2IDMzLjY1MjFDNS4xNjQwNiAzMy44NzA4IDUuMjA3MDMgMzQuMDU4MyA1LjI5Mjk3IDM0LjIxNDZDNS4zODE1MSAzNC4zNjgyIDUuNTA2NTEgMzQuNDg2NyA1LjY2Nzk3IDM0LjU3MDFDNS44MzIwMyAzNC42NTA4IDYuMDI2MDQgMzQuNjkxMiA2LjI1IDM0LjY5MTJDNi40NzM5NiAzNC42OTEyIDYuNjY2NjcgMzQuNjUwOCA2LjgyODEyIDM0LjU3MDFDNi45ODk1OCAzNC40ODY3IDcuMTEzMjggMzQuMzY4MiA3LjE5OTIyIDM0LjIxNDZDNy4yODc3NiAzNC4wNTgzIDcuMzMyMDMgMzMuODcwOCA3LjMzMjAzIDMzLjY1MjFaTTcuOTI1NzggMzAuOTk5OEM3LjkyNTc4IDMxLjI3NTggNy44NTI4NiAzMS41MjQ1IDcuNzA3MDMgMzEuNzQ1OEM3LjU2MTIgMzEuOTY3MiA3LjM2MTk4IDMyLjE0MTcgNy4xMDkzOCAzMi4yNjkzQzYuODU2NzcgMzIuMzk2OSA2LjU3MDMxIDMyLjQ2MDcgNi4yNSAzMi40NjA3QzUuOTI0NDggMzIuNDYwNyA1LjYzNDExIDMyLjM5NjkgNS4zNzg5MSAzMi4yNjkzQzUuMTI2MyAzMi4xNDE3IDQuOTI4MzkgMzEuOTY3MiA0Ljc4NTE2IDMxLjc0NThDNC42NDE5MyAzMS41MjQ1IDQuNTcwMzEgMzEuMjc1OCA0LjU3MDMxIDMwLjk5OThDNC41NzAzMSAzMC42NjkgNC42NDE5MyAzMC4zODc4IDQuNzg1MTYgMzAuMTU2QzQuOTMwOTkgMjkuOTI0MiA1LjEzMDIxIDI5Ljc0NzIgNS4zODI4MSAyOS42MjQ4QzUuNjM1NDIgMjkuNTAyNCA1LjkyMzE4IDI5LjQ0MTIgNi4yNDYwOSAyOS40NDEyQzYuNTcxNjEgMjkuNDQxMiA2Ljg2MDY4IDI5LjUwMjQgNy4xMTMyOCAyOS42MjQ4QzcuMzY1ODkgMjkuNzQ3MiA3LjU2MzggMjkuOTI0MiA3LjcwNzAzIDMwLjE1NkM3Ljg1Mjg2IDMwLjM4NzggNy45MjU3OCAzMC42NjkgNy45MjU3OCAzMC45OTk4Wk03LjIwMzEyIDMxLjAxMTVDNy4yMDMxMiAzMC44MjE0IDcuMTYyNzYgMzAuNjUzNCA3LjA4MjAzIDMwLjUwNzZDNy4wMDEzIDMwLjM2MTcgNi44ODkzMiAzMC4yNDcyIDYuNzQ2MDkgMzAuMTYzOEM2LjYwMjg2IDMwLjA3NzkgNi40MzYyIDMwLjAzNDkgNi4yNDYwOSAzMC4wMzQ5QzYuMDU1OTkgMzAuMDM0OSA1Ljg4OTMyIDMwLjA3NTMgNS43NDYwOSAzMC4xNTZDNS42MDU0NyAzMC4yMzQxIDUuNDk0NzkgMzAuMzQ2MSA1LjQxNDA2IDMwLjQ5MTlDNS4zMzU5NCAzMC42Mzc4IDUuMjk2ODggMzAuODExIDUuMjk2ODggMzEuMDExNUM1LjI5Njg4IDMxLjIwNjggNS4zMzU5NCAzMS4zNzc0IDUuNDE0MDYgMzEuNTIzMkM1LjQ5NDc5IDMxLjY2OSA1LjYwNjc3IDMxLjc4MjMgNS43NSAzMS44NjNDNS44OTMyMyAzMS45NDM4IDYuMDU5OSAzMS45ODQxIDYuMjUgMzEuOTg0MUM2LjQ0MDEgMzEuOTg0MSA2LjYwNTQ3IDMxLjk0MzggNi43NDYwOSAzMS44NjNDNi44ODkzMiAzMS43ODIzIDcuMDAxMyAzMS42NjkgNy4wODIwMyAzMS41MjMyQzcuMTYyNzYgMzEuMzc3NCA3LjIwMzEyIDMxLjIwNjggNy4yMDMxMiAzMS4wMTE1Wk0xMi42NzUyIDMxLjkwOTlWMzIuNzc3MUMxMi42NzUyIDMzLjI0MzIgMTIuNjMzNSAzMy42MzY1IDEyLjU1MDIgMzMuOTU2OEMxMi40NjY4IDM0LjI3NzEgMTIuMzQ3IDM0LjUzNDkgMTIuMTkwOCAzNC43MzAyQzEyLjAzNDUgMzQuOTI1NSAxMS44NDU3IDM1LjA2NzUgMTEuNjI0NCAzNS4xNTZDMTEuNDA1NiAzNS4yNDE5IDExLjE1ODIgMzUuMjg0OSAxMC44ODIyIDM1LjI4NDlDMTAuNjYzNSAzNS4yODQ5IDEwLjQ2MTYgMzUuMjU3NiAxMC4yNzY3IDM1LjIwMjlDMTAuMDkxOCAzNS4xNDgyIDkuOTI1MTcgMzUuMDYxIDkuNzc2NzMgMzQuOTQxMkM5LjYzMDkgMzQuODE4OCA5LjUwNTkgMzQuNjU5OSA5LjQwMTczIDM0LjQ2NDZDOS4yOTc1NyAzNC4yNjkzIDkuMjE4MTQgMzQuMDMyMyA5LjE2MzQ1IDMzLjc1MzdDOS4xMDg3NyAzMy40NzUgOS4wODE0MiAzMy4xNDk1IDkuMDgxNDIgMzIuNzc3MVYzMS45MDk5QzkuMDgxNDIgMzEuNDQzOCA5LjEyMzA5IDMxLjA1MzEgOS4yMDY0MiAzMC43MzhDOS4yOTIzNiAzMC40MjI5IDkuNDEzNDUgMzAuMTcwMyA5LjU2OTcgMjkuOTgwMkM5LjcyNTk1IDI5Ljc4NzUgOS45MTM0NSAyOS42NDk1IDEwLjEzMjIgMjkuNTY2MkMxMC4zNTM2IDI5LjQ4MjggMTAuNjAxIDI5LjQ0MTIgMTAuODc0NCAyOS40NDEyQzExLjA5NTcgMjkuNDQxMiAxMS4yOTg5IDI5LjQ2ODUgMTEuNDgzOCAyOS41MjMyQzExLjY3MTMgMjkuNTc1MyAxMS44Mzc5IDI5LjY1OTkgMTEuOTgzOCAyOS43NzcxQzEyLjEyOTYgMjkuODkxNyAxMi4yNTMzIDMwLjA0NTMgMTIuMzU0OSAzMC4yMzhDMTIuNDU5IDMwLjQyODEgMTIuNTM4NSAzMC42NjEyIDEyLjU5MzEgMzAuOTM3M0MxMi42NDc4IDMxLjIxMzMgMTIuNjc1MiAzMS41Mzc1IDEyLjY3NTIgMzEuOTA5OVpNMTEuOTQ4NiAzMi44OTQzVjMxLjc4ODhDMTEuOTQ4NiAzMS41MzM2IDExLjkzMyAzMS4zMDk3IDExLjkwMTcgMzEuMTE2OUMxMS44NzMxIDMwLjkyMTYgMTEuODMwMSAzMC43NTUgMTEuNzcyOCAzMC42MTY5QzExLjcxNTUgMzAuNDc4OSAxMS42NDI2IDMwLjM2NjkgMTEuNTU0MSAzMC4yODFDMTEuNDY4MSAzMC4xOTUxIDExLjM2NzkgMzAuMTMyNiAxMS4yNTMzIDMwLjA5MzVDMTEuMTQxMyAzMC4wNTE4IDExLjAxNSAzMC4wMzEgMTAuODc0NCAzMC4wMzFDMTAuNzAyNSAzMC4wMzEgMTAuNTUwMiAzMC4wNjM2IDEwLjQxNzQgMzAuMTI4N0MxMC4yODQ1IDMwLjE5MTIgMTAuMTcyNiAzMC4yOTE0IDEwLjA4MTQgMzAuNDI5NEM5Ljk5Mjg4IDMwLjU2NzUgOS45MjUxNyAzMC43NDg1IDkuODc4MyAzMC45NzI0QzkuODMxNDIgMzEuMTk2NCA5LjgwNzk4IDMxLjQ2ODUgOS44MDc5OCAzMS43ODg4VjMyLjg5NDNDOS44MDc5OCAzMy4xNDk1IDkuODIyMzEgMzMuMzc0OCA5Ljg1MDk1IDMzLjU3MDFDOS44ODIyIDMzLjc2NTQgOS45Mjc3OCAzMy45MzQ3IDkuOTg3NjcgMzQuMDc3OUMxMC4wNDc2IDM0LjIxODUgMTAuMTIwNSAzNC4zMzQ0IDEwLjIwNjQgMzQuNDI1NUMxMC4yOTI0IDM0LjUxNjcgMTAuMzkxMyAzNC41ODQ0IDEwLjUwMzMgMzQuNjI4N0MxMC42MTc5IDM0LjY3MDMgMTAuNzQ0MiAzNC42OTEyIDEwLjg4MjIgMzQuNjkxMkMxMS4wNTkzIDM0LjY5MTIgMTEuMjE0MiAzNC42NTczIDExLjM0NyAzNC41ODk2QzExLjQ3OTkgMzQuNTIxOSAxMS41OTA1IDM0LjQxNjQgMTEuNjc5MSAzNC4yNzMyQzExLjc3MDIgMzQuMTI3NCAxMS44Mzc5IDMzLjk0MTIgMTEuODgyMiAzMy43MTQ2QzExLjkyNjUgMzMuNDg1NCAxMS45NDg2IDMzLjIxMiAxMS45NDg2IDMyLjg5NDNaTTEzLjY3NDYgMzAuOTEzOFYzMC42MTNDMTMuNjc0NiAzMC4zOTY5IDEzLjcyMTQgMzAuMjAwMyAxMy44MTUyIDMwLjAyMzJDMTMuOTA4OSAyOS44NDYxIDE0LjA0MzEgMjkuNzA0MiAxNC4yMTc1IDI5LjU5NzRDMTQuMzkyIDI5LjQ5MDYgMTQuNTk5IDI5LjQzNzMgMTQuODM4NiAyOS40MzczQzE1LjA4MzQgMjkuNDM3MyAxNS4yOTE4IDI5LjQ5MDYgMTUuNDYzNiAyOS41OTc0QzE1LjYzODEgMjkuNzA0MiAxNS43NzIyIDI5Ljg0NjEgMTUuODY2IDMwLjAyMzJDMTUuOTU5NyAzMC4yMDAzIDE2LjAwNjYgMzAuMzk2OSAxNi4wMDY2IDMwLjYxM1YzMC45MTM4QzE2LjAwNjYgMzEuMTI0OCAxNS45NTk3IDMxLjMxODggMTUuODY2IDMxLjQ5NThDMTUuNzc0OCAzMS42NzI5IDE1LjY0MiAzMS44MTQ5IDE1LjQ2NzUgMzEuOTIxNkMxNS4yOTU3IDMyLjAyODQgMTUuMDg4NiAzMi4wODE4IDE0Ljg0NjQgMzIuMDgxOEMxNC42MDQzIDMyLjA4MTggMTQuMzk0NiAzMi4wMjg0IDE0LjIxNzUgMzEuOTIxNkMxNC4wNDMxIDMxLjgxNDkgMTMuOTA4OSAzMS42NzI5IDEzLjgxNTIgMzEuNDk1OEMxMy43MjE0IDMxLjMxODggMTMuNjc0NiAzMS4xMjQ4IDEzLjY3NDYgMzAuOTEzOFpNMTQuMjE3NSAzMC42MTNWMzAuOTEzOEMxNC4yMTc1IDMxLjAzMzYgMTQuMjM5NyAzMS4xNDY5IDE0LjI4MzkgMzEuMjUzN0MxNC4zMzA4IDMxLjM2MDQgMTQuNDAxMSAzMS40NDc3IDE0LjQ5NDkgMzEuNTE1NEMxNC41ODg2IDMxLjU4MDUgMTQuNzA1OCAzMS42MTMgMTQuODQ2NCAzMS42MTNDMTQuOTg3MSAzMS42MTMgMTUuMTAyOSAzMS41ODA1IDE1LjE5NDEgMzEuNTE1NEMxNS4yODUyIDMxLjQ0NzcgMTUuMzUyOSAzMS4zNjA0IDE1LjM5NzIgMzEuMjUzN0MxNS40NDE1IDMxLjE0NjkgMTUuNDYzNiAzMS4wMzM2IDE1LjQ2MzYgMzAuOTEzOFYzMC42MTNDMTUuNDYzNiAzMC40OTA2IDE1LjQ0MDIgMzAuMzc2MSAxNS4zOTMzIDMwLjI2OTNDMTUuMzQ5IDMwLjE1OTkgMTUuMjggMzAuMDcyNyAxNS4xODYzIDMwLjAwNzZDMTUuMDk1MSAyOS45Mzk5IDE0Ljk3OTMgMjkuOTA2IDE0LjgzODYgMjkuOTA2QzE0LjcwMDYgMjkuOTA2IDE0LjU4NDcgMjkuOTM5OSAxNC40OTEgMzAuMDA3NkMxNC4zOTk4IDMwLjA3MjcgMTQuMzMwOCAzMC4xNTk5IDE0LjI4MzkgMzAuMjY5M0MxNC4yMzk3IDMwLjM3NjEgMTQuMjE3NSAzMC40OTA2IDE0LjIxNzUgMzAuNjEzWk0xNi40NDQxIDM0LjExNjlWMzMuODEyM0MxNi40NDQxIDMzLjU5ODcgMTYuNDkxIDMzLjQwMzQgMTYuNTg0NyAzMy4yMjYzQzE2LjY3ODUgMzMuMDQ5MiAxNi44MTI2IDMyLjkwNzMgMTYuOTg3MSAzMi44MDA1QzE3LjE2MTUgMzIuNjkzOCAxNy4zNjg2IDMyLjY0MDQgMTcuNjA4MiAzMi42NDA0QzE3Ljg1MjkgMzIuNjQwNCAxOC4wNjEzIDMyLjY5MzggMTguMjMzMiAzMi44MDA1QzE4LjQwNzYgMzIuOTA3MyAxOC41NDE4IDMzLjA0OTIgMTguNjM1NSAzMy4yMjYzQzE4LjcyOTMgMzMuNDAzNCAxOC43NzYxIDMzLjU5ODcgMTguNzc2MSAzMy44MTIzVjM0LjExNjlDMTguNzc2MSAzNC4zMzA1IDE4LjcyOTMgMzQuNTI1OCAxOC42MzU1IDM0LjcwMjlDMTguNTQ0NCAzNC44OCAxOC40MTE1IDM1LjAyMTkgMTguMjM3MSAzNS4xMjg3QzE4LjA2NTIgMzUuMjM1NCAxNy44NTgyIDM1LjI4ODggMTcuNjE2IDM1LjI4ODhDMTcuMzczOCAzNS4yODg4IDE3LjE2NTQgMzUuMjM1NCAxNi45OTEgMzUuMTI4N0MxNi44MTY1IDM1LjAyMTkgMTYuNjgxMSAzNC44OCAxNi41ODQ3IDM0LjcwMjlDMTYuNDkxIDM0LjUyNTggMTYuNDQ0MSAzNC4zMzA1IDE2LjQ0NDEgMzQuMTE2OVpNMTYuOTg3MSAzMy44MTIzVjM0LjExNjlDMTYuOTg3MSAzNC4yMzY3IDE3LjAwOTIgMzQuMzUxMyAxNy4wNTM1IDM0LjQ2MDdDMTcuMTAwMyAzNC41Njc1IDE3LjE3MDcgMzQuNjU0NyAxNy4yNjQ0IDM0LjcyMjRDMTcuMzU4MiAzNC43ODc1IDE3LjQ3NTMgMzQuODIwMSAxNy42MTYgMzQuODIwMUMxNy43NTY2IDM0LjgyMDEgMTcuODcyNSAzNC43ODc1IDE3Ljk2MzYgMzQuNzIyNEMxOC4wNTc0IDM0LjY1NDcgMTguMTI2NCAzNC41Njc1IDE4LjE3MDcgMzQuNDYwN0MxOC4yMTQ5IDM0LjM1MzkgMTguMjM3MSAzNC4yMzkzIDE4LjIzNzEgMzQuMTE2OVYzMy44MTIzQzE4LjIzNzEgMzMuNjg5OSAxOC4yMTM2IDMzLjU3NTMgMTguMTY2OCAzMy40Njg1QzE4LjEyMjUgMzMuMzYxNyAxOC4wNTM1IDMzLjI3NTggMTcuOTU5NyAzMy4yMTA3QzE3Ljg2ODYgMzMuMTQzIDE3Ljc1MTQgMzMuMTA5MSAxNy42MDgyIDMzLjEwOTFDMTcuNDcwMSAzMy4xMDkxIDE3LjM1NDMgMzMuMTQzIDE3LjI2MDUgMzMuMjEwN0MxNy4xNjk0IDMzLjI3NTggMTcuMTAwMyAzMy4zNjE3IDE3LjA1MzUgMzMuNDY4NUMxNy4wMDkyIDMzLjU3NTMgMTYuOTg3MSAzMy42ODk5IDE2Ljk4NzEgMzMuODEyM1pNMTcuNzg3OCAzMC4zMjc5TDE1LjAxMDUgMzQuNzczMkwxNC42MDQzIDM0LjUxNTRMMTcuMzgxNiAzMC4wNzAxTDE3Ljc4NzggMzAuMzI3OVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTcuMjQ2MDkgNTcuNzE4M0g3LjMwODU5VjU4LjMzMTVINy4yNDYwOUM2Ljg2MzI4IDU4LjMzMTUgNi41NDI5NyA1OC4zOTQgNi4yODUxNiA1OC41MTlDNi4wMjczNCA1OC42NDE0IDUuODIyOTIgNTguODA2OCA1LjY3MTg4IDU5LjAxNTFDNS41MjA4MyA1OS4yMjA5IDUuNDExNDYgNTkuNDUyNiA1LjM0Mzc1IDU5LjcxMDRDNS4yNzg2NSA1OS45NjgzIDUuMjQ2MDkgNjAuMjMgNS4yNDYwOSA2MC40OTU2VjYxLjMzMTVDNS4yNDYwOSA2MS41ODQxIDUuMjc2MDQgNjEuODA4MSA1LjMzNTk0IDYyLjAwMzRDNS4zOTU4MyA2Mi4xOTYxIDUuNDc3ODYgNjIuMzU4OSA1LjU4MjAzIDYyLjQ5MTdDNS42ODYyIDYyLjYyNDUgNS44MDMzOSA2Mi43MjQ4IDUuOTMzNTkgNjIuNzkyNUM2LjA2NjQxIDYyLjg2MDIgNi4yMDQ0MyA2Mi44OTQgNi4zNDc2NiA2Mi44OTRDNi41MTQzMiA2Mi44OTQgNi42NjI3NiA2Mi44NjI4IDYuNzkyOTcgNjIuODAwM0M2LjkyMzE4IDYyLjczNTIgNy4wMzI1NSA2Mi42NDUzIDcuMTIxMDkgNjIuNTMwOEM3LjIxMjI0IDYyLjQxMzYgNy4yODEyNSA2Mi4yNzU2IDcuMzI4MTIgNjIuMTE2N0M3LjM3NSA2MS45NTc4IDcuMzk4NDQgNjEuNzgzNCA3LjM5ODQ0IDYxLjU5MzNDNy4zOTg0NCA2MS40MjQgNy4zNzc2IDYxLjI2MTIgNy4zMzU5NCA2MS4xMDVDNy4yOTQyNyA2MC45NDYxIDcuMjMwNDcgNjAuODA1NSA3LjE0NDUzIDYwLjY4MzFDNy4wNTg1OSA2MC41NTgxIDYuOTUwNTIgNjAuNDYwNCA2LjgyMDMxIDYwLjM5MDFDNi42OTI3MSA2MC4zMTcyIDYuNTQwMzYgNjAuMjgwOCA2LjM2MzI4IDYwLjI4MDhDNi4xNjI3NiA2MC4yODA4IDUuOTc1MjYgNjAuMzMwMiA1LjgwMDc4IDYwLjQyOTJDNS42Mjg5MSA2MC41MjU2IDUuNDg2OTggNjAuNjUzMiA1LjM3NSA2MC44MTJDNS4yNjU2MiA2MC45NjgzIDUuMjAzMTIgNjEuMTM4OCA1LjE4NzUgNjEuMzIzN0w0LjgwNDY5IDYxLjMxOThDNC44NDExNSA2MS4wMjgyIDQuOTA4ODUgNjAuNzc5NSA1LjAwNzgxIDYwLjU3MzdDNS4xMDkzOCA2MC4zNjU0IDUuMjM0MzggNjAuMTk2MSA1LjM4MjgxIDYwLjA2NTlDNS41MzM4NSA1OS45MzMxIDUuNzAxODIgNTkuODM2OCA1Ljg4NjcyIDU5Ljc3NjlDNi4wNzQyMiA1OS43MTQ0IDYuMjcyMTQgNTkuNjgzMSA2LjQ4MDQ3IDU5LjY4MzFDNi43NjQzMiA1OS42ODMxIDcuMDA5MTEgNTkuNzM2NSA3LjIxNDg0IDU5Ljg0MzNDNy40MjA1NyA1OS45NSA3LjU4OTg0IDYwLjA5MzMgNy43MjI2NiA2MC4yNzI5QzcuODU1NDcgNjAuNDUgNy45NTMxMiA2MC42NTA2IDguMDE1NjIgNjAuODc0NUM4LjA4MDczIDYxLjA5NTkgOC4xMTMyOCA2MS4zMjM3IDguMTEzMjggNjEuNTU4MUM4LjExMzI4IDYxLjgyNjMgOC4wNzU1MiA2Mi4wNzc2IDggNjIuMzEyQzcuOTI0NDggNjIuNTQ2NCA3LjgxMTIgNjIuNzUyMSA3LjY2MDE2IDYyLjkyOTJDNy41MTE3MiA2My4xMDYzIDcuMzI4MTIgNjMuMjQ0MyA3LjEwOTM4IDYzLjM0MzNDNi44OTA2MiA2My40NDIyIDYuNjM2NzIgNjMuNDkxNyA2LjM0NzY2IDYzLjQ5MTdDNi4wNDAzNiA2My40OTE3IDUuNzcyMTQgNjMuNDI5MiA1LjU0Mjk3IDYzLjMwNDJDNS4zMTM4IDYzLjE3NjYgNS4xMjM3IDYzLjAwNzMgNC45NzI2NiA2Mi43OTY0QzQuODIxNjEgNjIuNTg1NCA0LjcwODMzIDYyLjM1MTEgNC42MzI4MSA2Mi4wOTMzQzQuNTU3MjkgNjEuODM1NCA0LjUxOTUzIDYxLjU3MzcgNC41MTk1MyA2MS4zMDgxVjYwLjk2ODNDNC41MTk1MyA2MC41NjcyIDQuNTU5OSA2MC4xNzQgNC42NDA2MiA1OS43ODg2QzQuNzIxMzUgNTkuNDAzMiA0Ljg2MDY4IDU5LjA1NDIgNS4wNTg1OSA1OC43NDE3QzUuMjU5MTEgNTguNDI5MiA1LjUzNjQ2IDU4LjE4MDUgNS44OTA2MiA1Ny45OTU2QzYuMjQ0NzkgNTcuODEwNyA2LjY5NjYxIDU3LjcxODMgNy4yNDYwOSA1Ny43MTgzWk0xMi42NzUyIDYwLjExNjdWNjAuOTgzOUMxMi42NzUyIDYxLjQ1IDEyLjYzMzUgNjEuODQzMyAxMi41NTAyIDYyLjE2MzZDMTIuNDY2OCA2Mi40ODM5IDEyLjM0NyA2Mi43NDE3IDEyLjE5MDggNjIuOTM3QzEyLjAzNDUgNjMuMTMyMyAxMS44NDU3IDYzLjI3NDMgMTEuNjI0NCA2My4zNjI4QzExLjQwNTYgNjMuNDQ4NyAxMS4xNTgyIDYzLjQ5MTcgMTAuODgyMiA2My40OTE3QzEwLjY2MzUgNjMuNDkxNyAxMC40NjE2IDYzLjQ2NDQgMTAuMjc2NyA2My40MDk3QzEwLjA5MTggNjMuMzU1IDkuOTI1MTcgNjMuMjY3NyA5Ljc3NjczIDYzLjE0NzlDOS42MzA5IDYzLjAyNTYgOS41MDU5IDYyLjg2NjcgOS40MDE3MyA2Mi42NzE0QzkuMjk3NTcgNjIuNDc2MSA5LjIxODE0IDYyLjIzOTEgOS4xNjM0NSA2MS45NjA0QzkuMTA4NzcgNjEuNjgxOCA5LjA4MTQyIDYxLjM1NjMgOS4wODE0MiA2MC45ODM5VjYwLjExNjdDOS4wODE0MiA1OS42NTA2IDkuMTIzMDkgNTkuMjU5OSA5LjIwNjQyIDU4Ljk0NDhDOS4yOTIzNiA1OC42Mjk3IDkuNDEzNDUgNTguMzc3MSA5LjU2OTcgNTguMTg3QzkuNzI1OTUgNTcuOTk0MyA5LjkxMzQ1IDU3Ljg1NjMgMTAuMTMyMiA1Ny43NzI5QzEwLjM1MzYgNTcuNjg5NiAxMC42MDEgNTcuNjQ3OSAxMC44NzQ0IDU3LjY0NzlDMTEuMDk1NyA1Ny42NDc5IDExLjI5ODkgNTcuNjc1MyAxMS40ODM4IDU3LjczQzExLjY3MTMgNTcuNzgyMSAxMS44Mzc5IDU3Ljg2NjcgMTEuOTgzOCA1Ny45ODM5QzEyLjEyOTYgNTguMDk4NSAxMi4yNTMzIDU4LjI1MjEgMTIuMzU0OSA1OC40NDQ4QzEyLjQ1OSA1OC42MzQ5IDEyLjUzODUgNTguODY4IDEyLjU5MzEgNTkuMTQ0QzEyLjY0NzggNTkuNDIwMSAxMi42NzUyIDU5Ljc0NDMgMTIuNjc1MiA2MC4xMTY3Wk0xMS45NDg2IDYxLjEwMTFWNTkuOTk1NkMxMS45NDg2IDU5Ljc0MDQgMTEuOTMzIDU5LjUxNjQgMTEuOTAxNyA1OS4zMjM3QzExLjg3MzEgNTkuMTI4NCAxMS44MzAxIDU4Ljk2MTggMTEuNzcyOCA1OC44MjM3QzExLjcxNTUgNTguNjg1NyAxMS42NDI2IDU4LjU3MzcgMTEuNTU0MSA1OC40ODc4QzExLjQ2ODEgNTguNDAxOSAxMS4zNjc5IDU4LjMzOTQgMTEuMjUzMyA1OC4zMDAzQzExLjE0MTMgNTguMjU4NiAxMS4wMTUgNTguMjM3OCAxMC44NzQ0IDU4LjIzNzhDMTAuNzAyNSA1OC4yMzc4IDEwLjU1MDIgNTguMjcwMyAxMC40MTc0IDU4LjMzNTRDMTAuMjg0NSA1OC4zOTc5IDEwLjE3MjYgNTguNDk4MiAxMC4wODE0IDU4LjYzNjJDOS45OTI4OCA1OC43NzQzIDkuOTI1MTcgNTguOTU1MiA5Ljg3ODMgNTkuMTc5MkM5LjgzMTQyIDU5LjQwMzIgOS44MDc5OCA1OS42NzUzIDkuODA3OTggNTkuOTk1NlY2MS4xMDExQzkuODA3OTggNjEuMzU2MyA5LjgyMjMxIDYxLjU4MTUgOS44NTA5NSA2MS43NzY5QzkuODgyMiA2MS45NzIyIDkuOTI3NzggNjIuMTQxNCA5Ljk4NzY3IDYyLjI4NDdDMTAuMDQ3NiA2Mi40MjUzIDEwLjEyMDUgNjIuNTQxMiAxMC4yMDY0IDYyLjYzMjNDMTAuMjkyNCA2Mi43MjM1IDEwLjM5MTMgNjIuNzkxMiAxMC41MDMzIDYyLjgzNTRDMTAuNjE3OSA2Mi44NzcxIDEwLjc0NDIgNjIuODk3OSAxMC44ODIyIDYyLjg5NzlDMTEuMDU5MyA2Mi44OTc5IDExLjIxNDIgNjIuODY0MSAxMS4zNDcgNjIuNzk2NEMxMS40Nzk5IDYyLjcyODcgMTEuNTkwNSA2Mi42MjMyIDExLjY3OTEgNjIuNDhDMTEuNzcwMiA2Mi4zMzQxIDExLjgzNzkgNjIuMTQ3OSAxMS44ODIyIDYxLjkyMTRDMTEuOTI2NSA2MS42OTIyIDExLjk0ODYgNjEuNDE4OCAxMS45NDg2IDYxLjEwMTFaTTEzLjY3NDYgNTkuMTIwNlY1OC44MTk4QzEzLjY3NDYgNTguNjAzNyAxMy43MjE0IDU4LjQwNzEgMTMuODE1MiA1OC4yM0MxMy45MDg5IDU4LjA1MjkgMTQuMDQzMSA1Ny45MTEgMTQuMjE3NSA1Ny44MDQyQzE0LjM5MiA1Ny42OTc0IDE0LjU5OSA1Ny42NDQgMTQuODM4NiA1Ny42NDRDMTUuMDgzNCA1Ny42NDQgMTUuMjkxOCA1Ny42OTc0IDE1LjQ2MzYgNTcuODA0MkMxNS42MzgxIDU3LjkxMSAxNS43NzIyIDU4LjA1MjkgMTUuODY2IDU4LjIzQzE1Ljk1OTcgNTguNDA3MSAxNi4wMDY2IDU4LjYwMzcgMTYuMDA2NiA1OC44MTk4VjU5LjEyMDZDMTYuMDA2NiA1OS4zMzE1IDE1Ljk1OTcgNTkuNTI1NiAxNS44NjYgNTkuNzAyNkMxNS43NzQ4IDU5Ljg3OTcgMTUuNjQyIDYwLjAyMTYgMTUuNDY3NSA2MC4xMjg0QzE1LjI5NTcgNjAuMjM1MiAxNS4wODg2IDYwLjI4ODYgMTQuODQ2NCA2MC4yODg2QzE0LjYwNDMgNjAuMjg4NiAxNC4zOTQ2IDYwLjIzNTIgMTQuMjE3NSA2MC4xMjg0QzE0LjA0MzEgNjAuMDIxNiAxMy45MDg5IDU5Ljg3OTcgMTMuODE1MiA1OS43MDI2QzEzLjcyMTQgNTkuNTI1NiAxMy42NzQ2IDU5LjMzMTUgMTMuNjc0NiA1OS4xMjA2Wk0xNC4yMTc1IDU4LjgxOThWNTkuMTIwNkMxNC4yMTc1IDU5LjI0MDQgMTQuMjM5NyA1OS4zNTM3IDE0LjI4MzkgNTkuNDYwNEMxNC4zMzA4IDU5LjU2NzIgMTQuNDAxMSA1OS42NTQ1IDE0LjQ5NDkgNTkuNzIyMkMxNC41ODg2IDU5Ljc4NzMgMTQuNzA1OCA1OS44MTk4IDE0Ljg0NjQgNTkuODE5OEMxNC45ODcxIDU5LjgxOTggMTUuMTAyOSA1OS43ODczIDE1LjE5NDEgNTkuNzIyMkMxNS4yODUyIDU5LjY1NDUgMTUuMzUyOSA1OS41NjcyIDE1LjM5NzIgNTkuNDYwNEMxNS40NDE1IDU5LjM1MzcgMTUuNDYzNiA1OS4yNDA0IDE1LjQ2MzYgNTkuMTIwNlY1OC44MTk4QzE1LjQ2MzYgNTguNjk3NCAxNS40NDAyIDU4LjU4MjggMTUuMzkzMyA1OC40NzYxQzE1LjM0OSA1OC4zNjY3IDE1LjI4IDU4LjI3OTUgMTUuMTg2MyA1OC4yMTQ0QzE1LjA5NTEgNTguMTQ2NiAxNC45NzkzIDU4LjExMjggMTQuODM4NiA1OC4xMTI4QzE0LjcwMDYgNTguMTEyOCAxNC41ODQ3IDU4LjE0NjYgMTQuNDkxIDU4LjIxNDRDMTQuMzk5OCA1OC4yNzk1IDE0LjMzMDggNTguMzY2NyAxNC4yODM5IDU4LjQ3NjFDMTQuMjM5NyA1OC41ODI4IDE0LjIxNzUgNTguNjk3NCAxNC4yMTc1IDU4LjgxOThaTTE2LjQ0NDEgNjIuMzIzN1Y2Mi4wMTlDMTYuNDQ0MSA2MS44MDU1IDE2LjQ5MSA2MS42MTAyIDE2LjU4NDcgNjEuNDMzMUMxNi42Nzg1IDYxLjI1NiAxNi44MTI2IDYxLjExNDEgMTYuOTg3MSA2MS4wMDczQzE3LjE2MTUgNjAuOTAwNiAxNy4zNjg2IDYwLjg0NzIgMTcuNjA4MiA2MC44NDcyQzE3Ljg1MjkgNjAuODQ3MiAxOC4wNjEzIDYwLjkwMDYgMTguMjMzMiA2MS4wMDczQzE4LjQwNzYgNjEuMTE0MSAxOC41NDE4IDYxLjI1NiAxOC42MzU1IDYxLjQzMzFDMTguNzI5MyA2MS42MTAyIDE4Ljc3NjEgNjEuODA1NSAxOC43NzYxIDYyLjAxOVY2Mi4zMjM3QzE4Ljc3NjEgNjIuNTM3MyAxOC43MjkzIDYyLjczMjYgMTguNjM1NSA2Mi45MDk3QzE4LjU0NDQgNjMuMDg2OCAxOC40MTE1IDYzLjIyODcgMTguMjM3MSA2My4zMzU0QzE4LjA2NTIgNjMuNDQyMiAxNy44NTgyIDYzLjQ5NTYgMTcuNjE2IDYzLjQ5NTZDMTcuMzczOCA2My40OTU2IDE3LjE2NTQgNjMuNDQyMiAxNi45OTEgNjMuMzM1NEMxNi44MTY1IDYzLjIyODcgMTYuNjgxMSA2My4wODY4IDE2LjU4NDcgNjIuOTA5N0MxNi40OTEgNjIuNzMyNiAxNi40NDQxIDYyLjUzNzMgMTYuNDQ0MSA2Mi4zMjM3Wk0xNi45ODcxIDYyLjAxOVY2Mi4zMjM3QzE2Ljk4NzEgNjIuNDQzNSAxNy4wMDkyIDYyLjU1ODEgMTcuMDUzNSA2Mi42Njc1QzE3LjEwMDMgNjIuNzc0MyAxNy4xNzA3IDYyLjg2MTUgMTcuMjY0NCA2Mi45MjkyQzE3LjM1ODIgNjIuOTk0MyAxNy40NzUzIDYzLjAyNjkgMTcuNjE2IDYzLjAyNjlDMTcuNzU2NiA2My4wMjY5IDE3Ljg3MjUgNjIuOTk0MyAxNy45NjM2IDYyLjkyOTJDMTguMDU3NCA2Mi44NjE1IDE4LjEyNjQgNjIuNzc0MyAxOC4xNzA3IDYyLjY2NzVDMTguMjE0OSA2Mi41NjA3IDE4LjIzNzEgNjIuNDQ2MSAxOC4yMzcxIDYyLjMyMzdWNjIuMDE5QzE4LjIzNzEgNjEuODk2NiAxOC4yMTM2IDYxLjc4MjEgMTguMTY2OCA2MS42NzUzQzE4LjEyMjUgNjEuNTY4NSAxOC4wNTM1IDYxLjQ4MjYgMTcuOTU5NyA2MS40MTc1QzE3Ljg2ODYgNjEuMzQ5OCAxNy43NTE0IDYxLjMxNTkgMTcuNjA4MiA2MS4zMTU5QzE3LjQ3MDEgNjEuMzE1OSAxNy4zNTQzIDYxLjM0OTggMTcuMjYwNSA2MS40MTc1QzE3LjE2OTQgNjEuNDgyNiAxNy4xMDAzIDYxLjU2ODUgMTcuMDUzNSA2MS42NzUzQzE3LjAwOTIgNjEuNzgyMSAxNi45ODcxIDYxLjg5NjYgMTYuOTg3MSA2Mi4wMTlaTTE3Ljc4NzggNTguNTM0N0wxNS4wMTA1IDYyLjk4TDE0LjYwNDMgNjIuNzIyMkwxNy4zODE2IDU4LjI3NjlMMTcuNzg3OCA1OC41MzQ3WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNOC4zMTY0MSA4OS43MDYzVjkwLjNINC4yMDcwM1Y4OS44NzQzTDYuNzUzOTEgODUuOTMyOUg3LjM0Mzc1TDYuNzEwOTQgODcuMDczNUw1LjAyNzM0IDg5LjcwNjNIOC4zMTY0MVpNNy41MjM0NCA4NS45MzI5VjkxLjYyMDRINi44MDA3OFY4NS45MzI5SDcuNTIzNDRaTTEyLjY3NTIgODguMzIzNVY4OS4xOTA3QzEyLjY3NTIgODkuNjU2OCAxMi42MzM1IDkwLjA1IDEyLjU1MDIgOTAuMzcwNEMxMi40NjY4IDkwLjY5MDcgMTIuMzQ3IDkwLjk0ODUgMTIuMTkwOCA5MS4xNDM4QzEyLjAzNDUgOTEuMzM5MSAxMS44NDU3IDkxLjQ4MSAxMS42MjQ0IDkxLjU2OTZDMTEuNDA1NiA5MS42NTU1IDExLjE1ODIgOTEuNjk4NSAxMC44ODIyIDkxLjY5ODVDMTAuNjYzNSA5MS42OTg1IDEwLjQ2MTYgOTEuNjcxMSAxMC4yNzY3IDkxLjYxNjVDMTAuMDkxOCA5MS41NjE4IDkuOTI1MTcgOTEuNDc0NSA5Ljc3NjczIDkxLjM1NDdDOS42MzA5IDkxLjIzMjMgOS41MDU5IDkxLjA3MzUgOS40MDE3MyA5MC44NzgyQzkuMjk3NTcgOTAuNjgyOSA5LjIxODE0IDkwLjQ0NTkgOS4xNjM0NSA5MC4xNjcyQzkuMTA4NzcgODkuODg4NiA5LjA4MTQyIDg5LjU2MzEgOS4wODE0MiA4OS4xOTA3Vjg4LjMyMzVDOS4wODE0MiA4Ny44NTczIDkuMTIzMDkgODcuNDY2NyA5LjIwNjQyIDg3LjE1MTZDOS4yOTIzNiA4Ni44MzY1IDkuNDEzNDUgODYuNTgzOSA5LjU2OTcgODYuMzkzOEM5LjcyNTk1IDg2LjIwMTEgOS45MTM0NSA4Ni4wNjMxIDEwLjEzMjIgODUuOTc5N0MxMC4zNTM2IDg1Ljg5NjQgMTAuNjAxIDg1Ljg1NDcgMTAuODc0NCA4NS44NTQ3QzExLjA5NTcgODUuODU0NyAxMS4yOTg5IDg1Ljg4MjEgMTEuNDgzOCA4NS45MzY4QzExLjY3MTMgODUuOTg4OSAxMS44Mzc5IDg2LjA3MzUgMTEuOTgzOCA4Ni4xOTA3QzEyLjEyOTYgODYuMzA1MyAxMi4yNTMzIDg2LjQ1ODkgMTIuMzU0OSA4Ni42NTE2QzEyLjQ1OSA4Ni44NDE3IDEyLjUzODUgODcuMDc0OCAxMi41OTMxIDg3LjM1MDhDMTIuNjQ3OCA4Ny42MjY5IDEyLjY3NTIgODcuOTUxMSAxMi42NzUyIDg4LjMyMzVaTTExLjk0ODYgODkuMzA3OVY4OC4yMDI0QzExLjk0ODYgODcuOTQ3MiAxMS45MzMgODcuNzIzMiAxMS45MDE3IDg3LjUzMDVDMTEuODczMSA4Ny4zMzUyIDExLjgzMDEgODcuMTY4NSAxMS43NzI4IDg3LjAzMDVDMTEuNzE1NSA4Ni44OTI1IDExLjY0MjYgODYuNzgwNSAxMS41NTQxIDg2LjY5NDZDMTEuNDY4MSA4Ni42MDg2IDExLjM2NzkgODYuNTQ2MSAxMS4yNTMzIDg2LjUwNzFDMTEuMTQxMyA4Ni40NjU0IDExLjAxNSA4Ni40NDQ2IDEwLjg3NDQgODYuNDQ0NkMxMC43MDI1IDg2LjQ0NDYgMTAuNTUwMiA4Ni40NzcxIDEwLjQxNzQgODYuNTQyMkMxMC4yODQ1IDg2LjYwNDcgMTAuMTcyNiA4Ni43MDUgMTAuMDgxNCA4Ni44NDNDOS45OTI4OCA4Ni45ODEgOS45MjUxNyA4Ny4xNjIgOS44NzgzIDg3LjM4NkM5LjgzMTQyIDg3LjYwOTkgOS44MDc5OCA4Ny44ODIxIDkuODA3OTggODguMjAyNFY4OS4zMDc5QzkuODA3OTggODkuNTYzMSA5LjgyMjMxIDg5Ljc4ODMgOS44NTA5NSA4OS45ODM2QzkuODgyMiA5MC4xNzkgOS45Mjc3OCA5MC4zNDgyIDkuOTg3NjcgOTAuNDkxNUMxMC4wNDc2IDkwLjYzMjEgMTAuMTIwNSA5MC43NDggMTAuMjA2NCA5MC44MzkxQzEwLjI5MjQgOTAuOTMwMyAxMC4zOTEzIDkwLjk5OCAxMC41MDMzIDkxLjA0MjJDMTAuNjE3OSA5MS4wODM5IDEwLjc0NDIgOTEuMTA0NyAxMC44ODIyIDkxLjEwNDdDMTEuMDU5MyA5MS4xMDQ3IDExLjIxNDIgOTEuMDcwOSAxMS4zNDcgOTEuMDAzMkMxMS40Nzk5IDkwLjkzNTUgMTEuNTkwNSA5MC44MyAxMS42NzkxIDkwLjY4NjhDMTEuNzcwMiA5MC41NDA5IDExLjgzNzkgOTAuMzU0NyAxMS44ODIyIDkwLjEyODJDMTEuOTI2NSA4OS44OTkgMTEuOTQ4NiA4OS42MjU2IDExLjk0ODYgODkuMzA3OVpNMTMuNjc0NiA4Ny4zMjc0Vjg3LjAyNjZDMTMuNjc0NiA4Ni44MTA1IDEzLjcyMTQgODYuNjEzOSAxMy44MTUyIDg2LjQzNjhDMTMuOTA4OSA4Ni4yNTk3IDE0LjA0MzEgODYuMTE3OCAxNC4yMTc1IDg2LjAxMUMxNC4zOTIgODUuOTA0MiAxNC41OTkgODUuODUwOCAxNC44Mzg2IDg1Ljg1MDhDMTUuMDgzNCA4NS44NTA4IDE1LjI5MTggODUuOTA0MiAxNS40NjM2IDg2LjAxMUMxNS42MzgxIDg2LjExNzggMTUuNzcyMiA4Ni4yNTk3IDE1Ljg2NiA4Ni40MzY4QzE1Ljk1OTcgODYuNjEzOSAxNi4wMDY2IDg2LjgxMDUgMTYuMDA2NiA4Ny4wMjY2Vjg3LjMyNzRDMTYuMDA2NiA4Ny41MzgzIDE1Ljk1OTcgODcuNzMyMyAxNS44NjYgODcuOTA5NEMxNS43NzQ4IDg4LjA4NjUgMTUuNjQyIDg4LjIyODQgMTUuNDY3NSA4OC4zMzUyQzE1LjI5NTcgODguNDQyIDE1LjA4ODYgODguNDk1NCAxNC44NDY0IDg4LjQ5NTRDMTQuNjA0MyA4OC40OTU0IDE0LjM5NDYgODguNDQyIDE0LjIxNzUgODguMzM1MkMxNC4wNDMxIDg4LjIyODQgMTMuOTA4OSA4OC4wODY1IDEzLjgxNTIgODcuOTA5NEMxMy43MjE0IDg3LjczMjMgMTMuNjc0NiA4Ny41MzgzIDEzLjY3NDYgODcuMzI3NFpNMTQuMjE3NSA4Ny4wMjY2Vjg3LjMyNzRDMTQuMjE3NSA4Ny40NDcyIDE0LjIzOTcgODcuNTYwNSAxNC4yODM5IDg3LjY2NzJDMTQuMzMwOCA4Ny43NzQgMTQuNDAxMSA4Ny44NjEyIDE0LjQ5NDkgODcuOTI5QzE0LjU4ODYgODcuOTk0MSAxNC43MDU4IDg4LjAyNjYgMTQuODQ2NCA4OC4wMjY2QzE0Ljk4NzEgODguMDI2NiAxNS4xMDI5IDg3Ljk5NDEgMTUuMTk0MSA4Ny45MjlDMTUuMjg1MiA4Ny44NjEyIDE1LjM1MjkgODcuNzc0IDE1LjM5NzIgODcuNjY3MkMxNS40NDE1IDg3LjU2MDUgMTUuNDYzNiA4Ny40NDcyIDE1LjQ2MzYgODcuMzI3NFY4Ny4wMjY2QzE1LjQ2MzYgODYuOTA0MiAxNS40NDAyIDg2Ljc4OTYgMTUuMzkzMyA4Ni42ODI5QzE1LjM0OSA4Ni41NzM1IDE1LjI4IDg2LjQ4NjIgMTUuMTg2MyA4Ni40MjExQzE1LjA5NTEgODYuMzUzNCAxNC45NzkzIDg2LjMxOTYgMTQuODM4NiA4Ni4zMTk2QzE0LjcwMDYgODYuMzE5NiAxNC41ODQ3IDg2LjM1MzQgMTQuNDkxIDg2LjQyMTFDMTQuMzk5OCA4Ni40ODYyIDE0LjMzMDggODYuNTczNSAxNC4yODM5IDg2LjY4MjlDMTQuMjM5NyA4Ni43ODk2IDE0LjIxNzUgODYuOTA0MiAxNC4yMTc1IDg3LjAyNjZaTTE2LjQ0NDEgOTAuNTMwNVY5MC4yMjU4QzE2LjQ0NDEgOTAuMDEyMyAxNi40OTEgODkuODE3IDE2LjU4NDcgODkuNjM5OUMxNi42Nzg1IDg5LjQ2MjggMTYuODEyNiA4OS4zMjA5IDE2Ljk4NzEgODkuMjE0MUMxNy4xNjE1IDg5LjEwNzMgMTcuMzY4NiA4OS4wNTQgMTcuNjA4MiA4OS4wNTRDMTcuODUyOSA4OS4wNTQgMTguMDYxMyA4OS4xMDczIDE4LjIzMzIgODkuMjE0MUMxOC40MDc2IDg5LjMyMDkgMTguNTQxOCA4OS40NjI4IDE4LjYzNTUgODkuNjM5OUMxOC43MjkzIDg5LjgxNyAxOC43NzYxIDkwLjAxMjMgMTguNzc2MSA5MC4yMjU4VjkwLjUzMDVDMTguNzc2MSA5MC43NDQxIDE4LjcyOTMgOTAuOTM5NCAxOC42MzU1IDkxLjExNjVDMTguNTQ0NCA5MS4yOTM1IDE4LjQxMTUgOTEuNDM1NSAxOC4yMzcxIDkxLjU0MjJDMTguMDY1MiA5MS42NDkgMTcuODU4MiA5MS43MDI0IDE3LjYxNiA5MS43MDI0QzE3LjM3MzggOTEuNzAyNCAxNy4xNjU0IDkxLjY0OSAxNi45OTEgOTEuNTQyMkMxNi44MTY1IDkxLjQzNTUgMTYuNjgxMSA5MS4yOTM1IDE2LjU4NDcgOTEuMTE2NUMxNi40OTEgOTAuOTM5NCAxNi40NDQxIDkwLjc0NDEgMTYuNDQ0MSA5MC41MzA1Wk0xNi45ODcxIDkwLjIyNThWOTAuNTMwNUMxNi45ODcxIDkwLjY1MDMgMTcuMDA5MiA5MC43NjQ5IDE3LjA1MzUgOTAuODc0M0MxNy4xMDAzIDkwLjk4MSAxNy4xNzA3IDkxLjA2ODMgMTcuMjY0NCA5MS4xMzZDMTcuMzU4MiA5MS4yMDExIDE3LjQ3NTMgOTEuMjMzNiAxNy42MTYgOTEuMjMzNkMxNy43NTY2IDkxLjIzMzYgMTcuODcyNSA5MS4yMDExIDE3Ljk2MzYgOTEuMTM2QzE4LjA1NzQgOTEuMDY4MyAxOC4xMjY0IDkwLjk4MSAxOC4xNzA3IDkwLjg3NDNDMTguMjE0OSA5MC43Njc1IDE4LjIzNzEgOTAuNjUyOSAxOC4yMzcxIDkwLjUzMDVWOTAuMjI1OEMxOC4yMzcxIDkwLjEwMzQgMTguMjEzNiA4OS45ODg5IDE4LjE2NjggODkuODgyMUMxOC4xMjI1IDg5Ljc3NTMgMTguMDUzNSA4OS42ODk0IDE3Ljk1OTcgODkuNjI0M0MxNy44Njg2IDg5LjU1NjYgMTcuNzUxNCA4OS41MjI3IDE3LjYwODIgODkuNTIyN0MxNy40NzAxIDg5LjUyMjcgMTcuMzU0MyA4OS41NTY2IDE3LjI2MDUgODkuNjI0M0MxNy4xNjk0IDg5LjY4OTQgMTcuMTAwMyA4OS43NzUzIDE3LjA1MzUgODkuODgyMUMxNy4wMDkyIDg5Ljk4ODkgMTYuOTg3MSA5MC4xMDM0IDE2Ljk4NzEgOTAuMjI1OFpNMTcuNzg3OCA4Ni43NDE1TDE1LjAxMDUgOTEuMTg2OEwxNC42MDQzIDkwLjkyOUwxNy4zODE2IDg2LjQ4MzZMMTcuNzg3OCA4Ni43NDE1WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNOC4xOTkyMiAxMTkuMjMzVjExOS44MjdINC40NzY1NlYxMTkuMzA4TDYuMzM5ODQgMTE3LjIzM0M2LjU2OTAxIDExNi45NzggNi43NDYwOSAxMTYuNzYyIDYuODcxMDkgMTE2LjU4NUM2Ljk5ODcgMTE2LjQwNSA3LjA4NzI0IDExNi4yNDUgNy4xMzY3MiAxMTYuMTA0QzcuMTg4OCAxMTUuOTYxIDcuMjE0ODQgMTE1LjgxNSA3LjIxNDg0IDExNS42NjdDNy4yMTQ4NCAxMTUuNDc5IDcuMTc1NzggMTE1LjMxIDcuMDk3NjYgMTE1LjE1OUM3LjAyMjE0IDExNS4wMDYgNi45MTAxNiAxMTQuODgzIDYuNzYxNzIgMTE0Ljc5MkM2LjYxMzI4IDExNC43MDEgNi40MzM1OSAxMTQuNjU1IDYuMjIyNjYgMTE0LjY1NUM1Ljk3MDA1IDExNC42NTUgNS43NTkxMSAxMTQuNzA1IDUuNTg5ODQgMTE0LjgwNEM1LjQyMzE4IDExNC45IDUuMjk4MTggMTE1LjAzNSA1LjIxNDg0IDExNS4yMUM1LjEzMTUxIDExNS4zODQgNS4wODk4NCAxMTUuNTg1IDUuMDg5ODQgMTE1LjgxMkg0LjM2NzE5QzQuMzY3MTkgMTE1LjQ5MSA0LjQzNzUgMTE1LjE5OCA0LjU3ODEyIDExNC45MzNDNC43MTg3NSAxMTQuNjY3IDQuOTI3MDggMTE0LjQ1NiA1LjIwMzEyIDExNC4zQzUuNDc5MTcgMTE0LjE0MSA1LjgxOTAxIDExNC4wNjIgNi4yMjI2NiAxMTQuMDYyQzYuNTgyMDMgMTE0LjA2MiA2Ljg4OTMyIDExNC4xMjUgNy4xNDQ1MyAxMTQuMjUzQzcuMzk5NzQgMTE0LjM3OCA3LjU5NTA1IDExNC41NTUgNy43MzA0NyAxMTQuNzg0QzcuODY4NDkgMTE1LjAxMSA3LjkzNzUgMTE1LjI3NiA3LjkzNzUgMTE1LjU4MUM3LjkzNzUgMTE1Ljc0OCA3LjkwODg1IDExNS45MTcgNy44NTE1NiAxMTYuMDg5QzcuNzk2ODggMTE2LjI1OCA3LjcyMDA1IDExNi40MjcgNy42MjEwOSAxMTYuNTk3QzcuNTI0NzQgMTE2Ljc2NiA3LjQxMTQ2IDExNi45MzMgNy4yODEyNSAxMTcuMDk3QzcuMTUzNjUgMTE3LjI2MSA3LjAxNjkzIDExNy40MjIgNi44NzEwOSAxMTcuNTgxTDUuMzQ3NjYgMTE5LjIzM0g4LjE5OTIyWk0xMi42NzUyIDExNi41M1YxMTcuMzk3QzEyLjY3NTIgMTE3Ljg2NCAxMi42MzM1IDExOC4yNTcgMTIuNTUwMiAxMTguNTc3QzEyLjQ2NjggMTE4Ljg5NyAxMi4zNDcgMTE5LjE1NSAxMi4xOTA4IDExOS4zNTFDMTIuMDM0NSAxMTkuNTQ2IDExLjg0NTcgMTE5LjY4OCAxMS42MjQ0IDExOS43NzZDMTEuNDA1NiAxMTkuODYyIDExLjE1ODIgMTE5LjkwNSAxMC44ODIyIDExOS45MDVDMTAuNjYzNSAxMTkuOTA1IDEwLjQ2MTYgMTE5Ljg3OCAxMC4yNzY3IDExOS44MjNDMTAuMDkxOCAxMTkuNzY5IDkuOTI1MTcgMTE5LjY4MSA5Ljc3NjczIDExOS41NjJDOS42MzA5IDExOS40MzkgOS41MDU5IDExOS4yOCA5LjQwMTczIDExOS4wODVDOS4yOTc1NyAxMTguODkgOS4yMTgxNCAxMTguNjUzIDkuMTYzNDUgMTE4LjM3NEM5LjEwODc3IDExOC4wOTUgOS4wODE0MiAxMTcuNzcgOS4wODE0MiAxMTcuMzk3VjExNi41M0M5LjA4MTQyIDExNi4wNjQgOS4xMjMwOSAxMTUuNjc0IDkuMjA2NDIgMTE1LjM1OEM5LjI5MjM2IDExNS4wNDMgOS40MTM0NSAxMTQuNzkxIDkuNTY5NyAxMTQuNjAxQzkuNzI1OTUgMTE0LjQwOCA5LjkxMzQ1IDExNC4yNyAxMC4xMzIyIDExNC4xODdDMTAuMzUzNiAxMTQuMTAzIDEwLjYwMSAxMTQuMDYyIDEwLjg3NDQgMTE0LjA2MkMxMS4wOTU3IDExNC4wNjIgMTEuMjk4OSAxMTQuMDg5IDExLjQ4MzggMTE0LjE0NEMxMS42NzEzIDExNC4xOTYgMTEuODM3OSAxMTQuMjggMTEuOTgzOCAxMTQuMzk3QzEyLjEyOTYgMTE0LjUxMiAxMi4yNTMzIDExNC42NjYgMTIuMzU0OSAxMTQuODU4QzEyLjQ1OSAxMTUuMDQ5IDEyLjUzODUgMTE1LjI4MiAxMi41OTMxIDExNS41NThDMTIuNjQ3OCAxMTUuODM0IDEyLjY3NTIgMTE2LjE1OCAxMi42NzUyIDExNi41M1pNMTEuOTQ4NiAxMTcuNTE1VjExNi40MDlDMTEuOTQ4NiAxMTYuMTU0IDExLjkzMyAxMTUuOTMgMTEuOTAxNyAxMTUuNzM3QzExLjg3MzEgMTE1LjU0MiAxMS44MzAxIDExNS4zNzUgMTEuNzcyOCAxMTUuMjM3QzExLjcxNTUgMTE1LjA5OSAxMS42NDI2IDExNC45ODcgMTEuNTU0MSAxMTQuOTAxQzExLjQ2ODEgMTE0LjgxNSAxMS4zNjc5IDExNC43NTMgMTEuMjUzMyAxMTQuNzE0QzExLjE0MTMgMTE0LjY3MiAxMS4wMTUgMTE0LjY1MSAxMC44NzQ0IDExNC42NTFDMTAuNzAyNSAxMTQuNjUxIDEwLjU1MDIgMTE0LjY4NCAxMC40MTc0IDExNC43NDlDMTAuMjg0NSAxMTQuODEyIDEwLjE3MjYgMTE0LjkxMiAxMC4wODE0IDExNS4wNUM5Ljk5Mjg4IDExNS4xODggOS45MjUxNyAxMTUuMzY5IDkuODc4MyAxMTUuNTkzQzkuODMxNDIgMTE1LjgxNyA5LjgwNzk4IDExNi4wODkgOS44MDc5OCAxMTYuNDA5VjExNy41MTVDOS44MDc5OCAxMTcuNzcgOS44MjIzMSAxMTcuOTk1IDkuODUwOTUgMTE4LjE5QzkuODgyMiAxMTguMzg2IDkuOTI3NzggMTE4LjU1NSA5Ljk4NzY3IDExOC42OThDMTAuMDQ3NiAxMTguODM5IDEwLjEyMDUgMTE4Ljk1NSAxMC4yMDY0IDExOS4wNDZDMTAuMjkyNCAxMTkuMTM3IDEwLjM5MTMgMTE5LjIwNSAxMC41MDMzIDExOS4yNDlDMTAuNjE3OSAxMTkuMjkxIDEwLjc0NDIgMTE5LjMxMiAxMC44ODIyIDExOS4zMTJDMTEuMDU5MyAxMTkuMzEyIDExLjIxNDIgMTE5LjI3OCAxMS4zNDcgMTE5LjIxQzExLjQ3OTkgMTE5LjE0MiAxMS41OTA1IDExOS4wMzcgMTEuNjc5MSAxMTguODk0QzExLjc3MDIgMTE4Ljc0OCAxMS44Mzc5IDExOC41NjIgMTEuODgyMiAxMTguMzM1QzExLjkyNjUgMTE4LjEwNiAxMS45NDg2IDExNy44MzIgMTEuOTQ4NiAxMTcuNTE1Wk0xMy42NzQ2IDExNS41MzRWMTE1LjIzM0MxMy42NzQ2IDExNS4wMTcgMTMuNzIxNCAxMTQuODIxIDEzLjgxNTIgMTE0LjY0NEMxMy45MDg5IDExNC40NjYgMTQuMDQzMSAxMTQuMzI1IDE0LjIxNzUgMTE0LjIxOEMxNC4zOTIgMTE0LjExMSAxNC41OTkgMTE0LjA1OCAxNC44Mzg2IDExNC4wNThDMTUuMDgzNCAxMTQuMDU4IDE1LjI5MTggMTE0LjExMSAxNS40NjM2IDExNC4yMThDMTUuNjM4MSAxMTQuMzI1IDE1Ljc3MjIgMTE0LjQ2NiAxNS44NjYgMTE0LjY0NEMxNS45NTk3IDExNC44MjEgMTYuMDA2NiAxMTUuMDE3IDE2LjAwNjYgMTE1LjIzM1YxMTUuNTM0QzE2LjAwNjYgMTE1Ljc0NSAxNS45NTk3IDExNS45MzkgMTUuODY2IDExNi4xMTZDMTUuNzc0OCAxMTYuMjkzIDE1LjY0MiAxMTYuNDM1IDE1LjQ2NzUgMTE2LjU0MkMxNS4yOTU3IDExNi42NDkgMTUuMDg4NiAxMTYuNzAyIDE0Ljg0NjQgMTE2LjcwMkMxNC42MDQzIDExNi43MDIgMTQuMzk0NiAxMTYuNjQ5IDE0LjIxNzUgMTE2LjU0MkMxNC4wNDMxIDExNi40MzUgMTMuOTA4OSAxMTYuMjkzIDEzLjgxNTIgMTE2LjExNkMxMy43MjE0IDExNS45MzkgMTMuNjc0NiAxMTUuNzQ1IDEzLjY3NDYgMTE1LjUzNFpNMTQuMjE3NSAxMTUuMjMzVjExNS41MzRDMTQuMjE3NSAxMTUuNjU0IDE0LjIzOTcgMTE1Ljc2NyAxNC4yODM5IDExNS44NzRDMTQuMzMwOCAxMTUuOTgxIDE0LjQwMTEgMTE2LjA2OCAxNC40OTQ5IDExNi4xMzZDMTQuNTg4NiAxMTYuMjAxIDE0LjcwNTggMTE2LjIzMyAxNC44NDY0IDExNi4yMzNDMTQuOTg3MSAxMTYuMjMzIDE1LjEwMjkgMTE2LjIwMSAxNS4xOTQxIDExNi4xMzZDMTUuMjg1MiAxMTYuMDY4IDE1LjM1MjkgMTE1Ljk4MSAxNS4zOTcyIDExNS44NzRDMTUuNDQxNSAxMTUuNzY3IDE1LjQ2MzYgMTE1LjY1NCAxNS40NjM2IDExNS41MzRWMTE1LjIzM0MxNS40NjM2IDExNS4xMTEgMTUuNDQwMiAxMTQuOTk2IDE1LjM5MzMgMTE0Ljg5QzE1LjM0OSAxMTQuNzggMTUuMjggMTE0LjY5MyAxNS4xODYzIDExNC42MjhDMTUuMDk1MSAxMTQuNTYgMTQuOTc5MyAxMTQuNTI2IDE0LjgzODYgMTE0LjUyNkMxNC43MDA2IDExNC41MjYgMTQuNTg0NyAxMTQuNTYgMTQuNDkxIDExNC42MjhDMTQuMzk5OCAxMTQuNjkzIDE0LjMzMDggMTE0Ljc4IDE0LjI4MzkgMTE0Ljg5QzE0LjIzOTcgMTE0Ljk5NiAxNC4yMTc1IDExNS4xMTEgMTQuMjE3NSAxMTUuMjMzWk0xNi40NDQxIDExOC43MzdWMTE4LjQzM0MxNi40NDQxIDExOC4yMTkgMTYuNDkxIDExOC4wMjQgMTYuNTg0NyAxMTcuODQ3QzE2LjY3ODUgMTE3LjY3IDE2LjgxMjYgMTE3LjUyOCAxNi45ODcxIDExNy40MjFDMTcuMTYxNSAxMTcuMzE0IDE3LjM2ODYgMTE3LjI2MSAxNy42MDgyIDExNy4yNjFDMTcuODUyOSAxMTcuMjYxIDE4LjA2MTMgMTE3LjMxNCAxOC4yMzMyIDExNy40MjFDMTguNDA3NiAxMTcuNTI4IDE4LjU0MTggMTE3LjY3IDE4LjYzNTUgMTE3Ljg0N0MxOC43MjkzIDExOC4wMjQgMTguNzc2MSAxMTguMjE5IDE4Ljc3NjEgMTE4LjQzM1YxMTguNzM3QzE4Ljc3NjEgMTE4Ljk1MSAxOC43MjkzIDExOS4xNDYgMTguNjM1NSAxMTkuMzIzQzE4LjU0NDQgMTE5LjUgMTguNDExNSAxMTkuNjQyIDE4LjIzNzEgMTE5Ljc0OUMxOC4wNjUyIDExOS44NTYgMTcuODU4MiAxMTkuOTA5IDE3LjYxNiAxMTkuOTA5QzE3LjM3MzggMTE5LjkwOSAxNy4xNjU0IDExOS44NTYgMTYuOTkxIDExOS43NDlDMTYuODE2NSAxMTkuNjQyIDE2LjY4MTEgMTE5LjUgMTYuNTg0NyAxMTkuMzIzQzE2LjQ5MSAxMTkuMTQ2IDE2LjQ0NDEgMTE4Ljk1MSAxNi40NDQxIDExOC43MzdaTTE2Ljk4NzEgMTE4LjQzM1YxMTguNzM3QzE2Ljk4NzEgMTE4Ljg1NyAxNy4wMDkyIDExOC45NzIgMTcuMDUzNSAxMTkuMDgxQzE3LjEwMDMgMTE5LjE4OCAxNy4xNzA3IDExOS4yNzUgMTcuMjY0NCAxMTkuMzQzQzE3LjM1ODIgMTE5LjQwOCAxNy40NzUzIDExOS40NCAxNy42MTYgMTE5LjQ0QzE3Ljc1NjYgMTE5LjQ0IDE3Ljg3MjUgMTE5LjQwOCAxNy45NjM2IDExOS4zNDNDMTguMDU3NCAxMTkuMjc1IDE4LjEyNjQgMTE5LjE4OCAxOC4xNzA3IDExOS4wODFDMTguMjE0OSAxMTguOTc0IDE4LjIzNzEgMTE4Ljg2IDE4LjIzNzEgMTE4LjczN1YxMTguNDMzQzE4LjIzNzEgMTE4LjMxIDE4LjIxMzYgMTE4LjE5NiAxOC4xNjY4IDExOC4wODlDMTguMTIyNSAxMTcuOTgyIDE4LjA1MzUgMTE3Ljg5NiAxNy45NTk3IDExNy44MzFDMTcuODY4NiAxMTcuNzYzIDE3Ljc1MTQgMTE3LjcyOSAxNy42MDgyIDExNy43MjlDMTcuNDcwMSAxMTcuNzI5IDE3LjM1NDMgMTE3Ljc2MyAxNy4yNjA1IDExNy44MzFDMTcuMTY5NCAxMTcuODk2IDE3LjEwMDMgMTE3Ljk4MiAxNy4wNTM1IDExOC4wODlDMTcuMDA5MiAxMTguMTk2IDE2Ljk4NzEgMTE4LjMxIDE2Ljk4NzEgMTE4LjQzM1pNMTcuNzg3OCAxMTQuOTQ4TDE1LjAxMDUgMTE5LjM5NEwxNC42MDQzIDExOS4xMzZMMTcuMzgxNiAxMTQuNjlMMTcuNzg3OCAxMTQuOTQ4WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNMTMuMDQzIDE0NC43MzdWMTQ1LjYwNEMxMy4wNDMgMTQ2LjA3IDEzLjAwMTMgMTQ2LjQ2NCAxMi45MTggMTQ2Ljc4NEMxMi44MzQ2IDE0Ny4xMDQgMTIuNzE0OCAxNDcuMzYyIDEyLjU1ODYgMTQ3LjU1N0MxMi40MDIzIDE0Ny43NTMgMTIuMjEzNSAxNDcuODk1IDExLjk5MjIgMTQ3Ljk4M0MxMS43NzM0IDE0OC4wNjkgMTEuNTI2IDE0OC4xMTIgMTEuMjUgMTQ4LjExMkMxMS4wMzEyIDE0OC4xMTIgMTAuODI5NCAxNDguMDg1IDEwLjY0NDUgMTQ4LjAzQzEwLjQ1OTYgMTQ3Ljk3NSAxMC4yOTMgMTQ3Ljg4OCAxMC4xNDQ1IDE0Ny43NjhDOS45OTg3IDE0Ny42NDYgOS44NzM3IDE0Ny40ODcgOS43Njk1MyAxNDcuMjkyQzkuNjY1MzYgMTQ3LjA5NiA5LjU4NTk0IDE0Ni44NTkgOS41MzEyNSAxNDYuNTgxQzkuNDc2NTYgMTQ2LjMwMiA5LjQ0OTIyIDE0NS45NzcgOS40NDkyMiAxNDUuNjA0VjE0NC43MzdDOS40NDkyMiAxNDQuMjcxIDkuNDkwODkgMTQzLjg4IDkuNTc0MjIgMTQzLjU2NUM5LjY2MDE2IDE0My4yNSA5Ljc4MTI1IDE0Mi45OTcgOS45Mzc1IDE0Mi44MDdDMTAuMDkzOCAxNDIuNjE1IDEwLjI4MTIgMTQyLjQ3NyAxMC41IDE0Mi4zOTNDMTAuNzIxNCAxNDIuMzEgMTAuOTY4OCAxNDIuMjY4IDExLjI0MjIgMTQyLjI2OEMxMS40NjM1IDE0Mi4yNjggMTEuNjY2NyAxNDIuMjk2IDExLjg1MTYgMTQyLjM1QzEyLjAzOTEgMTQyLjQwMiAxMi4yMDU3IDE0Mi40ODcgMTIuMzUxNiAxNDIuNjA0QzEyLjQ5NzQgMTQyLjcxOSAxMi42MjExIDE0Mi44NzIgMTIuNzIyNyAxNDMuMDY1QzEyLjgyNjggMTQzLjI1NSAxMi45MDYyIDE0My40ODggMTIuOTYwOSAxNDMuNzY0QzEzLjAxNTYgMTQ0LjA0IDEzLjA0MyAxNDQuMzY1IDEzLjA0MyAxNDQuNzM3Wk0xMi4zMTY0IDE0NS43MjFWMTQ0LjYxNkMxMi4zMTY0IDE0NC4zNjEgMTIuMzAwOCAxNDQuMTM3IDEyLjI2OTUgMTQzLjk0NEMxMi4yNDA5IDE0My43NDkgMTIuMTk3OSAxNDMuNTgyIDEyLjE0MDYgMTQzLjQ0NEMxMi4wODMzIDE0My4zMDYgMTIuMDEwNCAxNDMuMTk0IDExLjkyMTkgMTQzLjEwOEMxMS44MzU5IDE0My4wMjIgMTEuNzM1NyAxNDIuOTYgMTEuNjIxMSAxNDIuOTIxQzExLjUwOTEgMTQyLjg3OSAxMS4zODI4IDE0Mi44NTggMTEuMjQyMiAxNDIuODU4QzExLjA3MDMgMTQyLjg1OCAxMC45MTggMTQyLjg5MSAxMC43ODUyIDE0Mi45NTZDMTAuNjUyMyAxNDMuMDE4IDEwLjU0MDQgMTQzLjExOSAxMC40NDkyIDE0My4yNTdDMTAuMzYwNyAxNDMuMzk1IDEwLjI5MyAxNDMuNTc2IDEwLjI0NjEgMTQzLjhDMTAuMTk5MiAxNDQuMDI0IDEwLjE3NTggMTQ0LjI5NiAxMC4xNzU4IDE0NC42MTZWMTQ1LjcyMUMxMC4xNzU4IDE0NS45NzcgMTAuMTkwMSAxNDYuMjAyIDEwLjIxODggMTQ2LjM5N0MxMC4yNSAxNDYuNTkzIDEwLjI5NTYgMTQ2Ljc2MiAxMC4zNTU1IDE0Ni45MDVDMTAuNDE1NCAxNDcuMDQ2IDEwLjQ4ODMgMTQ3LjE2MiAxMC41NzQyIDE0Ny4yNTNDMTAuNjYwMiAxNDcuMzQ0IDEwLjc1OTEgMTQ3LjQxMiAxMC44NzExIDE0Ny40NTZDMTAuOTg1NyAxNDcuNDk3IDExLjExMiAxNDcuNTE4IDExLjI1IDE0Ny41MThDMTEuNDI3MSAxNDcuNTE4IDExLjU4MiAxNDcuNDg0IDExLjcxNDggMTQ3LjQxN0MxMS44NDc3IDE0Ny4zNDkgMTEuOTU4MyAxNDcuMjQ0IDEyLjA0NjkgMTQ3LjFDMTIuMTM4IDE0Ni45NTUgMTIuMjA1NyAxNDYuNzY4IDEyLjI1IDE0Ni41NDJDMTIuMjk0MyAxNDYuMzEzIDEyLjMxNjQgMTQ2LjAzOSAxMi4zMTY0IDE0NS43MjFaTTE0LjA0MjQgMTQzLjc0MVYxNDMuNDRDMTQuMDQyNCAxNDMuMjI0IDE0LjA4OTIgMTQzLjAyNyAxNC4xODMgMTQyLjg1QzE0LjI3NjcgMTQyLjY3MyAxNC40MTA4IDE0Mi41MzEgMTQuNTg1MyAxNDIuNDI1QzE0Ljc1OTggMTQyLjMxOCAxNC45NjY4IDE0Mi4yNjQgMTUuMjA2NCAxNDIuMjY0QzE1LjQ1MTIgMTQyLjI2NCAxNS42NTk1IDE0Mi4zMTggMTUuODMxNCAxNDIuNDI1QzE2LjAwNTkgMTQyLjUzMSAxNi4xNCAxNDIuNjczIDE2LjIzMzggMTQyLjg1QzE2LjMyNzUgMTQzLjAyNyAxNi4zNzQ0IDE0My4yMjQgMTYuMzc0NCAxNDMuNDRWMTQzLjc0MUMxNi4zNzQ0IDE0My45NTIgMTYuMzI3NSAxNDQuMTQ2IDE2LjIzMzggMTQ0LjMyM0MxNi4xNDI2IDE0NC41IDE2LjAwOTggMTQ0LjY0MiAxNS44MzUzIDE0NC43NDlDMTUuNjYzNSAxNDQuODU2IDE1LjQ1NjQgMTQ0LjkwOSAxNS4yMTQyIDE0NC45MDlDMTQuOTcyIDE0NC45MDkgMTQuNzYyNCAxNDQuODU2IDE0LjU4NTMgMTQ0Ljc0OUMxNC40MTA4IDE0NC42NDIgMTQuMjc2NyAxNDQuNSAxNC4xODMgMTQ0LjMyM0MxNC4wODkyIDE0NC4xNDYgMTQuMDQyNCAxNDMuOTUyIDE0LjA0MjQgMTQzLjc0MVpNMTQuNTg1MyAxNDMuNDRWMTQzLjc0MUMxNC41ODUzIDE0My44NjEgMTQuNjA3NSAxNDMuOTc0IDE0LjY1MTcgMTQ0LjA4MUMxNC42OTg2IDE0NC4xODggMTQuNzY4OSAxNDQuMjc1IDE0Ljg2MjcgMTQ0LjM0M0MxNC45NTY0IDE0NC40MDggMTUuMDczNiAxNDQuNDQgMTUuMjE0MiAxNDQuNDRDMTUuMzU0OSAxNDQuNDQgMTUuNDcwNyAxNDQuNDA4IDE1LjU2MTkgMTQ0LjM0M0MxNS42NTMgMTQ0LjI3NSAxNS43MjA3IDE0NC4xODggMTUuNzY1IDE0NC4wODFDMTUuODA5MyAxNDMuOTc0IDE1LjgzMTQgMTQzLjg2MSAxNS44MzE0IDE0My43NDFWMTQzLjQ0QzE1LjgzMTQgMTQzLjMxOCAxNS44MDggMTQzLjIwMyAxNS43NjExIDE0My4wOTZDMTUuNzE2OCAxNDIuOTg3IDE1LjY0NzggMTQyLjkgMTUuNTU0MSAxNDIuODM1QzE1LjQ2MjkgMTQyLjc2NyAxNS4zNDcgMTQyLjczMyAxNS4yMDY0IDE0Mi43MzNDMTUuMDY4NCAxNDIuNzMzIDE0Ljk1MjUgMTQyLjc2NyAxNC44NTg4IDE0Mi44MzVDMTQuNzY3NiAxNDIuOSAxNC42OTg2IDE0Mi45ODcgMTQuNjUxNyAxNDMuMDk2QzE0LjYwNzUgMTQzLjIwMyAxNC41ODUzIDE0My4zMTggMTQuNTg1MyAxNDMuNDRaTTE2LjgxMTkgMTQ2Ljk0NFYxNDYuNjM5QzE2LjgxMTkgMTQ2LjQyNiAxNi44NTg4IDE0Ni4yMzEgMTYuOTUyNSAxNDYuMDUzQzE3LjA0NjMgMTQ1Ljg3NiAxNy4xODA0IDE0NS43MzQgMTcuMzU0OSAxNDUuNjI4QzE3LjUyOTMgMTQ1LjUyMSAxNy43MzY0IDE0NS40NjggMTcuOTc2IDE0NS40NjhDMTguMjIwNyAxNDUuNDY4IDE4LjQyOTEgMTQ1LjUyMSAxOC42MDEgMTQ1LjYyOEMxOC43NzU0IDE0NS43MzQgMTguOTA5NSAxNDUuODc2IDE5LjAwMzMgMTQ2LjA1M0MxOS4wOTcgMTQ2LjIzMSAxOS4xNDM5IDE0Ni40MjYgMTkuMTQzOSAxNDYuNjM5VjE0Ni45NDRDMTkuMTQzOSAxNDcuMTU4IDE5LjA5NyAxNDcuMzUzIDE5LjAwMzMgMTQ3LjUzQzE4LjkxMjIgMTQ3LjcwNyAxOC43NzkzIDE0Ny44NDkgMTguNjA0OSAxNDcuOTU2QzE4LjQzMyAxNDguMDYzIDE4LjIyNiAxNDguMTE2IDE3Ljk4MzggMTQ4LjExNkMxNy43NDE2IDE0OC4xMTYgMTcuNTMzMiAxNDguMDYzIDE3LjM1ODggMTQ3Ljk1NkMxNy4xODQzIDE0Ny44NDkgMTcuMDQ4OSAxNDcuNzA3IDE2Ljk1MjUgMTQ3LjUzQzE2Ljg1ODggMTQ3LjM1MyAxNi44MTE5IDE0Ny4xNTggMTYuODExOSAxNDYuOTQ0Wk0xNy4zNTQ5IDE0Ni42MzlWMTQ2Ljk0NEMxNy4zNTQ5IDE0Ny4wNjQgMTcuMzc3IDE0Ny4xNzggMTcuNDIxMyAxNDcuMjg4QzE3LjQ2ODEgMTQ3LjM5NSAxNy41Mzg1IDE0Ny40ODIgMTcuNjMyMiAxNDcuNTVDMTcuNzI2IDE0Ny42MTUgMTcuODQzMSAxNDcuNjQ3IDE3Ljk4MzggMTQ3LjY0N0MxOC4xMjQ0IDE0Ny42NDcgMTguMjQwMyAxNDcuNjE1IDE4LjMzMTQgMTQ3LjU1QzE4LjQyNTIgMTQ3LjQ4MiAxOC40OTQyIDE0Ny4zOTUgMTguNTM4NSAxNDcuMjg4QzE4LjU4MjcgMTQ3LjE4MSAxOC42MDQ5IDE0Ny4wNjYgMTguNjA0OSAxNDYuOTQ0VjE0Ni42MzlDMTguNjA0OSAxNDYuNTE3IDE4LjU4MTQgMTQ2LjQwMiAxOC41MzQ1IDE0Ni4yOTZDMTguNDkwMyAxNDYuMTg5IDE4LjQyMTMgMTQ2LjEwMyAxOC4zMjc1IDE0Ni4wMzhDMTguMjM2NCAxNDUuOTcgMTguMTE5MiAxNDUuOTM2IDE3Ljk3NiAxNDUuOTM2QzE3LjgzNzkgMTQ1LjkzNiAxNy43MjIgMTQ1Ljk3IDE3LjYyODMgMTQ2LjAzOEMxNy41MzcyIDE0Ni4xMDMgMTcuNDY4MSAxNDYuMTg5IDE3LjQyMTMgMTQ2LjI5NkMxNy4zNzcgMTQ2LjQwMiAxNy4zNTQ5IDE0Ni41MTcgMTcuMzU0OSAxNDYuNjM5Wk0xOC4xNTU2IDE0My4xNTVMMTUuMzc4MyAxNDcuNkwxNC45NzIgMTQ3LjM0M0wxNy43NDk0IDE0Mi44OTdMMTguMTU1NiAxNDMuMTU1WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNMjUgNC4xNjExM0wyMDAgNC4xNjExNiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPHBhdGggZD0iTTI1IDMzLjE2MTFMMjAwIDMzLjE2MTIiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIi8+CjxwYXRoIGQ9Ik0yNSA2MS4xNjExTDIwMCA2MS4xNjEyIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIvPgo8cGF0aCBkPSJNMjUgODkuMTYxMUwyMDAgODkuMTYxMiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPHBhdGggZD0iTTI1IDExOC4xNjFMMjAwIDExOC4xNjEiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIi8+CjxsaW5lIHgxPSIyMy4yIiB5MT0iMTQ1Ljk2MSIgeDI9IjIwMi44IiB5Mj0iMTQ1Ljk2MSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuNyIgc3Ryb2tlLXdpZHRoPSIwLjQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiLz4KPGxpbmUgeDE9IjQwLjQ1IiB5MT0iMTQ4LjA3MiIgeDI9IjQwLjQ1IiB5Mj0iMTQ3LjI1IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNMzIuNTA5MSAxNTIuMDI1VjE1Mi44OTNDMzIuNTA5MSAxNTMuMzU5IDMyLjQ2NzQgMTUzLjc1MiAzMi4zODQxIDE1NC4wNzJDMzIuMzAwNyAxNTQuMzkzIDMyLjE4MDkgMTU0LjY1IDMyLjAyNDcgMTU0Ljg0NkMzMS44Njg0IDE1NS4wNDEgMzEuNjc5NiAxNTUuMTgzIDMxLjQ1ODMgMTU1LjI3MUMzMS4yMzk1IDE1NS4zNTcgMzAuOTkyMSAxNTUuNCAzMC43MTYxIDE1NS40QzMwLjQ5NzMgMTU1LjQgMzAuMjk1NSAxNTUuMzczIDMwLjExMDYgMTU1LjMxOEMyOS45MjU3IDE1NS4yNjQgMjkuNzU5MSAxNTUuMTc2IDI5LjYxMDYgMTU1LjA1N0MyOS40NjQ4IDE1NC45MzQgMjkuMzM5OCAxNTQuNzc1IDI5LjIzNTYgMTU0LjU4QzI5LjEzMTUgMTU0LjM4NSAyOS4wNTIgMTU0LjE0OCAyOC45OTczIDE1My44NjlDMjguOTQyNyAxNTMuNTkgMjguOTE1MyAxNTMuMjY1IDI4LjkxNTMgMTUyLjg5M1YxNTIuMDI1QzI4LjkxNTMgMTUxLjU1OSAyOC45NTcgMTUxLjE2OSAyOS4wNDAzIDE1MC44NTRDMjkuMTI2MiAxNTAuNTM4IDI5LjI0NzMgMTUwLjI4NiAyOS40MDM2IDE1MC4wOTZDMjkuNTU5OCAxNDkuOTAzIDI5Ljc0NzMgMTQ5Ljc2NSAyOS45NjYxIDE0OS42ODJDMzAuMTg3NCAxNDkuNTk4IDMwLjQzNDggMTQ5LjU1NyAzMC43MDgzIDE0OS41NTdDMzAuOTI5NiAxNDkuNTU3IDMxLjEzMjggMTQ5LjU4NCAzMS4zMTc3IDE0OS42MzlDMzEuNTA1MiAxNDkuNjkxIDMxLjY3MTggMTQ5Ljc3NSAzMS44MTc3IDE0OS44OTNDMzEuOTYzNSAxNTAuMDA3IDMyLjA4NzIgMTUwLjE2MSAzMi4xODg3IDE1MC4zNTRDMzIuMjkyOSAxNTAuNTQ0IDMyLjM3MjMgMTUwLjc3NyAzMi40MjcgMTUxLjA1M0MzMi40ODE3IDE1MS4zMjkgMzIuNTA5MSAxNTEuNjUzIDMyLjUwOTEgMTUyLjAyNVpNMzEuNzgyNSAxNTMuMDFWMTUxLjkwNEMzMS43ODI1IDE1MS42NDkgMzEuNzY2OSAxNTEuNDI1IDMxLjczNTYgMTUxLjIzMkMzMS43MDcgMTUxLjAzNyAzMS42NjQgMTUwLjg3IDMxLjYwNjcgMTUwLjczMkMzMS41NDk0IDE1MC41OTQgMzEuNDc2NSAxNTAuNDgyIDMxLjM4OCAxNTAuMzk2QzMxLjMwMiAxNTAuMzExIDMxLjIwMTggMTUwLjI0OCAzMS4wODcyIDE1MC4yMDlDMzAuOTc1MiAxNTAuMTY3IDMwLjg0ODkgMTUwLjE0NiAzMC43MDgzIDE1MC4xNDZDMzAuNTM2NCAxNTAuMTQ2IDMwLjM4NDEgMTUwLjE3OSAzMC4yNTEyIDE1MC4yNDRDMzAuMTE4NCAxNTAuMzA3IDMwLjAwNjUgMTUwLjQwNyAyOS45MTUzIDE1MC41NDVDMjkuODI2OCAxNTAuNjgzIDI5Ljc1OTEgMTUwLjg2NCAyOS43MTIyIDE1MS4wODhDMjkuNjY1MyAxNTEuMzEyIDI5LjY0MTkgMTUxLjU4NCAyOS42NDE5IDE1MS45MDRWMTUzLjAxQzI5LjY0MTkgMTUzLjI2NSAyOS42NTYyIDE1My40OSAyOS42ODQ4IDE1My42ODZDMjkuNzE2MSAxNTMuODgxIDI5Ljc2MTcgMTU0LjA1IDI5LjgyMTYgMTU0LjE5M0MyOS44ODE1IDE1NC4zMzQgMjkuOTU0NCAxNTQuNDUgMzAuMDQwMyAxNTQuNTQxQzMwLjEyNjIgMTU0LjYzMiAzMC4yMjUyIDE1NC43IDMwLjMzNzIgMTU0Ljc0NEMzMC40NTE4IDE1NC43ODYgMzAuNTc4MSAxNTQuODA3IDMwLjcxNjEgMTU0LjgwN0MzMC44OTMyIDE1NC44MDcgMzEuMDQ4MSAxNTQuNzczIDMxLjE4MDkgMTU0LjcwNUMzMS4zMTM3IDE1NC42MzcgMzEuNDI0NCAxNTQuNTMyIDMxLjUxMyAxNTQuMzg5QzMxLjYwNDEgMTU0LjI0MyAzMS42NzE4IDE1NC4wNTcgMzEuNzE2MSAxNTMuODNDMzEuNzYwNCAxNTMuNjAxIDMxLjc4MjUgMTUzLjMyNyAzMS43ODI1IDE1My4wMVpNMzUuODk2NCAxNDkuNjA0VjE1NS4zMjJIMzUuMTczN1YxNTAuNTA2TDMzLjcxNjcgMTUxLjAzN1YxNTAuMzg1TDM1Ljc4MzEgMTQ5LjYwNEgzNS44OTY0Wk00MS4xMTI0IDE0OS42MzVWMTU1LjMyMkg0MC4zNTg1VjE0OS42MzVINDEuMTEyNFpNNDMuNDk1MiAxNTIuMTkzVjE1Mi44MTFINDAuOTQ4M1YxNTIuMTkzSDQzLjQ5NTJaTTQzLjg4MTkgMTQ5LjYzNVYxNTAuMjUySDQwLjk0ODNWMTQ5LjYzNUg0My44ODE5Wk00Ni40MjE2IDE1NS40QzQ2LjEyNzMgMTU1LjQgNDUuODYwNCAxNTUuMzUxIDQ1LjYyMDggMTU1LjI1MkM0NS4zODM4IDE1NS4xNSA0NS4xNzk0IDE1NS4wMDggNDUuMDA3NSAxNTQuODI2QzQ0LjgzODMgMTU0LjY0NCA0NC43MDgxIDE1NC40MjggNDQuNjE2OSAxNTQuMTc4QzQ0LjUyNTggMTUzLjkyOCA0NC40ODAyIDE1My42NTQgNDQuNDgwMiAxNTMuMzU3VjE1My4xOTNDNDQuNDgwMiAxNTIuODUgNDQuNTMxIDE1Mi41NDQgNDQuNjMyNSAxNTIuMjc1QzQ0LjczNDEgMTUyLjAwNSA0NC44NzIxIDE1MS43NzUgNDUuMDQ2NiAxNTEuNTg4QzQ1LjIyMTEgMTUxLjQgNDUuNDE5IDE1MS4yNTggNDUuNjQwMyAxNTEuMTYyQzQ1Ljg2MTcgMTUxLjA2NiA0Ni4wOTA5IDE1MS4wMTggNDYuMzI3OCAxNTEuMDE4QzQ2LjYyOTkgMTUxLjAxOCA0Ni44OTAzIDE1MS4wNyA0Ny4xMDkxIDE1MS4xNzRDNDcuMzMwNSAxNTEuMjc4IDQ3LjUxMTQgMTUxLjQyNCA0Ny42NTIxIDE1MS42MTFDNDcuNzkyNyAxNTEuNzk2IDQ3Ljg5NjkgMTUyLjAxNSA0Ny45NjQ2IDE1Mi4yNjhDNDguMDMyMyAxNTIuNTE4IDQ4LjA2NjEgMTUyLjc5MSA0OC4wNjYxIDE1My4wODhWMTUzLjQxMkg0NC45MDk5VjE1Mi44MjJINDcuMzQzNVYxNTIuNzY4QzQ3LjMzMzEgMTUyLjU4IDQ3LjI5NCAxNTIuMzk4IDQ3LjIyNjMgMTUyLjIyMUM0Ny4xNjEyIDE1Mi4wNDQgNDcuMDU3IDE1MS44OTggNDYuOTEzOCAxNTEuNzgzQzQ2Ljc3MDYgMTUxLjY2OSA0Ni41NzUyIDE1MS42MTEgNDYuMzI3OCAxNTEuNjExQzQ2LjE2MzggMTUxLjYxMSA0Ni4wMTI3IDE1MS42NDYgNDUuODc0NyAxNTEuNzE3QzQ1LjczNjcgMTUxLjc4NSA0NS42MTgyIDE1MS44ODYgNDUuNTE5MyAxNTIuMDIxQzQ1LjQyMDMgMTUyLjE1NyA0NS4zNDM1IDE1Mi4zMjIgNDUuMjg4OCAxNTIuNTE4QzQ1LjIzNDEgMTUyLjcxMyA0NS4yMDY4IDE1Mi45MzggNDUuMjA2OCAxNTMuMTkzVjE1My4zNTdDNDUuMjA2OCAxNTMuNTU4IDQ1LjIzNDEgMTUzLjc0NyA0NS4yODg4IDE1My45MjRDNDUuMzQ2MSAxNTQuMDk4IDQ1LjQyODEgMTU0LjI1MiA0NS41MzQ5IDE1NC4zODVDNDUuNjQ0MyAxNTQuNTE4IDQ1Ljc3NTggMTU0LjYyMiA0NS45Mjk0IDE1NC42OTdDNDYuMDg1NyAxNTQuNzczIDQ2LjI2MjcgMTU0LjgxMSA0Ni40NjA3IDE1NC44MTFDNDYuNzE1OSAxNTQuODExIDQ2LjkzMiAxNTQuNzU4IDQ3LjEwOTEgMTU0LjY1NEM0Ny4yODYyIDE1NC41NSA0Ny40NDExIDE1NC40MTEgNDcuNTczOSAxNTQuMjM2TDQ4LjAxMTQgMTU0LjU4NEM0Ny45MjAzIDE1NC43MjIgNDcuODA0NCAxNTQuODU0IDQ3LjY2MzggMTU0Ljk3OUM0Ny41MjMyIDE1NS4xMDQgNDcuMzUgMTU1LjIwNSA0Ny4xNDQzIDE1NS4yODNDNDYuOTQxMSAxNTUuMzYxIDQ2LjcwMDIgMTU1LjQgNDYuNDIxNiAxNTUuNFpNNDguOTg4NiAxNDkuMzIySDQ5LjcxNTJWMTU0LjUwMkw0OS42NTI3IDE1NS4zMjJINDguOTg4NlYxNDkuMzIyWk01Mi41NzA2IDE1My4xNzRWMTUzLjI1NkM1Mi41NzA2IDE1My41NjMgNTIuNTM0MiAxNTMuODQ4IDUyLjQ2MTMgMTU0LjExMUM1Mi4zODgzIDE1NC4zNzIgNTIuMjgxNiAxNTQuNTk4IDUyLjE0MDkgMTU0Ljc5MUM1Mi4wMDAzIDE1NC45ODQgNTEuODI4NCAxNTUuMTMzIDUxLjYyNTMgMTU1LjI0QzUxLjQyMjIgMTU1LjM0NyA1MS4xODkxIDE1NS40IDUwLjkyNjEgMTU1LjRDNTAuNjU3OSAxNTUuNCA1MC40MjIyIDE1NS4zNTUgNTAuMjE5MSAxNTUuMjY0QzUwLjAxODUgMTU1LjE3IDQ5Ljg0OTMgMTU1LjAzNiA0OS43MTEzIDE1NC44NjFDNDkuNTczMiAxNTQuNjg3IDQ5LjQ2MjYgMTU0LjQ3NiA0OS4zNzkyIDE1NC4yMjlDNDkuMjk4NSAxNTMuOTgxIDQ5LjI0MjUgMTUzLjcwMiA0OS4yMTEzIDE1My4zOTNWMTUzLjAzM0M0OS4yNDI1IDE1Mi43MjEgNDkuMjk4NSAxNTIuNDQxIDQ5LjM3OTIgMTUyLjE5M0M0OS40NjI2IDE1MS45NDYgNDkuNTczMiAxNTEuNzM1IDQ5LjcxMTMgMTUxLjU2MUM0OS44NDkzIDE1MS4zODMgNTAuMDE4NSAxNTEuMjQ5IDUwLjIxOTEgMTUxLjE1OEM1MC40MTk2IDE1MS4wNjQgNTAuNjUyNyAxNTEuMDE4IDUwLjkxODMgMTUxLjAxOEM1MS4xODM5IDE1MS4wMTggNTEuNDE5NiAxNTEuMDcgNTEuNjI1MyAxNTEuMTc0QzUxLjgzMSAxNTEuMjc1IDUyLjAwMjkgMTUxLjQyMSA1Mi4xNDA5IDE1MS42MTFDNTIuMjgxNiAxNTEuODAxIDUyLjM4ODMgMTUyLjAyOSA1Mi40NjEzIDE1Mi4yOTVDNTIuNTM0MiAxNTIuNTU4IDUyLjU3MDYgMTUyLjg1MSA1Mi41NzA2IDE1My4xNzRaTTUxLjg0NDEgMTUzLjI1NlYxNTMuMTc0QzUxLjg0NDEgMTUyLjk2MyA1MS44MjQ1IDE1Mi43NjUgNTEuNzg1NSAxNTIuNThDNTEuNzQ2NCAxNTIuMzkzIDUxLjY4MzkgMTUyLjIyOSA1MS41OTggMTUyLjA4OEM1MS41MTIgMTUxLjk0NSA1MS4zOTg4IDE1MS44MzMgNTEuMjU4MSAxNTEuNzUyQzUxLjExNzUgMTUxLjY2OSA1MC45NDQzIDE1MS42MjcgNTAuNzM4NiAxNTEuNjI3QzUwLjU1NjMgMTUxLjYyNyA1MC4zOTc1IDE1MS42NTggNTAuMjYyIDE1MS43MjFDNTAuMTI5MiAxNTEuNzgzIDUwLjAxNTkgMTUxLjg2OCA0OS45MjIyIDE1MS45NzVDNDkuODI4NCAxNTIuMDc5IDQ5Ljc1MTYgMTUyLjE5OSA0OS42OTE3IDE1Mi4zMzRDNDkuNjM0NCAxNTIuNDY3IDQ5LjU5MTUgMTUyLjYwNSA0OS41NjI4IDE1Mi43NDhWMTUzLjY4OUM0OS42MDQ1IDE1My44NzIgNDkuNjcyMiAxNTQuMDQ4IDQ5Ljc2NTkgMTU0LjIxN0M0OS44NjIzIDE1NC4zODMgNDkuOTg5OSAxNTQuNTIgNTAuMTQ4OCAxNTQuNjI3QzUwLjMxMDIgMTU0LjczNCA1MC41MDk0IDE1NC43ODcgNTAuNzQ2NCAxNTQuNzg3QzUwLjk0MTcgMTU0Ljc4NyA1MS4xMDg0IDE1NC43NDggNTEuMjQ2NCAxNTQuNjdDNTEuMzg3IDE1NC41ODkgNTEuNTAwMyAxNTQuNDc5IDUxLjU4NjMgMTU0LjMzOEM1MS42NzQ4IDE1NC4xOTcgNTEuNzM5OSAxNTQuMDM1IDUxLjc4MTYgMTUzLjg1QzUxLjgyMzIgMTUzLjY2NSA1MS44NDQxIDE1My40NjcgNTEuODQ0MSAxNTMuMjU2WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDFfNDE4NF85NDUyNykiPgo8bGluZSB4MT0iNzUuODQ5OSIgeTE9IjE0Ny4wNzIiIHgyPSI3NS44NDk5IiB5Mj0iMTQ2LjI1IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNNjcuOTA5IDE1Mi4wMjVWMTUyLjg5MkM2Ny45MDkgMTUzLjM1OCA2Ny44NjczIDE1My43NTIgNjcuNzg0IDE1NC4wNzJDNjcuNzAwNiAxNTQuMzkyIDY3LjU4MDggMTU0LjY1IDY3LjQyNDYgMTU0Ljg0NUM2Ny4yNjgzIDE1NS4wNDEgNjcuMDc5NSAxNTUuMTgzIDY2Ljg1ODIgMTU1LjI3MUM2Ni42Mzk0IDE1NS4zNTcgNjYuMzkyIDE1NS40IDY2LjExNiAxNTUuNEM2NS44OTcyIDE1NS40IDY1LjY5NTQgMTU1LjM3MyA2NS41MTA1IDE1NS4zMThDNjUuMzI1NiAxNTUuMjYzIDY1LjE1OSAxNTUuMTc2IDY1LjAxMDUgMTU1LjA1NkM2NC44NjQ3IDE1NC45MzQgNjQuNzM5NyAxNTQuNzc1IDY0LjYzNTUgMTU0LjU4QzY0LjUzMTQgMTU0LjM4NSA2NC40NTE5IDE1NC4xNDggNjQuMzk3MiAxNTMuODY5QzY0LjM0MjYgMTUzLjU5IDY0LjMxNTIgMTUzLjI2NSA2NC4zMTUyIDE1Mi44OTJWMTUyLjAyNUM2NC4zMTUyIDE1MS41NTkgNjQuMzU2OSAxNTEuMTY4IDY0LjQ0MDIgMTUwLjg1M0M2NC41MjYxIDE1MC41MzggNjQuNjQ3MiAxNTAuMjg2IDY0LjgwMzUgMTUwLjA5NUM2NC45NTk3IDE0OS45MDMgNjUuMTQ3MiAxNDkuNzY1IDY1LjM2NiAxNDkuNjgxQzY1LjU4NzMgMTQ5LjU5OCA2NS44MzQ3IDE0OS41NTYgNjYuMTA4MiAxNDkuNTU2QzY2LjMyOTUgMTQ5LjU1NiA2Ni41MzI3IDE0OS41ODQgNjYuNzE3NiAxNDkuNjM4QzY2LjkwNTEgMTQ5LjY5MSA2Ny4wNzE3IDE0OS43NzUgNjcuMjE3NiAxNDkuODkyQzY3LjM2MzQgMTUwLjAwNyA2Ny40ODcxIDE1MC4xNjEgNjcuNTg4NiAxNTAuMzUzQzY3LjY5MjggMTUwLjU0MyA2Ny43NzIyIDE1MC43NzYgNjcuODI2OSAxNTEuMDUyQzY3Ljg4MTYgMTUxLjMyOSA2Ny45MDkgMTUxLjY1MyA2Ny45MDkgMTUyLjAyNVpNNjcuMTgyNCAxNTMuMDFWMTUxLjkwNEM2Ny4xODI0IDE1MS42NDkgNjcuMTY2OCAxNTEuNDI1IDY3LjEzNTUgMTUxLjIzMkM2Ny4xMDY5IDE1MS4wMzcgNjcuMDYzOSAxNTAuODcgNjcuMDA2NiAxNTAuNzMyQzY2Ljk0OTMgMTUwLjU5NCA2Ni44NzY0IDE1MC40ODIgNjYuNzg3OSAxNTAuMzk2QzY2LjcwMTkgMTUwLjMxIDY2LjYwMTcgMTUwLjI0OCA2Ni40ODcxIDE1MC4yMDlDNjYuMzc1MSAxNTAuMTY3IDY2LjI0ODggMTUwLjE0NiA2Ni4xMDgyIDE1MC4xNDZDNjUuOTM2MyAxNTAuMTQ2IDY1Ljc4NCAxNTAuMTc5IDY1LjY1MTEgMTUwLjI0NEM2NS41MTgzIDE1MC4zMDYgNjUuNDA2NCAxNTAuNDA3IDY1LjMxNTIgMTUwLjU0NUM2NS4yMjY3IDE1MC42ODMgNjUuMTU5IDE1MC44NjQgNjUuMTEyMSAxNTEuMDg4QzY1LjA2NTIgMTUxLjMxMiA2NS4wNDE4IDE1MS41ODQgNjUuMDQxOCAxNTEuOTA0VjE1My4wMUM2NS4wNDE4IDE1My4yNjUgNjUuMDU2MSAxNTMuNDkgNjUuMDg0NyAxNTMuNjg1QzY1LjExNiAxNTMuODgxIDY1LjE2MTYgMTU0LjA1IDY1LjIyMTUgMTU0LjE5M0M2NS4yODE0IDE1NC4zMzQgNjUuMzU0MyAxNTQuNDUgNjUuNDQwMiAxNTQuNTQxQzY1LjUyNjEgMTU0LjYzMiA2NS42MjUxIDE1NC43IDY1LjczNzEgMTU0Ljc0NEM2NS44NTE3IDE1NC43ODYgNjUuOTc4IDE1NC44MDYgNjYuMTE2IDE1NC44MDZDNjYuMjkzMSAxNTQuODA2IDY2LjQ0OCAxNTQuNzczIDY2LjU4MDggMTU0LjcwNUM2Ni43MTM2IDE1NC42MzcgNjYuODI0MyAxNTQuNTMyIDY2LjkxMjkgMTU0LjM4OEM2Ny4wMDQgMTU0LjI0MyA2Ny4wNzE3IDE1NC4wNTYgNjcuMTE2IDE1My44M0M2Ny4xNjAzIDE1My42MDEgNjcuMTgyNCAxNTMuMzI3IDY3LjE4MjQgMTUzLjAxWk03Mi42NDc4IDE1NC43MjhWMTU1LjMyMkg2OC45MjUyVjE1NC44MDJMNzAuNzg4NSAxNTIuNzI4QzcxLjAxNzYgMTUyLjQ3MyA3MS4xOTQ3IDE1Mi4yNTcgNzEuMzE5NyAxNTIuMDhDNzEuNDQ3MyAxNTEuOSA3MS41MzU5IDE1MS43NCA3MS41ODUzIDE1MS41OTlDNzEuNjM3NCAxNTEuNDU2IDcxLjY2MzUgMTUxLjMxIDcxLjY2MzUgMTUxLjE2MkM3MS42NjM1IDE1MC45NzQgNzEuNjI0NCAxNTAuODA1IDcxLjU0NjMgMTUwLjY1NEM3MS40NzA4IDE1MC41IDcxLjM1ODggMTUwLjM3OCA3MS4yMTAzIDE1MC4yODdDNzEuMDYxOSAxNTAuMTk2IDcwLjg4MjIgMTUwLjE1IDcwLjY3MTMgMTUwLjE1QzcwLjQxODcgMTUwLjE1IDcwLjIwNzcgMTUwLjIgNzAuMDM4NSAxNTAuMjk5QzY5Ljg3MTggMTUwLjM5NSA2OS43NDY4IDE1MC41MyA2OS42NjM1IDE1MC43MDVDNjkuNTgwMSAxNTAuODc5IDY5LjUzODUgMTUxLjA4IDY5LjUzODUgMTUxLjMwNkg2OC44MTU4QzY4LjgxNTggMTUwLjk4NiA2OC44ODYxIDE1MC42OTMgNjkuMDI2NyAxNTAuNDI3QzY5LjE2NzQgMTUwLjE2MiA2OS4zNzU3IDE0OS45NTEgNjkuNjUxNyAxNDkuNzk1QzY5LjkyNzggMTQ5LjYzNiA3MC4yNjc2IDE0OS41NTYgNzAuNjcxMyAxNDkuNTU2QzcxLjAzMDcgMTQ5LjU1NiA3MS4zMzc5IDE0OS42MiA3MS41OTMyIDE0OS43NDhDNzEuODQ4NCAxNDkuODczIDcyLjA0MzcgMTUwLjA1IDcyLjE3OTEgMTUwLjI3OUM3Mi4zMTcxIDE1MC41MDYgNzIuMzg2MSAxNTAuNzcxIDcyLjM4NjEgMTUxLjA3NkM3Mi4zODYxIDE1MS4yNDMgNzIuMzU3NSAxNTEuNDEyIDcyLjMwMDIgMTUxLjU4NEM3Mi4yNDU1IDE1MS43NTMgNzIuMTY4NyAxNTEuOTIyIDcyLjA2OTcgMTUyLjA5MkM3MS45NzM0IDE1Mi4yNjEgNzEuODYwMSAxNTIuNDI3IDcxLjcyOTkgMTUyLjU5MkM3MS42MDIzIDE1Mi43NTYgNzEuNDY1NSAxNTIuOTE3IDcxLjMxOTcgMTUzLjA3Nkw2OS43OTYzIDE1NC43MjhINzIuNjQ3OFpNNzYuNTEyMyAxNDkuNjM1VjE1NS4zMjJINzUuNzU4NFYxNDkuNjM1SDc2LjUxMjNaTTc4Ljg5NTEgMTUyLjE5M1YxNTIuODFINzYuMzQ4MlYxNTIuMTkzSDc4Ljg5NTFaTTc5LjI4MTggMTQ5LjYzNVYxNTAuMjUySDc2LjM0ODJWMTQ5LjYzNUg3OS4yODE4Wk04MS44MjE1IDE1NS40QzgxLjUyNzIgMTU1LjQgODEuMjYwMyAxNTUuMzUxIDgxLjAyMDcgMTU1LjI1MkM4MC43ODM3IDE1NS4xNSA4MC41NzkzIDE1NS4wMDggODAuNDA3NCAxNTQuODI2QzgwLjIzODIgMTU0LjY0NCA4MC4xMDggMTU0LjQyNyA4MC4wMTY4IDE1NC4xNzdDNzkuOTI1NyAxNTMuOTI3IDc5Ljg4MDEgMTUzLjY1NCA3OS44ODAxIDE1My4zNTdWMTUzLjE5M0M3OS44ODAxIDE1Mi44NDkgNzkuOTMwOSAxNTIuNTQzIDgwLjAzMjQgMTUyLjI3NUM4MC4xMzQgMTUyLjAwNCA4MC4yNzIgMTUxLjc3NSA4MC40NDY1IDE1MS41ODhDODAuNjIxIDE1MS40IDgwLjgxODkgMTUxLjI1OCA4MS4wNDAyIDE1MS4xNjJDODEuMjYxNiAxNTEuMDY2IDgxLjQ5MDggMTUxLjAxNyA4MS43Mjc3IDE1MS4wMTdDODIuMDI5OCAxNTEuMDE3IDgyLjI5MDIgMTUxLjA2OSA4Mi41MDkgMTUxLjE3NEM4Mi43MzA0IDE1MS4yNzggODIuOTExMyAxNTEuNDI0IDgzLjA1MiAxNTEuNjExQzgzLjE5MjYgMTUxLjc5NiA4My4yOTY4IDE1Mi4wMTUgODMuMzY0NSAxNTIuMjY3QzgzLjQzMjIgMTUyLjUxNyA4My40NjYgMTUyLjc5MSA4My40NjYgMTUzLjA4OFYxNTMuNDEySDgwLjMwOThWMTUyLjgyMkg4Mi43NDM0VjE1Mi43NjdDODIuNzMzIDE1Mi41OCA4Mi42OTM5IDE1Mi4zOTggODIuNjI2MiAxNTIuMjJDODIuNTYxMSAxNTIuMDQzIDgyLjQ1NjkgMTUxLjg5OCA4Mi4zMTM3IDE1MS43ODNDODIuMTcwNSAxNTEuNjY4IDgxLjk3NTEgMTUxLjYxMSA4MS43Mjc3IDE1MS42MTFDODEuNTYzNyAxNTEuNjExIDgxLjQxMjYgMTUxLjY0NiA4MS4yNzQ2IDE1MS43MTdDODEuMTM2NiAxNTEuNzg0IDgxLjAxODEgMTUxLjg4NiA4MC45MTkyIDE1Mi4wMjFDODAuODIwMiAxNTIuMTU3IDgwLjc0MzQgMTUyLjMyMiA4MC42ODg3IDE1Mi41MTdDODAuNjM0IDE1Mi43MTMgODAuNjA2NyAxNTIuOTM4IDgwLjYwNjcgMTUzLjE5M1YxNTMuMzU3QzgwLjYwNjcgMTUzLjU1OCA4MC42MzQgMTUzLjc0NyA4MC42ODg3IDE1My45MjRDODAuNzQ2IDE1NC4wOTggODAuODI4IDE1NC4yNTIgODAuOTM0OCAxNTQuMzg1QzgxLjA0NDIgMTU0LjUxNyA4MS4xNzU3IDE1NC42MjIgODEuMzI5MyAxNTQuNjk3QzgxLjQ4NTYgMTU0Ljc3MyA4MS42NjI2IDE1NC44MSA4MS44NjA2IDE1NC44MUM4Mi4xMTU4IDE1NC44MSA4Mi4zMzE5IDE1NC43NTggODIuNTA5IDE1NC42NTRDODIuNjg2MSAxNTQuNTUgODIuODQxIDE1NC40MTEgODIuOTczOCAxNTQuMjM2TDgzLjQxMTMgMTU0LjU4NEM4My4zMjAyIDE1NC43MjIgODMuMjA0MyAxNTQuODUzIDgzLjA2MzcgMTU0Ljk3OEM4Mi45MjMxIDE1NS4xMDMgODIuNzQ5OSAxNTUuMjA1IDgyLjU0NDIgMTU1LjI4M0M4Mi4zNDEgMTU1LjM2MSA4Mi4xMDAxIDE1NS40IDgxLjgyMTUgMTU1LjRaTTg0LjM4ODUgMTQ5LjMyMkg4NS4xMTUxVjE1NC41MDJMODUuMDUyNiAxNTUuMzIySDg0LjM4ODVWMTQ5LjMyMlpNODcuOTcwNSAxNTMuMTc0VjE1My4yNTZDODcuOTcwNSAxNTMuNTYzIDg3LjkzNDEgMTUzLjg0OCA4Ny44NjEyIDE1NC4xMTFDODcuNzg4MiAxNTQuMzcyIDg3LjY4MTUgMTU0LjU5OCA4Ny41NDA4IDE1NC43OTFDODcuNDAwMiAxNTQuOTgzIDg3LjIyODMgMTU1LjEzMyA4Ny4wMjUyIDE1NS4yNEM4Ni44MjIxIDE1NS4zNDcgODYuNTg5IDE1NS40IDg2LjMyNiAxNTUuNEM4Ni4wNTc4IDE1NS40IDg1LjgyMjEgMTU1LjM1NSA4NS42MTkgMTU1LjI2M0M4NS40MTg1IDE1NS4xNyA4NS4yNDkyIDE1NS4wMzYgODUuMTExMiAxNTQuODYxQzg0Ljk3MzEgMTU0LjY4NyA4NC44NjI1IDE1NC40NzYgODQuNzc5MSAxNTQuMjI4Qzg0LjY5ODQgMTUzLjk4MSA4NC42NDI0IDE1My43MDIgODQuNjExMiAxNTMuMzkyVjE1My4wMzNDODQuNjQyNCAxNTIuNzIgODQuNjk4NCAxNTIuNDQxIDg0Ljc3OTEgMTUyLjE5M0M4NC44NjI1IDE1MS45NDYgODQuOTczMSAxNTEuNzM1IDg1LjExMTIgMTUxLjU2Qzg1LjI0OTIgMTUxLjM4MyA4NS40MTg1IDE1MS4yNDkgODUuNjE5IDE1MS4xNThDODUuODE5NSAxNTEuMDY0IDg2LjA1MjYgMTUxLjAxNyA4Ni4zMTgyIDE1MS4wMTdDODYuNTgzOCAxNTEuMDE3IDg2LjgxOTUgMTUxLjA2OSA4Ny4wMjUyIDE1MS4xNzRDODcuMjMxIDE1MS4yNzUgODcuNDAyOCAxNTEuNDIxIDg3LjU0MDggMTUxLjYxMUM4Ny42ODE1IDE1MS44MDEgODcuNzg4MiAxNTIuMDI5IDg3Ljg2MTIgMTUyLjI5NUM4Ny45MzQxIDE1Mi41NTggODcuOTcwNSAxNTIuODUxIDg3Ljk3MDUgMTUzLjE3NFpNODcuMjQ0IDE1My4yNTZWMTUzLjE3NEM4Ny4yNDQgMTUyLjk2MyA4Ny4yMjQ0IDE1Mi43NjUgODcuMTg1NCAxNTIuNThDODcuMTQ2MyAxNTIuMzkyIDg3LjA4MzggMTUyLjIyOCA4Ni45OTc5IDE1Mi4wODhDODYuOTExOSAxNTEuOTQ0IDg2Ljc5ODcgMTUxLjgzMiA4Ni42NTggMTUxLjc1MkM4Ni41MTc0IDE1MS42NjggODYuMzQ0MiAxNTEuNjI3IDg2LjEzODUgMTUxLjYyN0M4NS45NTYyIDE1MS42MjcgODUuNzk3NCAxNTEuNjU4IDg1LjY2MTkgMTUxLjcyQzg1LjUyOTEgMTUxLjc4MyA4NS40MTU4IDE1MS44NjggODUuMzIyMSAxNTEuOTc0Qzg1LjIyODMgMTUyLjA3OSA4NS4xNTE1IDE1Mi4xOTggODUuMDkxNiAxNTIuMzM0Qzg1LjAzNDMgMTUyLjQ2NyA4NC45OTE0IDE1Mi42MDUgODQuOTYyNyAxNTIuNzQ4VjE1My42ODlDODUuMDA0NCAxNTMuODcyIDg1LjA3MjEgMTU0LjA0NyA4NS4xNjU4IDE1NC4yMTdDODUuMjYyMiAxNTQuMzgzIDg1LjM4OTggMTU0LjUyIDg1LjU0ODcgMTU0LjYyN0M4NS43MTAxIDE1NC43MzMgODUuOTA5MyAxNTQuNzg3IDg2LjE0NjMgMTU0Ljc4N0M4Ni4zNDE2IDE1NC43ODcgODYuNTA4MyAxNTQuNzQ4IDg2LjY0NjMgMTU0LjY3Qzg2Ljc4NjkgMTU0LjU4OSA4Ni45MDAyIDE1NC40NzggODYuOTg2MiAxNTQuMzM4Qzg3LjA3NDcgMTU0LjE5NyA4Ny4xMzk4IDE1NC4wMzQgODcuMTgxNSAxNTMuODQ5Qzg3LjIyMzEgMTUzLjY2NCA4Ny4yNDQgMTUzLjQ2NyA4Ny4yNDQgMTUzLjI1NloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPC9nPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDJfNDE4NF85NDUyNykiPgo8bGluZSB4MT0iMTExLjI1IiB5MT0iMTQ3LjA3MiIgeDI9IjExMS4yNSIgeTI9IjE0Ni4yNSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuNSIgc3Ryb2tlLXdpZHRoPSIwLjUiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiLz4KPHBhdGggZD0iTTEwMy4zMDkgMTUyLjAyNVYxNTIuODkyQzEwMy4zMDkgMTUzLjM1OCAxMDMuMjY3IDE1My43NTIgMTAzLjE4NCAxNTQuMDcyQzEwMy4xMDEgMTU0LjM5MiAxMDIuOTgxIDE1NC42NSAxMDIuODI1IDE1NC44NDVDMTAyLjY2OCAxNTUuMDQxIDEwMi40OCAxNTUuMTgzIDEwMi4yNTggMTU1LjI3MUMxMDIuMDQgMTU1LjM1NyAxMDEuNzkyIDE1NS40IDEwMS41MTYgMTU1LjRDMTAxLjI5NyAxNTUuNCAxMDEuMDk2IDE1NS4zNzMgMTAwLjkxMSAxNTUuMzE4QzEwMC43MjYgMTU1LjI2MyAxMDAuNTU5IDE1NS4xNzYgMTAwLjQxMSAxNTUuMDU2QzEwMC4yNjUgMTU0LjkzNCAxMDAuMTQgMTU0Ljc3NSAxMDAuMDM2IDE1NC41OEM5OS45MzE1IDE1NC4zODUgOTkuODUyMSAxNTQuMTQ4IDk5Ljc5NzQgMTUzLjg2OUM5OS43NDI3IDE1My41OSA5OS43MTU0IDE1My4yNjUgOTkuNzE1NCAxNTIuODkyVjE1Mi4wMjVDOTkuNzE1NCAxNTEuNTU5IDk5Ljc1NyAxNTEuMTY4IDk5Ljg0MDQgMTUwLjg1M0M5OS45MjYzIDE1MC41MzggMTAwLjA0NyAxNTAuMjg2IDEwMC4yMDQgMTUwLjA5NUMxMDAuMzYgMTQ5LjkwMyAxMDAuNTQ3IDE0OS43NjUgMTAwLjc2NiAxNDkuNjgxQzEwMC45ODcgMTQ5LjU5OCAxMDEuMjM1IDE0OS41NTYgMTAxLjUwOCAxNDkuNTU2QzEwMS43MyAxNDkuNTU2IDEwMS45MzMgMTQ5LjU4NCAxMDIuMTE4IDE0OS42MzhDMTAyLjMwNSAxNDkuNjkxIDEwMi40NzIgMTQ5Ljc3NSAxMDIuNjE4IDE0OS44OTJDMTAyLjc2NCAxNTAuMDA3IDEwMi44ODcgMTUwLjE2MSAxMDIuOTg5IDE1MC4zNTNDMTAzLjA5MyAxNTAuNTQzIDEwMy4xNzIgMTUwLjc3NiAxMDMuMjI3IDE1MS4wNTJDMTAzLjI4MiAxNTEuMzI5IDEwMy4zMDkgMTUxLjY1MyAxMDMuMzA5IDE1Mi4wMjVaTTEwMi41ODMgMTUzLjAxVjE1MS45MDRDMTAyLjU4MyAxNTEuNjQ5IDEwMi41NjcgMTUxLjQyNSAxMDIuNTM2IDE1MS4yMzJDMTAyLjUwNyAxNTEuMDM3IDEwMi40NjQgMTUwLjg3IDEwMi40MDcgMTUwLjczMkMxMDIuMzQ5IDE1MC41OTQgMTAyLjI3NyAxNTAuNDgyIDEwMi4xODggMTUwLjM5NkMxMDIuMTAyIDE1MC4zMSAxMDIuMDAyIDE1MC4yNDggMTAxLjg4NyAxNTAuMjA5QzEwMS43NzUgMTUwLjE2NyAxMDEuNjQ5IDE1MC4xNDYgMTAxLjUwOCAxNTAuMTQ2QzEwMS4zMzYgMTUwLjE0NiAxMDEuMTg0IDE1MC4xNzkgMTAxLjA1MSAxNTAuMjQ0QzEwMC45MTggMTUwLjMwNiAxMDAuODA3IDE1MC40MDcgMTAwLjcxNSAxNTAuNTQ1QzEwMC42MjcgMTUwLjY4MyAxMDAuNTU5IDE1MC44NjQgMTAwLjUxMiAxNTEuMDg4QzEwMC40NjUgMTUxLjMxMiAxMDAuNDQyIDE1MS41ODQgMTAwLjQ0MiAxNTEuOTA0VjE1My4wMUMxMDAuNDQyIDE1My4yNjUgMTAwLjQ1NiAxNTMuNDkgMTAwLjQ4NSAxNTMuNjg1QzEwMC41MTYgMTUzLjg4MSAxMDAuNTYyIDE1NC4wNSAxMDAuNjIyIDE1NC4xOTNDMTAwLjY4MiAxNTQuMzM0IDEwMC43NTQgMTU0LjQ1IDEwMC44NCAxNTQuNTQxQzEwMC45MjYgMTU0LjYzMiAxMDEuMDI1IDE1NC43IDEwMS4xMzcgMTU0Ljc0NEMxMDEuMjUyIDE1NC43ODYgMTAxLjM3OCAxNTQuODA2IDEwMS41MTYgMTU0LjgwNkMxMDEuNjkzIDE1NC44MDYgMTAxLjg0OCAxNTQuNzczIDEwMS45ODEgMTU0LjcwNUMxMDIuMTE0IDE1NC42MzcgMTAyLjIyNCAxNTQuNTMyIDEwMi4zMTMgMTU0LjM4OEMxMDIuNDA0IDE1NC4yNDMgMTAyLjQ3MiAxNTQuMDU2IDEwMi41MTYgMTUzLjgzQzEwMi41NiAxNTMuNjAxIDEwMi41ODMgMTUzLjMyNyAxMDIuNTgzIDE1My4wMVpNMTA1LjM3NiAxNTIuMTIzSDEwNS44OTJDMTA2LjE0NCAxNTIuMTIzIDEwNi4zNTMgMTUyLjA4MSAxMDYuNTE3IDE1MS45OThDMTA2LjY4MyAxNTEuOTEyIDEwNi44MDcgMTUxLjc5NiAxMDYuODg4IDE1MS42NUMxMDYuOTcxIDE1MS41MDIgMTA3LjAxMyAxNTEuMzM1IDEwNy4wMTMgMTUxLjE1QzEwNy4wMTMgMTUwLjkzMSAxMDYuOTc2IDE1MC43NDggMTA2LjkwMyAxNTAuNTk5QzEwNi44MzEgMTUwLjQ1MSAxMDYuNzIxIDE1MC4zMzkgMTA2LjU3NSAxNTAuMjYzQzEwNi40MjkgMTUwLjE4OCAxMDYuMjQ1IDE1MC4xNSAxMDYuMDIxIDE1MC4xNUMxMDUuODE4IDE1MC4xNSAxMDUuNjM4IDE1MC4xOTEgMTA1LjQ4MiAxNTAuMjcxQzEwNS4zMjggMTUwLjM0OSAxMDUuMjA3IDE1MC40NjEgMTA1LjExOCAxNTAuNjA3QzEwNS4wMzIgMTUwLjc1MyAxMDQuOTg5IDE1MC45MjUgMTA0Ljk4OSAxNTEuMTIzSDEwNC4yNjdDMTA0LjI2NyAxNTAuODM0IDEwNC4zNCAxNTAuNTcxIDEwNC40ODUgMTUwLjMzNEMxMDQuNjMxIDE1MC4wOTcgMTA0LjgzNiAxNDkuOTA4IDEwNS4wOTkgMTQ5Ljc2N0MxMDUuMzY0IDE0OS42MjcgMTA1LjY3MiAxNDkuNTU2IDEwNi4wMjEgMTQ5LjU1NkMxMDYuMzY0IDE0OS41NTYgMTA2LjY2NSAxNDkuNjE4IDEwNi45MjMgMTQ5Ljc0QzEwNy4xODEgMTQ5Ljg2IDEwNy4zODEgMTUwLjAzOSAxMDcuNTI1IDE1MC4yNzlDMTA3LjY2OCAxNTAuNTE2IDEwNy43MzkgMTUwLjgxMiAxMDcuNzM5IDE1MS4xNjZDMTA3LjczOSAxNTEuMzA5IDEwNy43MDYgMTUxLjQ2MyAxMDcuNjM4IDE1MS42MjdDMTA3LjU3MyAxNTEuNzg4IDEwNy40NyAxNTEuOTM5IDEwNy4zMjkgMTUyLjA4QzEwNy4xOTEgMTUyLjIyIDEwNy4wMTIgMTUyLjMzNiAxMDYuNzkgMTUyLjQyN0MxMDYuNTY5IDE1Mi41MTYgMTA2LjMwMyAxNTIuNTYgMTA1Ljk5MyAxNTIuNTZIMTA1LjM3NlYxNTIuMTIzWk0xMDUuMzc2IDE1Mi43MTdWMTUyLjI4M0gxMDUuOTkzQzEwNi4zNTUgMTUyLjI4MyAxMDYuNjU1IDE1Mi4zMjYgMTA2Ljg5MiAxNTIuNDEyQzEwNy4xMjkgMTUyLjQ5OCAxMDcuMzE1IDE1Mi42MTIgMTA3LjQ1IDE1Mi43NTZDMTA3LjU4OCAxNTIuODk5IDEwNy42ODUgMTUzLjA1NiAxMDcuNzM5IDE1My4yMjhDMTA3Ljc5NyAxNTMuMzk4IDEwNy44MjUgMTUzLjU2NyAxMDcuODI1IDE1My43MzZDMTA3LjgyNSAxNTQuMDAyIDEwNy43OCAxNTQuMjM3IDEwNy42ODkgMTU0LjQ0M0MxMDcuNiAxNTQuNjQ5IDEwNy40NzQgMTU0LjgyMyAxMDcuMzEgMTU0Ljk2N0MxMDcuMTQ4IDE1NS4xMSAxMDYuOTU4IDE1NS4yMTggMTA2LjczOSAxNTUuMjkxQzEwNi41MjEgMTU1LjM2NCAxMDYuMjgyIDE1NS40IDEwNi4wMjUgMTU1LjRDMTA1Ljc3NyAxNTUuNCAxMDUuNTQ0IDE1NS4zNjUgMTA1LjMyNSAxNTUuMjk1QzEwNS4xMDkgMTU1LjIyNCAxMDQuOTE4IDE1NS4xMjMgMTA0Ljc1MSAxNTQuOTlDMTA0LjU4NCAxNTQuODU1IDEwNC40NTQgMTU0LjY4OSAxMDQuMzYgMTU0LjQ5NEMxMDQuMjY3IDE1NC4yOTYgMTA0LjIyIDE1NC4wNzEgMTA0LjIyIDE1My44MThIMTA0Ljk0M0MxMDQuOTQzIDE1NC4wMTYgMTA0Ljk4NSAxNTQuMTg5IDEwNS4wNzEgMTU0LjMzOEMxMDUuMTYgMTU0LjQ4NiAxMDUuMjg1IDE1NC42MDIgMTA1LjQ0NiAxNTQuNjg1QzEwNS42MSAxNTQuNzY2IDEwNS44MDMgMTU0LjgwNiAxMDYuMDI1IDE1NC44MDZDMTA2LjI0NiAxNTQuODA2IDEwNi40MzYgMTU0Ljc2OSAxMDYuNTk1IDE1NC42OTNDMTA2Ljc1NiAxNTQuNjE1IDEwNi44OCAxNTQuNDk4IDEwNi45NjYgMTU0LjM0MkMxMDcuMDU0IDE1NC4xODUgMTA3LjA5OSAxNTMuOTg5IDEwNy4wOTkgMTUzLjc1MkMxMDcuMDk5IDE1My41MTUgMTA3LjA0OSAxNTMuMzIxIDEwNi45NSAxNTMuMTdDMTA2Ljg1MSAxNTMuMDE2IDEwNi43MTEgMTUyLjkwMyAxMDYuNTI4IDE1Mi44M0MxMDYuMzQ5IDE1Mi43NTQgMTA2LjEzNyAxNTIuNzE3IDEwNS44OTIgMTUyLjcxN0gxMDUuMzc2Wk0xMTEuOTEyIDE0OS42MzVWMTU1LjMyMkgxMTEuMTU5VjE0OS42MzVIMTExLjkxMlpNMTE0LjI5NSAxNTIuMTkzVjE1Mi44MUgxMTEuNzQ4VjE1Mi4xOTNIMTE0LjI5NVpNMTE0LjY4MiAxNDkuNjM1VjE1MC4yNTJIMTExLjc0OFYxNDkuNjM1SDExNC42ODJaTTExNy4yMjIgMTU1LjRDMTE2LjkyNyAxNTUuNCAxMTYuNjYgMTU1LjM1MSAxMTYuNDIxIDE1NS4yNTJDMTE2LjE4NCAxNTUuMTUgMTE1Ljk3OSAxNTUuMDA4IDExNS44MDggMTU0LjgyNkMxMTUuNjM4IDE1NC42NDQgMTE1LjUwOCAxNTQuNDI3IDExNS40MTcgMTU0LjE3N0MxMTUuMzI2IDE1My45MjcgMTE1LjI4IDE1My42NTQgMTE1LjI4IDE1My4zNTdWMTUzLjE5M0MxMTUuMjggMTUyLjg0OSAxMTUuMzMxIDE1Mi41NDMgMTE1LjQzMyAxNTIuMjc1QzExNS41MzQgMTUyLjAwNCAxMTUuNjcyIDE1MS43NzUgMTE1Ljg0NyAxNTEuNTg4QzExNi4wMjEgMTUxLjQgMTE2LjIxOSAxNTEuMjU4IDExNi40NCAxNTEuMTYyQzExNi42NjIgMTUxLjA2NiAxMTYuODkxIDE1MS4wMTcgMTE3LjEyOCAxNTEuMDE3QzExNy40MyAxNTEuMDE3IDExNy42OSAxNTEuMDY5IDExNy45MDkgMTUxLjE3NEMxMTguMTMgMTUxLjI3OCAxMTguMzExIDE1MS40MjQgMTE4LjQ1MiAxNTEuNjExQzExOC41OTMgMTUxLjc5NiAxMTguNjk3IDE1Mi4wMTUgMTE4Ljc2NSAxNTIuMjY3QzExOC44MzIgMTUyLjUxNyAxMTguODY2IDE1Mi43OTEgMTE4Ljg2NiAxNTMuMDg4VjE1My40MTJIMTE1LjcxVjE1Mi44MjJIMTE4LjE0NFYxNTIuNzY3QzExOC4xMzMgMTUyLjU4IDExOC4wOTQgMTUyLjM5OCAxMTguMDI2IDE1Mi4yMkMxMTcuOTYxIDE1Mi4wNDMgMTE3Ljg1NyAxNTEuODk4IDExNy43MTQgMTUxLjc4M0MxMTcuNTcxIDE1MS42NjggMTE3LjM3NSAxNTEuNjExIDExNy4xMjggMTUxLjYxMUMxMTYuOTY0IDE1MS42MTEgMTE2LjgxMyAxNTEuNjQ2IDExNi42NzUgMTUxLjcxN0MxMTYuNTM3IDE1MS43ODQgMTE2LjQxOCAxNTEuODg2IDExNi4zMTkgMTUyLjAyMUMxMTYuMjIgMTUyLjE1NyAxMTYuMTQ0IDE1Mi4zMjIgMTE2LjA4OSAxNTIuNTE3QzExNi4wMzQgMTUyLjcxMyAxMTYuMDA3IDE1Mi45MzggMTE2LjAwNyAxNTMuMTkzVjE1My4zNTdDMTE2LjAwNyAxNTMuNTU4IDExNi4wMzQgMTUzLjc0NyAxMTYuMDg5IDE1My45MjRDMTE2LjE0NiAxNTQuMDk4IDExNi4yMjggMTU0LjI1MiAxMTYuMzM1IDE1NC4zODVDMTE2LjQ0NCAxNTQuNTE3IDExNi41NzYgMTU0LjYyMiAxMTYuNzI5IDE1NC42OTdDMTE2Ljg4NiAxNTQuNzczIDExNy4wNjMgMTU0LjgxIDExNy4yNjEgMTU0LjgxQzExNy41MTYgMTU0LjgxIDExNy43MzIgMTU0Ljc1OCAxMTcuOTA5IDE1NC42NTRDMTE4LjA4NiAxNTQuNTUgMTE4LjI0MSAxNTQuNDExIDExOC4zNzQgMTU0LjIzNkwxMTguODExIDE1NC41ODRDMTE4LjcyIDE1NC43MjIgMTE4LjYwNCAxNTQuODUzIDExOC40NjQgMTU0Ljk3OEMxMTguMzIzIDE1NS4xMDMgMTE4LjE1IDE1NS4yMDUgMTE3Ljk0NCAxNTUuMjgzQzExNy43NDEgMTU1LjM2MSAxMTcuNSAxNTUuNCAxMTcuMjIyIDE1NS40Wk0xMTkuNzg5IDE0OS4zMjJIMTIwLjUxNVYxNTQuNTAyTDEyMC40NTMgMTU1LjMyMkgxMTkuNzg5VjE0OS4zMjJaTTEyMy4zNzEgMTUzLjE3NFYxNTMuMjU2QzEyMy4zNzEgMTUzLjU2MyAxMjMuMzM0IDE1My44NDggMTIzLjI2MSAxNTQuMTExQzEyMy4xODggMTU0LjM3MiAxMjMuMDgyIDE1NC41OTggMTIyLjk0MSAxNTQuNzkxQzEyMi44IDE1NC45ODMgMTIyLjYyOCAxNTUuMTMzIDEyMi40MjUgMTU1LjI0QzEyMi4yMjIgMTU1LjM0NyAxMjEuOTg5IDE1NS40IDEyMS43MjYgMTU1LjRDMTIxLjQ1OCAxNTUuNCAxMjEuMjIyIDE1NS4zNTUgMTIxLjAxOSAxNTUuMjYzQzEyMC44MTkgMTU1LjE3IDEyMC42NDkgMTU1LjAzNiAxMjAuNTExIDE1NC44NjFDMTIwLjM3MyAxNTQuNjg3IDEyMC4yNjMgMTU0LjQ3NiAxMjAuMTc5IDE1NC4yMjhDMTIwLjA5OSAxNTMuOTgxIDEyMC4wNDMgMTUzLjcwMiAxMjAuMDExIDE1My4zOTJWMTUzLjAzM0MxMjAuMDQzIDE1Mi43MiAxMjAuMDk5IDE1Mi40NDEgMTIwLjE3OSAxNTIuMTkzQzEyMC4yNjMgMTUxLjk0NiAxMjAuMzczIDE1MS43MzUgMTIwLjUxMSAxNTEuNTZDMTIwLjY0OSAxNTEuMzgzIDEyMC44MTkgMTUxLjI0OSAxMjEuMDE5IDE1MS4xNThDMTIxLjIyIDE1MS4wNjQgMTIxLjQ1MyAxNTEuMDE3IDEyMS43MTggMTUxLjAxN0MxMjEuOTg0IDE1MS4wMTcgMTIyLjIyIDE1MS4wNjkgMTIyLjQyNSAxNTEuMTc0QzEyMi42MzEgMTUxLjI3NSAxMjIuODAzIDE1MS40MjEgMTIyLjk0MSAxNTEuNjExQzEyMy4wODIgMTUxLjgwMSAxMjMuMTg4IDE1Mi4wMjkgMTIzLjI2MSAxNTIuMjk1QzEyMy4zMzQgMTUyLjU1OCAxMjMuMzcxIDE1Mi44NTEgMTIzLjM3MSAxNTMuMTc0Wk0xMjIuNjQ0IDE1My4yNTZWMTUzLjE3NEMxMjIuNjQ0IDE1Mi45NjMgMTIyLjYyNSAxNTIuNzY1IDEyMi41ODYgMTUyLjU4QzEyMi41NDYgMTUyLjM5MiAxMjIuNDg0IDE1Mi4yMjggMTIyLjM5OCAxNTIuMDg4QzEyMi4zMTIgMTUxLjk0NCAxMjIuMTk5IDE1MS44MzIgMTIyLjA1OCAxNTEuNzUyQzEyMS45MTggMTUxLjY2OCAxMjEuNzQ0IDE1MS42MjcgMTIxLjUzOSAxNTEuNjI3QzEyMS4zNTYgMTUxLjYyNyAxMjEuMTk4IDE1MS42NTggMTIxLjA2MiAxNTEuNzJDMTIwLjkyOSAxNTEuNzgzIDEyMC44MTYgMTUxLjg2OCAxMjAuNzIyIDE1MS45NzRDMTIwLjYyOCAxNTIuMDc5IDEyMC41NTIgMTUyLjE5OCAxMjAuNDkyIDE1Mi4zMzRDMTIwLjQzNCAxNTIuNDY3IDEyMC4zOTIgMTUyLjYwNSAxMjAuMzYzIDE1Mi43NDhWMTUzLjY4OUMxMjAuNDA1IDE1My44NzIgMTIwLjQ3MiAxNTQuMDQ3IDEyMC41NjYgMTU0LjIxN0MxMjAuNjYyIDE1NC4zODMgMTIwLjc5IDE1NC41MiAxMjAuOTQ5IDE1NC42MjdDMTIxLjExIDE1NC43MzMgMTIxLjMwOSAxNTQuNzg3IDEyMS41NDYgMTU0Ljc4N0MxMjEuNzQyIDE1NC43ODcgMTIxLjkwOCAxNTQuNzQ4IDEyMi4wNDYgMTU0LjY3QzEyMi4xODcgMTU0LjU4OSAxMjIuMyAxNTQuNDc4IDEyMi4zODYgMTU0LjMzOEMxMjIuNDc1IDE1NC4xOTcgMTIyLjU0IDE1NC4wMzQgMTIyLjU4MiAxNTMuODQ5QzEyMi42MjMgMTUzLjY2NCAxMjIuNjQ0IDE1My40NjcgMTIyLjY0NCAxNTMuMjU2WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8L2c+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwM180MTg0Xzk0NTI3KSI+CjxsaW5lIHgxPSIxNDYuNjUiIHkxPSIxNDcuMDcyIiB4Mj0iMTQ2LjY1IiB5Mj0iMTQ2LjI1IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNMTM4LjcwOSAxNTIuMDI1VjE1Mi44OTJDMTM4LjcwOSAxNTMuMzU4IDEzOC42NjcgMTUzLjc1MiAxMzguNTg0IDE1NC4wNzJDMTM4LjUwMSAxNTQuMzkyIDEzOC4zODEgMTU0LjY1IDEzOC4yMjUgMTU0Ljg0NUMxMzguMDY4IDE1NS4wNDEgMTM3Ljg4IDE1NS4xODMgMTM3LjY1OCAxNTUuMjcxQzEzNy40MzkgMTU1LjM1NyAxMzcuMTkyIDE1NS40IDEzNi45MTYgMTU1LjRDMTM2LjY5NyAxNTUuNCAxMzYuNDk1IDE1NS4zNzMgMTM2LjMxMSAxNTUuMzE4QzEzNi4xMjYgMTU1LjI2MyAxMzUuOTU5IDE1NS4xNzYgMTM1LjgxMSAxNTUuMDU2QzEzNS42NjUgMTU0LjkzNCAxMzUuNTQgMTU0Ljc3NSAxMzUuNDM2IDE1NC41OEMxMzUuMzMxIDE1NC4zODUgMTM1LjI1MiAxNTQuMTQ4IDEzNS4xOTcgMTUzLjg2OUMxMzUuMTQzIDE1My41OSAxMzUuMTE1IDE1My4yNjUgMTM1LjExNSAxNTIuODkyVjE1Mi4wMjVDMTM1LjExNSAxNTEuNTU5IDEzNS4xNTcgMTUxLjE2OCAxMzUuMjQgMTUwLjg1M0MxMzUuMzI2IDE1MC41MzggMTM1LjQ0NyAxNTAuMjg2IDEzNS42MDQgMTUwLjA5NUMxMzUuNzYgMTQ5LjkwMyAxMzUuOTQ3IDE0OS43NjUgMTM2LjE2NiAxNDkuNjgxQzEzNi4zODcgMTQ5LjU5OCAxMzYuNjM1IDE0OS41NTYgMTM2LjkwOCAxNDkuNTU2QzEzNy4xMyAxNDkuNTU2IDEzNy4zMzMgMTQ5LjU4NCAxMzcuNTE4IDE0OS42MzhDMTM3LjcwNSAxNDkuNjkxIDEzNy44NzIgMTQ5Ljc3NSAxMzguMDE4IDE0OS44OTJDMTM4LjE2MyAxNTAuMDA3IDEzOC4yODcgMTUwLjE2MSAxMzguMzg5IDE1MC4zNTNDMTM4LjQ5MyAxNTAuNTQzIDEzOC41NzIgMTUwLjc3NiAxMzguNjI3IDE1MS4wNTJDMTM4LjY4MiAxNTEuMzI5IDEzOC43MDkgMTUxLjY1MyAxMzguNzA5IDE1Mi4wMjVaTTEzNy45ODIgMTUzLjAxVjE1MS45MDRDMTM3Ljk4MiAxNTEuNjQ5IDEzNy45NjcgMTUxLjQyNSAxMzcuOTM2IDE1MS4yMzJDMTM3LjkwNyAxNTEuMDM3IDEzNy44NjQgMTUwLjg3IDEzNy44MDcgMTUwLjczMkMxMzcuNzQ5IDE1MC41OTQgMTM3LjY3NiAxNTAuNDgyIDEzNy41ODggMTUwLjM5NkMxMzcuNTAyIDE1MC4zMSAxMzcuNDAyIDE1MC4yNDggMTM3LjI4NyAxNTAuMjA5QzEzNy4xNzUgMTUwLjE2NyAxMzcuMDQ5IDE1MC4xNDYgMTM2LjkwOCAxNTAuMTQ2QzEzNi43MzYgMTUwLjE0NiAxMzYuNTg0IDE1MC4xNzkgMTM2LjQ1MSAxNTAuMjQ0QzEzNi4zMTggMTUwLjMwNiAxMzYuMjA2IDE1MC40MDcgMTM2LjExNSAxNTAuNTQ1QzEzNi4wMjcgMTUwLjY4MyAxMzUuOTU5IDE1MC44NjQgMTM1LjkxMiAxNTEuMDg4QzEzNS44NjUgMTUxLjMxMiAxMzUuODQyIDE1MS41ODQgMTM1Ljg0MiAxNTEuOTA0VjE1My4wMUMxMzUuODQyIDE1My4yNjUgMTM1Ljg1NiAxNTMuNDkgMTM1Ljg4NSAxNTMuNjg1QzEzNS45MTYgMTUzLjg4MSAxMzUuOTYyIDE1NC4wNSAxMzYuMDIyIDE1NC4xOTNDMTM2LjA4MSAxNTQuMzM0IDEzNi4xNTQgMTU0LjQ1IDEzNi4yNCAxNTQuNTQxQzEzNi4zMjYgMTU0LjYzMiAxMzYuNDI1IDE1NC43IDEzNi41MzcgMTU0Ljc0NEMxMzYuNjUyIDE1NC43ODYgMTM2Ljc3OCAxNTQuODA2IDEzNi45MTYgMTU0LjgwNkMxMzcuMDkzIDE1NC44MDYgMTM3LjI0OCAxNTQuNzczIDEzNy4zODEgMTU0LjcwNUMxMzcuNTE0IDE1NC42MzcgMTM3LjYyNCAxNTQuNTMyIDEzNy43MTMgMTU0LjM4OEMxMzcuODA0IDE1NC4yNDMgMTM3Ljg3MiAxNTQuMDU2IDEzNy45MTYgMTUzLjgzQzEzNy45NiAxNTMuNjAxIDEzNy45ODIgMTUzLjMyNyAxMzcuOTgyIDE1My4wMVpNMTQzLjU2NSAxNTMuNDA4VjE1NC4wMDJIMTM5LjQ1NlYxNTMuNTc2TDE0Mi4wMDMgMTQ5LjYzNUgxNDIuNTkyTDE0MS45NiAxNTAuNzc1TDE0MC4yNzYgMTUzLjQwOEgxNDMuNTY1Wk0xNDIuNzcyIDE0OS42MzVWMTU1LjMyMkgxNDIuMDQ5VjE0OS42MzVIMTQyLjc3MlpNMTQ3LjMxMiAxNDkuNjM1VjE1NS4zMjJIMTQ2LjU1OFYxNDkuNjM1SDE0Ny4zMTJaTTE0OS42OTUgMTUyLjE5M1YxNTIuODFIMTQ3LjE0OFYxNTIuMTkzSDE0OS42OTVaTTE1MC4wODIgMTQ5LjYzNVYxNTAuMjUySDE0Ny4xNDhWMTQ5LjYzNUgxNTAuMDgyWk0xNTIuNjIyIDE1NS40QzE1Mi4zMjcgMTU1LjQgMTUyLjA2IDE1NS4zNTEgMTUxLjgyMSAxNTUuMjUyQzE1MS41ODQgMTU1LjE1IDE1MS4zNzkgMTU1LjAwOCAxNTEuMjA3IDE1NC44MjZDMTUxLjAzOCAxNTQuNjQ0IDE1MC45MDggMTU0LjQyNyAxNTAuODE3IDE1NC4xNzdDMTUwLjcyNiAxNTMuOTI3IDE1MC42OCAxNTMuNjU0IDE1MC42OCAxNTMuMzU3VjE1My4xOTNDMTUwLjY4IDE1Mi44NDkgMTUwLjczMSAxNTIuNTQzIDE1MC44MzIgMTUyLjI3NUMxNTAuOTM0IDE1Mi4wMDQgMTUxLjA3MiAxNTEuNzc1IDE1MS4yNDcgMTUxLjU4OEMxNTEuNDIxIDE1MS40IDE1MS42MTkgMTUxLjI1OCAxNTEuODQgMTUxLjE2MkMxNTIuMDYyIDE1MS4wNjYgMTUyLjI5MSAxNTEuMDE3IDE1Mi41MjggMTUxLjAxN0MxNTIuODMgMTUxLjAxNyAxNTMuMDkgMTUxLjA2OSAxNTMuMzA5IDE1MS4xNzRDMTUzLjUzIDE1MS4yNzggMTUzLjcxMSAxNTEuNDI0IDE1My44NTIgMTUxLjYxMUMxNTMuOTkzIDE1MS43OTYgMTU0LjA5NyAxNTIuMDE1IDE1NC4xNjUgMTUyLjI2N0MxNTQuMjMyIDE1Mi41MTcgMTU0LjI2NiAxNTIuNzkxIDE1NC4yNjYgMTUzLjA4OFYxNTMuNDEySDE1MS4xMVYxNTIuODIySDE1My41NDNWMTUyLjc2N0MxNTMuNTMzIDE1Mi41OCAxNTMuNDk0IDE1Mi4zOTggMTUzLjQyNiAxNTIuMjJDMTUzLjM2MSAxNTIuMDQzIDE1My4yNTcgMTUxLjg5OCAxNTMuMTE0IDE1MS43ODNDMTUyLjk3MSAxNTEuNjY4IDE1Mi43NzUgMTUxLjYxMSAxNTIuNTI4IDE1MS42MTFDMTUyLjM2NCAxNTEuNjExIDE1Mi4yMTMgMTUxLjY0NiAxNTIuMDc1IDE1MS43MTdDMTUxLjkzNyAxNTEuNzg0IDE1MS44MTggMTUxLjg4NiAxNTEuNzE5IDE1Mi4wMjFDMTUxLjYyIDE1Mi4xNTcgMTUxLjU0MyAxNTIuMzIyIDE1MS40ODkgMTUyLjUxN0MxNTEuNDM0IDE1Mi43MTMgMTUxLjQwNyAxNTIuOTM4IDE1MS40MDcgMTUzLjE5M1YxNTMuMzU3QzE1MS40MDcgMTUzLjU1OCAxNTEuNDM0IDE1My43NDcgMTUxLjQ4OSAxNTMuOTI0QzE1MS41NDYgMTU0LjA5OCAxNTEuNjI4IDE1NC4yNTIgMTUxLjczNSAxNTQuMzg1QzE1MS44NDQgMTU0LjUxNyAxNTEuOTc2IDE1NC42MjIgMTUyLjEyOSAxNTQuNjk3QzE1Mi4yODYgMTU0Ljc3MyAxNTIuNDYzIDE1NC44MSAxNTIuNjYxIDE1NC44MUMxNTIuOTE2IDE1NC44MSAxNTMuMTMyIDE1NC43NTggMTUzLjMwOSAxNTQuNjU0QzE1My40ODYgMTU0LjU1IDE1My42NDEgMTU0LjQxMSAxNTMuNzc0IDE1NC4yMzZMMTU0LjIxMSAxNTQuNTg0QzE1NC4xMiAxNTQuNzIyIDE1NC4wMDQgMTU0Ljg1MyAxNTMuODY0IDE1NC45NzhDMTUzLjcyMyAxNTUuMTAzIDE1My41NSAxNTUuMjA1IDE1My4zNDQgMTU1LjI4M0MxNTMuMTQxIDE1NS4zNjEgMTUyLjkgMTU1LjQgMTUyLjYyMiAxNTUuNFpNMTU1LjE4OSAxNDkuMzIySDE1NS45MTVWMTU0LjUwMkwxNTUuODUzIDE1NS4zMjJIMTU1LjE4OVYxNDkuMzIyWk0xNTguNzcxIDE1My4xNzRWMTUzLjI1NkMxNTguNzcxIDE1My41NjMgMTU4LjczNCAxNTMuODQ4IDE1OC42NjEgMTU0LjExMUMxNTguNTg4IDE1NC4zNzIgMTU4LjQ4MiAxNTQuNTk4IDE1OC4zNDEgMTU0Ljc5MUMxNTguMiAxNTQuOTgzIDE1OC4wMjggMTU1LjEzMyAxNTcuODI1IDE1NS4yNEMxNTcuNjIyIDE1NS4zNDcgMTU3LjM4OSAxNTUuNCAxNTcuMTI2IDE1NS40QzE1Ni44NTggMTU1LjQgMTU2LjYyMiAxNTUuMzU1IDE1Ni40MTkgMTU1LjI2M0MxNTYuMjE4IDE1NS4xNyAxNTYuMDQ5IDE1NS4wMzYgMTU1LjkxMSAxNTQuODYxQzE1NS43NzMgMTU0LjY4NyAxNTUuNjYzIDE1NC40NzYgMTU1LjU3OSAxNTQuMjI4QzE1NS40OTggMTUzLjk4MSAxNTUuNDQyIDE1My43MDIgMTU1LjQxMSAxNTMuMzkyVjE1My4wMzNDMTU1LjQ0MiAxNTIuNzIgMTU1LjQ5OCAxNTIuNDQxIDE1NS41NzkgMTUyLjE5M0MxNTUuNjYzIDE1MS45NDYgMTU1Ljc3MyAxNTEuNzM1IDE1NS45MTEgMTUxLjU2QzE1Ni4wNDkgMTUxLjM4MyAxNTYuMjE4IDE1MS4yNDkgMTU2LjQxOSAxNTEuMTU4QzE1Ni42MiAxNTEuMDY0IDE1Ni44NTMgMTUxLjAxNyAxNTcuMTE4IDE1MS4wMTdDMTU3LjM4NCAxNTEuMDE3IDE1Ny42MiAxNTEuMDY5IDE1Ny44MjUgMTUxLjE3NEMxNTguMDMxIDE1MS4yNzUgMTU4LjIwMyAxNTEuNDIxIDE1OC4zNDEgMTUxLjYxMUMxNTguNDgyIDE1MS44MDEgMTU4LjU4OCAxNTIuMDI5IDE1OC42NjEgMTUyLjI5NUMxNTguNzM0IDE1Mi41NTggMTU4Ljc3MSAxNTIuODUxIDE1OC43NzEgMTUzLjE3NFpNMTU4LjA0NCAxNTMuMjU2VjE1My4xNzRDMTU4LjA0NCAxNTIuOTYzIDE1OC4wMjQgMTUyLjc2NSAxNTcuOTg1IDE1Mi41OEMxNTcuOTQ2IDE1Mi4zOTIgMTU3Ljg4NCAxNTIuMjI4IDE1Ny43OTggMTUyLjA4OEMxNTcuNzEyIDE1MS45NDQgMTU3LjU5OSAxNTEuODMyIDE1Ny40NTggMTUxLjc1MkMxNTcuMzE3IDE1MS42NjggMTU3LjE0NCAxNTEuNjI3IDE1Ni45MzkgMTUxLjYyN0MxNTYuNzU2IDE1MS42MjcgMTU2LjU5NyAxNTEuNjU4IDE1Ni40NjIgMTUxLjcyQzE1Ni4zMjkgMTUxLjc4MyAxNTYuMjE2IDE1MS44NjggMTU2LjEyMiAxNTEuOTc0QzE1Ni4wMjggMTUyLjA3OSAxNTUuOTUyIDE1Mi4xOTggMTU1Ljg5MiAxNTIuMzM0QzE1NS44MzQgMTUyLjQ2NyAxNTUuNzkxIDE1Mi42MDUgMTU1Ljc2MyAxNTIuNzQ4VjE1My42ODlDMTU1LjgwNCAxNTMuODcyIDE1NS44NzIgMTU0LjA0NyAxNTUuOTY2IDE1NC4yMTdDMTU2LjA2MiAxNTQuMzgzIDE1Ni4xOSAxNTQuNTIgMTU2LjM0OSAxNTQuNjI3QzE1Ni41MSAxNTQuNzMzIDE1Ni43MDkgMTU0Ljc4NyAxNTYuOTQ2IDE1NC43ODdDMTU3LjE0MiAxNTQuNzg3IDE1Ny4zMDggMTU0Ljc0OCAxNTcuNDQ2IDE1NC42N0MxNTcuNTg3IDE1NC41ODkgMTU3LjcgMTU0LjQ3OCAxNTcuNzg2IDE1NC4zMzhDMTU3Ljg3NSAxNTQuMTk3IDE1Ny45NCAxNTQuMDM0IDE1Ny45ODIgMTUzLjg0OUMxNTguMDIzIDE1My42NjQgMTU4LjA0NCAxNTMuNDY3IDE1OC4wNDQgMTUzLjI1NloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPC9nPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDRfNDE4NF85NDUyNykiPgo8bGluZSB4MT0iMTgyLjA1IiB5MT0iMTQ3LjA3MiIgeDI9IjE4Mi4wNSIgeTI9IjE0Ni4yNSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuNSIgc3Ryb2tlLXdpZHRoPSIwLjUiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiLz4KPHBhdGggZD0iTTE3NC4xMDkgMTUyLjAyNVYxNTIuODkyQzE3NC4xMDkgMTUzLjM1OCAxNzQuMDY3IDE1My43NTIgMTczLjk4NCAxNTQuMDcyQzE3My45MDEgMTU0LjM5MiAxNzMuNzgxIDE1NC42NSAxNzMuNjI1IDE1NC44NDVDMTczLjQ2OSAxNTUuMDQxIDE3My4yOCAxNTUuMTgzIDE3My4wNTggMTU1LjI3MUMxNzIuODQgMTU1LjM1NyAxNzIuNTkyIDE1NS40IDE3Mi4zMTYgMTU1LjRDMTcyLjA5NyAxNTUuNCAxNzEuODk2IDE1NS4zNzMgMTcxLjcxMSAxNTUuMzE4QzE3MS41MjYgMTU1LjI2MyAxNzEuMzU5IDE1NS4xNzYgMTcxLjIxMSAxNTUuMDU2QzE3MS4wNjUgMTU0LjkzNCAxNzAuOTQgMTU0Ljc3NSAxNzAuODM2IDE1NC41OEMxNzAuNzMyIDE1NC4zODUgMTcwLjY1MiAxNTQuMTQ4IDE3MC41OTcgMTUzLjg2OUMxNzAuNTQzIDE1My41OSAxNzAuNTE1IDE1My4yNjUgMTcwLjUxNSAxNTIuODkyVjE1Mi4wMjVDMTcwLjUxNSAxNTEuNTU5IDE3MC41NTcgMTUxLjE2OCAxNzAuNjQgMTUwLjg1M0MxNzAuNzI2IDE1MC41MzggMTcwLjg0NyAxNTAuMjg2IDE3MS4wMDQgMTUwLjA5NUMxNzEuMTYgMTQ5LjkwMyAxNzEuMzQ3IDE0OS43NjUgMTcxLjU2NiAxNDkuNjgxQzE3MS43ODggMTQ5LjU5OCAxNzIuMDM1IDE0OS41NTYgMTcyLjMwOCAxNDkuNTU2QzE3Mi41MyAxNDkuNTU2IDE3Mi43MzMgMTQ5LjU4NCAxNzIuOTE4IDE0OS42MzhDMTczLjEwNSAxNDkuNjkxIDE3My4yNzIgMTQ5Ljc3NSAxNzMuNDE4IDE0OS44OTJDMTczLjU2NCAxNTAuMDA3IDE3My42ODcgMTUwLjE2MSAxNzMuNzg5IDE1MC4zNTNDMTczLjg5MyAxNTAuNTQzIDE3My45NzIgMTUwLjc3NiAxNzQuMDI3IDE1MS4wNTJDMTc0LjA4MiAxNTEuMzI5IDE3NC4xMDkgMTUxLjY1MyAxNzQuMTA5IDE1Mi4wMjVaTTE3My4zODMgMTUzLjAxVjE1MS45MDRDMTczLjM4MyAxNTEuNjQ5IDE3My4zNjcgMTUxLjQyNSAxNzMuMzM2IDE1MS4yMzJDMTczLjMwNyAxNTEuMDM3IDE3My4yNjQgMTUwLjg3IDE3My4yMDcgMTUwLjczMkMxNzMuMTUgMTUwLjU5NCAxNzMuMDc3IDE1MC40ODIgMTcyLjk4OCAxNTAuMzk2QzE3Mi45MDIgMTUwLjMxIDE3Mi44MDIgMTUwLjI0OCAxNzIuNjg3IDE1MC4yMDlDMTcyLjU3NSAxNTAuMTY3IDE3Mi40NDkgMTUwLjE0NiAxNzIuMzA4IDE1MC4xNDZDMTcyLjEzNiAxNTAuMTQ2IDE3MS45ODQgMTUwLjE3OSAxNzEuODUxIDE1MC4yNDRDMTcxLjcxOSAxNTAuMzA2IDE3MS42MDcgMTUwLjQwNyAxNzEuNTE1IDE1MC41NDVDMTcxLjQyNyAxNTAuNjgzIDE3MS4zNTkgMTUwLjg2NCAxNzEuMzEyIDE1MS4wODhDMTcxLjI2NSAxNTEuMzEyIDE3MS4yNDIgMTUxLjU4NCAxNzEuMjQyIDE1MS45MDRWMTUzLjAxQzE3MS4yNDIgMTUzLjI2NSAxNzEuMjU2IDE1My40OSAxNzEuMjg1IDE1My42ODVDMTcxLjMxNiAxNTMuODgxIDE3MS4zNjIgMTU0LjA1IDE3MS40MjIgMTU0LjE5M0MxNzEuNDgyIDE1NC4zMzQgMTcxLjU1NCAxNTQuNDUgMTcxLjY0IDE1NC41NDFDMTcxLjcyNiAxNTQuNjMyIDE3MS44MjUgMTU0LjcgMTcxLjkzNyAxNTQuNzQ0QzE3Mi4wNTIgMTU0Ljc4NiAxNzIuMTc4IDE1NC44MDYgMTcyLjMxNiAxNTQuODA2QzE3Mi40OTMgMTU0LjgwNiAxNzIuNjQ4IDE1NC43NzMgMTcyLjc4MSAxNTQuNzA1QzE3Mi45MTQgMTU0LjYzNyAxNzMuMDI1IDE1NC41MzIgMTczLjExMyAxNTQuMzg4QzE3My4yMDQgMTU0LjI0MyAxNzMuMjcyIDE1NC4wNTYgMTczLjMxNiAxNTMuODNDMTczLjM2IDE1My42MDEgMTczLjM4MyAxNTMuMzI3IDE3My4zODMgMTUzLjAxWk0xNzYuMDM2IDE1Mi42MTVMMTc1LjQ1NyAxNTIuNDY3TDE3NS43NDMgMTQ5LjYzNUgxNzguNjYxVjE1MC4zMDJIMTc2LjM1NkwxNzYuMTg0IDE1MS44NDlDMTc2LjI4OCAxNTEuNzg5IDE3Ni40MiAxNTEuNzMzIDE3Ni41NzkgMTUxLjY4MUMxNzYuNzQgMTUxLjYyOSAxNzYuOTI1IDE1MS42MDMgMTc3LjEzMyAxNTEuNjAzQzE3Ny4zOTYgMTUxLjYwMyAxNzcuNjMyIDE1MS42NDkgMTc3Ljg0IDE1MS43NEMxNzguMDQ5IDE1MS44MjkgMTc4LjIyNiAxNTEuOTU2IDE3OC4zNzEgMTUyLjEyM0MxNzguNTIgMTUyLjI4OSAxNzguNjMzIDE1Mi40OSAxNzguNzExIDE1Mi43MjRDMTc4Ljc4OSAxNTIuOTU5IDE3OC44MjkgMTUzLjIyIDE3OC44MjkgMTUzLjUxQzE3OC44MjkgMTUzLjc4MyAxNzguNzkxIDE1NC4wMzQgMTc4LjcxNSAxNTQuMjYzQzE3OC42NDIgMTU0LjQ5MyAxNzguNTMyIDE1NC42OTMgMTc4LjM4MyAxNTQuODY1QzE3OC4yMzUgMTU1LjAzNCAxNzguMDQ3IDE1NS4xNjYgMTc3LjgyMSAxNTUuMjZDMTc3LjU5NyAxNTUuMzUzIDE3Ny4zMzIgMTU1LjQgMTc3LjAyOCAxNTUuNEMxNzYuNzk5IDE1NS40IDE3Ni41ODEgMTU1LjM2OSAxNzYuMzc1IDE1NS4zMDZDMTc2LjE3MiAxNTUuMjQxIDE3NS45OSAxNTUuMTQ0IDE3NS44MjkgMTU1LjAxM0MxNzUuNjcgMTU0Ljg4MSAxNzUuNTM5IDE1NC43MTcgMTc1LjQzOCAxNTQuNTIxQzE3NS4zMzkgMTU0LjMyMyAxNzUuMjc2IDE1NC4wOTIgMTc1LjI1IDE1My44MjZIMTc1LjkzOEMxNzUuOTY5IDE1NC4wMzkgMTc2LjAzMiAxNTQuMjE5IDE3Ni4xMjUgMTU0LjM2NUMxNzYuMjE5IDE1NC41MTEgMTc2LjM0MiAxNTQuNjIyIDE3Ni40OTMgMTU0LjY5N0MxNzYuNjQ2IDE1NC43NyAxNzYuODI1IDE1NC44MDYgMTc3LjAyOCAxNTQuODA2QzE3Ny4yIDE1NC44MDYgMTc3LjM1MiAxNTQuNzc2IDE3Ny40ODUgMTU0LjcxN0MxNzcuNjE4IDE1NC42NTcgMTc3LjczIDE1NC41NzEgMTc3LjgyMSAxNTQuNDU5QzE3Ny45MTIgMTU0LjM0NyAxNzcuOTgxIDE1NC4yMTEgMTc4LjAyOCAxNTQuMDUyQzE3OC4wNzcgMTUzLjg5NCAxNzguMTAyIDE1My43MTUgMTc4LjEwMiAxNTMuNTE3QzE3OC4xMDIgMTUzLjMzOCAxNzguMDc3IDE1My4xNzEgMTc4LjAyOCAxNTMuMDE3QzE3Ny45NzggMTUyLjg2NCAxNzcuOTA0IDE1Mi43MyAxNzcuODA1IDE1Mi42MTVDMTc3LjcwOSAxNTIuNSAxNzcuNTkgMTUyLjQxMiAxNzcuNDUgMTUyLjM0OUMxNzcuMzA5IDE1Mi4yODQgMTc3LjE0OCAxNTIuMjUyIDE3Ni45NjUgMTUyLjI1MkMxNzYuNzIzIDE1Mi4yNTIgMTc2LjUzOSAxNTIuMjg0IDE3Ni40MTQgMTUyLjM0OUMxNzYuMjkyIDE1Mi40MTQgMTc2LjE2NiAxNTIuNTAzIDE3Ni4wMzYgMTUyLjYxNVpNMTgyLjcxMyAxNDkuNjM1VjE1NS4zMjJIMTgxLjk1OVYxNDkuNjM1SDE4Mi43MTNaTTE4NS4wOTUgMTUyLjE5M1YxNTIuODFIMTgyLjU0OFYxNTIuMTkzSDE4NS4wOTVaTTE4NS40ODIgMTQ5LjYzNVYxNTAuMjUySDE4Mi41NDhWMTQ5LjYzNUgxODUuNDgyWk0xODguMDIyIDE1NS40QzE4Ny43MjcgMTU1LjQgMTg3LjQ2IDE1NS4zNTEgMTg3LjIyMSAxNTUuMjUyQzE4Ni45ODQgMTU1LjE1IDE4Ni43OCAxNTUuMDA4IDE4Ni42MDggMTU0LjgyNkMxODYuNDM4IDE1NC42NDQgMTg2LjMwOCAxNTQuNDI3IDE4Ni4yMTcgMTU0LjE3N0MxODYuMTI2IDE1My45MjcgMTg2LjA4IDE1My42NTQgMTg2LjA4IDE1My4zNTdWMTUzLjE5M0MxODYuMDggMTUyLjg0OSAxODYuMTMxIDE1Mi41NDMgMTg2LjIzMyAxNTIuMjc1QzE4Ni4zMzQgMTUyLjAwNCAxODYuNDcyIDE1MS43NzUgMTg2LjY0NyAxNTEuNTg4QzE4Ni44MjEgMTUxLjQgMTg3LjAxOSAxNTEuMjU4IDE4Ny4yNCAxNTEuMTYyQzE4Ny40NjIgMTUxLjA2NiAxODcuNjkxIDE1MS4wMTcgMTg3LjkyOCAxNTEuMDE3QzE4OC4yMyAxNTEuMDE3IDE4OC40OSAxNTEuMDY5IDE4OC43MDkgMTUxLjE3NEMxODguOTMxIDE1MS4yNzggMTg5LjExMiAxNTEuNDI0IDE4OS4yNTIgMTUxLjYxMUMxODkuMzkzIDE1MS43OTYgMTg5LjQ5NyAxNTIuMDE1IDE4OS41NjUgMTUyLjI2N0MxODkuNjMyIDE1Mi41MTcgMTg5LjY2NiAxNTIuNzkxIDE4OS42NjYgMTUzLjA4OFYxNTMuNDEySDE4Ni41MVYxNTIuODIySDE4OC45NDRWMTUyLjc2N0MxODguOTMzIDE1Mi41OCAxODguODk0IDE1Mi4zOTggMTg4LjgyNiAxNTIuMjJDMTg4Ljc2MSAxNTIuMDQzIDE4OC42NTcgMTUxLjg5OCAxODguNTE0IDE1MS43ODNDMTg4LjM3MSAxNTEuNjY4IDE4OC4xNzUgMTUxLjYxMSAxODcuOTI4IDE1MS42MTFDMTg3Ljc2NCAxNTEuNjExIDE4Ny42MTMgMTUxLjY0NiAxODcuNDc1IDE1MS43MTdDMTg3LjMzNyAxNTEuNzg0IDE4Ny4yMTggMTUxLjg4NiAxODcuMTE5IDE1Mi4wMjFDMTg3LjAyIDE1Mi4xNTcgMTg2Ljk0NCAxNTIuMzIyIDE4Ni44ODkgMTUyLjUxN0MxODYuODM0IDE1Mi43MTMgMTg2LjgwNyAxNTIuOTM4IDE4Ni44MDcgMTUzLjE5M1YxNTMuMzU3QzE4Ni44MDcgMTUzLjU1OCAxODYuODM0IDE1My43NDcgMTg2Ljg4OSAxNTMuOTI0QzE4Ni45NDYgMTU0LjA5OCAxODcuMDI4IDE1NC4yNTIgMTg3LjEzNSAxNTQuMzg1QzE4Ny4yNDQgMTU0LjUxNyAxODcuMzc2IDE1NC42MjIgMTg3LjUzIDE1NC42OTdDMTg3LjY4NiAxNTQuNzczIDE4Ny44NjMgMTU0LjgxIDE4OC4wNjEgMTU0LjgxQzE4OC4zMTYgMTU0LjgxIDE4OC41MzIgMTU0Ljc1OCAxODguNzA5IDE1NC42NTRDMTg4Ljg4NiAxNTQuNTUgMTg5LjA0MSAxNTQuNDExIDE4OS4xNzQgMTU0LjIzNkwxODkuNjEyIDE1NC41ODRDMTg5LjUyIDE1NC43MjIgMTg5LjQwNSAxNTQuODUzIDE4OS4yNjQgMTU0Ljk3OEMxODkuMTIzIDE1NS4xMDMgMTg4Ljk1IDE1NS4yMDUgMTg4Ljc0NCAxNTUuMjgzQzE4OC41NDEgMTU1LjM2MSAxODguMyAxNTUuNCAxODguMDIyIDE1NS40Wk0xOTAuNTg5IDE0OS4zMjJIMTkxLjMxNVYxNTQuNTAyTDE5MS4yNTMgMTU1LjMyMkgxOTAuNTg5VjE0OS4zMjJaTTE5NC4xNzEgMTUzLjE3NFYxNTMuMjU2QzE5NC4xNzEgMTUzLjU2MyAxOTQuMTM0IDE1My44NDggMTk0LjA2MSAxNTQuMTExQzE5My45ODggMTU0LjM3MiAxOTMuODgyIDE1NC41OTggMTkzLjc0MSAxNTQuNzkxQzE5My42IDE1NC45ODMgMTkzLjQyOSAxNTUuMTMzIDE5My4yMjUgMTU1LjI0QzE5My4wMjIgMTU1LjM0NyAxOTIuNzg5IDE1NS40IDE5Mi41MjYgMTU1LjRDMTkyLjI1OCAxNTUuNCAxOTIuMDIyIDE1NS4zNTUgMTkxLjgxOSAxNTUuMjYzQzE5MS42MTkgMTU1LjE3IDE5MS40NDkgMTU1LjAzNiAxOTEuMzExIDE1NC44NjFDMTkxLjE3MyAxNTQuNjg3IDE5MS4wNjMgMTU0LjQ3NiAxOTAuOTc5IDE1NC4yMjhDMTkwLjg5OSAxNTMuOTgxIDE5MC44NDMgMTUzLjcwMiAxOTAuODExIDE1My4zOTJWMTUzLjAzM0MxOTAuODQzIDE1Mi43MiAxOTAuODk5IDE1Mi40NDEgMTkwLjk3OSAxNTIuMTkzQzE5MS4wNjMgMTUxLjk0NiAxOTEuMTczIDE1MS43MzUgMTkxLjMxMSAxNTEuNTZDMTkxLjQ0OSAxNTEuMzgzIDE5MS42MTkgMTUxLjI0OSAxOTEuODE5IDE1MS4xNThDMTkyLjAyIDE1MS4wNjQgMTkyLjI1MyAxNTEuMDE3IDE5Mi41MTggMTUxLjAxN0MxOTIuNzg0IDE1MS4wMTcgMTkzLjAyIDE1MS4wNjkgMTkzLjIyNSAxNTEuMTc0QzE5My40MzEgMTUxLjI3NSAxOTMuNjAzIDE1MS40MjEgMTkzLjc0MSAxNTEuNjExQzE5My44ODIgMTUxLjgwMSAxOTMuOTg4IDE1Mi4wMjkgMTk0LjA2MSAxNTIuMjk1QzE5NC4xMzQgMTUyLjU1OCAxOTQuMTcxIDE1Mi44NTEgMTk0LjE3MSAxNTMuMTc0Wk0xOTMuNDQ0IDE1My4yNTZWMTUzLjE3NEMxOTMuNDQ0IDE1Mi45NjMgMTkzLjQyNSAxNTIuNzY1IDE5My4zODYgMTUyLjU4QzE5My4zNDcgMTUyLjM5MiAxOTMuMjg0IDE1Mi4yMjggMTkzLjE5OCAxNTIuMDg4QzE5My4xMTIgMTUxLjk0NCAxOTIuOTk5IDE1MS44MzIgMTkyLjg1OCAxNTEuNzUyQzE5Mi43MTggMTUxLjY2OCAxOTIuNTQ0IDE1MS42MjcgMTkyLjMzOSAxNTEuNjI3QzE5Mi4xNTYgMTUxLjYyNyAxOTEuOTk4IDE1MS42NTggMTkxLjg2MiAxNTEuNzJDMTkxLjcyOSAxNTEuNzgzIDE5MS42MTYgMTUxLjg2OCAxOTEuNTIyIDE1MS45NzRDMTkxLjQyOSAxNTIuMDc5IDE5MS4zNTIgMTUyLjE5OCAxOTEuMjkyIDE1Mi4zMzRDMTkxLjIzNSAxNTIuNDY3IDE5MS4xOTIgMTUyLjYwNSAxOTEuMTYzIDE1Mi43NDhWMTUzLjY4OUMxOTEuMjA1IDE1My44NzIgMTkxLjI3MiAxNTQuMDQ3IDE5MS4zNjYgMTU0LjIxN0MxOTEuNDYyIDE1NC4zODMgMTkxLjU5IDE1NC41MiAxOTEuNzQ5IDE1NC42MjdDMTkxLjkxIDE1NC43MzMgMTkyLjExIDE1NC43ODcgMTkyLjM0NyAxNTQuNzg3QzE5Mi41NDIgMTU0Ljc4NyAxOTIuNzA4IDE1NC43NDggMTkyLjg0NyAxNTQuNjdDMTkyLjk4NyAxNTQuNTg5IDE5My4xIDE1NC40NzggMTkzLjE4NiAxNTQuMzM4QzE5My4yNzUgMTU0LjE5NyAxOTMuMzQgMTU0LjAzNCAxOTMuMzgyIDE1My44NDlDMTkzLjQyMyAxNTMuNjY0IDE5My40NDQgMTUzLjQ2NyAxOTMuNDQ0IDE1My4yNTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjwvZz4KPHBhdGggZD0iTTI3IDEwNy40NDVMNDAgODguMDk2Mkw3NS45NDA3IDQ0Ljk4OTVDNzYuMjYxMSA0NC42MDUyIDc2LjgxNjEgNDQuNTE2OCA3Ny4yNCA0NC43ODI2TDExMC41NTYgNjUuNjcxNkMxMTAuODM0IDY1Ljg0NiAxMTEuMTggNjUuODcyOCAxMTEuNDgyIDY1Ljc0MzNMMTQ3IDUwLjQ5OUwxODQuMDMyIDM5LjgxODNDMTg0LjMyNyAzOS43MzMxIDE4NC41NjcgMzkuNTE2OCAxODQuNjgyIDM5LjIzMThMMTk4LjUgNSIgc3Ryb2tlPSIjRkZDMTA3IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8cGF0aCBkPSJNMjggMTI5LjE2MUw0MCAxMTIuMTRMNzYgNzkuMTYxNEwxMTEgODkuMjY3OEwxNDcgNzkuMTYxNEwxNzggMTIwLjY1MUwxOTkgODkuMjY3OCIgc3Ryb2tlPSIjNENBRjUwIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8cGF0aCBkPSJNMjcgMTE4TDQwIDcxLjc3OTJMNzYgNjQuNTYzNUwxMTAuNSAxNy42NjExTDE0Ni41IDQyLjkxNjJMMTgyIDcxLjc3OTJMMTk4LjUgMTA4IiBzdHJva2U9IiMyMTk2RjMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNDE4NF85NDUyNyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8Y2xpcFBhdGggaWQ9ImNsaXAxXzQxODRfOTQ1MjciPgo8cmVjdCB3aWR0aD0iMzUuNCIgaGVpZ2h0PSIxMC4zMjIiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1OC4zOTk5IDE0NikiLz4KPC9jbGlwUGF0aD4KPGNsaXBQYXRoIGlkPSJjbGlwMl80MTg0Xzk0NTI3Ij4KPHJlY3Qgd2lkdGg9IjM1LjQiIGhlaWdodD0iMTAuMzIyIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOTMuOCAxNDYpIi8+CjwvY2xpcFBhdGg+CjxjbGlwUGF0aCBpZD0iY2xpcDNfNDE4NF85NDUyNyI+CjxyZWN0IHdpZHRoPSIzNS40IiBoZWlnaHQ9IjEwLjMyMiIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyOS4yIDE0NikiLz4KPC9jbGlwUGF0aD4KPGNsaXBQYXRoIGlkPSJjbGlwNF80MTg0Xzk0NTI3Ij4KPHJlY3Qgd2lkdGg9IjM1LjQiIGhlaWdodD0iMTAuMzIyIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTY0LjYgMTQ2KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=", + "description": "Displays changes to time-series data over time—for example, temperature or humidity readings.", + "descriptor": { + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n chartType: 'line',\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings('line'),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n", + "settingsSchema": "{}", + "dataKeySettingsSchema": "{}", + "latestDataKeySettingsSchema": "{}", + "settingsDirective": "tb-time-series-chart-widget-settings", + "dataKeySettingsDirective": "tb-time-series-chart-key-settings", + "latestDataKeySettingsDirective": "", + "hasBasicMode": true, + "basicModeDirective": "tb-time-series-chart-basic-config", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":true,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":false,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"emptyCircle\",\"pointSize\":4,\"fillAreaSettings\":{\"type\":\"opacity\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":true,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":false,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"emptyCircle\",\"pointSize\":4,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.3409583261715494,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"decimals\":null,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":\"dd MMM yyyy HH:mm:ss\",\"lastUpdateAgo\":false,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Line chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + }, + "tags": [ + "chart", + "time series", + "time-series", + "line", + "line chart" + ] +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/point_chart.json b/application/src/main/data/json/system/widget_types/point_chart.json new file mode 100644 index 0000000000..f16d6823ee --- /dev/null +++ b/application/src/main/data/json/system/widget_types/point_chart.json @@ -0,0 +1,32 @@ +{ + "fqn": "point_chart", + "name": "Point chart", + "deprecated": false, + "image": "tb-image:Y2hhcnRfKDMpLnN2Zw==:IlBvaW50IGNoYXJ0IiBzeXN0ZW0gd2lkZ2V0IGltYWdl;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF80MTgyXzExMTkzKSI+CjxwYXRoIGQ9Ik0yLjg0NzY2IDEuMjgxMjVWN0gyLjEyNVYyLjE4MzU5TDAuNjY3OTY5IDIuNzE0ODRWMi4wNjI1TDIuNzM0MzggMS4yODEyNUgyLjg0NzY2Wk04LjY3NTE3IDMuNzAzMTJWNC41NzAzMUM4LjY3NTE3IDUuMDM2NDYgOC42MzM1MSA1LjQyOTY5IDguNTUwMTcgNS43NUM4LjQ2Njg0IDYuMDcwMzEgOC4zNDcwNSA2LjMyODEyIDguMTkwOCA2LjUyMzQ0QzguMDM0NTUgNi43MTg3NSA3Ljg0NTc1IDYuODYwNjggNy42MjQzOSA2Ljk0OTIyQzcuNDA1NjQgNy4wMzUxNiA3LjE1ODI1IDcuMDc4MTIgNi44ODIyIDcuMDc4MTJDNi42NjM0NSA3LjA3ODEyIDYuNDYxNjMgNy4wNTA3OCA2LjI3NjczIDYuOTk2MDlDNi4wOTE4NCA2Ljk0MTQxIDUuOTI1MTcgNi44NTQxNyA1Ljc3NjczIDYuNzM0MzhDNS42MzA5IDYuNjExOTggNS41MDU5IDYuNDUzMTIgNS40MDE3MyA2LjI1NzgxQzUuMjk3NTcgNi4wNjI1IDUuMjE4MTQgNS44MjU1MiA1LjE2MzQ1IDUuNTQ2ODhDNS4xMDg3NyA1LjI2ODIzIDUuMDgxNDIgNC45NDI3MSA1LjA4MTQyIDQuNTcwMzFWMy43MDMxMkM1LjA4MTQyIDMuMjM2OTggNS4xMjMwOSAyLjg0NjM1IDUuMjA2NDIgMi41MzEyNUM1LjI5MjM2IDIuMjE2MTUgNS40MTM0NSAxLjk2MzU0IDUuNTY5NyAxLjc3MzQ0QzUuNzI1OTUgMS41ODA3MyA1LjkxMzQ1IDEuNDQyNzEgNi4xMzIyIDEuMzU5MzhDNi4zNTM1NiAxLjI3NjA0IDYuNjAwOTUgMS4yMzQzOCA2Ljg3NDM5IDEuMjM0MzhDNy4wOTU3NSAxLjIzNDM4IDcuMjk4ODcgMS4yNjE3MiA3LjQ4Mzc3IDEuMzE2NDFDNy42NzEyNyAxLjM2ODQ5IDcuODM3OTMgMS40NTMxMiA3Ljk4Mzc3IDEuNTcwMzFDOC4xMjk2IDEuNjg0OSA4LjI1MzMgMS44Mzg1NCA4LjM1NDg2IDIuMDMxMjVDOC40NTkwMyAyLjIyMTM1IDguNTM4NDUgMi40NTQ0MyA4LjU5MzE0IDIuNzMwNDdDOC42NDc4MyAzLjAwNjUxIDguNjc1MTcgMy4zMzA3MyA4LjY3NTE3IDMuNzAzMTJaTTcuOTQ4NjEgNC42ODc1VjMuNTgyMDNDNy45NDg2MSAzLjMyNjgyIDcuOTMyOTggMy4xMDI4NiA3LjkwMTczIDIuOTEwMTZDNy44NzMwOSAyLjcxNDg0IDcuODMwMTIgMi41NDgxOCA3Ljc3MjgzIDIuNDEwMTZDNy43MTU1NCAyLjI3MjE0IDcuNjQyNjIgMi4xNjAxNiA3LjU1NDA4IDIuMDc0MjJDNy40NjgxNCAxLjk4ODI4IDcuMzY3ODggMS45MjU3OCA3LjI1MzMgMS44ODY3MkM3LjE0MTMyIDEuODQ1MDUgNy4wMTUwMiAxLjgyNDIyIDYuODc0MzkgMS44MjQyMkM2LjcwMjUyIDEuODI0MjIgNi41NTAxNyAxLjg1Njc3IDYuNDE3MzYgMS45MjE4OEM2LjI4NDU1IDEuOTg0MzggNi4xNzI1NyAyLjA4NDY0IDYuMDgxNDIgMi4yMjI2NkM1Ljk5Mjg4IDIuMzYwNjggNS45MjUxNyAyLjU0MTY3IDUuODc4MyAyLjc2NTYyQzUuODMxNDIgMi45ODk1OCA1LjgwNzk4IDMuMjYxNzIgNS44MDc5OCAzLjU4MjAzVjQuNjg3NUM1LjgwNzk4IDQuOTQyNzEgNS44MjIzMSA1LjE2Nzk3IDUuODUwOTUgNS4zNjMyOEM1Ljg4MjIgNS41NTg1OSA1LjkyNzc4IDUuNzI3ODYgNS45ODc2NyA1Ljg3MTA5QzYuMDQ3NTcgNi4wMTE3MiA2LjEyMDQ4IDYuMTI3NiA2LjIwNjQyIDYuMjE4NzVDNi4yOTIzNiA2LjMwOTkgNi4zOTEzMiA2LjM3NzYgNi41MDMzIDYuNDIxODhDNi42MTc4OCA2LjQ2MzU0IDYuNzQ0MTggNi40ODQzOCA2Ljg4MjIgNi40ODQzOEM3LjA1OTI5IDYuNDg0MzggNy4yMTQyMyA2LjQ1MDUyIDcuMzQ3MDUgNi4zODI4MUM3LjQ3OTg2IDYuMzE1MSA3LjU5MDU0IDYuMjA5NjQgNy42NzkwOCA2LjA2NjQxQzcuNzcwMjIgNS45MjA1NyA3LjgzNzkzIDUuNzM0MzggNy44ODIyIDUuNTA3ODFDNy45MjY0NyA1LjI3ODY1IDcuOTQ4NjEgNS4wMDUyMSA3Ljk0ODYxIDQuNjg3NVpNMTMuMzA3NCAzLjcwMzEyVjQuNTcwMzFDMTMuMzA3NCA1LjAzNjQ2IDEzLjI2NTcgNS40Mjk2OSAxMy4xODI0IDUuNzVDMTMuMDk5IDYuMDcwMzEgMTIuOTc5MyA2LjMyODEyIDEyLjgyMyA2LjUyMzQ0QzEyLjY2NjggNi43MTg3NSAxMi40Nzc5IDYuODYwNjggMTIuMjU2NiA2Ljk0OTIyQzEyLjAzNzggNy4wMzUxNiAxMS43OTA0IDcuMDc4MTIgMTEuNTE0NCA3LjA3ODEyQzExLjI5NTcgNy4wNzgxMiAxMS4wOTM4IDcuMDUwNzggMTAuOTA4OSA2Ljk5NjA5QzEwLjcyNCA2Ljk0MTQxIDEwLjU1NzQgNi44NTQxNyAxMC40MDg5IDYuNzM0MzhDMTAuMjYzMSA2LjYxMTk4IDEwLjEzODEgNi40NTMxMiAxMC4wMzM5IDYuMjU3ODFDOS45Mjk3NyA2LjA2MjUgOS44NTAzNCA1LjgyNTUyIDkuNzk1NjYgNS41NDY4OEM5Ljc0MDk3IDUuMjY4MjMgOS43MTM2MyA0Ljk0MjcxIDkuNzEzNjMgNC41NzAzMVYzLjcwMzEyQzkuNzEzNjMgMy4yMzY5OCA5Ljc1NTI5IDIuODQ2MzUgOS44Mzg2MyAyLjUzMTI1QzkuOTI0NTYgMi4yMTYxNSAxMC4wNDU3IDEuOTYzNTQgMTAuMjAxOSAxLjc3MzQ0QzEwLjM1ODIgMS41ODA3MyAxMC41NDU3IDEuNDQyNzEgMTAuNzY0NCAxLjM1OTM4QzEwLjk4NTggMS4yNzYwNCAxMS4yMzMyIDEuMjM0MzggMTEuNTA2NiAxLjIzNDM4QzExLjcyNzkgMS4yMzQzOCAxMS45MzExIDEuMjYxNzIgMTIuMTE2IDEuMzE2NDFDMTIuMzAzNSAxLjM2ODQ5IDEyLjQ3MDEgMS40NTMxMiAxMi42MTYgMS41NzAzMUMxMi43NjE4IDEuNjg0OSAxMi44ODU1IDEuODM4NTQgMTIuOTg3MSAyLjAzMTI1QzEzLjA5MTIgMi4yMjEzNSAxMy4xNzA3IDIuNDU0NDMgMTMuMjI1MyAyLjczMDQ3QzEzLjI4IDMuMDA2NTEgMTMuMzA3NCAzLjMzMDczIDEzLjMwNzQgMy43MDMxMlpNMTIuNTgwOCA0LjY4NzVWMy41ODIwM0MxMi41ODA4IDMuMzI2ODIgMTIuNTY1MiAzLjEwMjg2IDEyLjUzMzkgMi45MTAxNkMxMi41MDUzIDIuNzE0ODQgMTIuNDYyMyAyLjU0ODE4IDEyLjQwNSAyLjQxMDE2QzEyLjM0NzcgMi4yNzIxNCAxMi4yNzQ4IDIuMTYwMTYgMTIuMTg2MyAyLjA3NDIyQzEyLjEwMDMgMS45ODgyOCAxMi4wMDAxIDEuOTI1NzggMTEuODg1NSAxLjg4NjcyQzExLjc3MzUgMS44NDUwNSAxMS42NDcyIDEuODI0MjIgMTEuNTA2NiAxLjgyNDIyQzExLjMzNDcgMS44MjQyMiAxMS4xODI0IDEuODU2NzcgMTEuMDQ5NiAxLjkyMTg4QzEwLjkxNjggMS45ODQzOCAxMC44MDQ4IDIuMDg0NjQgMTAuNzEzNiAyLjIyMjY2QzEwLjYyNTEgMi4zNjA2OCAxMC41NTc0IDIuNTQxNjcgMTAuNTEwNSAyLjc2NTYyQzEwLjQ2MzYgMi45ODk1OCAxMC40NDAyIDMuMjYxNzIgMTAuNDQwMiAzLjU4MjAzVjQuNjg3NUMxMC40NDAyIDQuOTQyNzEgMTAuNDU0NSA1LjE2Nzk3IDEwLjQ4MzIgNS4zNjMyOEMxMC41MTQ0IDUuNTU4NTkgMTAuNTYgNS43Mjc4NiAxMC42MTk5IDUuODcxMDlDMTAuNjc5OCA2LjAxMTcyIDEwLjc1MjcgNi4xMjc2IDEwLjgzODYgNi4yMTg3NUMxMC45MjQ2IDYuMzA5OSAxMS4wMjM1IDYuMzc3NiAxMS4xMzU1IDYuNDIxODhDMTEuMjUwMSA2LjQ2MzU0IDExLjM3NjQgNi40ODQzOCAxMS41MTQ0IDYuNDg0MzhDMTEuNjkxNSA2LjQ4NDM4IDExLjg0NjQgNi40NTA1MiAxMS45NzkzIDYuMzgyODFDMTIuMTEyMSA2LjMxNTEgMTIuMjIyNyA2LjIwOTY0IDEyLjMxMTMgNi4wNjY0MUMxMi40MDI0IDUuOTIwNTcgMTIuNDcwMSA1LjczNDM4IDEyLjUxNDQgNS41MDc4MUMxMi41NTg3IDUuMjc4NjUgMTIuNTgwOCA1LjAwNTIxIDEyLjU4MDggNC42ODc1Wk0xNC4zMDY4IDIuNzA3MDNWMi40MDYyNUMxNC4zMDY4IDIuMTkwMSAxNC4zNTM2IDEuOTkzNDkgMTQuNDQ3NCAxLjgxNjQxQzE0LjU0MTEgMS42MzkzMiAxNC42NzUzIDEuNDk3NCAxNC44NDk3IDEuMzkwNjJDMTUuMDI0MiAxLjI4Mzg1IDE1LjIzMTIgMS4yMzA0NyAxNS40NzA4IDEuMjMwNDdDMTUuNzE1NiAxLjIzMDQ3IDE1LjkyNCAxLjI4Mzg1IDE2LjA5NTggMS4zOTA2MkMxNi4yNzAzIDEuNDk3NCAxNi40MDQ0IDEuNjM5MzIgMTYuNDk4MiAxLjgxNjQxQzE2LjU5MTkgMS45OTM0OSAxNi42Mzg4IDIuMTkwMSAxNi42Mzg4IDIuNDA2MjVWMi43MDcwM0MxNi42Mzg4IDIuOTE3OTcgMTYuNTkxOSAzLjExMTk4IDE2LjQ5ODIgMy4yODkwNkMxNi40MDcgMy40NjYxNSAxNi4yNzQyIDMuNjA4MDcgMTYuMDk5NyAzLjcxNDg0QzE1LjkyNzkgMy44MjE2MSAxNS43MjA4IDMuODc1IDE1LjQ3ODYgMy44NzVDMTUuMjM2NSAzLjg3NSAxNS4wMjY4IDMuODIxNjEgMTQuODQ5NyAzLjcxNDg0QzE0LjY3NTMgMy42MDgwNyAxNC41NDExIDMuNDY2MTUgMTQuNDQ3NCAzLjI4OTA2QzE0LjM1MzYgMy4xMTE5OCAxNC4zMDY4IDIuOTE3OTcgMTQuMzA2OCAyLjcwNzAzWk0xNC44NDk3IDIuNDA2MjVWMi43MDcwM0MxNC44NDk3IDIuODI2ODIgMTQuODcxOSAyLjk0MDEgMTQuOTE2MSAzLjA0Njg4QzE0Ljk2MyAzLjE1MzY1IDE1LjAzMzMgMy4yNDA4OSAxNS4xMjcxIDMuMzA4NTlDMTUuMjIwOCAzLjM3MzcgMTUuMzM4IDMuNDA2MjUgMTUuNDc4NiAzLjQwNjI1QzE1LjYxOTMgMy40MDYyNSAxNS43MzUyIDMuMzczNyAxNS44MjYzIDMuMzA4NTlDMTUuOTE3NCAzLjI0MDg5IDE1Ljk4NTIgMy4xNTM2NSAxNi4wMjk0IDMuMDQ2ODhDMTYuMDczNyAyLjk0MDEgMTYuMDk1OCAyLjgyNjgyIDE2LjA5NTggMi43MDcwM1YyLjQwNjI1QzE2LjA5NTggMi4yODM4NSAxNi4wNzI0IDIuMTY5MjcgMTYuMDI1NSAyLjA2MjVDMTUuOTgxMiAxLjk1MzEyIDE1LjkxMjIgMS44NjU4OSAxNS44MTg1IDEuODAwNzhDMTUuNzI3MyAxLjczMzA3IDE1LjYxMTUgMS42OTkyMiAxNS40NzA4IDEuNjk5MjJDMTUuMzMyOCAxLjY5OTIyIDE1LjIxNjkgMS43MzMwNyAxNS4xMjMyIDEuODAwNzhDMTUuMDMyIDEuODY1ODkgMTQuOTYzIDEuOTUzMTIgMTQuOTE2MSAyLjA2MjVDMTQuODcxOSAyLjE2OTI3IDE0Ljg0OTcgMi4yODM4NSAxNC44NDk3IDIuNDA2MjVaTTE3LjA3NjMgNS45MTAxNlY1LjYwNTQ3QzE3LjA3NjMgNS4zOTE5MyAxNy4xMjMyIDUuMTk2NjEgMTcuMjE2OSA1LjAxOTUzQzE3LjMxMDcgNC44NDI0NSAxNy40NDQ4IDQuNzAwNTIgMTcuNjE5MyA0LjU5Mzc1QzE3Ljc5MzcgNC40ODY5OCAxOC4wMDA4IDQuNDMzNTkgMTguMjQwNCA0LjQzMzU5QzE4LjQ4NTIgNC40MzM1OSAxOC42OTM1IDQuNDg2OTggMTguODY1NCA0LjU5Mzc1QzE5LjAzOTggNC43MDA1MiAxOS4xNzQgNC44NDI0NSAxOS4yNjc3IDUuMDE5NTNDMTkuMzYxNSA1LjE5NjYxIDE5LjQwODMgNS4zOTE5MyAxOS40MDgzIDUuNjA1NDdWNS45MTAxNkMxOS40MDgzIDYuMTIzNyAxOS4zNjE1IDYuMzE5MDEgMTkuMjY3NyA2LjQ5NjA5QzE5LjE3NjYgNi42NzMxOCAxOS4wNDM3IDYuODE1MSAxOC44NjkzIDYuOTIxODhDMTguNjk3NCA3LjAyODY1IDE4LjQ5MDQgNy4wODIwMyAxOC4yNDgyIDcuMDgyMDNDMTguMDA2IDcuMDgyMDMgMTcuNzk3NyA3LjAyODY1IDE3LjYyMzIgNi45MjE4OEMxNy40NDg3IDYuODE1MSAxNy4zMTMzIDYuNjczMTggMTcuMjE2OSA2LjQ5NjA5QzE3LjEyMzIgNi4zMTkwMSAxNy4wNzYzIDYuMTIzNyAxNy4wNzYzIDUuOTEwMTZaTTE3LjYxOTMgNS42MDU0N1Y1LjkxMDE2QzE3LjYxOTMgNi4wMjk5NSAxNy42NDE0IDYuMTQ0NTMgMTcuNjg1NyA2LjI1MzkxQzE3LjczMjUgNi4zNjA2OCAxNy44MDI5IDYuNDQ3OTIgMTcuODk2NiA2LjUxNTYyQzE3Ljk5MDQgNi41ODA3MyAxOC4xMDc1IDYuNjEzMjggMTguMjQ4MiA2LjYxMzI4QzE4LjM4ODggNi42MTMyOCAxOC41MDQ3IDYuNTgwNzMgMTguNTk1OCA2LjUxNTYyQzE4LjY4OTYgNi40NDc5MiAxOC43NTg2IDYuMzYwNjggMTguODAyOSA2LjI1MzkxQzE4Ljg0NzEgNi4xNDcxNCAxOC44NjkzIDYuMDMyNTUgMTguODY5MyA1LjkxMDE2VjUuNjA1NDdDMTguODY5MyA1LjQ4MzA3IDE4Ljg0NTggNS4zNjg0OSAxOC43OTkgNS4yNjE3MkMxOC43NTQ3IDUuMTU0OTUgMTguNjg1NyA1LjA2OTAxIDE4LjU5MTkgNS4wMDM5MUMxOC41MDA4IDQuOTM2MiAxOC4zODM2IDQuOTAyMzQgMTguMjQwNCA0LjkwMjM0QzE4LjEwMjMgNC45MDIzNCAxNy45ODY1IDQuOTM2MiAxNy44OTI3IDUuMDAzOTFDMTcuODAxNiA1LjA2OTAxIDE3LjczMjUgNS4xNTQ5NSAxNy42ODU3IDUuMjYxNzJDMTcuNjQxNCA1LjM2ODQ5IDE3LjYxOTMgNS40ODMwNyAxNy42MTkzIDUuNjA1NDdaTTE4LjQyIDIuMTIxMDlMMTUuNjQyNyA2LjU2NjQxTDE1LjIzNjUgNi4zMDg1OUwxOC4wMTM4IDEuODYzMjhMMTguNDIgMi4xMjEwOVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTguMDU4NTkgMzMuNjY3N0M4LjA1ODU5IDM0LjAxNDEgNy45Nzc4NiAzNC4zMDgzIDcuODE2NDEgMzQuNTUwNUM3LjY1NzU1IDM0Ljc5MDEgNy40NDE0MSAzNC45NzI0IDcuMTY3OTcgMzUuMDk3NEM2Ljg5NzE0IDM1LjIyMjQgNi41OTExNSAzNS4yODQ5IDYuMjUgMzUuMjg0OUM1LjkwODg1IDM1LjI4NDkgNS42MDE1NiAzNS4yMjI0IDUuMzI4MTIgMzUuMDk3NEM1LjA1NDY5IDM0Ljk3MjQgNC44Mzg1NCAzNC43OTAxIDQuNjc5NjkgMzQuNTUwNUM0LjUyMDgzIDM0LjMwODMgNC40NDE0MSAzNC4wMTQxIDQuNDQxNDEgMzMuNjY3N0M0LjQ0MTQxIDMzLjQ0MTIgNC40ODQzOCAzMy4yMzQxIDQuNTcwMzEgMzMuMDQ2NkM0LjY1ODg1IDMyLjg1NjUgNC43ODI1NSAzMi42OTEyIDQuOTQxNDEgMzIuNTUwNUM1LjEwMjg2IDMyLjQwOTkgNS4yOTI5NyAzMi4zMDE4IDUuNTExNzIgMzIuMjI2M0M1LjczMzA3IDMyLjE0ODIgNS45NzY1NiAzMi4xMDkxIDYuMjQyMTkgMzIuMTA5MUM2LjU5MTE1IDMyLjEwOTEgNi45MDIzNCAzMi4xNzY4IDcuMTc1NzggMzIuMzEyM0M3LjQ0OTIyIDMyLjQ0NTEgNy42NjQwNiAzMi42Mjg3IDcuODIwMzEgMzIuODYzQzcuOTc5MTcgMzMuMDk3NCA4LjA1ODU5IDMzLjM2NTYgOC4wNTg1OSAzMy42Njc3Wk03LjMzMjAzIDMzLjY1MjFDNy4zMzIwMyAzMy40NDEyIDcuMjg2NDYgMzMuMjU1IDcuMTk1MzEgMzMuMDkzNUM3LjEwNDE3IDMyLjkyOTQgNi45NzY1NiAzMi44MDE4IDYuODEyNSAzMi43MTA3QzYuNjQ4NDQgMzIuNjE5NSA2LjQ1ODMzIDMyLjU3NCA2LjI0MjE5IDMyLjU3NEM2LjAyMDgzIDMyLjU3NCA1LjgyOTQzIDMyLjYxOTUgNS42Njc5NyAzMi43MTA3QzUuNTA5MTEgMzIuODAxOCA1LjM4NTQyIDMyLjkyOTQgNS4yOTY4OCAzMy4wOTM1QzUuMjA4MzMgMzMuMjU1IDUuMTY0MDYgMzMuNDQxMiA1LjE2NDA2IDMzLjY1MjFDNS4xNjQwNiAzMy44NzA4IDUuMjA3MDMgMzQuMDU4MyA1LjI5Mjk3IDM0LjIxNDZDNS4zODE1MSAzNC4zNjgyIDUuNTA2NTEgMzQuNDg2NyA1LjY2Nzk3IDM0LjU3MDFDNS44MzIwMyAzNC42NTA4IDYuMDI2MDQgMzQuNjkxMiA2LjI1IDM0LjY5MTJDNi40NzM5NiAzNC42OTEyIDYuNjY2NjcgMzQuNjUwOCA2LjgyODEyIDM0LjU3MDFDNi45ODk1OCAzNC40ODY3IDcuMTEzMjggMzQuMzY4MiA3LjE5OTIyIDM0LjIxNDZDNy4yODc3NiAzNC4wNTgzIDcuMzMyMDMgMzMuODcwOCA3LjMzMjAzIDMzLjY1MjFaTTcuOTI1NzggMzAuOTk5OEM3LjkyNTc4IDMxLjI3NTggNy44NTI4NiAzMS41MjQ1IDcuNzA3MDMgMzEuNzQ1OEM3LjU2MTIgMzEuOTY3MiA3LjM2MTk4IDMyLjE0MTcgNy4xMDkzOCAzMi4yNjkzQzYuODU2NzcgMzIuMzk2OSA2LjU3MDMxIDMyLjQ2MDcgNi4yNSAzMi40NjA3QzUuOTI0NDggMzIuNDYwNyA1LjYzNDExIDMyLjM5NjkgNS4zNzg5MSAzMi4yNjkzQzUuMTI2MyAzMi4xNDE3IDQuOTI4MzkgMzEuOTY3MiA0Ljc4NTE2IDMxLjc0NThDNC42NDE5MyAzMS41MjQ1IDQuNTcwMzEgMzEuMjc1OCA0LjU3MDMxIDMwLjk5OThDNC41NzAzMSAzMC42NjkgNC42NDE5MyAzMC4zODc4IDQuNzg1MTYgMzAuMTU2QzQuOTMwOTkgMjkuOTI0MiA1LjEzMDIxIDI5Ljc0NzIgNS4zODI4MSAyOS42MjQ4QzUuNjM1NDIgMjkuNTAyNCA1LjkyMzE4IDI5LjQ0MTIgNi4yNDYwOSAyOS40NDEyQzYuNTcxNjEgMjkuNDQxMiA2Ljg2MDY4IDI5LjUwMjQgNy4xMTMyOCAyOS42MjQ4QzcuMzY1ODkgMjkuNzQ3MiA3LjU2MzggMjkuOTI0MiA3LjcwNzAzIDMwLjE1NkM3Ljg1Mjg2IDMwLjM4NzggNy45MjU3OCAzMC42NjkgNy45MjU3OCAzMC45OTk4Wk03LjIwMzEyIDMxLjAxMTVDNy4yMDMxMiAzMC44MjE0IDcuMTYyNzYgMzAuNjUzNCA3LjA4MjAzIDMwLjUwNzZDNy4wMDEzIDMwLjM2MTcgNi44ODkzMiAzMC4yNDcyIDYuNzQ2MDkgMzAuMTYzOEM2LjYwMjg2IDMwLjA3NzkgNi40MzYyIDMwLjAzNDkgNi4yNDYwOSAzMC4wMzQ5QzYuMDU1OTkgMzAuMDM0OSA1Ljg4OTMyIDMwLjA3NTMgNS43NDYwOSAzMC4xNTZDNS42MDU0NyAzMC4yMzQxIDUuNDk0NzkgMzAuMzQ2MSA1LjQxNDA2IDMwLjQ5MTlDNS4zMzU5NCAzMC42Mzc4IDUuMjk2ODggMzAuODExIDUuMjk2ODggMzEuMDExNUM1LjI5Njg4IDMxLjIwNjggNS4zMzU5NCAzMS4zNzc0IDUuNDE0MDYgMzEuNTIzMkM1LjQ5NDc5IDMxLjY2OSA1LjYwNjc3IDMxLjc4MjMgNS43NSAzMS44NjNDNS44OTMyMyAzMS45NDM4IDYuMDU5OSAzMS45ODQxIDYuMjUgMzEuOTg0MUM2LjQ0MDEgMzEuOTg0MSA2LjYwNTQ3IDMxLjk0MzggNi43NDYwOSAzMS44NjNDNi44ODkzMiAzMS43ODIzIDcuMDAxMyAzMS42NjkgNy4wODIwMyAzMS41MjMyQzcuMTYyNzYgMzEuMzc3NCA3LjIwMzEyIDMxLjIwNjggNy4yMDMxMiAzMS4wMTE1Wk0xMi42NzUyIDMxLjkwOTlWMzIuNzc3MUMxMi42NzUyIDMzLjI0MzIgMTIuNjMzNSAzMy42MzY1IDEyLjU1MDIgMzMuOTU2OEMxMi40NjY4IDM0LjI3NzEgMTIuMzQ3IDM0LjUzNDkgMTIuMTkwOCAzNC43MzAyQzEyLjAzNDUgMzQuOTI1NSAxMS44NDU3IDM1LjA2NzUgMTEuNjI0NCAzNS4xNTZDMTEuNDA1NiAzNS4yNDE5IDExLjE1ODIgMzUuMjg0OSAxMC44ODIyIDM1LjI4NDlDMTAuNjYzNSAzNS4yODQ5IDEwLjQ2MTYgMzUuMjU3NiAxMC4yNzY3IDM1LjIwMjlDMTAuMDkxOCAzNS4xNDgyIDkuOTI1MTcgMzUuMDYxIDkuNzc2NzMgMzQuOTQxMkM5LjYzMDkgMzQuODE4OCA5LjUwNTkgMzQuNjU5OSA5LjQwMTczIDM0LjQ2NDZDOS4yOTc1NyAzNC4yNjkzIDkuMjE4MTQgMzQuMDMyMyA5LjE2MzQ1IDMzLjc1MzdDOS4xMDg3NyAzMy40NzUgOS4wODE0MiAzMy4xNDk1IDkuMDgxNDIgMzIuNzc3MVYzMS45MDk5QzkuMDgxNDIgMzEuNDQzOCA5LjEyMzA5IDMxLjA1MzEgOS4yMDY0MiAzMC43MzhDOS4yOTIzNiAzMC40MjI5IDkuNDEzNDUgMzAuMTcwMyA5LjU2OTcgMjkuOTgwMkM5LjcyNTk1IDI5Ljc4NzUgOS45MTM0NSAyOS42NDk1IDEwLjEzMjIgMjkuNTY2MkMxMC4zNTM2IDI5LjQ4MjggMTAuNjAxIDI5LjQ0MTIgMTAuODc0NCAyOS40NDEyQzExLjA5NTcgMjkuNDQxMiAxMS4yOTg5IDI5LjQ2ODUgMTEuNDgzOCAyOS41MjMyQzExLjY3MTMgMjkuNTc1MyAxMS44Mzc5IDI5LjY1OTkgMTEuOTgzOCAyOS43NzcxQzEyLjEyOTYgMjkuODkxNyAxMi4yNTMzIDMwLjA0NTMgMTIuMzU0OSAzMC4yMzhDMTIuNDU5IDMwLjQyODEgMTIuNTM4NSAzMC42NjEyIDEyLjU5MzEgMzAuOTM3M0MxMi42NDc4IDMxLjIxMzMgMTIuNjc1MiAzMS41Mzc1IDEyLjY3NTIgMzEuOTA5OVpNMTEuOTQ4NiAzMi44OTQzVjMxLjc4ODhDMTEuOTQ4NiAzMS41MzM2IDExLjkzMyAzMS4zMDk3IDExLjkwMTcgMzEuMTE2OUMxMS44NzMxIDMwLjkyMTYgMTEuODMwMSAzMC43NTUgMTEuNzcyOCAzMC42MTY5QzExLjcxNTUgMzAuNDc4OSAxMS42NDI2IDMwLjM2NjkgMTEuNTU0MSAzMC4yODFDMTEuNDY4MSAzMC4xOTUxIDExLjM2NzkgMzAuMTMyNiAxMS4yNTMzIDMwLjA5MzVDMTEuMTQxMyAzMC4wNTE4IDExLjAxNSAzMC4wMzEgMTAuODc0NCAzMC4wMzFDMTAuNzAyNSAzMC4wMzEgMTAuNTUwMiAzMC4wNjM2IDEwLjQxNzQgMzAuMTI4N0MxMC4yODQ1IDMwLjE5MTIgMTAuMTcyNiAzMC4yOTE0IDEwLjA4MTQgMzAuNDI5NEM5Ljk5Mjg4IDMwLjU2NzUgOS45MjUxNyAzMC43NDg1IDkuODc4MyAzMC45NzI0QzkuODMxNDIgMzEuMTk2NCA5LjgwNzk4IDMxLjQ2ODUgOS44MDc5OCAzMS43ODg4VjMyLjg5NDNDOS44MDc5OCAzMy4xNDk1IDkuODIyMzEgMzMuMzc0OCA5Ljg1MDk1IDMzLjU3MDFDOS44ODIyIDMzLjc2NTQgOS45Mjc3OCAzMy45MzQ3IDkuOTg3NjcgMzQuMDc3OUMxMC4wNDc2IDM0LjIxODUgMTAuMTIwNSAzNC4zMzQ0IDEwLjIwNjQgMzQuNDI1NUMxMC4yOTI0IDM0LjUxNjcgMTAuMzkxMyAzNC41ODQ0IDEwLjUwMzMgMzQuNjI4N0MxMC42MTc5IDM0LjY3MDMgMTAuNzQ0MiAzNC42OTEyIDEwLjg4MjIgMzQuNjkxMkMxMS4wNTkzIDM0LjY5MTIgMTEuMjE0MiAzNC42NTczIDExLjM0NyAzNC41ODk2QzExLjQ3OTkgMzQuNTIxOSAxMS41OTA1IDM0LjQxNjQgMTEuNjc5MSAzNC4yNzMyQzExLjc3MDIgMzQuMTI3NCAxMS44Mzc5IDMzLjk0MTIgMTEuODgyMiAzMy43MTQ2QzExLjkyNjUgMzMuNDg1NCAxMS45NDg2IDMzLjIxMiAxMS45NDg2IDMyLjg5NDNaTTEzLjY3NDYgMzAuOTEzOFYzMC42MTNDMTMuNjc0NiAzMC4zOTY5IDEzLjcyMTQgMzAuMjAwMyAxMy44MTUyIDMwLjAyMzJDMTMuOTA4OSAyOS44NDYxIDE0LjA0MzEgMjkuNzA0MiAxNC4yMTc1IDI5LjU5NzRDMTQuMzkyIDI5LjQ5MDYgMTQuNTk5IDI5LjQzNzMgMTQuODM4NiAyOS40MzczQzE1LjA4MzQgMjkuNDM3MyAxNS4yOTE4IDI5LjQ5MDYgMTUuNDYzNiAyOS41OTc0QzE1LjYzODEgMjkuNzA0MiAxNS43NzIyIDI5Ljg0NjEgMTUuODY2IDMwLjAyMzJDMTUuOTU5NyAzMC4yMDAzIDE2LjAwNjYgMzAuMzk2OSAxNi4wMDY2IDMwLjYxM1YzMC45MTM4QzE2LjAwNjYgMzEuMTI0OCAxNS45NTk3IDMxLjMxODggMTUuODY2IDMxLjQ5NThDMTUuNzc0OCAzMS42NzI5IDE1LjY0MiAzMS44MTQ5IDE1LjQ2NzUgMzEuOTIxNkMxNS4yOTU3IDMyLjAyODQgMTUuMDg4NiAzMi4wODE4IDE0Ljg0NjQgMzIuMDgxOEMxNC42MDQzIDMyLjA4MTggMTQuMzk0NiAzMi4wMjg0IDE0LjIxNzUgMzEuOTIxNkMxNC4wNDMxIDMxLjgxNDkgMTMuOTA4OSAzMS42NzI5IDEzLjgxNTIgMzEuNDk1OEMxMy43MjE0IDMxLjMxODggMTMuNjc0NiAzMS4xMjQ4IDEzLjY3NDYgMzAuOTEzOFpNMTQuMjE3NSAzMC42MTNWMzAuOTEzOEMxNC4yMTc1IDMxLjAzMzYgMTQuMjM5NyAzMS4xNDY5IDE0LjI4MzkgMzEuMjUzN0MxNC4zMzA4IDMxLjM2MDQgMTQuNDAxMSAzMS40NDc3IDE0LjQ5NDkgMzEuNTE1NEMxNC41ODg2IDMxLjU4MDUgMTQuNzA1OCAzMS42MTMgMTQuODQ2NCAzMS42MTNDMTQuOTg3MSAzMS42MTMgMTUuMTAyOSAzMS41ODA1IDE1LjE5NDEgMzEuNTE1NEMxNS4yODUyIDMxLjQ0NzcgMTUuMzUyOSAzMS4zNjA0IDE1LjM5NzIgMzEuMjUzN0MxNS40NDE1IDMxLjE0NjkgMTUuNDYzNiAzMS4wMzM2IDE1LjQ2MzYgMzAuOTEzOFYzMC42MTNDMTUuNDYzNiAzMC40OTA2IDE1LjQ0MDIgMzAuMzc2MSAxNS4zOTMzIDMwLjI2OTNDMTUuMzQ5IDMwLjE1OTkgMTUuMjggMzAuMDcyNyAxNS4xODYzIDMwLjAwNzZDMTUuMDk1MSAyOS45Mzk5IDE0Ljk3OTMgMjkuOTA2IDE0LjgzODYgMjkuOTA2QzE0LjcwMDYgMjkuOTA2IDE0LjU4NDcgMjkuOTM5OSAxNC40OTEgMzAuMDA3NkMxNC4zOTk4IDMwLjA3MjcgMTQuMzMwOCAzMC4xNTk5IDE0LjI4MzkgMzAuMjY5M0MxNC4yMzk3IDMwLjM3NjEgMTQuMjE3NSAzMC40OTA2IDE0LjIxNzUgMzAuNjEzWk0xNi40NDQxIDM0LjExNjlWMzMuODEyM0MxNi40NDQxIDMzLjU5ODcgMTYuNDkxIDMzLjQwMzQgMTYuNTg0NyAzMy4yMjYzQzE2LjY3ODUgMzMuMDQ5MiAxNi44MTI2IDMyLjkwNzMgMTYuOTg3MSAzMi44MDA1QzE3LjE2MTUgMzIuNjkzOCAxNy4zNjg2IDMyLjY0MDQgMTcuNjA4MiAzMi42NDA0QzE3Ljg1MjkgMzIuNjQwNCAxOC4wNjEzIDMyLjY5MzggMTguMjMzMiAzMi44MDA1QzE4LjQwNzYgMzIuOTA3MyAxOC41NDE4IDMzLjA0OTIgMTguNjM1NSAzMy4yMjYzQzE4LjcyOTMgMzMuNDAzNCAxOC43NzYxIDMzLjU5ODcgMTguNzc2MSAzMy44MTIzVjM0LjExNjlDMTguNzc2MSAzNC4zMzA1IDE4LjcyOTMgMzQuNTI1OCAxOC42MzU1IDM0LjcwMjlDMTguNTQ0NCAzNC44OCAxOC40MTE1IDM1LjAyMTkgMTguMjM3MSAzNS4xMjg3QzE4LjA2NTIgMzUuMjM1NCAxNy44NTgyIDM1LjI4ODggMTcuNjE2IDM1LjI4ODhDMTcuMzczOCAzNS4yODg4IDE3LjE2NTQgMzUuMjM1NCAxNi45OTEgMzUuMTI4N0MxNi44MTY1IDM1LjAyMTkgMTYuNjgxMSAzNC44OCAxNi41ODQ3IDM0LjcwMjlDMTYuNDkxIDM0LjUyNTggMTYuNDQ0MSAzNC4zMzA1IDE2LjQ0NDEgMzQuMTE2OVpNMTYuOTg3MSAzMy44MTIzVjM0LjExNjlDMTYuOTg3MSAzNC4yMzY3IDE3LjAwOTIgMzQuMzUxMyAxNy4wNTM1IDM0LjQ2MDdDMTcuMTAwMyAzNC41Njc1IDE3LjE3MDcgMzQuNjU0NyAxNy4yNjQ0IDM0LjcyMjRDMTcuMzU4MiAzNC43ODc1IDE3LjQ3NTMgMzQuODIwMSAxNy42MTYgMzQuODIwMUMxNy43NTY2IDM0LjgyMDEgMTcuODcyNSAzNC43ODc1IDE3Ljk2MzYgMzQuNzIyNEMxOC4wNTc0IDM0LjY1NDcgMTguMTI2NCAzNC41Njc1IDE4LjE3MDcgMzQuNDYwN0MxOC4yMTQ5IDM0LjM1MzkgMTguMjM3MSAzNC4yMzkzIDE4LjIzNzEgMzQuMTE2OVYzMy44MTIzQzE4LjIzNzEgMzMuNjg5OSAxOC4yMTM2IDMzLjU3NTMgMTguMTY2OCAzMy40Njg1QzE4LjEyMjUgMzMuMzYxNyAxOC4wNTM1IDMzLjI3NTggMTcuOTU5NyAzMy4yMTA3QzE3Ljg2ODYgMzMuMTQzIDE3Ljc1MTQgMzMuMTA5MSAxNy42MDgyIDMzLjEwOTFDMTcuNDcwMSAzMy4xMDkxIDE3LjM1NDMgMzMuMTQzIDE3LjI2MDUgMzMuMjEwN0MxNy4xNjk0IDMzLjI3NTggMTcuMTAwMyAzMy4zNjE3IDE3LjA1MzUgMzMuNDY4NUMxNy4wMDkyIDMzLjU3NTMgMTYuOTg3MSAzMy42ODk5IDE2Ljk4NzEgMzMuODEyM1pNMTcuNzg3OCAzMC4zMjc5TDE1LjAxMDUgMzQuNzczMkwxNC42MDQzIDM0LjUxNTRMMTcuMzgxNiAzMC4wNzAxTDE3Ljc4NzggMzAuMzI3OVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTcuMjQ2MDkgNTcuNzE4M0g3LjMwODU5VjU4LjMzMTVINy4yNDYwOUM2Ljg2MzI4IDU4LjMzMTUgNi41NDI5NyA1OC4zOTQgNi4yODUxNiA1OC41MTlDNi4wMjczNCA1OC42NDE0IDUuODIyOTIgNTguODA2OCA1LjY3MTg4IDU5LjAxNTFDNS41MjA4MyA1OS4yMjA5IDUuNDExNDYgNTkuNDUyNiA1LjM0Mzc1IDU5LjcxMDRDNS4yNzg2NSA1OS45NjgzIDUuMjQ2MDkgNjAuMjMgNS4yNDYwOSA2MC40OTU2VjYxLjMzMTVDNS4yNDYwOSA2MS41ODQxIDUuMjc2MDQgNjEuODA4MSA1LjMzNTk0IDYyLjAwMzRDNS4zOTU4MyA2Mi4xOTYxIDUuNDc3ODYgNjIuMzU4OSA1LjU4MjAzIDYyLjQ5MTdDNS42ODYyIDYyLjYyNDUgNS44MDMzOSA2Mi43MjQ4IDUuOTMzNTkgNjIuNzkyNUM2LjA2NjQxIDYyLjg2MDIgNi4yMDQ0MyA2Mi44OTQgNi4zNDc2NiA2Mi44OTRDNi41MTQzMiA2Mi44OTQgNi42NjI3NiA2Mi44NjI4IDYuNzkyOTcgNjIuODAwM0M2LjkyMzE4IDYyLjczNTIgNy4wMzI1NSA2Mi42NDUzIDcuMTIxMDkgNjIuNTMwOEM3LjIxMjI0IDYyLjQxMzYgNy4yODEyNSA2Mi4yNzU2IDcuMzI4MTIgNjIuMTE2N0M3LjM3NSA2MS45NTc4IDcuMzk4NDQgNjEuNzgzNCA3LjM5ODQ0IDYxLjU5MzNDNy4zOTg0NCA2MS40MjQgNy4zNzc2IDYxLjI2MTIgNy4zMzU5NCA2MS4xMDVDNy4yOTQyNyA2MC45NDYxIDcuMjMwNDcgNjAuODA1NSA3LjE0NDUzIDYwLjY4MzFDNy4wNTg1OSA2MC41NTgxIDYuOTUwNTIgNjAuNDYwNCA2LjgyMDMxIDYwLjM5MDFDNi42OTI3MSA2MC4zMTcyIDYuNTQwMzYgNjAuMjgwOCA2LjM2MzI4IDYwLjI4MDhDNi4xNjI3NiA2MC4yODA4IDUuOTc1MjYgNjAuMzMwMiA1LjgwMDc4IDYwLjQyOTJDNS42Mjg5MSA2MC41MjU2IDUuNDg2OTggNjAuNjUzMiA1LjM3NSA2MC44MTJDNS4yNjU2MiA2MC45NjgzIDUuMjAzMTIgNjEuMTM4OCA1LjE4NzUgNjEuMzIzN0w0LjgwNDY5IDYxLjMxOThDNC44NDExNSA2MS4wMjgyIDQuOTA4ODUgNjAuNzc5NSA1LjAwNzgxIDYwLjU3MzdDNS4xMDkzOCA2MC4zNjU0IDUuMjM0MzggNjAuMTk2MSA1LjM4MjgxIDYwLjA2NTlDNS41MzM4NSA1OS45MzMxIDUuNzAxODIgNTkuODM2OCA1Ljg4NjcyIDU5Ljc3NjlDNi4wNzQyMiA1OS43MTQ0IDYuMjcyMTQgNTkuNjgzMSA2LjQ4MDQ3IDU5LjY4MzFDNi43NjQzMiA1OS42ODMxIDcuMDA5MTEgNTkuNzM2NSA3LjIxNDg0IDU5Ljg0MzNDNy40MjA1NyA1OS45NSA3LjU4OTg0IDYwLjA5MzMgNy43MjI2NiA2MC4yNzI5QzcuODU1NDcgNjAuNDUgNy45NTMxMiA2MC42NTA2IDguMDE1NjIgNjAuODc0NUM4LjA4MDczIDYxLjA5NTkgOC4xMTMyOCA2MS4zMjM3IDguMTEzMjggNjEuNTU4MUM4LjExMzI4IDYxLjgyNjMgOC4wNzU1MiA2Mi4wNzc2IDggNjIuMzEyQzcuOTI0NDggNjIuNTQ2NCA3LjgxMTIgNjIuNzUyMSA3LjY2MDE2IDYyLjkyOTJDNy41MTE3MiA2My4xMDYzIDcuMzI4MTIgNjMuMjQ0MyA3LjEwOTM4IDYzLjM0MzNDNi44OTA2MiA2My40NDIyIDYuNjM2NzIgNjMuNDkxNyA2LjM0NzY2IDYzLjQ5MTdDNi4wNDAzNiA2My40OTE3IDUuNzcyMTQgNjMuNDI5MiA1LjU0Mjk3IDYzLjMwNDJDNS4zMTM4IDYzLjE3NjYgNS4xMjM3IDYzLjAwNzMgNC45NzI2NiA2Mi43OTY0QzQuODIxNjEgNjIuNTg1NCA0LjcwODMzIDYyLjM1MTEgNC42MzI4MSA2Mi4wOTMzQzQuNTU3MjkgNjEuODM1NCA0LjUxOTUzIDYxLjU3MzcgNC41MTk1MyA2MS4zMDgxVjYwLjk2ODNDNC41MTk1MyA2MC41NjcyIDQuNTU5OSA2MC4xNzQgNC42NDA2MiA1OS43ODg2QzQuNzIxMzUgNTkuNDAzMiA0Ljg2MDY4IDU5LjA1NDIgNS4wNTg1OSA1OC43NDE3QzUuMjU5MTEgNTguNDI5MiA1LjUzNjQ2IDU4LjE4MDUgNS44OTA2MiA1Ny45OTU2QzYuMjQ0NzkgNTcuODEwNyA2LjY5NjYxIDU3LjcxODMgNy4yNDYwOSA1Ny43MTgzWk0xMi42NzUyIDYwLjExNjdWNjAuOTgzOUMxMi42NzUyIDYxLjQ1IDEyLjYzMzUgNjEuODQzMyAxMi41NTAyIDYyLjE2MzZDMTIuNDY2OCA2Mi40ODM5IDEyLjM0NyA2Mi43NDE3IDEyLjE5MDggNjIuOTM3QzEyLjAzNDUgNjMuMTMyMyAxMS44NDU3IDYzLjI3NDMgMTEuNjI0NCA2My4zNjI4QzExLjQwNTYgNjMuNDQ4NyAxMS4xNTgyIDYzLjQ5MTcgMTAuODgyMiA2My40OTE3QzEwLjY2MzUgNjMuNDkxNyAxMC40NjE2IDYzLjQ2NDQgMTAuMjc2NyA2My40MDk3QzEwLjA5MTggNjMuMzU1IDkuOTI1MTcgNjMuMjY3NyA5Ljc3NjczIDYzLjE0NzlDOS42MzA5IDYzLjAyNTYgOS41MDU5IDYyLjg2NjcgOS40MDE3MyA2Mi42NzE0QzkuMjk3NTcgNjIuNDc2MSA5LjIxODE0IDYyLjIzOTEgOS4xNjM0NSA2MS45NjA0QzkuMTA4NzcgNjEuNjgxOCA5LjA4MTQyIDYxLjM1NjMgOS4wODE0MiA2MC45ODM5VjYwLjExNjdDOS4wODE0MiA1OS42NTA2IDkuMTIzMDkgNTkuMjU5OSA5LjIwNjQyIDU4Ljk0NDhDOS4yOTIzNiA1OC42Mjk3IDkuNDEzNDUgNTguMzc3MSA5LjU2OTcgNTguMTg3QzkuNzI1OTUgNTcuOTk0MyA5LjkxMzQ1IDU3Ljg1NjMgMTAuMTMyMiA1Ny43NzI5QzEwLjM1MzYgNTcuNjg5NiAxMC42MDEgNTcuNjQ3OSAxMC44NzQ0IDU3LjY0NzlDMTEuMDk1NyA1Ny42NDc5IDExLjI5ODkgNTcuNjc1MyAxMS40ODM4IDU3LjczQzExLjY3MTMgNTcuNzgyMSAxMS44Mzc5IDU3Ljg2NjcgMTEuOTgzOCA1Ny45ODM5QzEyLjEyOTYgNTguMDk4NSAxMi4yNTMzIDU4LjI1MjEgMTIuMzU0OSA1OC40NDQ4QzEyLjQ1OSA1OC42MzQ5IDEyLjUzODUgNTguODY4IDEyLjU5MzEgNTkuMTQ0QzEyLjY0NzggNTkuNDIwMSAxMi42NzUyIDU5Ljc0NDMgMTIuNjc1MiA2MC4xMTY3Wk0xMS45NDg2IDYxLjEwMTFWNTkuOTk1NkMxMS45NDg2IDU5Ljc0MDQgMTEuOTMzIDU5LjUxNjQgMTEuOTAxNyA1OS4zMjM3QzExLjg3MzEgNTkuMTI4NCAxMS44MzAxIDU4Ljk2MTggMTEuNzcyOCA1OC44MjM3QzExLjcxNTUgNTguNjg1NyAxMS42NDI2IDU4LjU3MzcgMTEuNTU0MSA1OC40ODc4QzExLjQ2ODEgNTguNDAxOSAxMS4zNjc5IDU4LjMzOTQgMTEuMjUzMyA1OC4zMDAzQzExLjE0MTMgNTguMjU4NiAxMS4wMTUgNTguMjM3OCAxMC44NzQ0IDU4LjIzNzhDMTAuNzAyNSA1OC4yMzc4IDEwLjU1MDIgNTguMjcwMyAxMC40MTc0IDU4LjMzNTRDMTAuMjg0NSA1OC4zOTc5IDEwLjE3MjYgNTguNDk4MiAxMC4wODE0IDU4LjYzNjJDOS45OTI4OCA1OC43NzQzIDkuOTI1MTcgNTguOTU1MiA5Ljg3ODMgNTkuMTc5MkM5LjgzMTQyIDU5LjQwMzIgOS44MDc5OCA1OS42NzUzIDkuODA3OTggNTkuOTk1NlY2MS4xMDExQzkuODA3OTggNjEuMzU2MyA5LjgyMjMxIDYxLjU4MTUgOS44NTA5NSA2MS43NzY5QzkuODgyMiA2MS45NzIyIDkuOTI3NzggNjIuMTQxNCA5Ljk4NzY3IDYyLjI4NDdDMTAuMDQ3NiA2Mi40MjUzIDEwLjEyMDUgNjIuNTQxMiAxMC4yMDY0IDYyLjYzMjNDMTAuMjkyNCA2Mi43MjM1IDEwLjM5MTMgNjIuNzkxMiAxMC41MDMzIDYyLjgzNTRDMTAuNjE3OSA2Mi44NzcxIDEwLjc0NDIgNjIuODk3OSAxMC44ODIyIDYyLjg5NzlDMTEuMDU5MyA2Mi44OTc5IDExLjIxNDIgNjIuODY0MSAxMS4zNDcgNjIuNzk2NEMxMS40Nzk5IDYyLjcyODcgMTEuNTkwNSA2Mi42MjMyIDExLjY3OTEgNjIuNDhDMTEuNzcwMiA2Mi4zMzQxIDExLjgzNzkgNjIuMTQ3OSAxMS44ODIyIDYxLjkyMTRDMTEuOTI2NSA2MS42OTIyIDExLjk0ODYgNjEuNDE4OCAxMS45NDg2IDYxLjEwMTFaTTEzLjY3NDYgNTkuMTIwNlY1OC44MTk4QzEzLjY3NDYgNTguNjAzNyAxMy43MjE0IDU4LjQwNzEgMTMuODE1MiA1OC4yM0MxMy45MDg5IDU4LjA1MjkgMTQuMDQzMSA1Ny45MTEgMTQuMjE3NSA1Ny44MDQyQzE0LjM5MiA1Ny42OTc0IDE0LjU5OSA1Ny42NDQgMTQuODM4NiA1Ny42NDRDMTUuMDgzNCA1Ny42NDQgMTUuMjkxOCA1Ny42OTc0IDE1LjQ2MzYgNTcuODA0MkMxNS42MzgxIDU3LjkxMSAxNS43NzIyIDU4LjA1MjkgMTUuODY2IDU4LjIzQzE1Ljk1OTcgNTguNDA3MSAxNi4wMDY2IDU4LjYwMzcgMTYuMDA2NiA1OC44MTk4VjU5LjEyMDZDMTYuMDA2NiA1OS4zMzE1IDE1Ljk1OTcgNTkuNTI1NiAxNS44NjYgNTkuNzAyNkMxNS43NzQ4IDU5Ljg3OTcgMTUuNjQyIDYwLjAyMTYgMTUuNDY3NSA2MC4xMjg0QzE1LjI5NTcgNjAuMjM1MiAxNS4wODg2IDYwLjI4ODYgMTQuODQ2NCA2MC4yODg2QzE0LjYwNDMgNjAuMjg4NiAxNC4zOTQ2IDYwLjIzNTIgMTQuMjE3NSA2MC4xMjg0QzE0LjA0MzEgNjAuMDIxNiAxMy45MDg5IDU5Ljg3OTcgMTMuODE1MiA1OS43MDI2QzEzLjcyMTQgNTkuNTI1NiAxMy42NzQ2IDU5LjMzMTUgMTMuNjc0NiA1OS4xMjA2Wk0xNC4yMTc1IDU4LjgxOThWNTkuMTIwNkMxNC4yMTc1IDU5LjI0MDQgMTQuMjM5NyA1OS4zNTM3IDE0LjI4MzkgNTkuNDYwNEMxNC4zMzA4IDU5LjU2NzIgMTQuNDAxMSA1OS42NTQ1IDE0LjQ5NDkgNTkuNzIyMkMxNC41ODg2IDU5Ljc4NzMgMTQuNzA1OCA1OS44MTk4IDE0Ljg0NjQgNTkuODE5OEMxNC45ODcxIDU5LjgxOTggMTUuMTAyOSA1OS43ODczIDE1LjE5NDEgNTkuNzIyMkMxNS4yODUyIDU5LjY1NDUgMTUuMzUyOSA1OS41NjcyIDE1LjM5NzIgNTkuNDYwNEMxNS40NDE1IDU5LjM1MzcgMTUuNDYzNiA1OS4yNDA0IDE1LjQ2MzYgNTkuMTIwNlY1OC44MTk4QzE1LjQ2MzYgNTguNjk3NCAxNS40NDAyIDU4LjU4MjggMTUuMzkzMyA1OC40NzYxQzE1LjM0OSA1OC4zNjY3IDE1LjI4IDU4LjI3OTUgMTUuMTg2MyA1OC4yMTQ0QzE1LjA5NTEgNTguMTQ2NiAxNC45NzkzIDU4LjExMjggMTQuODM4NiA1OC4xMTI4QzE0LjcwMDYgNTguMTEyOCAxNC41ODQ3IDU4LjE0NjYgMTQuNDkxIDU4LjIxNDRDMTQuMzk5OCA1OC4yNzk1IDE0LjMzMDggNTguMzY2NyAxNC4yODM5IDU4LjQ3NjFDMTQuMjM5NyA1OC41ODI4IDE0LjIxNzUgNTguNjk3NCAxNC4yMTc1IDU4LjgxOThaTTE2LjQ0NDEgNjIuMzIzN1Y2Mi4wMTlDMTYuNDQ0MSA2MS44MDU1IDE2LjQ5MSA2MS42MTAyIDE2LjU4NDcgNjEuNDMzMUMxNi42Nzg1IDYxLjI1NiAxNi44MTI2IDYxLjExNDEgMTYuOTg3MSA2MS4wMDczQzE3LjE2MTUgNjAuOTAwNiAxNy4zNjg2IDYwLjg0NzIgMTcuNjA4MiA2MC44NDcyQzE3Ljg1MjkgNjAuODQ3MiAxOC4wNjEzIDYwLjkwMDYgMTguMjMzMiA2MS4wMDczQzE4LjQwNzYgNjEuMTE0MSAxOC41NDE4IDYxLjI1NiAxOC42MzU1IDYxLjQzMzFDMTguNzI5MyA2MS42MTAyIDE4Ljc3NjEgNjEuODA1NSAxOC43NzYxIDYyLjAxOVY2Mi4zMjM3QzE4Ljc3NjEgNjIuNTM3MyAxOC43MjkzIDYyLjczMjYgMTguNjM1NSA2Mi45MDk3QzE4LjU0NDQgNjMuMDg2OCAxOC40MTE1IDYzLjIyODcgMTguMjM3MSA2My4zMzU0QzE4LjA2NTIgNjMuNDQyMiAxNy44NTgyIDYzLjQ5NTYgMTcuNjE2IDYzLjQ5NTZDMTcuMzczOCA2My40OTU2IDE3LjE2NTQgNjMuNDQyMiAxNi45OTEgNjMuMzM1NEMxNi44MTY1IDYzLjIyODcgMTYuNjgxMSA2My4wODY4IDE2LjU4NDcgNjIuOTA5N0MxNi40OTEgNjIuNzMyNiAxNi40NDQxIDYyLjUzNzMgMTYuNDQ0MSA2Mi4zMjM3Wk0xNi45ODcxIDYyLjAxOVY2Mi4zMjM3QzE2Ljk4NzEgNjIuNDQzNSAxNy4wMDkyIDYyLjU1ODEgMTcuMDUzNSA2Mi42Njc1QzE3LjEwMDMgNjIuNzc0MyAxNy4xNzA3IDYyLjg2MTUgMTcuMjY0NCA2Mi45MjkyQzE3LjM1ODIgNjIuOTk0MyAxNy40NzUzIDYzLjAyNjkgMTcuNjE2IDYzLjAyNjlDMTcuNzU2NiA2My4wMjY5IDE3Ljg3MjUgNjIuOTk0MyAxNy45NjM2IDYyLjkyOTJDMTguMDU3NCA2Mi44NjE1IDE4LjEyNjQgNjIuNzc0MyAxOC4xNzA3IDYyLjY2NzVDMTguMjE0OSA2Mi41NjA3IDE4LjIzNzEgNjIuNDQ2MSAxOC4yMzcxIDYyLjMyMzdWNjIuMDE5QzE4LjIzNzEgNjEuODk2NiAxOC4yMTM2IDYxLjc4MjEgMTguMTY2OCA2MS42NzUzQzE4LjEyMjUgNjEuNTY4NSAxOC4wNTM1IDYxLjQ4MjYgMTcuOTU5NyA2MS40MTc1QzE3Ljg2ODYgNjEuMzQ5OCAxNy43NTE0IDYxLjMxNTkgMTcuNjA4MiA2MS4zMTU5QzE3LjQ3MDEgNjEuMzE1OSAxNy4zNTQzIDYxLjM0OTggMTcuMjYwNSA2MS40MTc1QzE3LjE2OTQgNjEuNDgyNiAxNy4xMDAzIDYxLjU2ODUgMTcuMDUzNSA2MS42NzUzQzE3LjAwOTIgNjEuNzgyMSAxNi45ODcxIDYxLjg5NjYgMTYuOTg3MSA2Mi4wMTlaTTE3Ljc4NzggNTguNTM0N0wxNS4wMTA1IDYyLjk4TDE0LjYwNDMgNjIuNzIyMkwxNy4zODE2IDU4LjI3NjlMMTcuNzg3OCA1OC41MzQ3WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNOC4zMTY0MSA4OS43MDYzVjkwLjNINC4yMDcwM1Y4OS44NzQzTDYuNzUzOTEgODUuOTMyOUg3LjM0Mzc1TDYuNzEwOTQgODcuMDczNUw1LjAyNzM0IDg5LjcwNjNIOC4zMTY0MVpNNy41MjM0NCA4NS45MzI5VjkxLjYyMDRINi44MDA3OFY4NS45MzI5SDcuNTIzNDRaTTEyLjY3NTIgODguMzIzNVY4OS4xOTA3QzEyLjY3NTIgODkuNjU2OCAxMi42MzM1IDkwLjA1IDEyLjU1MDIgOTAuMzcwNEMxMi40NjY4IDkwLjY5MDcgMTIuMzQ3IDkwLjk0ODUgMTIuMTkwOCA5MS4xNDM4QzEyLjAzNDUgOTEuMzM5MSAxMS44NDU3IDkxLjQ4MSAxMS42MjQ0IDkxLjU2OTZDMTEuNDA1NiA5MS42NTU1IDExLjE1ODIgOTEuNjk4NSAxMC44ODIyIDkxLjY5ODVDMTAuNjYzNSA5MS42OTg1IDEwLjQ2MTYgOTEuNjcxMSAxMC4yNzY3IDkxLjYxNjVDMTAuMDkxOCA5MS41NjE4IDkuOTI1MTcgOTEuNDc0NSA5Ljc3NjczIDkxLjM1NDdDOS42MzA5IDkxLjIzMjMgOS41MDU5IDkxLjA3MzUgOS40MDE3MyA5MC44NzgyQzkuMjk3NTcgOTAuNjgyOSA5LjIxODE0IDkwLjQ0NTkgOS4xNjM0NSA5MC4xNjcyQzkuMTA4NzcgODkuODg4NiA5LjA4MTQyIDg5LjU2MzEgOS4wODE0MiA4OS4xOTA3Vjg4LjMyMzVDOS4wODE0MiA4Ny44NTczIDkuMTIzMDkgODcuNDY2NyA5LjIwNjQyIDg3LjE1MTZDOS4yOTIzNiA4Ni44MzY1IDkuNDEzNDUgODYuNTgzOSA5LjU2OTcgODYuMzkzOEM5LjcyNTk1IDg2LjIwMTEgOS45MTM0NSA4Ni4wNjMxIDEwLjEzMjIgODUuOTc5N0MxMC4zNTM2IDg1Ljg5NjQgMTAuNjAxIDg1Ljg1NDcgMTAuODc0NCA4NS44NTQ3QzExLjA5NTcgODUuODU0NyAxMS4yOTg5IDg1Ljg4MjEgMTEuNDgzOCA4NS45MzY4QzExLjY3MTMgODUuOTg4OSAxMS44Mzc5IDg2LjA3MzUgMTEuOTgzOCA4Ni4xOTA3QzEyLjEyOTYgODYuMzA1MyAxMi4yNTMzIDg2LjQ1ODkgMTIuMzU0OSA4Ni42NTE2QzEyLjQ1OSA4Ni44NDE3IDEyLjUzODUgODcuMDc0OCAxMi41OTMxIDg3LjM1MDhDMTIuNjQ3OCA4Ny42MjY5IDEyLjY3NTIgODcuOTUxMSAxMi42NzUyIDg4LjMyMzVaTTExLjk0ODYgODkuMzA3OVY4OC4yMDI0QzExLjk0ODYgODcuOTQ3MiAxMS45MzMgODcuNzIzMiAxMS45MDE3IDg3LjUzMDVDMTEuODczMSA4Ny4zMzUyIDExLjgzMDEgODcuMTY4NSAxMS43NzI4IDg3LjAzMDVDMTEuNzE1NSA4Ni44OTI1IDExLjY0MjYgODYuNzgwNSAxMS41NTQxIDg2LjY5NDZDMTEuNDY4MSA4Ni42MDg2IDExLjM2NzkgODYuNTQ2MSAxMS4yNTMzIDg2LjUwNzFDMTEuMTQxMyA4Ni40NjU0IDExLjAxNSA4Ni40NDQ2IDEwLjg3NDQgODYuNDQ0NkMxMC43MDI1IDg2LjQ0NDYgMTAuNTUwMiA4Ni40NzcxIDEwLjQxNzQgODYuNTQyMkMxMC4yODQ1IDg2LjYwNDcgMTAuMTcyNiA4Ni43MDUgMTAuMDgxNCA4Ni44NDNDOS45OTI4OCA4Ni45ODEgOS45MjUxNyA4Ny4xNjIgOS44NzgzIDg3LjM4NkM5LjgzMTQyIDg3LjYwOTkgOS44MDc5OCA4Ny44ODIxIDkuODA3OTggODguMjAyNFY4OS4zMDc5QzkuODA3OTggODkuNTYzMSA5LjgyMjMxIDg5Ljc4ODMgOS44NTA5NSA4OS45ODM2QzkuODgyMiA5MC4xNzkgOS45Mjc3OCA5MC4zNDgyIDkuOTg3NjcgOTAuNDkxNUMxMC4wNDc2IDkwLjYzMjEgMTAuMTIwNSA5MC43NDggMTAuMjA2NCA5MC44MzkxQzEwLjI5MjQgOTAuOTMwMyAxMC4zOTEzIDkwLjk5OCAxMC41MDMzIDkxLjA0MjJDMTAuNjE3OSA5MS4wODM5IDEwLjc0NDIgOTEuMTA0NyAxMC44ODIyIDkxLjEwNDdDMTEuMDU5MyA5MS4xMDQ3IDExLjIxNDIgOTEuMDcwOSAxMS4zNDcgOTEuMDAzMkMxMS40Nzk5IDkwLjkzNTUgMTEuNTkwNSA5MC44MyAxMS42NzkxIDkwLjY4NjhDMTEuNzcwMiA5MC41NDA5IDExLjgzNzkgOTAuMzU0NyAxMS44ODIyIDkwLjEyODJDMTEuOTI2NSA4OS44OTkgMTEuOTQ4NiA4OS42MjU2IDExLjk0ODYgODkuMzA3OVpNMTMuNjc0NiA4Ny4zMjc0Vjg3LjAyNjZDMTMuNjc0NiA4Ni44MTA1IDEzLjcyMTQgODYuNjEzOSAxMy44MTUyIDg2LjQzNjhDMTMuOTA4OSA4Ni4yNTk3IDE0LjA0MzEgODYuMTE3OCAxNC4yMTc1IDg2LjAxMUMxNC4zOTIgODUuOTA0MiAxNC41OTkgODUuODUwOCAxNC44Mzg2IDg1Ljg1MDhDMTUuMDgzNCA4NS44NTA4IDE1LjI5MTggODUuOTA0MiAxNS40NjM2IDg2LjAxMUMxNS42MzgxIDg2LjExNzggMTUuNzcyMiA4Ni4yNTk3IDE1Ljg2NiA4Ni40MzY4QzE1Ljk1OTcgODYuNjEzOSAxNi4wMDY2IDg2LjgxMDUgMTYuMDA2NiA4Ny4wMjY2Vjg3LjMyNzRDMTYuMDA2NiA4Ny41MzgzIDE1Ljk1OTcgODcuNzMyMyAxNS44NjYgODcuOTA5NEMxNS43NzQ4IDg4LjA4NjUgMTUuNjQyIDg4LjIyODQgMTUuNDY3NSA4OC4zMzUyQzE1LjI5NTcgODguNDQyIDE1LjA4ODYgODguNDk1NCAxNC44NDY0IDg4LjQ5NTRDMTQuNjA0MyA4OC40OTU0IDE0LjM5NDYgODguNDQyIDE0LjIxNzUgODguMzM1MkMxNC4wNDMxIDg4LjIyODQgMTMuOTA4OSA4OC4wODY1IDEzLjgxNTIgODcuOTA5NEMxMy43MjE0IDg3LjczMjMgMTMuNjc0NiA4Ny41MzgzIDEzLjY3NDYgODcuMzI3NFpNMTQuMjE3NSA4Ny4wMjY2Vjg3LjMyNzRDMTQuMjE3NSA4Ny40NDcyIDE0LjIzOTcgODcuNTYwNSAxNC4yODM5IDg3LjY2NzJDMTQuMzMwOCA4Ny43NzQgMTQuNDAxMSA4Ny44NjEyIDE0LjQ5NDkgODcuOTI5QzE0LjU4ODYgODcuOTk0MSAxNC43MDU4IDg4LjAyNjYgMTQuODQ2NCA4OC4wMjY2QzE0Ljk4NzEgODguMDI2NiAxNS4xMDI5IDg3Ljk5NDEgMTUuMTk0MSA4Ny45MjlDMTUuMjg1MiA4Ny44NjEyIDE1LjM1MjkgODcuNzc0IDE1LjM5NzIgODcuNjY3MkMxNS40NDE1IDg3LjU2MDUgMTUuNDYzNiA4Ny40NDcyIDE1LjQ2MzYgODcuMzI3NFY4Ny4wMjY2QzE1LjQ2MzYgODYuOTA0MiAxNS40NDAyIDg2Ljc4OTYgMTUuMzkzMyA4Ni42ODI5QzE1LjM0OSA4Ni41NzM1IDE1LjI4IDg2LjQ4NjIgMTUuMTg2MyA4Ni40MjExQzE1LjA5NTEgODYuMzUzNCAxNC45NzkzIDg2LjMxOTYgMTQuODM4NiA4Ni4zMTk2QzE0LjcwMDYgODYuMzE5NiAxNC41ODQ3IDg2LjM1MzQgMTQuNDkxIDg2LjQyMTFDMTQuMzk5OCA4Ni40ODYyIDE0LjMzMDggODYuNTczNSAxNC4yODM5IDg2LjY4MjlDMTQuMjM5NyA4Ni43ODk2IDE0LjIxNzUgODYuOTA0MiAxNC4yMTc1IDg3LjAyNjZaTTE2LjQ0NDEgOTAuNTMwNVY5MC4yMjU4QzE2LjQ0NDEgOTAuMDEyMyAxNi40OTEgODkuODE3IDE2LjU4NDcgODkuNjM5OUMxNi42Nzg1IDg5LjQ2MjggMTYuODEyNiA4OS4zMjA5IDE2Ljk4NzEgODkuMjE0MUMxNy4xNjE1IDg5LjEwNzMgMTcuMzY4NiA4OS4wNTQgMTcuNjA4MiA4OS4wNTRDMTcuODUyOSA4OS4wNTQgMTguMDYxMyA4OS4xMDczIDE4LjIzMzIgODkuMjE0MUMxOC40MDc2IDg5LjMyMDkgMTguNTQxOCA4OS40NjI4IDE4LjYzNTUgODkuNjM5OUMxOC43MjkzIDg5LjgxNyAxOC43NzYxIDkwLjAxMjMgMTguNzc2MSA5MC4yMjU4VjkwLjUzMDVDMTguNzc2MSA5MC43NDQxIDE4LjcyOTMgOTAuOTM5NCAxOC42MzU1IDkxLjExNjVDMTguNTQ0NCA5MS4yOTM1IDE4LjQxMTUgOTEuNDM1NSAxOC4yMzcxIDkxLjU0MjJDMTguMDY1MiA5MS42NDkgMTcuODU4MiA5MS43MDI0IDE3LjYxNiA5MS43MDI0QzE3LjM3MzggOTEuNzAyNCAxNy4xNjU0IDkxLjY0OSAxNi45OTEgOTEuNTQyMkMxNi44MTY1IDkxLjQzNTUgMTYuNjgxMSA5MS4yOTM1IDE2LjU4NDcgOTEuMTE2NUMxNi40OTEgOTAuOTM5NCAxNi40NDQxIDkwLjc0NDEgMTYuNDQ0MSA5MC41MzA1Wk0xNi45ODcxIDkwLjIyNThWOTAuNTMwNUMxNi45ODcxIDkwLjY1MDMgMTcuMDA5MiA5MC43NjQ5IDE3LjA1MzUgOTAuODc0M0MxNy4xMDAzIDkwLjk4MSAxNy4xNzA3IDkxLjA2ODMgMTcuMjY0NCA5MS4xMzZDMTcuMzU4MiA5MS4yMDExIDE3LjQ3NTMgOTEuMjMzNiAxNy42MTYgOTEuMjMzNkMxNy43NTY2IDkxLjIzMzYgMTcuODcyNSA5MS4yMDExIDE3Ljk2MzYgOTEuMTM2QzE4LjA1NzQgOTEuMDY4MyAxOC4xMjY0IDkwLjk4MSAxOC4xNzA3IDkwLjg3NDNDMTguMjE0OSA5MC43Njc1IDE4LjIzNzEgOTAuNjUyOSAxOC4yMzcxIDkwLjUzMDVWOTAuMjI1OEMxOC4yMzcxIDkwLjEwMzQgMTguMjEzNiA4OS45ODg5IDE4LjE2NjggODkuODgyMUMxOC4xMjI1IDg5Ljc3NTMgMTguMDUzNSA4OS42ODk0IDE3Ljk1OTcgODkuNjI0M0MxNy44Njg2IDg5LjU1NjYgMTcuNzUxNCA4OS41MjI3IDE3LjYwODIgODkuNTIyN0MxNy40NzAxIDg5LjUyMjcgMTcuMzU0MyA4OS41NTY2IDE3LjI2MDUgODkuNjI0M0MxNy4xNjk0IDg5LjY4OTQgMTcuMTAwMyA4OS43NzUzIDE3LjA1MzUgODkuODgyMUMxNy4wMDkyIDg5Ljk4ODkgMTYuOTg3MSA5MC4xMDM0IDE2Ljk4NzEgOTAuMjI1OFpNMTcuNzg3OCA4Ni43NDE1TDE1LjAxMDUgOTEuMTg2OEwxNC42MDQzIDkwLjkyOUwxNy4zODE2IDg2LjQ4MzZMMTcuNzg3OCA4Ni43NDE1WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNOC4xOTkyMiAxMTkuMjMzVjExOS44MjdINC40NzY1NlYxMTkuMzA4TDYuMzM5ODQgMTE3LjIzM0M2LjU2OTAxIDExNi45NzggNi43NDYwOSAxMTYuNzYyIDYuODcxMDkgMTE2LjU4NUM2Ljk5ODcgMTE2LjQwNSA3LjA4NzI0IDExNi4yNDUgNy4xMzY3MiAxMTYuMTA0QzcuMTg4OCAxMTUuOTYxIDcuMjE0ODQgMTE1LjgxNSA3LjIxNDg0IDExNS42NjdDNy4yMTQ4NCAxMTUuNDc5IDcuMTc1NzggMTE1LjMxIDcuMDk3NjYgMTE1LjE1OUM3LjAyMjE0IDExNS4wMDYgNi45MTAxNiAxMTQuODgzIDYuNzYxNzIgMTE0Ljc5MkM2LjYxMzI4IDExNC43MDEgNi40MzM1OSAxMTQuNjU1IDYuMjIyNjYgMTE0LjY1NUM1Ljk3MDA1IDExNC42NTUgNS43NTkxMSAxMTQuNzA1IDUuNTg5ODQgMTE0LjgwNEM1LjQyMzE4IDExNC45IDUuMjk4MTggMTE1LjAzNSA1LjIxNDg0IDExNS4yMUM1LjEzMTUxIDExNS4zODQgNS4wODk4NCAxMTUuNTg1IDUuMDg5ODQgMTE1LjgxMkg0LjM2NzE5QzQuMzY3MTkgMTE1LjQ5MSA0LjQzNzUgMTE1LjE5OCA0LjU3ODEyIDExNC45MzNDNC43MTg3NSAxMTQuNjY3IDQuOTI3MDggMTE0LjQ1NiA1LjIwMzEyIDExNC4zQzUuNDc5MTcgMTE0LjE0MSA1LjgxOTAxIDExNC4wNjIgNi4yMjI2NiAxMTQuMDYyQzYuNTgyMDMgMTE0LjA2MiA2Ljg4OTMyIDExNC4xMjUgNy4xNDQ1MyAxMTQuMjUzQzcuMzk5NzQgMTE0LjM3OCA3LjU5NTA1IDExNC41NTUgNy43MzA0NyAxMTQuNzg0QzcuODY4NDkgMTE1LjAxMSA3LjkzNzUgMTE1LjI3NiA3LjkzNzUgMTE1LjU4MUM3LjkzNzUgMTE1Ljc0OCA3LjkwODg1IDExNS45MTcgNy44NTE1NiAxMTYuMDg5QzcuNzk2ODggMTE2LjI1OCA3LjcyMDA1IDExNi40MjcgNy42MjEwOSAxMTYuNTk3QzcuNTI0NzQgMTE2Ljc2NiA3LjQxMTQ2IDExNi45MzMgNy4yODEyNSAxMTcuMDk3QzcuMTUzNjUgMTE3LjI2MSA3LjAxNjkzIDExNy40MjIgNi44NzEwOSAxMTcuNTgxTDUuMzQ3NjYgMTE5LjIzM0g4LjE5OTIyWk0xMi42NzUyIDExNi41M1YxMTcuMzk3QzEyLjY3NTIgMTE3Ljg2NCAxMi42MzM1IDExOC4yNTcgMTIuNTUwMiAxMTguNTc3QzEyLjQ2NjggMTE4Ljg5NyAxMi4zNDcgMTE5LjE1NSAxMi4xOTA4IDExOS4zNTFDMTIuMDM0NSAxMTkuNTQ2IDExLjg0NTcgMTE5LjY4OCAxMS42MjQ0IDExOS43NzZDMTEuNDA1NiAxMTkuODYyIDExLjE1ODIgMTE5LjkwNSAxMC44ODIyIDExOS45MDVDMTAuNjYzNSAxMTkuOTA1IDEwLjQ2MTYgMTE5Ljg3OCAxMC4yNzY3IDExOS44MjNDMTAuMDkxOCAxMTkuNzY5IDkuOTI1MTcgMTE5LjY4MSA5Ljc3NjczIDExOS41NjJDOS42MzA5IDExOS40MzkgOS41MDU5IDExOS4yOCA5LjQwMTczIDExOS4wODVDOS4yOTc1NyAxMTguODkgOS4yMTgxNCAxMTguNjUzIDkuMTYzNDUgMTE4LjM3NEM5LjEwODc3IDExOC4wOTUgOS4wODE0MiAxMTcuNzcgOS4wODE0MiAxMTcuMzk3VjExNi41M0M5LjA4MTQyIDExNi4wNjQgOS4xMjMwOSAxMTUuNjc0IDkuMjA2NDIgMTE1LjM1OEM5LjI5MjM2IDExNS4wNDMgOS40MTM0NSAxMTQuNzkxIDkuNTY5NyAxMTQuNjAxQzkuNzI1OTUgMTE0LjQwOCA5LjkxMzQ1IDExNC4yNyAxMC4xMzIyIDExNC4xODdDMTAuMzUzNiAxMTQuMTAzIDEwLjYwMSAxMTQuMDYyIDEwLjg3NDQgMTE0LjA2MkMxMS4wOTU3IDExNC4wNjIgMTEuMjk4OSAxMTQuMDg5IDExLjQ4MzggMTE0LjE0NEMxMS42NzEzIDExNC4xOTYgMTEuODM3OSAxMTQuMjggMTEuOTgzOCAxMTQuMzk3QzEyLjEyOTYgMTE0LjUxMiAxMi4yNTMzIDExNC42NjYgMTIuMzU0OSAxMTQuODU4QzEyLjQ1OSAxMTUuMDQ5IDEyLjUzODUgMTE1LjI4MiAxMi41OTMxIDExNS41NThDMTIuNjQ3OCAxMTUuODM0IDEyLjY3NTIgMTE2LjE1OCAxMi42NzUyIDExNi41M1pNMTEuOTQ4NiAxMTcuNTE1VjExNi40MDlDMTEuOTQ4NiAxMTYuMTU0IDExLjkzMyAxMTUuOTMgMTEuOTAxNyAxMTUuNzM3QzExLjg3MzEgMTE1LjU0MiAxMS44MzAxIDExNS4zNzUgMTEuNzcyOCAxMTUuMjM3QzExLjcxNTUgMTE1LjA5OSAxMS42NDI2IDExNC45ODcgMTEuNTU0MSAxMTQuOTAxQzExLjQ2ODEgMTE0LjgxNSAxMS4zNjc5IDExNC43NTMgMTEuMjUzMyAxMTQuNzE0QzExLjE0MTMgMTE0LjY3MiAxMS4wMTUgMTE0LjY1MSAxMC44NzQ0IDExNC42NTFDMTAuNzAyNSAxMTQuNjUxIDEwLjU1MDIgMTE0LjY4NCAxMC40MTc0IDExNC43NDlDMTAuMjg0NSAxMTQuODEyIDEwLjE3MjYgMTE0LjkxMiAxMC4wODE0IDExNS4wNUM5Ljk5Mjg4IDExNS4xODggOS45MjUxNyAxMTUuMzY5IDkuODc4MyAxMTUuNTkzQzkuODMxNDIgMTE1LjgxNyA5LjgwNzk4IDExNi4wODkgOS44MDc5OCAxMTYuNDA5VjExNy41MTVDOS44MDc5OCAxMTcuNzcgOS44MjIzMSAxMTcuOTk1IDkuODUwOTUgMTE4LjE5QzkuODgyMiAxMTguMzg2IDkuOTI3NzggMTE4LjU1NSA5Ljk4NzY3IDExOC42OThDMTAuMDQ3NiAxMTguODM5IDEwLjEyMDUgMTE4Ljk1NSAxMC4yMDY0IDExOS4wNDZDMTAuMjkyNCAxMTkuMTM3IDEwLjM5MTMgMTE5LjIwNSAxMC41MDMzIDExOS4yNDlDMTAuNjE3OSAxMTkuMjkxIDEwLjc0NDIgMTE5LjMxMiAxMC44ODIyIDExOS4zMTJDMTEuMDU5MyAxMTkuMzEyIDExLjIxNDIgMTE5LjI3OCAxMS4zNDcgMTE5LjIxQzExLjQ3OTkgMTE5LjE0MiAxMS41OTA1IDExOS4wMzcgMTEuNjc5MSAxMTguODk0QzExLjc3MDIgMTE4Ljc0OCAxMS44Mzc5IDExOC41NjIgMTEuODgyMiAxMTguMzM1QzExLjkyNjUgMTE4LjEwNiAxMS45NDg2IDExNy44MzIgMTEuOTQ4NiAxMTcuNTE1Wk0xMy42NzQ2IDExNS41MzRWMTE1LjIzM0MxMy42NzQ2IDExNS4wMTcgMTMuNzIxNCAxMTQuODIxIDEzLjgxNTIgMTE0LjY0NEMxMy45MDg5IDExNC40NjYgMTQuMDQzMSAxMTQuMzI1IDE0LjIxNzUgMTE0LjIxOEMxNC4zOTIgMTE0LjExMSAxNC41OTkgMTE0LjA1OCAxNC44Mzg2IDExNC4wNThDMTUuMDgzNCAxMTQuMDU4IDE1LjI5MTggMTE0LjExMSAxNS40NjM2IDExNC4yMThDMTUuNjM4MSAxMTQuMzI1IDE1Ljc3MjIgMTE0LjQ2NiAxNS44NjYgMTE0LjY0NEMxNS45NTk3IDExNC44MjEgMTYuMDA2NiAxMTUuMDE3IDE2LjAwNjYgMTE1LjIzM1YxMTUuNTM0QzE2LjAwNjYgMTE1Ljc0NSAxNS45NTk3IDExNS45MzkgMTUuODY2IDExNi4xMTZDMTUuNzc0OCAxMTYuMjkzIDE1LjY0MiAxMTYuNDM1IDE1LjQ2NzUgMTE2LjU0MkMxNS4yOTU3IDExNi42NDkgMTUuMDg4NiAxMTYuNzAyIDE0Ljg0NjQgMTE2LjcwMkMxNC42MDQzIDExNi43MDIgMTQuMzk0NiAxMTYuNjQ5IDE0LjIxNzUgMTE2LjU0MkMxNC4wNDMxIDExNi40MzUgMTMuOTA4OSAxMTYuMjkzIDEzLjgxNTIgMTE2LjExNkMxMy43MjE0IDExNS45MzkgMTMuNjc0NiAxMTUuNzQ1IDEzLjY3NDYgMTE1LjUzNFpNMTQuMjE3NSAxMTUuMjMzVjExNS41MzRDMTQuMjE3NSAxMTUuNjU0IDE0LjIzOTcgMTE1Ljc2NyAxNC4yODM5IDExNS44NzRDMTQuMzMwOCAxMTUuOTgxIDE0LjQwMTEgMTE2LjA2OCAxNC40OTQ5IDExNi4xMzZDMTQuNTg4NiAxMTYuMjAxIDE0LjcwNTggMTE2LjIzMyAxNC44NDY0IDExNi4yMzNDMTQuOTg3MSAxMTYuMjMzIDE1LjEwMjkgMTE2LjIwMSAxNS4xOTQxIDExNi4xMzZDMTUuMjg1MiAxMTYuMDY4IDE1LjM1MjkgMTE1Ljk4MSAxNS4zOTcyIDExNS44NzRDMTUuNDQxNSAxMTUuNzY3IDE1LjQ2MzYgMTE1LjY1NCAxNS40NjM2IDExNS41MzRWMTE1LjIzM0MxNS40NjM2IDExNS4xMTEgMTUuNDQwMiAxMTQuOTk2IDE1LjM5MzMgMTE0Ljg5QzE1LjM0OSAxMTQuNzggMTUuMjggMTE0LjY5MyAxNS4xODYzIDExNC42MjhDMTUuMDk1MSAxMTQuNTYgMTQuOTc5MyAxMTQuNTI2IDE0LjgzODYgMTE0LjUyNkMxNC43MDA2IDExNC41MjYgMTQuNTg0NyAxMTQuNTYgMTQuNDkxIDExNC42MjhDMTQuMzk5OCAxMTQuNjkzIDE0LjMzMDggMTE0Ljc4IDE0LjI4MzkgMTE0Ljg5QzE0LjIzOTcgMTE0Ljk5NiAxNC4yMTc1IDExNS4xMTEgMTQuMjE3NSAxMTUuMjMzWk0xNi40NDQxIDExOC43MzdWMTE4LjQzM0MxNi40NDQxIDExOC4yMTkgMTYuNDkxIDExOC4wMjQgMTYuNTg0NyAxMTcuODQ3QzE2LjY3ODUgMTE3LjY3IDE2LjgxMjYgMTE3LjUyOCAxNi45ODcxIDExNy40MjFDMTcuMTYxNSAxMTcuMzE0IDE3LjM2ODYgMTE3LjI2MSAxNy42MDgyIDExNy4yNjFDMTcuODUyOSAxMTcuMjYxIDE4LjA2MTMgMTE3LjMxNCAxOC4yMzMyIDExNy40MjFDMTguNDA3NiAxMTcuNTI4IDE4LjU0MTggMTE3LjY3IDE4LjYzNTUgMTE3Ljg0N0MxOC43MjkzIDExOC4wMjQgMTguNzc2MSAxMTguMjE5IDE4Ljc3NjEgMTE4LjQzM1YxMTguNzM3QzE4Ljc3NjEgMTE4Ljk1MSAxOC43MjkzIDExOS4xNDYgMTguNjM1NSAxMTkuMzIzQzE4LjU0NDQgMTE5LjUgMTguNDExNSAxMTkuNjQyIDE4LjIzNzEgMTE5Ljc0OUMxOC4wNjUyIDExOS44NTYgMTcuODU4MiAxMTkuOTA5IDE3LjYxNiAxMTkuOTA5QzE3LjM3MzggMTE5LjkwOSAxNy4xNjU0IDExOS44NTYgMTYuOTkxIDExOS43NDlDMTYuODE2NSAxMTkuNjQyIDE2LjY4MTEgMTE5LjUgMTYuNTg0NyAxMTkuMzIzQzE2LjQ5MSAxMTkuMTQ2IDE2LjQ0NDEgMTE4Ljk1MSAxNi40NDQxIDExOC43MzdaTTE2Ljk4NzEgMTE4LjQzM1YxMTguNzM3QzE2Ljk4NzEgMTE4Ljg1NyAxNy4wMDkyIDExOC45NzIgMTcuMDUzNSAxMTkuMDgxQzE3LjEwMDMgMTE5LjE4OCAxNy4xNzA3IDExOS4yNzUgMTcuMjY0NCAxMTkuMzQzQzE3LjM1ODIgMTE5LjQwOCAxNy40NzUzIDExOS40NCAxNy42MTYgMTE5LjQ0QzE3Ljc1NjYgMTE5LjQ0IDE3Ljg3MjUgMTE5LjQwOCAxNy45NjM2IDExOS4zNDNDMTguMDU3NCAxMTkuMjc1IDE4LjEyNjQgMTE5LjE4OCAxOC4xNzA3IDExOS4wODFDMTguMjE0OSAxMTguOTc0IDE4LjIzNzEgMTE4Ljg2IDE4LjIzNzEgMTE4LjczN1YxMTguNDMzQzE4LjIzNzEgMTE4LjMxIDE4LjIxMzYgMTE4LjE5NiAxOC4xNjY4IDExOC4wODlDMTguMTIyNSAxMTcuOTgyIDE4LjA1MzUgMTE3Ljg5NiAxNy45NTk3IDExNy44MzFDMTcuODY4NiAxMTcuNzYzIDE3Ljc1MTQgMTE3LjcyOSAxNy42MDgyIDExNy43MjlDMTcuNDcwMSAxMTcuNzI5IDE3LjM1NDMgMTE3Ljc2MyAxNy4yNjA1IDExNy44MzFDMTcuMTY5NCAxMTcuODk2IDE3LjEwMDMgMTE3Ljk4MiAxNy4wNTM1IDExOC4wODlDMTcuMDA5MiAxMTguMTk2IDE2Ljk4NzEgMTE4LjMxIDE2Ljk4NzEgMTE4LjQzM1pNMTcuNzg3OCAxMTQuOTQ4TDE1LjAxMDUgMTE5LjM5NEwxNC42MDQzIDExOS4xMzZMMTcuMzgxNiAxMTQuNjlMMTcuNzg3OCAxMTQuOTQ4WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNMTMuMDQzIDE0NC43MzdWMTQ1LjYwNEMxMy4wNDMgMTQ2LjA3IDEzLjAwMTMgMTQ2LjQ2NCAxMi45MTggMTQ2Ljc4NEMxMi44MzQ2IDE0Ny4xMDQgMTIuNzE0OCAxNDcuMzYyIDEyLjU1ODYgMTQ3LjU1N0MxMi40MDIzIDE0Ny43NTMgMTIuMjEzNSAxNDcuODk1IDExLjk5MjIgMTQ3Ljk4M0MxMS43NzM0IDE0OC4wNjkgMTEuNTI2IDE0OC4xMTIgMTEuMjUgMTQ4LjExMkMxMS4wMzEyIDE0OC4xMTIgMTAuODI5NCAxNDguMDg1IDEwLjY0NDUgMTQ4LjAzQzEwLjQ1OTYgMTQ3Ljk3NSAxMC4yOTMgMTQ3Ljg4OCAxMC4xNDQ1IDE0Ny43NjhDOS45OTg3IDE0Ny42NDYgOS44NzM3IDE0Ny40ODcgOS43Njk1MyAxNDcuMjkyQzkuNjY1MzYgMTQ3LjA5NiA5LjU4NTk0IDE0Ni44NTkgOS41MzEyNSAxNDYuNTgxQzkuNDc2NTYgMTQ2LjMwMiA5LjQ0OTIyIDE0NS45NzcgOS40NDkyMiAxNDUuNjA0VjE0NC43MzdDOS40NDkyMiAxNDQuMjcxIDkuNDkwODkgMTQzLjg4IDkuNTc0MjIgMTQzLjU2NUM5LjY2MDE2IDE0My4yNSA5Ljc4MTI1IDE0Mi45OTcgOS45Mzc1IDE0Mi44MDdDMTAuMDkzOCAxNDIuNjE1IDEwLjI4MTIgMTQyLjQ3NyAxMC41IDE0Mi4zOTNDMTAuNzIxNCAxNDIuMzEgMTAuOTY4OCAxNDIuMjY4IDExLjI0MjIgMTQyLjI2OEMxMS40NjM1IDE0Mi4yNjggMTEuNjY2NyAxNDIuMjk2IDExLjg1MTYgMTQyLjM1QzEyLjAzOTEgMTQyLjQwMiAxMi4yMDU3IDE0Mi40ODcgMTIuMzUxNiAxNDIuNjA0QzEyLjQ5NzQgMTQyLjcxOSAxMi42MjExIDE0Mi44NzIgMTIuNzIyNyAxNDMuMDY1QzEyLjgyNjggMTQzLjI1NSAxMi45MDYyIDE0My40ODggMTIuOTYwOSAxNDMuNzY0QzEzLjAxNTYgMTQ0LjA0IDEzLjA0MyAxNDQuMzY1IDEzLjA0MyAxNDQuNzM3Wk0xMi4zMTY0IDE0NS43MjFWMTQ0LjYxNkMxMi4zMTY0IDE0NC4zNjEgMTIuMzAwOCAxNDQuMTM3IDEyLjI2OTUgMTQzLjk0NEMxMi4yNDA5IDE0My43NDkgMTIuMTk3OSAxNDMuNTgyIDEyLjE0MDYgMTQzLjQ0NEMxMi4wODMzIDE0My4zMDYgMTIuMDEwNCAxNDMuMTk0IDExLjkyMTkgMTQzLjEwOEMxMS44MzU5IDE0My4wMjIgMTEuNzM1NyAxNDIuOTYgMTEuNjIxMSAxNDIuOTIxQzExLjUwOTEgMTQyLjg3OSAxMS4zODI4IDE0Mi44NTggMTEuMjQyMiAxNDIuODU4QzExLjA3MDMgMTQyLjg1OCAxMC45MTggMTQyLjg5MSAxMC43ODUyIDE0Mi45NTZDMTAuNjUyMyAxNDMuMDE4IDEwLjU0MDQgMTQzLjExOSAxMC40NDkyIDE0My4yNTdDMTAuMzYwNyAxNDMuMzk1IDEwLjI5MyAxNDMuNTc2IDEwLjI0NjEgMTQzLjhDMTAuMTk5MiAxNDQuMDI0IDEwLjE3NTggMTQ0LjI5NiAxMC4xNzU4IDE0NC42MTZWMTQ1LjcyMUMxMC4xNzU4IDE0NS45NzcgMTAuMTkwMSAxNDYuMjAyIDEwLjIxODggMTQ2LjM5N0MxMC4yNSAxNDYuNTkzIDEwLjI5NTYgMTQ2Ljc2MiAxMC4zNTU1IDE0Ni45MDVDMTAuNDE1NCAxNDcuMDQ2IDEwLjQ4ODMgMTQ3LjE2MiAxMC41NzQyIDE0Ny4yNTNDMTAuNjYwMiAxNDcuMzQ0IDEwLjc1OTEgMTQ3LjQxMiAxMC44NzExIDE0Ny40NTZDMTAuOTg1NyAxNDcuNDk3IDExLjExMiAxNDcuNTE4IDExLjI1IDE0Ny41MThDMTEuNDI3MSAxNDcuNTE4IDExLjU4MiAxNDcuNDg0IDExLjcxNDggMTQ3LjQxN0MxMS44NDc3IDE0Ny4zNDkgMTEuOTU4MyAxNDcuMjQ0IDEyLjA0NjkgMTQ3LjFDMTIuMTM4IDE0Ni45NTUgMTIuMjA1NyAxNDYuNzY4IDEyLjI1IDE0Ni41NDJDMTIuMjk0MyAxNDYuMzEzIDEyLjMxNjQgMTQ2LjAzOSAxMi4zMTY0IDE0NS43MjFaTTE0LjA0MjQgMTQzLjc0MVYxNDMuNDRDMTQuMDQyNCAxNDMuMjI0IDE0LjA4OTIgMTQzLjAyNyAxNC4xODMgMTQyLjg1QzE0LjI3NjcgMTQyLjY3MyAxNC40MTA4IDE0Mi41MzEgMTQuNTg1MyAxNDIuNDI1QzE0Ljc1OTggMTQyLjMxOCAxNC45NjY4IDE0Mi4yNjQgMTUuMjA2NCAxNDIuMjY0QzE1LjQ1MTIgMTQyLjI2NCAxNS42NTk1IDE0Mi4zMTggMTUuODMxNCAxNDIuNDI1QzE2LjAwNTkgMTQyLjUzMSAxNi4xNCAxNDIuNjczIDE2LjIzMzggMTQyLjg1QzE2LjMyNzUgMTQzLjAyNyAxNi4zNzQ0IDE0My4yMjQgMTYuMzc0NCAxNDMuNDRWMTQzLjc0MUMxNi4zNzQ0IDE0My45NTIgMTYuMzI3NSAxNDQuMTQ2IDE2LjIzMzggMTQ0LjMyM0MxNi4xNDI2IDE0NC41IDE2LjAwOTggMTQ0LjY0MiAxNS44MzUzIDE0NC43NDlDMTUuNjYzNSAxNDQuODU2IDE1LjQ1NjQgMTQ0LjkwOSAxNS4yMTQyIDE0NC45MDlDMTQuOTcyIDE0NC45MDkgMTQuNzYyNCAxNDQuODU2IDE0LjU4NTMgMTQ0Ljc0OUMxNC40MTA4IDE0NC42NDIgMTQuMjc2NyAxNDQuNSAxNC4xODMgMTQ0LjMyM0MxNC4wODkyIDE0NC4xNDYgMTQuMDQyNCAxNDMuOTUyIDE0LjA0MjQgMTQzLjc0MVpNMTQuNTg1MyAxNDMuNDRWMTQzLjc0MUMxNC41ODUzIDE0My44NjEgMTQuNjA3NSAxNDMuOTc0IDE0LjY1MTcgMTQ0LjA4MUMxNC42OTg2IDE0NC4xODggMTQuNzY4OSAxNDQuMjc1IDE0Ljg2MjcgMTQ0LjM0M0MxNC45NTY0IDE0NC40MDggMTUuMDczNiAxNDQuNDQgMTUuMjE0MiAxNDQuNDRDMTUuMzU0OSAxNDQuNDQgMTUuNDcwNyAxNDQuNDA4IDE1LjU2MTkgMTQ0LjM0M0MxNS42NTMgMTQ0LjI3NSAxNS43MjA3IDE0NC4xODggMTUuNzY1IDE0NC4wODFDMTUuODA5MyAxNDMuOTc0IDE1LjgzMTQgMTQzLjg2MSAxNS44MzE0IDE0My43NDFWMTQzLjQ0QzE1LjgzMTQgMTQzLjMxOCAxNS44MDggMTQzLjIwMyAxNS43NjExIDE0My4wOTZDMTUuNzE2OCAxNDIuOTg3IDE1LjY0NzggMTQyLjkgMTUuNTU0MSAxNDIuODM1QzE1LjQ2MjkgMTQyLjc2NyAxNS4zNDcgMTQyLjczMyAxNS4yMDY0IDE0Mi43MzNDMTUuMDY4NCAxNDIuNzMzIDE0Ljk1MjUgMTQyLjc2NyAxNC44NTg4IDE0Mi44MzVDMTQuNzY3NiAxNDIuOSAxNC42OTg2IDE0Mi45ODcgMTQuNjUxNyAxNDMuMDk2QzE0LjYwNzUgMTQzLjIwMyAxNC41ODUzIDE0My4zMTggMTQuNTg1MyAxNDMuNDRaTTE2LjgxMTkgMTQ2Ljk0NFYxNDYuNjM5QzE2LjgxMTkgMTQ2LjQyNiAxNi44NTg4IDE0Ni4yMzEgMTYuOTUyNSAxNDYuMDUzQzE3LjA0NjMgMTQ1Ljg3NiAxNy4xODA0IDE0NS43MzQgMTcuMzU0OSAxNDUuNjI4QzE3LjUyOTMgMTQ1LjUyMSAxNy43MzY0IDE0NS40NjggMTcuOTc2IDE0NS40NjhDMTguMjIwNyAxNDUuNDY4IDE4LjQyOTEgMTQ1LjUyMSAxOC42MDEgMTQ1LjYyOEMxOC43NzU0IDE0NS43MzQgMTguOTA5NSAxNDUuODc2IDE5LjAwMzMgMTQ2LjA1M0MxOS4wOTcgMTQ2LjIzMSAxOS4xNDM5IDE0Ni40MjYgMTkuMTQzOSAxNDYuNjM5VjE0Ni45NDRDMTkuMTQzOSAxNDcuMTU4IDE5LjA5NyAxNDcuMzUzIDE5LjAwMzMgMTQ3LjUzQzE4LjkxMjIgMTQ3LjcwNyAxOC43NzkzIDE0Ny44NDkgMTguNjA0OSAxNDcuOTU2QzE4LjQzMyAxNDguMDYzIDE4LjIyNiAxNDguMTE2IDE3Ljk4MzggMTQ4LjExNkMxNy43NDE2IDE0OC4xMTYgMTcuNTMzMiAxNDguMDYzIDE3LjM1ODggMTQ3Ljk1NkMxNy4xODQzIDE0Ny44NDkgMTcuMDQ4OSAxNDcuNzA3IDE2Ljk1MjUgMTQ3LjUzQzE2Ljg1ODggMTQ3LjM1MyAxNi44MTE5IDE0Ny4xNTggMTYuODExOSAxNDYuOTQ0Wk0xNy4zNTQ5IDE0Ni42MzlWMTQ2Ljk0NEMxNy4zNTQ5IDE0Ny4wNjQgMTcuMzc3IDE0Ny4xNzggMTcuNDIxMyAxNDcuMjg4QzE3LjQ2ODEgMTQ3LjM5NSAxNy41Mzg1IDE0Ny40ODIgMTcuNjMyMiAxNDcuNTVDMTcuNzI2IDE0Ny42MTUgMTcuODQzMSAxNDcuNjQ3IDE3Ljk4MzggMTQ3LjY0N0MxOC4xMjQ0IDE0Ny42NDcgMTguMjQwMyAxNDcuNjE1IDE4LjMzMTQgMTQ3LjU1QzE4LjQyNTIgMTQ3LjQ4MiAxOC40OTQyIDE0Ny4zOTUgMTguNTM4NSAxNDcuMjg4QzE4LjU4MjcgMTQ3LjE4MSAxOC42MDQ5IDE0Ny4wNjYgMTguNjA0OSAxNDYuOTQ0VjE0Ni42MzlDMTguNjA0OSAxNDYuNTE3IDE4LjU4MTQgMTQ2LjQwMiAxOC41MzQ1IDE0Ni4yOTZDMTguNDkwMyAxNDYuMTg5IDE4LjQyMTMgMTQ2LjEwMyAxOC4zMjc1IDE0Ni4wMzhDMTguMjM2NCAxNDUuOTcgMTguMTE5MiAxNDUuOTM2IDE3Ljk3NiAxNDUuOTM2QzE3LjgzNzkgMTQ1LjkzNiAxNy43MjIgMTQ1Ljk3IDE3LjYyODMgMTQ2LjAzOEMxNy41MzcyIDE0Ni4xMDMgMTcuNDY4MSAxNDYuMTg5IDE3LjQyMTMgMTQ2LjI5NkMxNy4zNzcgMTQ2LjQwMiAxNy4zNTQ5IDE0Ni41MTcgMTcuMzU0OSAxNDYuNjM5Wk0xOC4xNTU2IDE0My4xNTVMMTUuMzc4MyAxNDcuNkwxNC45NzIgMTQ3LjM0M0wxNy43NDk0IDE0Mi44OTdMMTguMTU1NiAxNDMuMTU1WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNMjUgNC4xNjExM0wyMDAgNC4xNjExNiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPHBhdGggZD0iTTI1IDMzLjE2MTFMMjAwIDMzLjE2MTIiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIi8+CjxwYXRoIGQ9Ik0yNSA2MS4xNjExTDIwMCA2MS4xNjEyIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIvPgo8cGF0aCBkPSJNMjUgODkuMTYxMUwyMDAgODkuMTYxMiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPHBhdGggZD0iTTI1IDExOC4xNjFMMjAwIDExOC4xNjEiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIi8+CjxsaW5lIHgxPSIyMy4yIiB5MT0iMTQ1Ljk2MSIgeDI9IjIwMi44IiB5Mj0iMTQ1Ljk2MSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuNyIgc3Ryb2tlLXdpZHRoPSIwLjQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAxXzQxODJfMTExOTMpIj4KPGxpbmUgeDE9IjQwLjQ1IiB5MT0iMTQ3LjA3MiIgeDI9IjQwLjQ1IiB5Mj0iMTQ2LjI1IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNMzIuNTA5MSAxNTIuMDI1VjE1Mi44OTNDMzIuNTA5MSAxNTMuMzU5IDMyLjQ2NzQgMTUzLjc1MiAzMi4zODQxIDE1NC4wNzJDMzIuMzAwNyAxNTQuMzkzIDMyLjE4MDkgMTU0LjY1IDMyLjAyNDcgMTU0Ljg0NkMzMS44Njg0IDE1NS4wNDEgMzEuNjc5NiAxNTUuMTgzIDMxLjQ1ODMgMTU1LjI3MUMzMS4yMzk1IDE1NS4zNTcgMzAuOTkyMSAxNTUuNCAzMC43MTYxIDE1NS40QzMwLjQ5NzMgMTU1LjQgMzAuMjk1NSAxNTUuMzczIDMwLjExMDYgMTU1LjMxOEMyOS45MjU3IDE1NS4yNjQgMjkuNzU5MSAxNTUuMTc2IDI5LjYxMDYgMTU1LjA1N0MyOS40NjQ4IDE1NC45MzQgMjkuMzM5OCAxNTQuNzc1IDI5LjIzNTYgMTU0LjU4QzI5LjEzMTUgMTU0LjM4NSAyOS4wNTIgMTU0LjE0OCAyOC45OTczIDE1My44NjlDMjguOTQyNyAxNTMuNTkgMjguOTE1MyAxNTMuMjY1IDI4LjkxNTMgMTUyLjg5M1YxNTIuMDI1QzI4LjkxNTMgMTUxLjU1OSAyOC45NTcgMTUxLjE2OSAyOS4wNDAzIDE1MC44NTRDMjkuMTI2MiAxNTAuNTM4IDI5LjI0NzMgMTUwLjI4NiAyOS40MDM2IDE1MC4wOTZDMjkuNTU5OCAxNDkuOTAzIDI5Ljc0NzMgMTQ5Ljc2NSAyOS45NjYxIDE0OS42ODJDMzAuMTg3NCAxNDkuNTk4IDMwLjQzNDggMTQ5LjU1NyAzMC43MDgzIDE0OS41NTdDMzAuOTI5NiAxNDkuNTU3IDMxLjEzMjggMTQ5LjU4NCAzMS4zMTc3IDE0OS42MzlDMzEuNTA1MiAxNDkuNjkxIDMxLjY3MTggMTQ5Ljc3NSAzMS44MTc3IDE0OS44OTNDMzEuOTYzNSAxNTAuMDA3IDMyLjA4NzIgMTUwLjE2MSAzMi4xODg3IDE1MC4zNTRDMzIuMjkyOSAxNTAuNTQ0IDMyLjM3MjMgMTUwLjc3NyAzMi40MjcgMTUxLjA1M0MzMi40ODE3IDE1MS4zMjkgMzIuNTA5MSAxNTEuNjUzIDMyLjUwOTEgMTUyLjAyNVpNMzEuNzgyNSAxNTMuMDFWMTUxLjkwNEMzMS43ODI1IDE1MS42NDkgMzEuNzY2OSAxNTEuNDI1IDMxLjczNTYgMTUxLjIzMkMzMS43MDcgMTUxLjAzNyAzMS42NjQgMTUwLjg3IDMxLjYwNjcgMTUwLjczMkMzMS41NDk0IDE1MC41OTQgMzEuNDc2NSAxNTAuNDgyIDMxLjM4OCAxNTAuMzk2QzMxLjMwMiAxNTAuMzExIDMxLjIwMTggMTUwLjI0OCAzMS4wODcyIDE1MC4yMDlDMzAuOTc1MiAxNTAuMTY3IDMwLjg0ODkgMTUwLjE0NiAzMC43MDgzIDE1MC4xNDZDMzAuNTM2NCAxNTAuMTQ2IDMwLjM4NDEgMTUwLjE3OSAzMC4yNTEyIDE1MC4yNDRDMzAuMTE4NCAxNTAuMzA3IDMwLjAwNjUgMTUwLjQwNyAyOS45MTUzIDE1MC41NDVDMjkuODI2OCAxNTAuNjgzIDI5Ljc1OTEgMTUwLjg2NCAyOS43MTIyIDE1MS4wODhDMjkuNjY1MyAxNTEuMzEyIDI5LjY0MTkgMTUxLjU4NCAyOS42NDE5IDE1MS45MDRWMTUzLjAxQzI5LjY0MTkgMTUzLjI2NSAyOS42NTYyIDE1My40OSAyOS42ODQ4IDE1My42ODZDMjkuNzE2MSAxNTMuODgxIDI5Ljc2MTcgMTU0LjA1IDI5LjgyMTYgMTU0LjE5M0MyOS44ODE1IDE1NC4zMzQgMjkuOTU0NCAxNTQuNDUgMzAuMDQwMyAxNTQuNTQxQzMwLjEyNjIgMTU0LjYzMiAzMC4yMjUyIDE1NC43IDMwLjMzNzIgMTU0Ljc0NEMzMC40NTE4IDE1NC43ODYgMzAuNTc4MSAxNTQuODA3IDMwLjcxNjEgMTU0LjgwN0MzMC44OTMyIDE1NC44MDcgMzEuMDQ4MSAxNTQuNzczIDMxLjE4MDkgMTU0LjcwNUMzMS4zMTM3IDE1NC42MzcgMzEuNDI0NCAxNTQuNTMyIDMxLjUxMyAxNTQuMzg5QzMxLjYwNDEgMTU0LjI0MyAzMS42NzE4IDE1NC4wNTcgMzEuNzE2MSAxNTMuODNDMzEuNzYwNCAxNTMuNjAxIDMxLjc4MjUgMTUzLjMyNyAzMS43ODI1IDE1My4wMVpNMzUuODk2NCAxNDkuNjA0VjE1NS4zMjJIMzUuMTczN1YxNTAuNTA2TDMzLjcxNjcgMTUxLjAzN1YxNTAuMzg1TDM1Ljc4MzEgMTQ5LjYwNEgzNS44OTY0Wk00MS4xMTI0IDE0OS42MzVWMTU1LjMyMkg0MC4zNTg1VjE0OS42MzVINDEuMTEyNFpNNDMuNDk1MiAxNTIuMTkzVjE1Mi44MTFINDAuOTQ4M1YxNTIuMTkzSDQzLjQ5NTJaTTQzLjg4MTkgMTQ5LjYzNVYxNTAuMjUySDQwLjk0ODNWMTQ5LjYzNUg0My44ODE5Wk00Ni40MjE2IDE1NS40QzQ2LjEyNzMgMTU1LjQgNDUuODYwNCAxNTUuMzUxIDQ1LjYyMDggMTU1LjI1MkM0NS4zODM4IDE1NS4xNSA0NS4xNzk0IDE1NS4wMDggNDUuMDA3NSAxNTQuODI2QzQ0LjgzODMgMTU0LjY0NCA0NC43MDgxIDE1NC40MjggNDQuNjE2OSAxNTQuMTc4QzQ0LjUyNTggMTUzLjkyOCA0NC40ODAyIDE1My42NTQgNDQuNDgwMiAxNTMuMzU3VjE1My4xOTNDNDQuNDgwMiAxNTIuODUgNDQuNTMxIDE1Mi41NDQgNDQuNjMyNSAxNTIuMjc1QzQ0LjczNDEgMTUyLjAwNSA0NC44NzIxIDE1MS43NzUgNDUuMDQ2NiAxNTEuNTg4QzQ1LjIyMTEgMTUxLjQgNDUuNDE5IDE1MS4yNTggNDUuNjQwMyAxNTEuMTYyQzQ1Ljg2MTcgMTUxLjA2NiA0Ni4wOTA5IDE1MS4wMTggNDYuMzI3OCAxNTEuMDE4QzQ2LjYyOTkgMTUxLjAxOCA0Ni44OTAzIDE1MS4wNyA0Ny4xMDkxIDE1MS4xNzRDNDcuMzMwNSAxNTEuMjc4IDQ3LjUxMTQgMTUxLjQyNCA0Ny42NTIxIDE1MS42MTFDNDcuNzkyNyAxNTEuNzk2IDQ3Ljg5NjkgMTUyLjAxNSA0Ny45NjQ2IDE1Mi4yNjhDNDguMDMyMyAxNTIuNTE4IDQ4LjA2NjEgMTUyLjc5MSA0OC4wNjYxIDE1My4wODhWMTUzLjQxMkg0NC45MDk5VjE1Mi44MjJINDcuMzQzNVYxNTIuNzY4QzQ3LjMzMzEgMTUyLjU4IDQ3LjI5NCAxNTIuMzk4IDQ3LjIyNjMgMTUyLjIyMUM0Ny4xNjEyIDE1Mi4wNDQgNDcuMDU3IDE1MS44OTggNDYuOTEzOCAxNTEuNzgzQzQ2Ljc3MDYgMTUxLjY2OSA0Ni41NzUyIDE1MS42MTEgNDYuMzI3OCAxNTEuNjExQzQ2LjE2MzggMTUxLjYxMSA0Ni4wMTI3IDE1MS42NDYgNDUuODc0NyAxNTEuNzE3QzQ1LjczNjcgMTUxLjc4NSA0NS42MTgyIDE1MS44ODYgNDUuNTE5MyAxNTIuMDIxQzQ1LjQyMDMgMTUyLjE1NyA0NS4zNDM1IDE1Mi4zMjIgNDUuMjg4OCAxNTIuNTE4QzQ1LjIzNDEgMTUyLjcxMyA0NS4yMDY4IDE1Mi45MzggNDUuMjA2OCAxNTMuMTkzVjE1My4zNTdDNDUuMjA2OCAxNTMuNTU4IDQ1LjIzNDEgMTUzLjc0NyA0NS4yODg4IDE1My45MjRDNDUuMzQ2MSAxNTQuMDk4IDQ1LjQyODEgMTU0LjI1MiA0NS41MzQ5IDE1NC4zODVDNDUuNjQ0MyAxNTQuNTE4IDQ1Ljc3NTggMTU0LjYyMiA0NS45Mjk0IDE1NC42OTdDNDYuMDg1NyAxNTQuNzczIDQ2LjI2MjcgMTU0LjgxMSA0Ni40NjA3IDE1NC44MTFDNDYuNzE1OSAxNTQuODExIDQ2LjkzMiAxNTQuNzU4IDQ3LjEwOTEgMTU0LjY1NEM0Ny4yODYyIDE1NC41NSA0Ny40NDExIDE1NC40MTEgNDcuNTczOSAxNTQuMjM2TDQ4LjAxMTQgMTU0LjU4NEM0Ny45MjAzIDE1NC43MjIgNDcuODA0NCAxNTQuODU0IDQ3LjY2MzggMTU0Ljk3OUM0Ny41MjMyIDE1NS4xMDQgNDcuMzUgMTU1LjIwNSA0Ny4xNDQzIDE1NS4yODNDNDYuOTQxMSAxNTUuMzYxIDQ2LjcwMDIgMTU1LjQgNDYuNDIxNiAxNTUuNFpNNDguOTg4NiAxNDkuMzIySDQ5LjcxNTJWMTU0LjUwMkw0OS42NTI3IDE1NS4zMjJINDguOTg4NlYxNDkuMzIyWk01Mi41NzA2IDE1My4xNzRWMTUzLjI1NkM1Mi41NzA2IDE1My41NjMgNTIuNTM0MiAxNTMuODQ4IDUyLjQ2MTMgMTU0LjExMUM1Mi4zODgzIDE1NC4zNzIgNTIuMjgxNiAxNTQuNTk4IDUyLjE0MDkgMTU0Ljc5MUM1Mi4wMDAzIDE1NC45ODQgNTEuODI4NCAxNTUuMTMzIDUxLjYyNTMgMTU1LjI0QzUxLjQyMjIgMTU1LjM0NyA1MS4xODkxIDE1NS40IDUwLjkyNjEgMTU1LjRDNTAuNjU3OSAxNTUuNCA1MC40MjIyIDE1NS4zNTUgNTAuMjE5MSAxNTUuMjY0QzUwLjAxODUgMTU1LjE3IDQ5Ljg0OTMgMTU1LjAzNiA0OS43MTEzIDE1NC44NjFDNDkuNTczMiAxNTQuNjg3IDQ5LjQ2MjYgMTU0LjQ3NiA0OS4zNzkyIDE1NC4yMjlDNDkuMjk4NSAxNTMuOTgxIDQ5LjI0MjUgMTUzLjcwMiA0OS4yMTEzIDE1My4zOTNWMTUzLjAzM0M0OS4yNDI1IDE1Mi43MjEgNDkuMjk4NSAxNTIuNDQxIDQ5LjM3OTIgMTUyLjE5M0M0OS40NjI2IDE1MS45NDYgNDkuNTczMiAxNTEuNzM1IDQ5LjcxMTMgMTUxLjU2MUM0OS44NDkzIDE1MS4zODMgNTAuMDE4NSAxNTEuMjQ5IDUwLjIxOTEgMTUxLjE1OEM1MC40MTk2IDE1MS4wNjQgNTAuNjUyNyAxNTEuMDE4IDUwLjkxODMgMTUxLjAxOEM1MS4xODM5IDE1MS4wMTggNTEuNDE5NiAxNTEuMDcgNTEuNjI1MyAxNTEuMTc0QzUxLjgzMSAxNTEuMjc1IDUyLjAwMjkgMTUxLjQyMSA1Mi4xNDA5IDE1MS42MTFDNTIuMjgxNiAxNTEuODAxIDUyLjM4ODMgMTUyLjAyOSA1Mi40NjEzIDE1Mi4yOTVDNTIuNTM0MiAxNTIuNTU4IDUyLjU3MDYgMTUyLjg1MSA1Mi41NzA2IDE1My4xNzRaTTUxLjg0NDEgMTUzLjI1NlYxNTMuMTc0QzUxLjg0NDEgMTUyLjk2MyA1MS44MjQ1IDE1Mi43NjUgNTEuNzg1NSAxNTIuNThDNTEuNzQ2NCAxNTIuMzkzIDUxLjY4MzkgMTUyLjIyOSA1MS41OTggMTUyLjA4OEM1MS41MTIgMTUxLjk0NSA1MS4zOTg4IDE1MS44MzMgNTEuMjU4MSAxNTEuNzUyQzUxLjExNzUgMTUxLjY2OSA1MC45NDQzIDE1MS42MjcgNTAuNzM4NiAxNTEuNjI3QzUwLjU1NjMgMTUxLjYyNyA1MC4zOTc1IDE1MS42NTggNTAuMjYyIDE1MS43MjFDNTAuMTI5MiAxNTEuNzgzIDUwLjAxNTkgMTUxLjg2OCA0OS45MjIyIDE1MS45NzVDNDkuODI4NCAxNTIuMDc5IDQ5Ljc1MTYgMTUyLjE5OSA0OS42OTE3IDE1Mi4zMzRDNDkuNjM0NCAxNTIuNDY3IDQ5LjU5MTUgMTUyLjYwNSA0OS41NjI4IDE1Mi43NDhWMTUzLjY4OUM0OS42MDQ1IDE1My44NzIgNDkuNjcyMiAxNTQuMDQ4IDQ5Ljc2NTkgMTU0LjIxN0M0OS44NjIzIDE1NC4zODMgNDkuOTg5OSAxNTQuNTIgNTAuMTQ4OCAxNTQuNjI3QzUwLjMxMDIgMTU0LjczNCA1MC41MDk0IDE1NC43ODcgNTAuNzQ2NCAxNTQuNzg3QzUwLjk0MTcgMTU0Ljc4NyA1MS4xMDg0IDE1NC43NDggNTEuMjQ2NCAxNTQuNjdDNTEuMzg3IDE1NC41ODkgNTEuNTAwMyAxNTQuNDc5IDUxLjU4NjMgMTU0LjMzOEM1MS42NzQ4IDE1NC4xOTcgNTEuNzM5OSAxNTQuMDM1IDUxLjc4MTYgMTUzLjg1QzUxLjgyMzIgMTUzLjY2NSA1MS44NDQxIDE1My40NjcgNTEuODQ0MSAxNTMuMjU2WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8L2c+CjxsaW5lIHgxPSI1OC4xNDk5IiB5MT0iMTQ3LjIzMyIgeDI9IjU4LjE0OTkiIHkyPSIxNDYuNDExIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDJfNDE4Ml8xMTE5MykiPgo8bGluZSB4MT0iNzUuODQ5OSIgeTE9IjE0Ny4yMzMiIHgyPSI3NS44NDk5IiB5Mj0iMTQ2LjQxMSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuNSIgc3Ryb2tlLXdpZHRoPSIwLjUiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiLz4KPHBhdGggZD0iTTY3LjkwOSAxNTIuMTg2VjE1My4wNTNDNjcuOTA5IDE1My41MiA2Ny44NjczIDE1My45MTMgNjcuNzg0IDE1NC4yMzNDNjcuNzAwNiAxNTQuNTUzIDY3LjU4MDggMTU0LjgxMSA2Ny40MjQ2IDE1NS4wMDdDNjcuMjY4MyAxNTUuMjAyIDY3LjA3OTUgMTU1LjM0NCA2Ni44NTgyIDE1NS40MzJDNjYuNjM5NCAxNTUuNTE4IDY2LjM5MiAxNTUuNTYxIDY2LjExNiAxNTUuNTYxQzY1Ljg5NzIgMTU1LjU2MSA2NS42OTU0IDE1NS41MzQgNjUuNTEwNSAxNTUuNDc5QzY1LjMyNTYgMTU1LjQyNSA2NS4xNTkgMTU1LjMzNyA2NS4wMTA1IDE1NS4yMThDNjQuODY0NyAxNTUuMDk1IDY0LjczOTcgMTU0LjkzNiA2NC42MzU1IDE1NC43NDFDNjQuNTMxNCAxNTQuNTQ2IDY0LjQ1MTkgMTU0LjMwOSA2NC4zOTcyIDE1NC4wM0M2NC4zNDI2IDE1My43NTEgNjQuMzE1MiAxNTMuNDI2IDY0LjMxNTIgMTUzLjA1M1YxNTIuMTg2QzY0LjMxNTIgMTUxLjcyIDY0LjM1NjkgMTUxLjMzIDY0LjQ0MDIgMTUxLjAxNEM2NC41MjYxIDE1MC42OTkgNjQuNjQ3MiAxNTAuNDQ3IDY0LjgwMzUgMTUwLjI1N0M2NC45NTk3IDE1MC4wNjQgNjUuMTQ3MiAxNDkuOTI2IDY1LjM2NiAxNDkuODQzQzY1LjU4NzMgMTQ5Ljc1OSA2NS44MzQ3IDE0OS43MTggNjYuMTA4MiAxNDkuNzE4QzY2LjMyOTUgMTQ5LjcxOCA2Ni41MzI3IDE0OS43NDUgNjYuNzE3NiAxNDkuOEM2Ni45MDUxIDE0OS44NTIgNjcuMDcxNyAxNDkuOTM2IDY3LjIxNzYgMTUwLjA1M0M2Ny4zNjM0IDE1MC4xNjggNjcuNDg3MSAxNTAuMzIyIDY3LjU4ODYgMTUwLjUxNEM2Ny42OTI4IDE1MC43MDUgNjcuNzcyMiAxNTAuOTM4IDY3LjgyNjkgMTUxLjIxNEM2Ny44ODE2IDE1MS40OSA2Ny45MDkgMTUxLjgxNCA2Ny45MDkgMTUyLjE4NlpNNjcuMTgyNCAxNTMuMTcxVjE1Mi4wNjVDNjcuMTgyNCAxNTEuODEgNjcuMTY2OCAxNTEuNTg2IDY3LjEzNTUgMTUxLjM5M0M2Ny4xMDY5IDE1MS4xOTggNjcuMDYzOSAxNTEuMDMxIDY3LjAwNjYgMTUwLjg5M0M2Ni45NDkzIDE1MC43NTUgNjYuODc2NCAxNTAuNjQzIDY2Ljc4NzkgMTUwLjU1N0M2Ni43MDE5IDE1MC40NzEgNjYuNjAxNyAxNTAuNDA5IDY2LjQ4NzEgMTUwLjM3QzY2LjM3NTEgMTUwLjMyOCA2Ni4yNDg4IDE1MC4zMDcgNjYuMTA4MiAxNTAuMzA3QzY1LjkzNjMgMTUwLjMwNyA2NS43ODQgMTUwLjM0IDY1LjY1MTEgMTUwLjQwNUM2NS41MTgzIDE1MC40NjggNjUuNDA2NCAxNTAuNTY4IDY1LjMxNTIgMTUwLjcwNkM2NS4yMjY3IDE1MC44NDQgNjUuMTU5IDE1MS4wMjUgNjUuMTEyMSAxNTEuMjQ5QzY1LjA2NTIgMTUxLjQ3MyA2NS4wNDE4IDE1MS43NDUgNjUuMDQxOCAxNTIuMDY1VjE1My4xNzFDNjUuMDQxOCAxNTMuNDI2IDY1LjA1NjEgMTUzLjY1MSA2NS4wODQ3IDE1My44NDZDNjUuMTE2IDE1NC4wNDIgNjUuMTYxNiAxNTQuMjExIDY1LjIyMTUgMTU0LjM1NEM2NS4yODE0IDE1NC40OTUgNjUuMzU0MyAxNTQuNjExIDY1LjQ0MDIgMTU0LjcwMkM2NS41MjYxIDE1NC43OTMgNjUuNjI1MSAxNTQuODYxIDY1LjczNzEgMTU0LjkwNUM2NS44NTE3IDE1NC45NDcgNjUuOTc4IDE1NC45NjggNjYuMTE2IDE1NC45NjhDNjYuMjkzMSAxNTQuOTY4IDY2LjQ0OCAxNTQuOTM0IDY2LjU4MDggMTU0Ljg2NkM2Ni43MTM2IDE1NC43OTggNjYuODI0MyAxNTQuNjkzIDY2LjkxMjkgMTU0LjU1QzY3LjAwNCAxNTQuNDA0IDY3LjA3MTcgMTU0LjIxOCA2Ny4xMTYgMTUzLjk5MUM2Ny4xNjAzIDE1My43NjIgNjcuMTgyNCAxNTMuNDg4IDY3LjE4MjQgMTUzLjE3MVpNNjkuOTc2IDE1Mi4yODRINzAuNDkxNkM3MC43NDQyIDE1Mi4yODQgNzAuOTUyNSAxNTIuMjQyIDcxLjExNjYgMTUyLjE1OUM3MS4yODMzIDE1Mi4wNzMgNzEuNDA3IDE1MS45NTcgNzEuNDg3NyAxNTEuODExQzcxLjU3MSAxNTEuNjYzIDcxLjYxMjcgMTUxLjQ5NiA3MS42MTI3IDE1MS4zMTFDNzEuNjEyNyAxNTEuMDkzIDcxLjU3NjIgMTUwLjkwOSA3MS41MDMzIDE1MC43NkM3MS40MzA0IDE1MC42MTIgNzEuMzIxIDE1MC41IDcxLjE3NTIgMTUwLjQyNUM3MS4wMjkzIDE1MC4zNDkgNzAuODQ0NSAxNTAuMzExIDcwLjYyMDUgMTUwLjMxMUM3MC40MTc0IDE1MC4zMTEgNzAuMjM3NyAxNTAuMzUyIDcwLjA4MTQgMTUwLjQzMkM2OS45Mjc4IDE1MC41MSA2OS44MDY3IDE1MC42MjIgNjkuNzE4MiAxNTAuNzY4QzY5LjYzMjIgMTUwLjkxNCA2OS41ODkyIDE1MS4wODYgNjkuNTg5MiAxNTEuMjg0SDY4Ljg2NjZDNjguODY2NiAxNTAuOTk1IDY4LjkzOTUgMTUwLjczMiA2OS4wODUzIDE1MC40OTVDNjkuMjMxMiAxNTAuMjU4IDY5LjQzNTYgMTUwLjA2OSA2OS42OTg2IDE0OS45MjhDNjkuOTY0MiAxNDkuNzg4IDcwLjI3MTUgMTQ5LjcxOCA3MC42MjA1IDE0OS43MThDNzAuOTY0MiAxNDkuNzE4IDcxLjI2NSAxNDkuNzc5IDcxLjUyMjggMTQ5LjkwMUM3MS43ODA3IDE1MC4wMjEgNzEuOTgxMiAxNTAuMjAxIDcyLjEyNDQgMTUwLjQ0QzcyLjI2NzYgMTUwLjY3NyA3Mi4zMzkyIDE1MC45NzMgNzIuMzM5MiAxNTEuMzI3QzcyLjMzOTIgMTUxLjQ3IDcyLjMwNTQgMTUxLjYyNCA3Mi4yMzc3IDE1MS43ODhDNzIuMTcyNiAxNTEuOTQ5IDcyLjA2OTcgMTUyLjEgNzEuOTI5MSAxNTIuMjQxQzcxLjc5MTEgMTUyLjM4MiA3MS42MTE0IDE1Mi40OTcgNzEuMzkgMTUyLjU4OUM3MS4xNjg3IDE1Mi42NzcgNzAuOTAzIDE1Mi43MjEgNzAuNTkzMiAxNTIuNzIxSDY5Ljk3NlYxNTIuMjg0Wk02OS45NzYgMTUyLjg3OFYxNTIuNDQ0SDcwLjU5MzJDNzAuOTU1MSAxNTIuNDQ0IDcxLjI1NDYgMTUyLjQ4NyA3MS40OTE2IDE1Mi41NzNDNzEuNzI4NiAxNTIuNjU5IDcxLjkxNDggMTUyLjc3NCA3Mi4wNTAyIDE1Mi45MTdDNzIuMTg4MiAxNTMuMDYgNzIuMjg0NiAxNTMuMjE4IDcyLjMzOTIgMTUzLjM4OUM3Mi4zOTY1IDE1My41NTkgNzIuNDI1MiAxNTMuNzI4IDcyLjQyNTIgMTUzLjg5N0M3Mi40MjUyIDE1NC4xNjMgNzIuMzc5NiAxNTQuMzk5IDcyLjI4ODUgMTU0LjYwNEM3Mi4xOTk5IDE1NC44MSA3Mi4wNzM2IDE1NC45ODQgNzEuOTA5NiAxNTUuMTI4QzcxLjc0ODEgMTU1LjI3MSA3MS41NTggMTU1LjM3OSA3MS4zMzkyIDE1NS40NTJDNzEuMTIwNSAxNTUuNTI1IDcwLjg4MjIgMTU1LjU2MSA3MC42MjQ0IDE1NS41NjFDNzAuMzc3IDE1NS41NjEgNzAuMTQzOSAxNTUuNTI2IDY5LjkyNTIgMTU1LjQ1NkM2OS43MDkgMTU1LjM4NSA2OS41MTc2IDE1NS4yODQgNjkuMzUxIDE1NS4xNTFDNjkuMTg0MyAxNTUuMDE2IDY5LjA1NDEgMTU0Ljg1IDY4Ljk2MDMgMTU0LjY1NUM2OC44NjY2IDE1NC40NTcgNjguODE5NyAxNTQuMjMyIDY4LjgxOTcgMTUzLjk3OUg2OS41NDI0QzY5LjU0MjQgMTU0LjE3NyA2OS41ODUzIDE1NC4zNSA2OS42NzEzIDE1NC40OTlDNjkuNzU5OCAxNTQuNjQ3IDY5Ljg4NDggMTU0Ljc2MyA3MC4wNDYzIDE1NC44NDZDNzAuMjEwMyAxNTQuOTI3IDcwLjQwMyAxNTQuOTY4IDcwLjYyNDQgMTU0Ljk2OEM3MC44NDU4IDE1NC45NjggNzEuMDM1OSAxNTQuOTMgNzEuMTk0NyAxNTQuODU0QzcxLjM1NjIgMTU0Ljc3NiA3MS40Nzk5IDE1NC42NTkgNzEuNTY1OCAxNTQuNTAzQzcxLjY1NDMgMTU0LjM0NiA3MS42OTg2IDE1NC4xNSA3MS42OTg2IDE1My45MTNDNzEuNjk4NiAxNTMuNjc2IDcxLjY0OTEgMTUzLjQ4MiA3MS41NTAyIDE1My4zMzFDNzEuNDUxMiAxNTMuMTc3IDcxLjMxMDYgMTUzLjA2NCA3MS4xMjgzIDE1Mi45OTFDNzAuOTQ4NiAxNTIuOTE1IDcwLjczNjQgMTUyLjg3OCA3MC40OTE2IDE1Mi44NzhINjkuOTc2Wk03Ni41MTIzIDE0OS43OTZWMTU1LjQ4M0g3NS43NTg0VjE0OS43OTZINzYuNTEyM1pNNzguODk1MSAxNTIuMzU0VjE1Mi45NzFINzYuMzQ4MlYxNTIuMzU0SDc4Ljg5NTFaTTc5LjI4MTggMTQ5Ljc5NlYxNTAuNDEzSDc2LjM0ODJWMTQ5Ljc5Nkg3OS4yODE4Wk04MS44MjE1IDE1NS41NjFDODEuNTI3MiAxNTUuNTYxIDgxLjI2MDMgMTU1LjUxMiA4MS4wMjA3IDE1NS40MTNDODAuNzgzNyAxNTUuMzExIDgwLjU3OTMgMTU1LjE2OSA4MC40MDc0IDE1NC45ODdDODAuMjM4MiAxNTQuODA1IDgwLjEwOCAxNTQuNTg5IDgwLjAxNjggMTU0LjMzOUM3OS45MjU3IDE1NC4wODkgNzkuODgwMSAxNTMuODE1IDc5Ljg4MDEgMTUzLjUxOFYxNTMuMzU0Qzc5Ljg4MDEgMTUzLjAxIDc5LjkzMDkgMTUyLjcwNSA4MC4wMzI0IDE1Mi40MzZDODAuMTM0IDE1Mi4xNjUgODAuMjcyIDE1MS45MzYgODAuNDQ2NSAxNTEuNzQ5QzgwLjYyMSAxNTEuNTYxIDgwLjgxODkgMTUxLjQxOSA4MS4wNDAyIDE1MS4zMjNDODEuMjYxNiAxNTEuMjI3IDgxLjQ5MDggMTUxLjE3OCA4MS43Mjc3IDE1MS4xNzhDODIuMDI5OCAxNTEuMTc4IDgyLjI5MDIgMTUxLjIzMSA4Mi41MDkgMTUxLjMzNUM4Mi43MzA0IDE1MS40MzkgODIuOTExMyAxNTEuNTg1IDgzLjA1MiAxNTEuNzcyQzgzLjE5MjYgMTUxLjk1NyA4My4yOTY4IDE1Mi4xNzYgODMuMzY0NSAxNTIuNDI4QzgzLjQzMjIgMTUyLjY3OCA4My40NjYgMTUyLjk1MiA4My40NjYgMTUzLjI0OVYxNTMuNTczSDgwLjMwOThWMTUyLjk4M0g4Mi43NDM0VjE1Mi45MjhDODIuNzMzIDE1Mi43NDEgODIuNjkzOSAxNTIuNTU5IDgyLjYyNjIgMTUyLjM4MkM4Mi41NjExIDE1Mi4yMDUgODIuNDU2OSAxNTIuMDU5IDgyLjMxMzcgMTUxLjk0NEM4Mi4xNzA1IDE1MS44MyA4MS45NzUxIDE1MS43NzIgODEuNzI3NyAxNTEuNzcyQzgxLjU2MzcgMTUxLjc3MiA4MS40MTI2IDE1MS44MDcgODEuMjc0NiAxNTEuODc4QzgxLjEzNjYgMTUxLjk0NSA4MS4wMTgxIDE1Mi4wNDcgODAuOTE5MiAxNTIuMTgyQzgwLjgyMDIgMTUyLjMxOCA4MC43NDM0IDE1Mi40ODMgODAuNjg4NyAxNTIuNjc4QzgwLjYzNCAxNTIuODc0IDgwLjYwNjcgMTUzLjA5OSA4MC42MDY3IDE1My4zNTRWMTUzLjUxOEM4MC42MDY3IDE1My43MTkgODAuNjM0IDE1My45MDggODAuNjg4NyAxNTQuMDg1QzgwLjc0NiAxNTQuMjU5IDgwLjgyOCAxNTQuNDEzIDgwLjkzNDggMTU0LjU0NkM4MS4wNDQyIDE1NC42NzggODEuMTc1NyAxNTQuNzgzIDgxLjMyOTMgMTU0Ljg1OEM4MS40ODU2IDE1NC45MzQgODEuNjYyNiAxNTQuOTcxIDgxLjg2MDYgMTU0Ljk3MUM4Mi4xMTU4IDE1NC45NzEgODIuMzMxOSAxNTQuOTE5IDgyLjUwOSAxNTQuODE1QzgyLjY4NjEgMTU0LjcxMSA4Mi44NDEgMTU0LjU3MiA4Mi45NzM4IDE1NC4zOTdMODMuNDExMyAxNTQuNzQ1QzgzLjMyMDIgMTU0Ljg4MyA4My4yMDQzIDE1NS4wMTQgODMuMDYzNyAxNTUuMTM5QzgyLjkyMzEgMTU1LjI2NCA4Mi43NDk5IDE1NS4zNjYgODIuNTQ0MiAxNTUuNDQ0QzgyLjM0MSAxNTUuNTIyIDgyLjEwMDEgMTU1LjU2MSA4MS44MjE1IDE1NS41NjFaTTg0LjM4ODUgMTQ5LjQ4M0g4NS4xMTUxVjE1NC42NjNMODUuMDUyNiAxNTUuNDgzSDg0LjM4ODVWMTQ5LjQ4M1pNODcuOTcwNSAxNTMuMzM1VjE1My40MTdDODcuOTcwNSAxNTMuNzI0IDg3LjkzNDEgMTU0LjAwOSA4Ny44NjEyIDE1NC4yNzJDODcuNzg4MiAxNTQuNTMzIDg3LjY4MTUgMTU0Ljc1OSA4Ny41NDA4IDE1NC45NTJDODcuNDAwMiAxNTUuMTQ1IDg3LjIyODMgMTU1LjI5NCA4Ny4wMjUyIDE1NS40MDFDODYuODIyMSAxNTUuNTA4IDg2LjU4OSAxNTUuNTYxIDg2LjMyNiAxNTUuNTYxQzg2LjA1NzggMTU1LjU2MSA4NS44MjIxIDE1NS41MTYgODUuNjE5IDE1NS40MjVDODUuNDE4NSAxNTUuMzMxIDg1LjI0OTIgMTU1LjE5NyA4NS4xMTEyIDE1NS4wMjJDODQuOTczMSAxNTQuODQ4IDg0Ljg2MjUgMTU0LjYzNyA4NC43NzkxIDE1NC4zODlDODQuNjk4NCAxNTQuMTQyIDg0LjY0MjQgMTUzLjg2MyA4NC42MTEyIDE1My41NTNWMTUzLjE5NEM4NC42NDI0IDE1Mi44ODIgODQuNjk4NCAxNTIuNjAyIDg0Ljc3OTEgMTUyLjM1NEM4NC44NjI1IDE1Mi4xMDcgODQuOTczMSAxNTEuODk2IDg1LjExMTIgMTUxLjcyMUM4NS4yNDkyIDE1MS41NDQgODUuNDE4NSAxNTEuNDEgODUuNjE5IDE1MS4zMTlDODUuODE5NSAxNTEuMjI1IDg2LjA1MjYgMTUxLjE3OCA4Ni4zMTgyIDE1MS4xNzhDODYuNTgzOCAxNTEuMTc4IDg2LjgxOTUgMTUxLjIzMSA4Ny4wMjUyIDE1MS4zMzVDODcuMjMxIDE1MS40MzYgODcuNDAyOCAxNTEuNTgyIDg3LjU0MDggMTUxLjc3MkM4Ny42ODE1IDE1MS45NjIgODcuNzg4MiAxNTIuMTkgODcuODYxMiAxNTIuNDU2Qzg3LjkzNDEgMTUyLjcxOSA4Ny45NzA1IDE1My4wMTIgODcuOTcwNSAxNTMuMzM1Wk04Ny4yNDQgMTUzLjQxN1YxNTMuMzM1Qzg3LjI0NCAxNTMuMTI0IDg3LjIyNDQgMTUyLjkyNiA4Ny4xODU0IDE1Mi43NDFDODcuMTQ2MyAxNTIuNTUzIDg3LjA4MzggMTUyLjM4OSA4Ni45OTc5IDE1Mi4yNDlDODYuOTExOSAxNTIuMTA2IDg2Ljc5ODcgMTUxLjk5NCA4Ni42NTggMTUxLjkxM0M4Ni41MTc0IDE1MS44MyA4Ni4zNDQyIDE1MS43ODggODYuMTM4NSAxNTEuNzg4Qzg1Ljk1NjIgMTUxLjc4OCA4NS43OTc0IDE1MS44MTkgODUuNjYxOSAxNTEuODgyQzg1LjUyOTEgMTUxLjk0NCA4NS40MTU4IDE1Mi4wMjkgODUuMzIyMSAxNTIuMTM1Qzg1LjIyODMgMTUyLjI0IDg1LjE1MTUgMTUyLjM1OSA4NS4wOTE2IDE1Mi40OTVDODUuMDM0MyAxNTIuNjI4IDg0Ljk5MTQgMTUyLjc2NiA4NC45NjI3IDE1Mi45MDlWMTUzLjg1Qzg1LjAwNDQgMTU0LjAzMyA4NS4wNzIxIDE1NC4yMDggODUuMTY1OCAxNTQuMzc4Qzg1LjI2MjIgMTU0LjU0NCA4NS4zODk4IDE1NC42ODEgODUuNTQ4NyAxNTQuNzg4Qzg1LjcxMDEgMTU0Ljg5NSA4NS45MDkzIDE1NC45NDggODYuMTQ2MyAxNTQuOTQ4Qzg2LjM0MTYgMTU0Ljk0OCA4Ni41MDgzIDE1NC45MDkgODYuNjQ2MyAxNTQuODMxQzg2Ljc4NjkgMTU0Ljc1IDg2LjkwMDIgMTU0LjYzOSA4Ni45ODYyIDE1NC40OTlDODcuMDc0NyAxNTQuMzU4IDg3LjEzOTggMTU0LjE5NSA4Ny4xODE1IDE1NC4wMUM4Ny4yMjMxIDE1My44MjYgODcuMjQ0IDE1My42MjggODcuMjQ0IDE1My40MTdaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjwvZz4KPGxpbmUgeDE9IjkzLjU1IiB5MT0iMTQ3LjIzMyIgeDI9IjkzLjU1IiB5Mj0iMTQ2LjQxMSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuNSIgc3Ryb2tlLXdpZHRoPSIwLjUiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAzXzQxODJfMTExOTMpIj4KPGxpbmUgeDE9IjExMS4yNSIgeTE9IjE0Ny4yMzMiIHgyPSIxMTEuMjUiIHkyPSIxNDYuNDExIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNMTAzLjMwOSAxNTIuMTg2VjE1My4wNTNDMTAzLjMwOSAxNTMuNTIgMTAzLjI2NyAxNTMuOTEzIDEwMy4xODQgMTU0LjIzM0MxMDMuMTAxIDE1NC41NTMgMTAyLjk4MSAxNTQuODExIDEwMi44MjUgMTU1LjAwN0MxMDIuNjY4IDE1NS4yMDIgMTAyLjQ4IDE1NS4zNDQgMTAyLjI1OCAxNTUuNDMyQzEwMi4wNCAxNTUuNTE4IDEwMS43OTIgMTU1LjU2MSAxMDEuNTE2IDE1NS41NjFDMTAxLjI5NyAxNTUuNTYxIDEwMS4wOTYgMTU1LjUzNCAxMDAuOTExIDE1NS40NzlDMTAwLjcyNiAxNTUuNDI1IDEwMC41NTkgMTU1LjMzNyAxMDAuNDExIDE1NS4yMThDMTAwLjI2NSAxNTUuMDk1IDEwMC4xNCAxNTQuOTM2IDEwMC4wMzYgMTU0Ljc0MUM5OS45MzE1IDE1NC41NDYgOTkuODUyMSAxNTQuMzA5IDk5Ljc5NzQgMTU0LjAzQzk5Ljc0MjcgMTUzLjc1MSA5OS43MTU0IDE1My40MjYgOTkuNzE1NCAxNTMuMDUzVjE1Mi4xODZDOTkuNzE1NCAxNTEuNzIgOTkuNzU3IDE1MS4zMyA5OS44NDA0IDE1MS4wMTRDOTkuOTI2MyAxNTAuNjk5IDEwMC4wNDcgMTUwLjQ0NyAxMDAuMjA0IDE1MC4yNTdDMTAwLjM2IDE1MC4wNjQgMTAwLjU0NyAxNDkuOTI2IDEwMC43NjYgMTQ5Ljg0M0MxMDAuOTg3IDE0OS43NTkgMTAxLjIzNSAxNDkuNzE4IDEwMS41MDggMTQ5LjcxOEMxMDEuNzMgMTQ5LjcxOCAxMDEuOTMzIDE0OS43NDUgMTAyLjExOCAxNDkuOEMxMDIuMzA1IDE0OS44NTIgMTAyLjQ3MiAxNDkuOTM2IDEwMi42MTggMTUwLjA1M0MxMDIuNzY0IDE1MC4xNjggMTAyLjg4NyAxNTAuMzIyIDEwMi45ODkgMTUwLjUxNEMxMDMuMDkzIDE1MC43MDUgMTAzLjE3MiAxNTAuOTM4IDEwMy4yMjcgMTUxLjIxNEMxMDMuMjgyIDE1MS40OSAxMDMuMzA5IDE1MS44MTQgMTAzLjMwOSAxNTIuMTg2Wk0xMDIuNTgzIDE1My4xNzFWMTUyLjA2NUMxMDIuNTgzIDE1MS44MSAxMDIuNTY3IDE1MS41ODYgMTAyLjUzNiAxNTEuMzkzQzEwMi41MDcgMTUxLjE5OCAxMDIuNDY0IDE1MS4wMzEgMTAyLjQwNyAxNTAuODkzQzEwMi4zNDkgMTUwLjc1NSAxMDIuMjc3IDE1MC42NDMgMTAyLjE4OCAxNTAuNTU3QzEwMi4xMDIgMTUwLjQ3MSAxMDIuMDAyIDE1MC40MDkgMTAxLjg4NyAxNTAuMzdDMTAxLjc3NSAxNTAuMzI4IDEwMS42NDkgMTUwLjMwNyAxMDEuNTA4IDE1MC4zMDdDMTAxLjMzNiAxNTAuMzA3IDEwMS4xODQgMTUwLjM0IDEwMS4wNTEgMTUwLjQwNUMxMDAuOTE4IDE1MC40NjggMTAwLjgwNyAxNTAuNTY4IDEwMC43MTUgMTUwLjcwNkMxMDAuNjI3IDE1MC44NDQgMTAwLjU1OSAxNTEuMDI1IDEwMC41MTIgMTUxLjI0OUMxMDAuNDY1IDE1MS40NzMgMTAwLjQ0MiAxNTEuNzQ1IDEwMC40NDIgMTUyLjA2NVYxNTMuMTcxQzEwMC40NDIgMTUzLjQyNiAxMDAuNDU2IDE1My42NTEgMTAwLjQ4NSAxNTMuODQ2QzEwMC41MTYgMTU0LjA0MiAxMDAuNTYyIDE1NC4yMTEgMTAwLjYyMiAxNTQuMzU0QzEwMC42ODIgMTU0LjQ5NSAxMDAuNzU0IDE1NC42MTEgMTAwLjg0IDE1NC43MDJDMTAwLjkyNiAxNTQuNzkzIDEwMS4wMjUgMTU0Ljg2MSAxMDEuMTM3IDE1NC45MDVDMTAxLjI1MiAxNTQuOTQ3IDEwMS4zNzggMTU0Ljk2OCAxMDEuNTE2IDE1NC45NjhDMTAxLjY5MyAxNTQuOTY4IDEwMS44NDggMTU0LjkzNCAxMDEuOTgxIDE1NC44NjZDMTAyLjExNCAxNTQuNzk4IDEwMi4yMjQgMTU0LjY5MyAxMDIuMzEzIDE1NC41NUMxMDIuNDA0IDE1NC40MDQgMTAyLjQ3MiAxNTQuMjE4IDEwMi41MTYgMTUzLjk5MUMxMDIuNTYgMTUzLjc2MiAxMDIuNTgzIDE1My40ODggMTAyLjU4MyAxNTMuMTcxWk0xMDUuMjM1IDE1Mi43NzZMMTA0LjY1NyAxNTIuNjI4TDEwNC45NDMgMTQ5Ljc5NkgxMDcuODZWMTUwLjQ2NEgxMDUuNTU2TDEwNS4zODQgMTUyLjAxQzEwNS40ODggMTUxLjk1MSAxMDUuNjIgMTUxLjg5NSAxMDUuNzc4IDE1MS44NDNDMTA1Ljk0IDE1MS43OSAxMDYuMTI1IDE1MS43NjQgMTA2LjMzMyAxNTEuNzY0QzEwNi41OTYgMTUxLjc2NCAxMDYuODMyIDE1MS44MSAxMDcuMDQgMTUxLjkwMUMxMDcuMjQ5IDE1MS45OSAxMDcuNDI2IDE1Mi4xMTcgMTA3LjU3MSAxNTIuMjg0QzEwNy43MiAxNTIuNDUxIDEwNy44MzMgMTUyLjY1MSAxMDcuOTExIDE1Mi44ODVDMTA3Ljk4OSAxNTMuMTIgMTA4LjAyOCAxNTMuMzgyIDEwOC4wMjggMTUzLjY3MUMxMDguMDI4IDE1My45NDQgMTA3Ljk5MSAxNTQuMTk1IDEwNy45MTUgMTU0LjQyNUMxMDcuODQyIDE1NC42NTQgMTA3LjczMiAxNTQuODU0IDEwNy41ODMgMTU1LjAyNkMxMDcuNDM1IDE1NS4xOTUgMTA3LjI0NyAxNTUuMzI3IDEwNy4wMjEgMTU1LjQyMUMxMDYuNzk3IDE1NS41MTQgMTA2LjUzMiAxNTUuNTYxIDEwNi4yMjggMTU1LjU2MUMxMDUuOTk5IDE1NS41NjEgMTA1Ljc4MSAxNTUuNTMgMTA1LjU3NSAxNTUuNDY4QzEwNS4zNzIgMTU1LjQwMiAxMDUuMTkgMTU1LjMwNSAxMDUuMDI4IDE1NS4xNzVDMTA0Ljg3IDE1NS4wNDIgMTA0LjczOSAxNTQuODc4IDEwNC42MzggMTU0LjY4MkMxMDQuNTM5IDE1NC40ODQgMTA0LjQ3NiAxNTQuMjUzIDEwNC40NSAxNTMuOTg3SDEwNS4xMzhDMTA1LjE2OSAxNTQuMjAxIDEwNS4yMzIgMTU0LjM4IDEwNS4zMjUgMTU0LjUyNkMxMDUuNDE5IDE1NC42NzIgMTA1LjU0MSAxNTQuNzgzIDEwNS42OTMgMTU0Ljg1OEMxMDUuODQ2IDE1NC45MzEgMTA2LjAyNSAxNTQuOTY4IDEwNi4yMjggMTU0Ljk2OEMxMDYuNCAxNTQuOTY4IDEwNi41NTIgMTU0LjkzOCAxMDYuNjg1IDE1NC44NzhDMTA2LjgxOCAxNTQuODE4IDEwNi45MjkgMTU0LjczMiAxMDcuMDIxIDE1NC42MkMxMDcuMTEyIDE1NC41MDggMTA3LjE4MSAxNTQuMzcyIDEwNy4yMjggMTU0LjIxNEMxMDcuMjc3IDE1NC4wNTUgMTA3LjMwMiAxNTMuODc2IDEwNy4zMDIgMTUzLjY3OEMxMDcuMzAyIDE1My40OTkgMTA3LjI3NyAxNTMuMzMyIDEwNy4yMjggMTUzLjE3OEMxMDcuMTc4IDE1My4wMjUgMTA3LjEwNCAxNTIuODkxIDEwNy4wMDUgMTUyLjc3NkMxMDYuOTA5IDE1Mi42NjIgMTA2Ljc5IDE1Mi41NzMgMTA2LjY1IDE1Mi41MUMxMDYuNTA5IDE1Mi40NDUgMTA2LjM0NyAxNTIuNDEzIDEwNi4xNjUgMTUyLjQxM0MxMDUuOTIzIDE1Mi40MTMgMTA1LjczOSAxNTIuNDQ1IDEwNS42MTQgMTUyLjUxQzEwNS40OTIgMTUyLjU3NiAxMDUuMzY2IDE1Mi42NjQgMTA1LjIzNSAxNTIuNzc2Wk0xMTEuOTEyIDE0OS43OTZWMTU1LjQ4M0gxMTEuMTU5VjE0OS43OTZIMTExLjkxMlpNMTE0LjI5NSAxNTIuMzU0VjE1Mi45NzFIMTExLjc0OFYxNTIuMzU0SDExNC4yOTVaTTExNC42ODIgMTQ5Ljc5NlYxNTAuNDEzSDExMS43NDhWMTQ5Ljc5NkgxMTQuNjgyWk0xMTcuMjIyIDE1NS41NjFDMTE2LjkyNyAxNTUuNTYxIDExNi42NiAxNTUuNTEyIDExNi40MjEgMTU1LjQxM0MxMTYuMTg0IDE1NS4zMTEgMTE1Ljk3OSAxNTUuMTY5IDExNS44MDggMTU0Ljk4N0MxMTUuNjM4IDE1NC44MDUgMTE1LjUwOCAxNTQuNTg5IDExNS40MTcgMTU0LjMzOUMxMTUuMzI2IDE1NC4wODkgMTE1LjI4IDE1My44MTUgMTE1LjI4IDE1My41MThWMTUzLjM1NEMxMTUuMjggMTUzLjAxIDExNS4zMzEgMTUyLjcwNSAxMTUuNDMzIDE1Mi40MzZDMTE1LjUzNCAxNTIuMTY1IDExNS42NzIgMTUxLjkzNiAxMTUuODQ3IDE1MS43NDlDMTE2LjAyMSAxNTEuNTYxIDExNi4yMTkgMTUxLjQxOSAxMTYuNDQgMTUxLjMyM0MxMTYuNjYyIDE1MS4yMjcgMTE2Ljg5MSAxNTEuMTc4IDExNy4xMjggMTUxLjE3OEMxMTcuNDMgMTUxLjE3OCAxMTcuNjkgMTUxLjIzMSAxMTcuOTA5IDE1MS4zMzVDMTE4LjEzIDE1MS40MzkgMTE4LjMxMSAxNTEuNTg1IDExOC40NTIgMTUxLjc3MkMxMTguNTkzIDE1MS45NTcgMTE4LjY5NyAxNTIuMTc2IDExOC43NjUgMTUyLjQyOEMxMTguODMyIDE1Mi42NzggMTE4Ljg2NiAxNTIuOTUyIDExOC44NjYgMTUzLjI0OVYxNTMuNTczSDExNS43MVYxNTIuOTgzSDExOC4xNDRWMTUyLjkyOEMxMTguMTMzIDE1Mi43NDEgMTE4LjA5NCAxNTIuNTU5IDExOC4wMjYgMTUyLjM4MkMxMTcuOTYxIDE1Mi4yMDUgMTE3Ljg1NyAxNTIuMDU5IDExNy43MTQgMTUxLjk0NEMxMTcuNTcxIDE1MS44MyAxMTcuMzc1IDE1MS43NzIgMTE3LjEyOCAxNTEuNzcyQzExNi45NjQgMTUxLjc3MiAxMTYuODEzIDE1MS44MDcgMTE2LjY3NSAxNTEuODc4QzExNi41MzcgMTUxLjk0NSAxMTYuNDE4IDE1Mi4wNDcgMTE2LjMxOSAxNTIuMTgyQzExNi4yMiAxNTIuMzE4IDExNi4xNDQgMTUyLjQ4MyAxMTYuMDg5IDE1Mi42NzhDMTE2LjAzNCAxNTIuODc0IDExNi4wMDcgMTUzLjA5OSAxMTYuMDA3IDE1My4zNTRWMTUzLjUxOEMxMTYuMDA3IDE1My43MTkgMTE2LjAzNCAxNTMuOTA4IDExNi4wODkgMTU0LjA4NUMxMTYuMTQ2IDE1NC4yNTkgMTE2LjIyOCAxNTQuNDEzIDExNi4zMzUgMTU0LjU0NkMxMTYuNDQ0IDE1NC42NzggMTE2LjU3NiAxNTQuNzgzIDExNi43MjkgMTU0Ljg1OEMxMTYuODg2IDE1NC45MzQgMTE3LjA2MyAxNTQuOTcxIDExNy4yNjEgMTU0Ljk3MUMxMTcuNTE2IDE1NC45NzEgMTE3LjczMiAxNTQuOTE5IDExNy45MDkgMTU0LjgxNUMxMTguMDg2IDE1NC43MTEgMTE4LjI0MSAxNTQuNTcyIDExOC4zNzQgMTU0LjM5N0wxMTguODExIDE1NC43NDVDMTE4LjcyIDE1NC44ODMgMTE4LjYwNCAxNTUuMDE0IDExOC40NjQgMTU1LjEzOUMxMTguMzIzIDE1NS4yNjQgMTE4LjE1IDE1NS4zNjYgMTE3Ljk0NCAxNTUuNDQ0QzExNy43NDEgMTU1LjUyMiAxMTcuNSAxNTUuNTYxIDExNy4yMjIgMTU1LjU2MVpNMTE5Ljc4OSAxNDkuNDgzSDEyMC41MTVWMTU0LjY2M0wxMjAuNDUzIDE1NS40ODNIMTE5Ljc4OVYxNDkuNDgzWk0xMjMuMzcxIDE1My4zMzVWMTUzLjQxN0MxMjMuMzcxIDE1My43MjQgMTIzLjMzNCAxNTQuMDA5IDEyMy4yNjEgMTU0LjI3MkMxMjMuMTg4IDE1NC41MzMgMTIzLjA4MiAxNTQuNzU5IDEyMi45NDEgMTU0Ljk1MkMxMjIuOCAxNTUuMTQ1IDEyMi42MjggMTU1LjI5NCAxMjIuNDI1IDE1NS40MDFDMTIyLjIyMiAxNTUuNTA4IDEyMS45ODkgMTU1LjU2MSAxMjEuNzI2IDE1NS41NjFDMTIxLjQ1OCAxNTUuNTYxIDEyMS4yMjIgMTU1LjUxNiAxMjEuMDE5IDE1NS40MjVDMTIwLjgxOSAxNTUuMzMxIDEyMC42NDkgMTU1LjE5NyAxMjAuNTExIDE1NS4wMjJDMTIwLjM3MyAxNTQuODQ4IDEyMC4yNjMgMTU0LjYzNyAxMjAuMTc5IDE1NC4zODlDMTIwLjA5OSAxNTQuMTQyIDEyMC4wNDMgMTUzLjg2MyAxMjAuMDExIDE1My41NTNWMTUzLjE5NEMxMjAuMDQzIDE1Mi44ODIgMTIwLjA5OSAxNTIuNjAyIDEyMC4xNzkgMTUyLjM1NEMxMjAuMjYzIDE1Mi4xMDcgMTIwLjM3MyAxNTEuODk2IDEyMC41MTEgMTUxLjcyMUMxMjAuNjQ5IDE1MS41NDQgMTIwLjgxOSAxNTEuNDEgMTIxLjAxOSAxNTEuMzE5QzEyMS4yMiAxNTEuMjI1IDEyMS40NTMgMTUxLjE3OCAxMjEuNzE4IDE1MS4xNzhDMTIxLjk4NCAxNTEuMTc4IDEyMi4yMiAxNTEuMjMxIDEyMi40MjUgMTUxLjMzNUMxMjIuNjMxIDE1MS40MzYgMTIyLjgwMyAxNTEuNTgyIDEyMi45NDEgMTUxLjc3MkMxMjMuMDgyIDE1MS45NjIgMTIzLjE4OCAxNTIuMTkgMTIzLjI2MSAxNTIuNDU2QzEyMy4zMzQgMTUyLjcxOSAxMjMuMzcxIDE1My4wMTIgMTIzLjM3MSAxNTMuMzM1Wk0xMjIuNjQ0IDE1My40MTdWMTUzLjMzNUMxMjIuNjQ0IDE1My4xMjQgMTIyLjYyNSAxNTIuOTI2IDEyMi41ODYgMTUyLjc0MUMxMjIuNTQ2IDE1Mi41NTMgMTIyLjQ4NCAxNTIuMzg5IDEyMi4zOTggMTUyLjI0OUMxMjIuMzEyIDE1Mi4xMDYgMTIyLjE5OSAxNTEuOTk0IDEyMi4wNTggMTUxLjkxM0MxMjEuOTE4IDE1MS44MyAxMjEuNzQ0IDE1MS43ODggMTIxLjUzOSAxNTEuNzg4QzEyMS4zNTYgMTUxLjc4OCAxMjEuMTk4IDE1MS44MTkgMTIxLjA2MiAxNTEuODgyQzEyMC45MjkgMTUxLjk0NCAxMjAuODE2IDE1Mi4wMjkgMTIwLjcyMiAxNTIuMTM1QzEyMC42MjggMTUyLjI0IDEyMC41NTIgMTUyLjM1OSAxMjAuNDkyIDE1Mi40OTVDMTIwLjQzNCAxNTIuNjI4IDEyMC4zOTIgMTUyLjc2NiAxMjAuMzYzIDE1Mi45MDlWMTUzLjg1QzEyMC40MDUgMTU0LjAzMyAxMjAuNDcyIDE1NC4yMDggMTIwLjU2NiAxNTQuMzc4QzEyMC42NjIgMTU0LjU0NCAxMjAuNzkgMTU0LjY4MSAxMjAuOTQ5IDE1NC43ODhDMTIxLjExIDE1NC44OTUgMTIxLjMwOSAxNTQuOTQ4IDEyMS41NDYgMTU0Ljk0OEMxMjEuNzQyIDE1NC45NDggMTIxLjkwOCAxNTQuOTA5IDEyMi4wNDYgMTU0LjgzMUMxMjIuMTg3IDE1NC43NSAxMjIuMyAxNTQuNjM5IDEyMi4zODYgMTU0LjQ5OUMxMjIuNDc1IDE1NC4zNTggMTIyLjU0IDE1NC4xOTUgMTIyLjU4MiAxNTQuMDFDMTIyLjYyMyAxNTMuODI2IDEyMi42NDQgMTUzLjYyOCAxMjIuNjQ0IDE1My40MTdaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjwvZz4KPGxpbmUgeDE9IjEyOC45NSIgeTE9IjE0Ny4yMzMiIHgyPSIxMjguOTUiIHkyPSIxNDYuNDExIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDRfNDE4Ml8xMTE5MykiPgo8bGluZSB4MT0iMTQ2LjY1IiB5MT0iMTQ3LjIzMyIgeDI9IjE0Ni42NSIgeTI9IjE0Ni40MTEiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjUiIHN0cm9rZS13aWR0aD0iMC41IiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIi8+CjxwYXRoIGQ9Ik0xMzguNzA5IDE1Mi4xODZWMTUzLjA1M0MxMzguNzA5IDE1My41MiAxMzguNjY3IDE1My45MTMgMTM4LjU4NCAxNTQuMjMzQzEzOC41MDEgMTU0LjU1MyAxMzguMzgxIDE1NC44MTEgMTM4LjIyNSAxNTUuMDA3QzEzOC4wNjggMTU1LjIwMiAxMzcuODggMTU1LjM0NCAxMzcuNjU4IDE1NS40MzJDMTM3LjQzOSAxNTUuNTE4IDEzNy4xOTIgMTU1LjU2MSAxMzYuOTE2IDE1NS41NjFDMTM2LjY5NyAxNTUuNTYxIDEzNi40OTUgMTU1LjUzNCAxMzYuMzExIDE1NS40NzlDMTM2LjEyNiAxNTUuNDI1IDEzNS45NTkgMTU1LjMzNyAxMzUuODExIDE1NS4yMThDMTM1LjY2NSAxNTUuMDk1IDEzNS41NCAxNTQuOTM2IDEzNS40MzYgMTU0Ljc0MUMxMzUuMzMxIDE1NC41NDYgMTM1LjI1MiAxNTQuMzA5IDEzNS4xOTcgMTU0LjAzQzEzNS4xNDMgMTUzLjc1MSAxMzUuMTE1IDE1My40MjYgMTM1LjExNSAxNTMuMDUzVjE1Mi4xODZDMTM1LjExNSAxNTEuNzIgMTM1LjE1NyAxNTEuMzMgMTM1LjI0IDE1MS4wMTRDMTM1LjMyNiAxNTAuNjk5IDEzNS40NDcgMTUwLjQ0NyAxMzUuNjA0IDE1MC4yNTdDMTM1Ljc2IDE1MC4wNjQgMTM1Ljk0NyAxNDkuOTI2IDEzNi4xNjYgMTQ5Ljg0M0MxMzYuMzg3IDE0OS43NTkgMTM2LjYzNSAxNDkuNzE4IDEzNi45MDggMTQ5LjcxOEMxMzcuMTMgMTQ5LjcxOCAxMzcuMzMzIDE0OS43NDUgMTM3LjUxOCAxNDkuOEMxMzcuNzA1IDE0OS44NTIgMTM3Ljg3MiAxNDkuOTM2IDEzOC4wMTggMTUwLjA1M0MxMzguMTYzIDE1MC4xNjggMTM4LjI4NyAxNTAuMzIyIDEzOC4zODkgMTUwLjUxNEMxMzguNDkzIDE1MC43MDUgMTM4LjU3MiAxNTAuOTM4IDEzOC42MjcgMTUxLjIxNEMxMzguNjgyIDE1MS40OSAxMzguNzA5IDE1MS44MTQgMTM4LjcwOSAxNTIuMTg2Wk0xMzcuOTgyIDE1My4xNzFWMTUyLjA2NUMxMzcuOTgyIDE1MS44MSAxMzcuOTY3IDE1MS41ODYgMTM3LjkzNiAxNTEuMzkzQzEzNy45MDcgMTUxLjE5OCAxMzcuODY0IDE1MS4wMzEgMTM3LjgwNyAxNTAuODkzQzEzNy43NDkgMTUwLjc1NSAxMzcuNjc2IDE1MC42NDMgMTM3LjU4OCAxNTAuNTU3QzEzNy41MDIgMTUwLjQ3MSAxMzcuNDAyIDE1MC40MDkgMTM3LjI4NyAxNTAuMzdDMTM3LjE3NSAxNTAuMzI4IDEzNy4wNDkgMTUwLjMwNyAxMzYuOTA4IDE1MC4zMDdDMTM2LjczNiAxNTAuMzA3IDEzNi41ODQgMTUwLjM0IDEzNi40NTEgMTUwLjQwNUMxMzYuMzE4IDE1MC40NjggMTM2LjIwNiAxNTAuNTY4IDEzNi4xMTUgMTUwLjcwNkMxMzYuMDI3IDE1MC44NDQgMTM1Ljk1OSAxNTEuMDI1IDEzNS45MTIgMTUxLjI0OUMxMzUuODY1IDE1MS40NzMgMTM1Ljg0MiAxNTEuNzQ1IDEzNS44NDIgMTUyLjA2NVYxNTMuMTcxQzEzNS44NDIgMTUzLjQyNiAxMzUuODU2IDE1My42NTEgMTM1Ljg4NSAxNTMuODQ2QzEzNS45MTYgMTU0LjA0MiAxMzUuOTYyIDE1NC4yMTEgMTM2LjAyMiAxNTQuMzU0QzEzNi4wODEgMTU0LjQ5NSAxMzYuMTU0IDE1NC42MTEgMTM2LjI0IDE1NC43MDJDMTM2LjMyNiAxNTQuNzkzIDEzNi40MjUgMTU0Ljg2MSAxMzYuNTM3IDE1NC45MDVDMTM2LjY1MiAxNTQuOTQ3IDEzNi43NzggMTU0Ljk2OCAxMzYuOTE2IDE1NC45NjhDMTM3LjA5MyAxNTQuOTY4IDEzNy4yNDggMTU0LjkzNCAxMzcuMzgxIDE1NC44NjZDMTM3LjUxNCAxNTQuNzk4IDEzNy42MjQgMTU0LjY5MyAxMzcuNzEzIDE1NC41NUMxMzcuODA0IDE1NC40MDQgMTM3Ljg3MiAxNTQuMjE4IDEzNy45MTYgMTUzLjk5MUMxMzcuOTYgMTUzLjc2MiAxMzcuOTgyIDE1My40ODggMTM3Ljk4MiAxNTMuMTcxWk0xNDMuMzk3IDE0OS43OTZWMTUwLjIwMkwxNDEuMDQyIDE1NS40ODNIMTQwLjI4TDE0Mi42MzEgMTUwLjM4OUgxMzkuNTUzVjE0OS43OTZIMTQzLjM5N1pNMTQ3LjMxMiAxNDkuNzk2VjE1NS40ODNIMTQ2LjU1OFYxNDkuNzk2SDE0Ny4zMTJaTTE0OS42OTUgMTUyLjM1NFYxNTIuOTcxSDE0Ny4xNDhWMTUyLjM1NEgxNDkuNjk1Wk0xNTAuMDgyIDE0OS43OTZWMTUwLjQxM0gxNDcuMTQ4VjE0OS43OTZIMTUwLjA4MlpNMTUyLjYyMiAxNTUuNTYxQzE1Mi4zMjcgMTU1LjU2MSAxNTIuMDYgMTU1LjUxMiAxNTEuODIxIDE1NS40MTNDMTUxLjU4NCAxNTUuMzExIDE1MS4zNzkgMTU1LjE2OSAxNTEuMjA3IDE1NC45ODdDMTUxLjAzOCAxNTQuODA1IDE1MC45MDggMTU0LjU4OSAxNTAuODE3IDE1NC4zMzlDMTUwLjcyNiAxNTQuMDg5IDE1MC42OCAxNTMuODE1IDE1MC42OCAxNTMuNTE4VjE1My4zNTRDMTUwLjY4IDE1My4wMSAxNTAuNzMxIDE1Mi43MDUgMTUwLjgzMiAxNTIuNDM2QzE1MC45MzQgMTUyLjE2NSAxNTEuMDcyIDE1MS45MzYgMTUxLjI0NyAxNTEuNzQ5QzE1MS40MjEgMTUxLjU2MSAxNTEuNjE5IDE1MS40MTkgMTUxLjg0IDE1MS4zMjNDMTUyLjA2MiAxNTEuMjI3IDE1Mi4yOTEgMTUxLjE3OCAxNTIuNTI4IDE1MS4xNzhDMTUyLjgzIDE1MS4xNzggMTUzLjA5IDE1MS4yMzEgMTUzLjMwOSAxNTEuMzM1QzE1My41MyAxNTEuNDM5IDE1My43MTEgMTUxLjU4NSAxNTMuODUyIDE1MS43NzJDMTUzLjk5MyAxNTEuOTU3IDE1NC4wOTcgMTUyLjE3NiAxNTQuMTY1IDE1Mi40MjhDMTU0LjIzMiAxNTIuNjc4IDE1NC4yNjYgMTUyLjk1MiAxNTQuMjY2IDE1My4yNDlWMTUzLjU3M0gxNTEuMTFWMTUyLjk4M0gxNTMuNTQzVjE1Mi45MjhDMTUzLjUzMyAxNTIuNzQxIDE1My40OTQgMTUyLjU1OSAxNTMuNDI2IDE1Mi4zODJDMTUzLjM2MSAxNTIuMjA1IDE1My4yNTcgMTUyLjA1OSAxNTMuMTE0IDE1MS45NDRDMTUyLjk3MSAxNTEuODMgMTUyLjc3NSAxNTEuNzcyIDE1Mi41MjggMTUxLjc3MkMxNTIuMzY0IDE1MS43NzIgMTUyLjIxMyAxNTEuODA3IDE1Mi4wNzUgMTUxLjg3OEMxNTEuOTM3IDE1MS45NDUgMTUxLjgxOCAxNTIuMDQ3IDE1MS43MTkgMTUyLjE4MkMxNTEuNjIgMTUyLjMxOCAxNTEuNTQzIDE1Mi40ODMgMTUxLjQ4OSAxNTIuNjc4QzE1MS40MzQgMTUyLjg3NCAxNTEuNDA3IDE1My4wOTkgMTUxLjQwNyAxNTMuMzU0VjE1My41MThDMTUxLjQwNyAxNTMuNzE5IDE1MS40MzQgMTUzLjkwOCAxNTEuNDg5IDE1NC4wODVDMTUxLjU0NiAxNTQuMjU5IDE1MS42MjggMTU0LjQxMyAxNTEuNzM1IDE1NC41NDZDMTUxLjg0NCAxNTQuNjc4IDE1MS45NzYgMTU0Ljc4MyAxNTIuMTI5IDE1NC44NThDMTUyLjI4NiAxNTQuOTM0IDE1Mi40NjMgMTU0Ljk3MSAxNTIuNjYxIDE1NC45NzFDMTUyLjkxNiAxNTQuOTcxIDE1My4xMzIgMTU0LjkxOSAxNTMuMzA5IDE1NC44MTVDMTUzLjQ4NiAxNTQuNzExIDE1My42NDEgMTU0LjU3MiAxNTMuNzc0IDE1NC4zOTdMMTU0LjIxMSAxNTQuNzQ1QzE1NC4xMiAxNTQuODgzIDE1NC4wMDQgMTU1LjAxNCAxNTMuODY0IDE1NS4xMzlDMTUzLjcyMyAxNTUuMjY0IDE1My41NSAxNTUuMzY2IDE1My4zNDQgMTU1LjQ0NEMxNTMuMTQxIDE1NS41MjIgMTUyLjkgMTU1LjU2MSAxNTIuNjIyIDE1NS41NjFaTTE1NS4xODkgMTQ5LjQ4M0gxNTUuOTE1VjE1NC42NjNMMTU1Ljg1MyAxNTUuNDgzSDE1NS4xODlWMTQ5LjQ4M1pNMTU4Ljc3MSAxNTMuMzM1VjE1My40MTdDMTU4Ljc3MSAxNTMuNzI0IDE1OC43MzQgMTU0LjAwOSAxNTguNjYxIDE1NC4yNzJDMTU4LjU4OCAxNTQuNTMzIDE1OC40ODIgMTU0Ljc1OSAxNTguMzQxIDE1NC45NTJDMTU4LjIgMTU1LjE0NSAxNTguMDI4IDE1NS4yOTQgMTU3LjgyNSAxNTUuNDAxQzE1Ny42MjIgMTU1LjUwOCAxNTcuMzg5IDE1NS41NjEgMTU3LjEyNiAxNTUuNTYxQzE1Ni44NTggMTU1LjU2MSAxNTYuNjIyIDE1NS41MTYgMTU2LjQxOSAxNTUuNDI1QzE1Ni4yMTggMTU1LjMzMSAxNTYuMDQ5IDE1NS4xOTcgMTU1LjkxMSAxNTUuMDIyQzE1NS43NzMgMTU0Ljg0OCAxNTUuNjYzIDE1NC42MzcgMTU1LjU3OSAxNTQuMzg5QzE1NS40OTggMTU0LjE0MiAxNTUuNDQyIDE1My44NjMgMTU1LjQxMSAxNTMuNTUzVjE1My4xOTRDMTU1LjQ0MiAxNTIuODgyIDE1NS40OTggMTUyLjYwMiAxNTUuNTc5IDE1Mi4zNTRDMTU1LjY2MyAxNTIuMTA3IDE1NS43NzMgMTUxLjg5NiAxNTUuOTExIDE1MS43MjFDMTU2LjA0OSAxNTEuNTQ0IDE1Ni4yMTggMTUxLjQxIDE1Ni40MTkgMTUxLjMxOUMxNTYuNjIgMTUxLjIyNSAxNTYuODUzIDE1MS4xNzggMTU3LjExOCAxNTEuMTc4QzE1Ny4zODQgMTUxLjE3OCAxNTcuNjIgMTUxLjIzMSAxNTcuODI1IDE1MS4zMzVDMTU4LjAzMSAxNTEuNDM2IDE1OC4yMDMgMTUxLjU4MiAxNTguMzQxIDE1MS43NzJDMTU4LjQ4MiAxNTEuOTYyIDE1OC41ODggMTUyLjE5IDE1OC42NjEgMTUyLjQ1NkMxNTguNzM0IDE1Mi43MTkgMTU4Ljc3MSAxNTMuMDEyIDE1OC43NzEgMTUzLjMzNVpNMTU4LjA0NCAxNTMuNDE3VjE1My4zMzVDMTU4LjA0NCAxNTMuMTI0IDE1OC4wMjQgMTUyLjkyNiAxNTcuOTg1IDE1Mi43NDFDMTU3Ljk0NiAxNTIuNTUzIDE1Ny44ODQgMTUyLjM4OSAxNTcuNzk4IDE1Mi4yNDlDMTU3LjcxMiAxNTIuMTA2IDE1Ny41OTkgMTUxLjk5NCAxNTcuNDU4IDE1MS45MTNDMTU3LjMxNyAxNTEuODMgMTU3LjE0NCAxNTEuNzg4IDE1Ni45MzkgMTUxLjc4OEMxNTYuNzU2IDE1MS43ODggMTU2LjU5NyAxNTEuODE5IDE1Ni40NjIgMTUxLjg4MkMxNTYuMzI5IDE1MS45NDQgMTU2LjIxNiAxNTIuMDI5IDE1Ni4xMjIgMTUyLjEzNUMxNTYuMDI4IDE1Mi4yNCAxNTUuOTUyIDE1Mi4zNTkgMTU1Ljg5MiAxNTIuNDk1QzE1NS44MzQgMTUyLjYyOCAxNTUuNzkxIDE1Mi43NjYgMTU1Ljc2MyAxNTIuOTA5VjE1My44NUMxNTUuODA0IDE1NC4wMzMgMTU1Ljg3MiAxNTQuMjA4IDE1NS45NjYgMTU0LjM3OEMxNTYuMDYyIDE1NC41NDQgMTU2LjE5IDE1NC42ODEgMTU2LjM0OSAxNTQuNzg4QzE1Ni41MSAxNTQuODk1IDE1Ni43MDkgMTU0Ljk0OCAxNTYuOTQ2IDE1NC45NDhDMTU3LjE0MiAxNTQuOTQ4IDE1Ny4zMDggMTU0LjkwOSAxNTcuNDQ2IDE1NC44MzFDMTU3LjU4NyAxNTQuNzUgMTU3LjcgMTU0LjYzOSAxNTcuNzg2IDE1NC40OTlDMTU3Ljg3NSAxNTQuMzU4IDE1Ny45NCAxNTQuMTk1IDE1Ny45ODIgMTU0LjAxQzE1OC4wMjMgMTUzLjgyNiAxNTguMDQ0IDE1My42MjggMTU4LjA0NCAxNTMuNDE3WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8L2c+CjxsaW5lIHgxPSIxNjQuMzUiIHkxPSIxNDcuMjMzIiB4Mj0iMTY0LjM1IiB5Mj0iMTQ2LjQxMSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuNSIgc3Ryb2tlLXdpZHRoPSIwLjUiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXA1XzQxODJfMTExOTMpIj4KPGxpbmUgeDE9IjE4Mi4wNSIgeTE9IjE0Ny4yMzMiIHgyPSIxODIuMDUiIHkyPSIxNDYuNDExIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNMTc0LjEwOSAxNTIuMTg2VjE1My4wNTNDMTc0LjEwOSAxNTMuNTIgMTc0LjA2NyAxNTMuOTEzIDE3My45ODQgMTU0LjIzM0MxNzMuOTAxIDE1NC41NTMgMTczLjc4MSAxNTQuODExIDE3My42MjUgMTU1LjAwN0MxNzMuNDY5IDE1NS4yMDIgMTczLjI4IDE1NS4zNDQgMTczLjA1OCAxNTUuNDMyQzE3Mi44NCAxNTUuNTE4IDE3Mi41OTIgMTU1LjU2MSAxNzIuMzE2IDE1NS41NjFDMTcyLjA5NyAxNTUuNTYxIDE3MS44OTYgMTU1LjUzNCAxNzEuNzExIDE1NS40NzlDMTcxLjUyNiAxNTUuNDI1IDE3MS4zNTkgMTU1LjMzNyAxNzEuMjExIDE1NS4yMThDMTcxLjA2NSAxNTUuMDk1IDE3MC45NCAxNTQuOTM2IDE3MC44MzYgMTU0Ljc0MUMxNzAuNzMyIDE1NC41NDYgMTcwLjY1MiAxNTQuMzA5IDE3MC41OTcgMTU0LjAzQzE3MC41NDMgMTUzLjc1MSAxNzAuNTE1IDE1My40MjYgMTcwLjUxNSAxNTMuMDUzVjE1Mi4xODZDMTcwLjUxNSAxNTEuNzIgMTcwLjU1NyAxNTEuMzMgMTcwLjY0IDE1MS4wMTRDMTcwLjcyNiAxNTAuNjk5IDE3MC44NDcgMTUwLjQ0NyAxNzEuMDA0IDE1MC4yNTdDMTcxLjE2IDE1MC4wNjQgMTcxLjM0NyAxNDkuOTI2IDE3MS41NjYgMTQ5Ljg0M0MxNzEuNzg4IDE0OS43NTkgMTcyLjAzNSAxNDkuNzE4IDE3Mi4zMDggMTQ5LjcxOEMxNzIuNTMgMTQ5LjcxOCAxNzIuNzMzIDE0OS43NDUgMTcyLjkxOCAxNDkuOEMxNzMuMTA1IDE0OS44NTIgMTczLjI3MiAxNDkuOTM2IDE3My40MTggMTUwLjA1M0MxNzMuNTY0IDE1MC4xNjggMTczLjY4NyAxNTAuMzIyIDE3My43ODkgMTUwLjUxNEMxNzMuODkzIDE1MC43MDUgMTczLjk3MiAxNTAuOTM4IDE3NC4wMjcgMTUxLjIxNEMxNzQuMDgyIDE1MS40OSAxNzQuMTA5IDE1MS44MTQgMTc0LjEwOSAxNTIuMTg2Wk0xNzMuMzgzIDE1My4xNzFWMTUyLjA2NUMxNzMuMzgzIDE1MS44MSAxNzMuMzY3IDE1MS41ODYgMTczLjMzNiAxNTEuMzkzQzE3My4zMDcgMTUxLjE5OCAxNzMuMjY0IDE1MS4wMzEgMTczLjIwNyAxNTAuODkzQzE3My4xNSAxNTAuNzU1IDE3My4wNzcgMTUwLjY0MyAxNzIuOTg4IDE1MC41NTdDMTcyLjkwMiAxNTAuNDcxIDE3Mi44MDIgMTUwLjQwOSAxNzIuNjg3IDE1MC4zN0MxNzIuNTc1IDE1MC4zMjggMTcyLjQ0OSAxNTAuMzA3IDE3Mi4zMDggMTUwLjMwN0MxNzIuMTM2IDE1MC4zMDcgMTcxLjk4NCAxNTAuMzQgMTcxLjg1MSAxNTAuNDA1QzE3MS43MTkgMTUwLjQ2OCAxNzEuNjA3IDE1MC41NjggMTcxLjUxNSAxNTAuNzA2QzE3MS40MjcgMTUwLjg0NCAxNzEuMzU5IDE1MS4wMjUgMTcxLjMxMiAxNTEuMjQ5QzE3MS4yNjUgMTUxLjQ3MyAxNzEuMjQyIDE1MS43NDUgMTcxLjI0MiAxNTIuMDY1VjE1My4xNzFDMTcxLjI0MiAxNTMuNDI2IDE3MS4yNTYgMTUzLjY1MSAxNzEuMjg1IDE1My44NDZDMTcxLjMxNiAxNTQuMDQyIDE3MS4zNjIgMTU0LjIxMSAxNzEuNDIyIDE1NC4zNTRDMTcxLjQ4MiAxNTQuNDk1IDE3MS41NTQgMTU0LjYxMSAxNzEuNjQgMTU0LjcwMkMxNzEuNzI2IDE1NC43OTMgMTcxLjgyNSAxNTQuODYxIDE3MS45MzcgMTU0LjkwNUMxNzIuMDUyIDE1NC45NDcgMTcyLjE3OCAxNTQuOTY4IDE3Mi4zMTYgMTU0Ljk2OEMxNzIuNDkzIDE1NC45NjggMTcyLjY0OCAxNTQuOTM0IDE3Mi43ODEgMTU0Ljg2NkMxNzIuOTE0IDE1NC43OTggMTczLjAyNSAxNTQuNjkzIDE3My4xMTMgMTU0LjU1QzE3My4yMDQgMTU0LjQwNCAxNzMuMjcyIDE1NC4yMTggMTczLjMxNiAxNTMuOTkxQzE3My4zNiAxNTMuNzYyIDE3My4zODMgMTUzLjQ4OCAxNzMuMzgzIDE1My4xNzFaTTE3NS44NCAxNTQuODc4SDE3NS45MTRDMTc2LjMzMSAxNTQuODc4IDE3Ni42NyAxNTQuODE5IDE3Ni45MyAxNTQuNzAyQzE3Ny4xOSAxNTQuNTg1IDE3Ny4zOTEgMTU0LjQyNyAxNzcuNTMyIDE1NC4yMjlDMTc3LjY3MiAxNTQuMDMxIDE3Ny43NjkgMTUzLjgwOSAxNzcuODIxIDE1My41NjFDMTc3Ljg3MyAxNTMuMzExIDE3Ny44OTkgMTUzLjA1NSAxNzcuODk5IDE1Mi43OTJWMTUxLjkyMUMxNzcuODk5IDE1MS42NjMgMTc3Ljg2OSAxNTEuNDM0IDE3Ny44MDkgMTUxLjIzM0MxNzcuNzUyIDE1MS4wMzMgMTc3LjY3MSAxNTAuODY1IDE3Ny41NjcgMTUwLjcyOUMxNzcuNDY1IDE1MC41OTQgMTc3LjM0OSAxNTAuNDkxIDE3Ny4yMTkgMTUwLjQyMUMxNzcuMDg5IDE1MC4zNSAxNzYuOTUxIDE1MC4zMTUgMTc2LjgwNSAxNTAuMzE1QzE3Ni42MzggMTUwLjMxNSAxNzYuNDg5IDE1MC4zNDkgMTc2LjM1NiAxNTAuNDE3QzE3Ni4yMjYgMTUwLjQ4MiAxNzYuMTE1IDE1MC41NzQgMTc2LjAyNCAxNTAuNjk0QzE3NS45MzUgMTUwLjgxNCAxNzUuODY4IDE1MC45NTUgMTc1LjgyMSAxNTEuMTE2QzE3NS43NzQgMTUxLjI3NyAxNzUuNzUgMTUxLjQ1MyAxNzUuNzUgMTUxLjY0M0MxNzUuNzUgMTUxLjgxMyAxNzUuNzcxIDE1MS45NzcgMTc1LjgxMyAxNTIuMTM1QzE3NS44NTUgMTUyLjI5NCAxNzUuOTE4IDE1Mi40MzggMTc2LjAwNCAxNTIuNTY1QzE3Ni4wOSAxNTIuNjkzIDE3Ni4xOTcgMTUyLjc5NCAxNzYuMzI1IDE1Mi44N0MxNzYuNDU1IDE1Mi45NDMgMTc2LjYwNyAxNTIuOTc5IDE3Ni43ODIgMTUyLjk3OUMxNzYuOTQzIDE1Mi45NzkgMTc3LjA5NCAxNTIuOTQ4IDE3Ny4yMzUgMTUyLjg4NUMxNzcuMzc4IDE1Mi44MiAxNzcuNTA0IDE1Mi43MzMgMTc3LjYxNCAxNTIuNjI0QzE3Ny43MjYgMTUyLjUxMiAxNzcuODE0IDE1Mi4zODUgMTc3Ljg3OSAxNTIuMjQ1QzE3Ny45NDcgMTUyLjEwNCAxNzcuOTg2IDE1MS45NTcgMTc3Ljk5NiAxNTEuODAzSDE3OC4zNEMxNzguMzQgMTUyLjAyIDE3OC4yOTcgMTUyLjIzMyAxNzguMjExIDE1Mi40NDRDMTc4LjEyOCAxNTIuNjUyIDE3OC4wMTEgMTUyLjg0MyAxNzcuODYgMTUzLjAxNEMxNzcuNzA5IDE1My4xODYgMTc3LjUzMiAxNTMuMzI0IDE3Ny4zMjkgMTUzLjQyOEMxNzcuMTI1IDE1My41MyAxNzYuOTA0IDE1My41ODEgMTc2LjY2NCAxNTMuNTgxQzE3Ni4zODMgMTUzLjU4MSAxNzYuMTQgMTUzLjUyNiAxNzUuOTM0IDE1My40MTdDMTc1LjcyOCAxNTMuMzA3IDE3NS41NTkgMTUzLjE2MiAxNzUuNDI2IDE1Mi45NzlDMTc1LjI5NiAxNTIuNzk3IDE3NS4xOTggMTUyLjU5NCAxNzUuMTMzIDE1Mi4zN0MxNzUuMDcxIDE1Mi4xNDMgMTc1LjAzOSAxNTEuOTE0IDE3NS4wMzkgMTUxLjY4MkMxNzUuMDM5IDE1MS40MTIgMTc1LjA3NyAxNTEuMTU4IDE3NS4xNTMgMTUwLjkyMUMxNzUuMjI4IDE1MC42ODQgMTc1LjM0IDE1MC40NzUgMTc1LjQ4OSAxNTAuMjk2QzE3NS42MzcgMTUwLjExMyAxNzUuODIxIDE0OS45NzEgMTc2LjAzOSAxNDkuODdDMTc2LjI2MSAxNDkuNzY4IDE3Ni41MTYgMTQ5LjcxOCAxNzYuODA1IDE0OS43MThDMTc3LjEzMSAxNDkuNzE4IDE3Ny40MDggMTQ5Ljc4MyAxNzcuNjM3IDE0OS45MTNDMTc3Ljg2NiAxNTAuMDQzIDE3OC4wNTIgMTUwLjIxOCAxNzguMTk2IDE1MC40MzZDMTc4LjM0MiAxNTAuNjU1IDE3OC40NDggMTUwLjkwMSAxNzguNTE2IDE1MS4xNzVDMTc4LjU4NCAxNTEuNDQ4IDE3OC42MTggMTUxLjcyOSAxNzguNjE4IDE1Mi4wMThWMTUyLjI4QzE3OC42MTggMTUyLjU3NCAxNzguNTk4IDE1Mi44NzQgMTc4LjU1OSAxNTMuMTc4QzE3OC41MjMgMTUzLjQ4MSAxNzguNDUxIDE1My43NyAxNzguMzQ0IDE1NC4wNDZDMTc4LjI0IDE1NC4zMjIgMTc4LjA4OCAxNTQuNTY5IDE3Ny44ODcgMTU0Ljc4OEMxNzcuNjg3IDE1NS4wMDQgMTc3LjQyNSAxNTUuMTc2IDE3Ny4xMDIgMTU1LjMwM0MxNzYuNzgyIDE1NS40MjggMTc2LjM4NiAxNTUuNDkxIDE3NS45MTQgMTU1LjQ5MUgxNzUuODRWMTU0Ljg3OFpNMTgyLjcxMyAxNDkuNzk2VjE1NS40ODNIMTgxLjk1OVYxNDkuNzk2SDE4Mi43MTNaTTE4NS4wOTUgMTUyLjM1NFYxNTIuOTcxSDE4Mi41NDhWMTUyLjM1NEgxODUuMDk1Wk0xODUuNDgyIDE0OS43OTZWMTUwLjQxM0gxODIuNTQ4VjE0OS43OTZIMTg1LjQ4MlpNMTg4LjAyMiAxNTUuNTYxQzE4Ny43MjcgMTU1LjU2MSAxODcuNDYgMTU1LjUxMiAxODcuMjIxIDE1NS40MTNDMTg2Ljk4NCAxNTUuMzExIDE4Ni43OCAxNTUuMTY5IDE4Ni42MDggMTU0Ljk4N0MxODYuNDM4IDE1NC44MDUgMTg2LjMwOCAxNTQuNTg5IDE4Ni4yMTcgMTU0LjMzOUMxODYuMTI2IDE1NC4wODkgMTg2LjA4IDE1My44MTUgMTg2LjA4IDE1My41MThWMTUzLjM1NEMxODYuMDggMTUzLjAxIDE4Ni4xMzEgMTUyLjcwNSAxODYuMjMzIDE1Mi40MzZDMTg2LjMzNCAxNTIuMTY1IDE4Ni40NzIgMTUxLjkzNiAxODYuNjQ3IDE1MS43NDlDMTg2LjgyMSAxNTEuNTYxIDE4Ny4wMTkgMTUxLjQxOSAxODcuMjQgMTUxLjMyM0MxODcuNDYyIDE1MS4yMjcgMTg3LjY5MSAxNTEuMTc4IDE4Ny45MjggMTUxLjE3OEMxODguMjMgMTUxLjE3OCAxODguNDkgMTUxLjIzMSAxODguNzA5IDE1MS4zMzVDMTg4LjkzMSAxNTEuNDM5IDE4OS4xMTIgMTUxLjU4NSAxODkuMjUyIDE1MS43NzJDMTg5LjM5MyAxNTEuOTU3IDE4OS40OTcgMTUyLjE3NiAxODkuNTY1IDE1Mi40MjhDMTg5LjYzMiAxNTIuNjc4IDE4OS42NjYgMTUyLjk1MiAxODkuNjY2IDE1My4yNDlWMTUzLjU3M0gxODYuNTFWMTUyLjk4M0gxODguOTQ0VjE1Mi45MjhDMTg4LjkzMyAxNTIuNzQxIDE4OC44OTQgMTUyLjU1OSAxODguODI2IDE1Mi4zODJDMTg4Ljc2MSAxNTIuMjA1IDE4OC42NTcgMTUyLjA1OSAxODguNTE0IDE1MS45NDRDMTg4LjM3MSAxNTEuODMgMTg4LjE3NSAxNTEuNzcyIDE4Ny45MjggMTUxLjc3MkMxODcuNzY0IDE1MS43NzIgMTg3LjYxMyAxNTEuODA3IDE4Ny40NzUgMTUxLjg3OEMxODcuMzM3IDE1MS45NDUgMTg3LjIxOCAxNTIuMDQ3IDE4Ny4xMTkgMTUyLjE4MkMxODcuMDIgMTUyLjMxOCAxODYuOTQ0IDE1Mi40ODMgMTg2Ljg4OSAxNTIuNjc4QzE4Ni44MzQgMTUyLjg3NCAxODYuODA3IDE1My4wOTkgMTg2LjgwNyAxNTMuMzU0VjE1My41MThDMTg2LjgwNyAxNTMuNzE5IDE4Ni44MzQgMTUzLjkwOCAxODYuODg5IDE1NC4wODVDMTg2Ljk0NiAxNTQuMjU5IDE4Ny4wMjggMTU0LjQxMyAxODcuMTM1IDE1NC41NDZDMTg3LjI0NCAxNTQuNjc4IDE4Ny4zNzYgMTU0Ljc4MyAxODcuNTMgMTU0Ljg1OEMxODcuNjg2IDE1NC45MzQgMTg3Ljg2MyAxNTQuOTcxIDE4OC4wNjEgMTU0Ljk3MUMxODguMzE2IDE1NC45NzEgMTg4LjUzMiAxNTQuOTE5IDE4OC43MDkgMTU0LjgxNUMxODguODg2IDE1NC43MTEgMTg5LjA0MSAxNTQuNTcyIDE4OS4xNzQgMTU0LjM5N0wxODkuNjEyIDE1NC43NDVDMTg5LjUyIDE1NC44ODMgMTg5LjQwNSAxNTUuMDE0IDE4OS4yNjQgMTU1LjEzOUMxODkuMTIzIDE1NS4yNjQgMTg4Ljk1IDE1NS4zNjYgMTg4Ljc0NCAxNTUuNDQ0QzE4OC41NDEgMTU1LjUyMiAxODguMyAxNTUuNTYxIDE4OC4wMjIgMTU1LjU2MVpNMTkwLjU4OSAxNDkuNDgzSDE5MS4zMTVWMTU0LjY2M0wxOTEuMjUzIDE1NS40ODNIMTkwLjU4OVYxNDkuNDgzWk0xOTQuMTcxIDE1My4zMzVWMTUzLjQxN0MxOTQuMTcxIDE1My43MjQgMTk0LjEzNCAxNTQuMDA5IDE5NC4wNjEgMTU0LjI3MkMxOTMuOTg4IDE1NC41MzMgMTkzLjg4MiAxNTQuNzU5IDE5My43NDEgMTU0Ljk1MkMxOTMuNiAxNTUuMTQ1IDE5My40MjkgMTU1LjI5NCAxOTMuMjI1IDE1NS40MDFDMTkzLjAyMiAxNTUuNTA4IDE5Mi43ODkgMTU1LjU2MSAxOTIuNTI2IDE1NS41NjFDMTkyLjI1OCAxNTUuNTYxIDE5Mi4wMjIgMTU1LjUxNiAxOTEuODE5IDE1NS40MjVDMTkxLjYxOSAxNTUuMzMxIDE5MS40NDkgMTU1LjE5NyAxOTEuMzExIDE1NS4wMjJDMTkxLjE3MyAxNTQuODQ4IDE5MS4wNjMgMTU0LjYzNyAxOTAuOTc5IDE1NC4zODlDMTkwLjg5OSAxNTQuMTQyIDE5MC44NDMgMTUzLjg2MyAxOTAuODExIDE1My41NTNWMTUzLjE5NEMxOTAuODQzIDE1Mi44ODIgMTkwLjg5OSAxNTIuNjAyIDE5MC45NzkgMTUyLjM1NEMxOTEuMDYzIDE1Mi4xMDcgMTkxLjE3MyAxNTEuODk2IDE5MS4zMTEgMTUxLjcyMUMxOTEuNDQ5IDE1MS41NDQgMTkxLjYxOSAxNTEuNDEgMTkxLjgxOSAxNTEuMzE5QzE5Mi4wMiAxNTEuMjI1IDE5Mi4yNTMgMTUxLjE3OCAxOTIuNTE4IDE1MS4xNzhDMTkyLjc4NCAxNTEuMTc4IDE5My4wMiAxNTEuMjMxIDE5My4yMjUgMTUxLjMzNUMxOTMuNDMxIDE1MS40MzYgMTkzLjYwMyAxNTEuNTgyIDE5My43NDEgMTUxLjc3MkMxOTMuODgyIDE1MS45NjIgMTkzLjk4OCAxNTIuMTkgMTk0LjA2MSAxNTIuNDU2QzE5NC4xMzQgMTUyLjcxOSAxOTQuMTcxIDE1My4wMTIgMTk0LjE3MSAxNTMuMzM1Wk0xOTMuNDQ0IDE1My40MTdWMTUzLjMzNUMxOTMuNDQ0IDE1My4xMjQgMTkzLjQyNSAxNTIuOTI2IDE5My4zODYgMTUyLjc0MUMxOTMuMzQ3IDE1Mi41NTMgMTkzLjI4NCAxNTIuMzg5IDE5My4xOTggMTUyLjI0OUMxOTMuMTEyIDE1Mi4xMDYgMTkyLjk5OSAxNTEuOTk0IDE5Mi44NTggMTUxLjkxM0MxOTIuNzE4IDE1MS44MyAxOTIuNTQ0IDE1MS43ODggMTkyLjMzOSAxNTEuNzg4QzE5Mi4xNTYgMTUxLjc4OCAxOTEuOTk4IDE1MS44MTkgMTkxLjg2MiAxNTEuODgyQzE5MS43MjkgMTUxLjk0NCAxOTEuNjE2IDE1Mi4wMjkgMTkxLjUyMiAxNTIuMTM1QzE5MS40MjkgMTUyLjI0IDE5MS4zNTIgMTUyLjM1OSAxOTEuMjkyIDE1Mi40OTVDMTkxLjIzNSAxNTIuNjI4IDE5MS4xOTIgMTUyLjc2NiAxOTEuMTYzIDE1Mi45MDlWMTUzLjg1QzE5MS4yMDUgMTU0LjAzMyAxOTEuMjcyIDE1NC4yMDggMTkxLjM2NiAxNTQuMzc4QzE5MS40NjIgMTU0LjU0NCAxOTEuNTkgMTU0LjY4MSAxOTEuNzQ5IDE1NC43ODhDMTkxLjkxIDE1NC44OTUgMTkyLjExIDE1NC45NDggMTkyLjM0NyAxNTQuOTQ4QzE5Mi41NDIgMTU0Ljk0OCAxOTIuNzA4IDE1NC45MDkgMTkyLjg0NyAxNTQuODMxQzE5Mi45ODcgMTU0Ljc1IDE5My4xIDE1NC42MzkgMTkzLjE4NiAxNTQuNDk5QzE5My4yNzUgMTU0LjM1OCAxOTMuMzQgMTU0LjE5NSAxOTMuMzgyIDE1NC4wMUMxOTMuNDIzIDE1My44MjYgMTkzLjQ0NCAxNTMuNjI4IDE5My40NDQgMTUzLjQxN1oiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPC9nPgo8Y2lyY2xlIGN4PSI0MSIgY3k9IjczLjE2MTEiIHI9IjMiIGZpbGw9IiMyMTk2RjMiLz4KPGNpcmNsZSBjeD0iNTkiIGN5PSI1MyIgcj0iMyIgZmlsbD0iIzIxOTZGMyIvPgo8Y2lyY2xlIGN4PSI3NyIgY3k9IjgxLjE2MTEiIHI9IjMiIGZpbGw9IiM0Q0FGNTAiLz4KPGNpcmNsZSBjeD0iOTUiIGN5PSI4NyIgcj0iMyIgZmlsbD0iIzRDQUY1MCIvPgo8Y2lyY2xlIGN4PSI0MSIgY3k9IjExMy4xNjEiIHI9IjMiIGZpbGw9IiM0Q0FGNTAiLz4KPGNpcmNsZSBjeD0iNTkiIGN5PSI5MyIgcj0iMyIgZmlsbD0iIzRDQUY1MCIvPgo8Y2lyY2xlIGN4PSIxMTIiIGN5PSI5MC4xNjExIiByPSIzIiBmaWxsPSIjNENBRjUwIi8+CjxjaXJjbGUgY3g9IjE0OCIgY3k9IjgxLjE2MTEiIHI9IjMiIGZpbGw9IiM0Q0FGNTAiLz4KPGNpcmNsZSBjeD0iMTMwIiBjeT0iMTAzIiByPSIzIiBmaWxsPSIjNENBRjUwIi8+CjxjaXJjbGUgY3g9IjE4MyIgY3k9IjEyMS4xNjEiIHI9IjMiIGZpbGw9IiM0Q0FGNTAiLz4KPGNpcmNsZSBjeD0iMTY2IiBjeT0iMTA5IiByPSIzIiBmaWxsPSIjNENBRjUwIi8+CjxjaXJjbGUgY3g9Ijc3IiBjeT0iMzciIHI9IjMiIGZpbGw9IiMyMTk2RjMiLz4KPGNpcmNsZSBjeD0iOTUiIGN5PSIzOSIgcj0iMyIgZmlsbD0iIzIxOTZGMyIvPgo8Y2lyY2xlIGN4PSIxMTIiIGN5PSIxOS4xNjExIiByPSIzIiBmaWxsPSIjMjE5NkYzIi8+CjxjaXJjbGUgY3g9IjE0OCIgY3k9IjM3IiByPSIzIiBmaWxsPSIjMjE5NkYzIi8+CjxjaXJjbGUgY3g9IjEzMCIgY3k9IjQ0IiByPSIzIiBmaWxsPSIjMjE5NkYzIi8+CjxjaXJjbGUgY3g9IjE4MyIgY3k9IjIzIiByPSIzIiBmaWxsPSIjMjE5NkYzIi8+CjxjaXJjbGUgY3g9IjE2NiIgY3k9IjQ0IiByPSIzIiBmaWxsPSIjMjE5NkYzIi8+CjxjaXJjbGUgY3g9Ijc3IiBjeT0iNzIiIHI9IjMiIGZpbGw9IiNGRkMxMDciLz4KPGNpcmNsZSBjeD0iOTUiIGN5PSI1NSIgcj0iMyIgZmlsbD0iI0ZGQzEwNyIvPgo8Y2lyY2xlIGN4PSIxMTIiIGN5PSI2Ni4xNjExIiByPSIzIiBmaWxsPSIjRkZDMTA3Ii8+CjxjaXJjbGUgY3g9IjE0OCIgY3k9IjUxLjE2MTEiIHI9IjMiIGZpbGw9IiNGRkMxMDciLz4KPGNpcmNsZSBjeD0iMTMwIiBjeT0iNzIiIHI9IjMiIGZpbGw9IiNGRkMxMDciLz4KPGNpcmNsZSBjeD0iMTgzIiBjeT0iODEiIHI9IjMiIGZpbGw9IiNGRkMxMDciLz4KPGNpcmNsZSBjeD0iMTY2IiBjeT0iNTkiIHI9IjMiIGZpbGw9IiNGRkMxMDciLz4KPGNpcmNsZSBjeD0iMjgiIGN5PSI4OSIgcj0iMyIgZmlsbD0iI0ZGQzEwNyIvPgo8Y2lyY2xlIGN4PSI0MSIgY3k9Ijk3IiByPSIzIiBmaWxsPSIjRkZDMTA3Ii8+CjxjaXJjbGUgY3g9IjU5IiBjeT0iNjguNTc4MSIgcj0iMyIgZmlsbD0iI0ZGQzEwNyIvPgo8Y2lyY2xlIGN4PSIyOCIgY3k9IjExOC4xNjEiIHI9IjMiIGZpbGw9IiMyMTk2RjMiLz4KPGNpcmNsZSBjeD0iMjgiIGN5PSIxMjkuNDg5IiByPSIzIiBmaWxsPSIjNENBRjUwIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNDE4Ml8xMTE5MyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8Y2xpcFBhdGggaWQ9ImNsaXAxXzQxODJfMTExOTMiPgo8cmVjdCB3aWR0aD0iMzUuNCIgaGVpZ2h0PSIxMCIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIzIDE0Ni4xNjEpIi8+CjwvY2xpcFBhdGg+CjxjbGlwUGF0aCBpZD0iY2xpcDJfNDE4Ml8xMTE5MyI+CjxyZWN0IHdpZHRoPSIzNS40IiBoZWlnaHQ9IjEwLjMyMiIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDU4LjM5OTkgMTQ2LjE2MSkiLz4KPC9jbGlwUGF0aD4KPGNsaXBQYXRoIGlkPSJjbGlwM180MTgyXzExMTkzIj4KPHJlY3Qgd2lkdGg9IjM1LjQiIGhlaWdodD0iMTAuMzIyIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOTMuOCAxNDYuMTYxKSIvPgo8L2NsaXBQYXRoPgo8Y2xpcFBhdGggaWQ9ImNsaXA0XzQxODJfMTExOTMiPgo8cmVjdCB3aWR0aD0iMzUuNCIgaGVpZ2h0PSIxMC4zMjIiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMjkuMiAxNDYuMTYxKSIvPgo8L2NsaXBQYXRoPgo8Y2xpcFBhdGggaWQ9ImNsaXA1XzQxODJfMTExOTMiPgo8cmVjdCB3aWR0aD0iMzUuNCIgaGVpZ2h0PSIxMC4zMjIiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxNjQuNiAxNDYuMTYxKSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=", + "description": "Displays changes to time-series data over time—for example, temperature or humidity readings.", + "descriptor": { + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n chartType: 'point',\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings('point'),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n", + "settingsSchema": "{}", + "dataKeySettingsSchema": "{}", + "latestDataKeySettingsSchema": "{}", + "settingsDirective": "tb-time-series-chart-widget-settings", + "dataKeySettingsDirective": "tb-time-series-chart-key-settings", + "latestDataKeySettingsDirective": "", + "hasBasicMode": true, + "basicModeDirective": "tb-time-series-chart-basic-config", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":false,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":true,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"circle\",\"pointSize\":8,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":false,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":true,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"circle\",\"pointSize\":8,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":\"dd MMM yyyy HH:mm:ss\",\"lastUpdateAgo\":false,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Point chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + }, + "tags": [ + "chart", + "time series", + "time-series", + "point", + "point chart" + ] +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/time_series_chart.json b/application/src/main/data/json/system/widget_types/time_series_chart.json index 7d2fff170f..688cce76fb 100644 --- a/application/src/main/data/json/system/widget_types/time_series_chart.json +++ b/application/src/main/data/json/system/widget_types/time_series_chart.json @@ -10,12 +10,12 @@ "sizeY": 5, "resources": [], "templateHtml": "\n", - "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", + "templateCss": "", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings(),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", "latestDataKeySettingsSchema": "{}", - "settingsDirective": "", + "settingsDirective": "tb-time-series-chart-widget-settings", "dataKeySettingsDirective": "tb-time-series-chart-key-settings", "latestDataKeySettingsDirective": "", "hasBasicMode": true, @@ -29,6 +29,8 @@ "line", "line chart", "bar", - "bar chart" + "bar chart", + "point", + "point chart" ] } \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/timeseries_bar_chart.json b/application/src/main/data/json/system/widget_types/timeseries_bar_chart.json index ff9c7971f0..ebb0d2680d 100644 --- a/application/src/main/data/json/system/widget_types/timeseries_bar_chart.json +++ b/application/src/main/data/json/system/widget_types/timeseries_bar_chart.json @@ -1,8 +1,8 @@ { "fqn": "charts.timeseries_bars_flot", "name": "Timeseries Bar Chart", - "deprecated": false, - "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAABOFBMVEUAAAA3oPR3d3d6enp8fHyBgYGDg4OGhoaNjY2RkZGSkpKTk5OUlJSVlZWWlpaXl5eYmJiZmZmampqcnJydnZ2enp6goKChoaGioqKjo6OkpKSlpaWmpqanp6eoqKipqamqqqqrq6usrKytra2urq6wsLCxsbGysrK0tLS1tbW2tra3t7e4uLi5ubm6urq8vLy9vb2+vr6/v7/AwMDBwcHDw8PExMTHx8fIyMjJycnLy8vNzc3Ozs7Pz8/S0tLT09PU1NTV1dXW1tbX19fZ2dna2trb29vc3Nzd3d3e3t7f39/h4eHi4uLj4+Pk5OTl5eXm5ubn5+fo6Ojq6urr6+vs7Ozt7e3u7u7v7+/w8PDx8fH09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz9/f3+/v7/xx////8KXFhiAAAAAWJLR0RnW9PpswAAAvtJREFUeNrt3GtXElEUBuDpZlAqylVHCyMwyi4SpkaWAl4qk8wwUhJlmOn9//+gLzbKbc6AAm5991p82WvPOedZZw6zYM3aGupCQ4tYkZDSsKDrocNVvz8jHQKYY1ZiT/6OAJllRPSpsnzIqIG9SjYmHpKfAzI4CIuHjJeBtyHfZwCatiI1/m+BYV2Dw35NniOEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQUp/6Wx+DgbRbxOAhEb/fXJ98akiH1AIAHtSWlqRDSvdDGcOHnYR0SPV7zVsaQyHeO0jTJapEV5C9bUz9fIjsvHRIefhRxHqpjxzA3ftajXOudHGJOtHV+1rV04/wHenDc2QQkFbLJISQwUDaP90IIYQQQgghpAvIxRO9gFxG4lZ9XBFIx6sihJD+QVRjEEIIIYQQcoMgahkhhBDSe0iLX3ydQdSLIKR9ghCJkIYEIYQQIhPS8d+AhPQboppFw9HMxBrs/lqCIbObpgezP67YjnQ8iwagHDzrryUZUgsUz/prCYZY05vn+msJhiwM6XrR7q/V9OJU4xQuEpcxRsezNPbXEn3Yzz9Hulg3IYT0C3LxxI2CNCQIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQlpA7P5a0iF2fy3hkLP+WsIhVbu/lvRby+6vJR1i99eSG6ffu1XHxlRNcfWq3I0iIK4tJK2nXVyV0lOqEmvjGWAtvnCu+jMzkQWMiOlYdRSd3MAH/UnVPWR/ApFfSkcpiOBvRc3XtAeYW1ZUJbZMD5C8azhWLX4xvZVR692Se0huHm9ySogxkhs3lFVewPM4WlFUlUMoJEYUox1+jAFIbLuHrKaQWlMu8TicDp+4gdw7/qS4t2qBohk4UUF2nieBfLyDM/ItgXhBucT113i14QbixW7M+SRNb6EQ0u8kHavyZQxj2/kgNUCsYDRoqXfEF/Mdu4G8D43uOtakh3R9H1DsyKZvOmneDjt+D/0DTzolrPMHmggAAAAASUVORK5CYII=", + "deprecated": true, + "image": "tb-image:dGltZXNlcmllc19iYXJfY2hhcnRfc3lzdGVtX3dpZGdldF9pbWFnZS5wbmc=:IlRpbWVzZXJpZXMgQmFyIENoYXJ0IiBzeXN0ZW0gd2lkZ2V0IGltYWdl;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAABOFBMVEUAAAA3oPR3d3d6enp8fHyBgYGDg4OGhoaNjY2RkZGSkpKTk5OUlJSVlZWWlpaXl5eYmJiZmZmampqcnJydnZ2enp6goKChoaGioqKjo6OkpKSlpaWmpqanp6eoqKipqamqqqqrq6usrKytra2urq6wsLCxsbGysrK0tLS1tbW2tra3t7e4uLi5ubm6urq8vLy9vb2+vr6/v7/AwMDBwcHDw8PExMTHx8fIyMjJycnLy8vNzc3Ozs7Pz8/S0tLT09PU1NTV1dXW1tbX19fZ2dna2trb29vc3Nzd3d3e3t7f39/h4eHi4uLj4+Pk5OTl5eXm5ubn5+fo6Ojq6urr6+vs7Ozt7e3u7u7v7+/w8PDx8fH09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz9/f3+/v7/xx////8KXFhiAAAAAWJLR0RnW9PpswAAAvtJREFUeNrt3GtXElEUBuDpZlAqylVHCyMwyi4SpkaWAl4qk8wwUhJlmOn9//+gLzbKbc6AAm5991p82WvPOedZZw6zYM3aGupCQ4tYkZDSsKDrocNVvz8jHQKYY1ZiT/6OAJllRPSpsnzIqIG9SjYmHpKfAzI4CIuHjJeBtyHfZwCatiI1/m+BYV2Dw35NniOEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQUp/6Wx+DgbRbxOAhEb/fXJ98akiH1AIAHtSWlqRDSvdDGcOHnYR0SPV7zVsaQyHeO0jTJapEV5C9bUz9fIjsvHRIefhRxHqpjxzA3ftajXOudHGJOtHV+1rV04/wHenDc2QQkFbLJISQwUDaP90IIYQQQgghpAvIxRO9gFxG4lZ9XBFIx6sihJD+QVRjEEIIIYQQcoMgahkhhBDSe0iLX3ydQdSLIKR9ghCJkIYEIYQQIhPS8d+AhPQboppFw9HMxBrs/lqCIbObpgezP67YjnQ8iwagHDzrryUZUgsUz/prCYZY05vn+msJhiwM6XrR7q/V9OJU4xQuEpcxRsezNPbXEn3Yzz9Hulg3IYT0C3LxxI2CNCQIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQlpA7P5a0iF2fy3hkLP+WsIhVbu/lvRby+6vJR1i99eSG6ffu1XHxlRNcfWq3I0iIK4tJK2nXVyV0lOqEmvjGWAtvnCu+jMzkQWMiOlYdRSd3MAH/UnVPWR/ApFfSkcpiOBvRc3XtAeYW1ZUJbZMD5C8azhWLX4xvZVR692Se0huHm9ySogxkhs3lFVewPM4WlFUlUMoJEYUox1+jAFIbLuHrKaQWlMu8TicDp+4gdw7/qS4t2qBohk4UUF2nieBfLyDM/ItgXhBucT113i14QbixW7M+SRNb6EQ0u8kHavyZQxj2/kgNUCsYDRoqXfEF/Mdu4G8D43uOtakh3R9H1DsyKZvOmneDjt+D/0DTzolrPMHmggAAAAASUVORK5CYII=", "description": "Displays changes to time-series data over time—for example, daily water consumption for the last month.", "descriptor": { "type": "timeseries", @@ -21,6 +21,5 @@ "basicModeDirective": "tb-flot-basic-config", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"stack\":true,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":null,\"max\":null,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"\"},\"defaultBarWidth\":600,\"barAlignment\":\"left\",\"comparisonEnabled\":false,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"bottom\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false},\"title\":\"Timeseries Bar Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"configMode\":\"basic\",\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\"}" }, - "externalId": null, "tags": null } \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/timeseries_line_chart.json b/application/src/main/data/json/system/widget_types/timeseries_line_chart.json index e83b213ae0..87f7330657 100644 --- a/application/src/main/data/json/system/widget_types/timeseries_line_chart.json +++ b/application/src/main/data/json/system/widget_types/timeseries_line_chart.json @@ -1,8 +1,8 @@ { "fqn": "charts.basic_timeseries", "name": "Timeseries Line Chart", - "deprecated": false, - "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAXk0lEQVR42u2deXwb1bXH+Y+lfY/utK98WkogZSk7LcujBF4pAT6lLaWvlBZeCrTw4AGFvrbQkPYRwlIWfyCr7WA7XrN4wzZx7DjxIu+2vEteZHmTJdmOd8faR9K838y1FUWWZVmaGQM585mPPmNZR/fOvV+de+455849iw/7OOuss/gojpaWFhI8cwQFVlQqVbp4JCUlzc3NaTSaveKhVqsJLBKMHCx2eDyeuLg4r9ebl5c3NjZGGosEpQEL+qm+vh4XKSkp0F4ZGRkWi4XAIsFowdqzZw/HcbiAurLb7Z2dndnZ2QQWCUYFVnd3d1FRkU914XV2djY5OZnAIsGowIqPj/cNfOXl5bDiY2Nj+/v7/anC0UIHHeEdwZUQxkRY8WS8k6AENhb5sUiQwCJBAosancAisEiQwCJBAosancAisEiQwCJBAosancAisEiQwCJBAosancAisEiQwCJBAosECSwCiwQJLBIksEiQwCKwSJDAIkECiwQJLAKLBAksEiSwSJDAIrBIkMAiQQKLBAksAosECSwSJLBIkMAisEiQwCJBAuuzK5jV7SKwCCyJBd+pd35nz7x6xE1gEViSCb7XIFCF84lCO4FFYEkj+IFaoOqSuPn1cfMX75nvnvQQWARWtIKvFg6CqnWx8+83Op8ptuP6hWN2AovAikowsd3FqIppcDaOuIv7uUtj5y+JnR+a9RBYBFaEgvs6BKow9r1V6wBV7PzdYRve3KJyEFgEViSC6VrXxSJVL+YN+6jC+ZHOBQV2Wfz8uNW75lXVTni2q50/zbLWGN0E1qdAEP4q0AOqXq92ZKi6/cHC+auPBKX1dp1zTarq9vBweWytctyaamETVZy3pFim7V6FwMKOX2yPQmwup9Pp0tLScnJy2J5NBFaII7uHA1Xora3Vwgi4FKz9ncIQeXWC5aTTq1hVwU1OD/fMUfv3PjzF0w1JlieL7HcftOIaF0qA5Xa7ExISfG/t2LED79SJB4EV4ijs4+BWQD+9orIzjJaChfMnWUJfxrY45a6qYc6T1O56pMB2ady8j6cfpllfOOZI1bgaxMpgSnGVSNtbRf2ygzU9Pf3mm29iYybs+wUthe2Z8KbRaMSOmATWcgd6iFH1t3K7j6GgYCWIs8Ub91nsnPRVbWpu0Yx7Pmh03i/iy851cfP3HLS+WuU40s8trc/OJsHTtj52ric8H1vkYLlcLrPZDC21fft2QIZ9VvGmyWTKzc0lsIIeRwc4phX+Umb377OgYOH80X5BSaRpXBJW1eXhXy53XL131scTBtxNH9t2qJ2Vw1zQavjOxw8LPrb7DlkdbjnBws6XBoMBF9hVdXx8fOfOnbjGRpilpaUE1tKjwuD+brzQkc+V2AM6bDmwdoju+NvTrW6PZFVl4UicN6dY4YyFF63O5A7Nk+8Eed9PnIHstmqHjGBhp8Jdu3YdOHAAJjx2kysuLsbWvbt378ZemP5U0X6FOJLKdevjTqJLHs0cAUZhnukV3awjPyjRS1KNA6qudXtOrgMZhYPhV8P/fLe4/5LYk5jMJpTp5N2vEANi0GvSWL4DHqDL9wpKAhoiqCZYTmPhfLvOAcG7D1g93miranV578wQLKo/lTpClBj6hOAWlTAg3rDPsqKbjfxYMgo2jbqvFOdTyFloWL63lutIjFPf3yeIlw5xUVZ1c4XA6J0ZllqzOxqw6s3un4omPywzL4G1JoLNY27mE3rssB390bh6sHD+X6UAxC9ybNFUtXLYjfELqRPZ3dyKJYYGC6/HBrlrE4X7golGYCkniN9xy5h7W42DUYXYXwiqVuxmmMyYuOF7GszuyKoKz+dNycI3/KPSEU6J4VR1T4sTpGI60jnhIbBkF+ydEpxDd2Sccg49nG+rN4XbW8udfy1ziGrPFllVnz0qWEX3HrL6+I4eLJxPiRk+Pz5gtXEEljyCjKd/3zd9KhKyzwJT3ee5jhKsMgN3+V5B5cCrudqq5uo4CF7xoQUe//BLDKeqNSb3hnSLtIkYBJZwwAeNJKr/2G/1j6yBpzRtWDytqpuZ1nm+xL6qqo7Me68RjaF36hyrLTGcqsJiWy+6fIsHuGhatc7sZhH3Mxos/bQHmcQYAnw8wZLFuPDGkYHQtlQ0YBUtJgAitBdmVWHq/bZAyJJArkQEJYZZ1deqhWH6+iTLqCWSePngrOepIjtrRrTemQWW082PznvbTgjxsnsOWf2DIU8ese9rdzGeJOytEBGVVyocYd5jkhhtBPTHBzn5wIJu/kWugO9v8m2nnG1el3CGbNVZh/f1GgdTeBjoETxFVFResMb82JcbLLTFpM0LIwnauEDPJXe4/ppvQOfhZ/TLXBuGuasSTuWQsBNx/scL7QltrgCrXG6wPupdSAA8seiZDHGPUKvMK4vpW8QlhikIcK9LspyWi9H7DF/1Rb7jXn74XX6+GaD5V5Xz8Cka1/WiCO4IjYkoaqO46E1esKC64VeUFawdTc6NB63wPbK8qNAnxiAYT3ekWx4rtMe3LhtckxssnL/OE3TDPxcTAJe7R/QcS1tAnylT1Q/bhGxYhNih13nj+7zq7NPO2gtnau/jRxJ4x3DZEHfXolWK1KDM7lPaVF6wgD8zC+QDyzzvZfFgdkIbIyKLjJEHc23wJm/KHEHqyHv1Tnj/cns4mKVhWk4KgHWgU2gcuMfmHN4Q9xgjLlG8KcW6XMKCHFV9tkQYqV/K+YivPI9XncOP7uPHMviex/m6i/wh6z16TVLu83/Oyo1tnAr4BhnBwsDkM2KqZUuy/psY3IBNUAJoTGvAx3KC7QbdaMfLjSOh0ldYOGVXk3O5e2wdcyPlC5oYzg4lfwPwPjyRqZ4v+4oAkOGN0zy009pDha+XHb7fWv4lH2Heys+fbLzTpHmta6BGbXbICxZSY9m46wtiSA4WZlXQ2Ehqy+91rZXiWU5wuvnXaPHxtqcbR5ZlImkxARBuyaX3iDeZ++OPxxwKK9cW45i1ej3qX1DwcIF+wXJH5hYML2anAvfnik826yrN2q3zDbd7Kz/ng8xd9dWZpgd554gsYCEHbUO60Chv1DpYKAqRV8nB+suCF9u+hiNaUMHOgXphBBEberLlUfYjDnqyDHQopKX3+HcxsIjE4hqToqO22mw/qb4LNR+tvPmKuBOYLxtPemDVsA7FeVfKBOwKf5Em01xP37GRjs2W+ptx457K83mPTRawEtuE3yKcubBpYOWwZEUk0UoIFrwm+N1cFj+tbXt/qvm3zaaZTwxYHH7E6Jjp5l+5q74iXjykHrEF/fwuMUv4tjSLurk1aKQ5q4dT2BycaP096uysubh12PSQOMO4cnFFBiY90LKhS2wbNuj6imUZCi0u741ifgimXWzAZn/uOq6XECwsXX8k5bC5/FqmGGz117cZBj4JYOl7s1AfV/WFYL1zoJGr/gb+nG26v8k8v/TzcB1BJ6Fx3i/p890azHm2WmuLyq7wPGO4813U1lP5hc6BBvxZbuBYqg/0FhRE+E4+WcBiE5n7Mq2+Yt6sFZTWbUlTbq80YA2NaisL72VI2WuvstddLfRlzbfRkWsL1gGVxl57JSoz2B3P3tEONnPV38Q7c+q7m0yzS0XeqRca5/bkKV/bPF8SGGlWBqxefQGvOhdjmb43x/cmVnj/6bjDf066NmDBRckyTDK0tq6BamPnWzp9Yb3RCS8A3sQqvGjB4qZ4/YselWAtWisuMHR9AJug2TQ517hR/Kl90b9RlAerSfV3QX3WXeNvV2mGNK5qYaKOIbLZOLVcAiBSo3B/Bb0c81/DalbSM6IZbGcDt0n7RvQlSg9WjKpn84Hddccecldd4JspQJc01Lx8R0IH1L7LEylYXk7wy9UKv35Pxbl5BY+W6Uf8TE7HifbnxOLOMWtfWxOwwLdDJdx1r/7jgH+1G/odtZcJP4b6H2DOFZgAKJqhD+TYEKi4TpzrvF3nUGwCK04Dxx11l6N6Uy0Ph3aRKAuWtZs3x/KdD3lqvuHvQLPVXTve+hSGqgVvh+qc+iN31jan8B7rqsGaOsqrr2Hf01V+z8bExv85GsT+MHa+Iyrzs1EuNJnCYGFahKIxpVrGqh1ko6St/rrWYXNAAuCV8XPChEv0LyAAFV6JLoyz0d+j2mydb9ggKNTG25rMFkkaJwqwnKP8eLYQSKq/xB+mE6XfUpc+NNi9t214yFdM56AaHh1nxaJLrfqrfM9j/HSpGLZf6bD1CjEEJth45XBfFkt3LB4I/sPS9+ZiQBRsmsaNWapGxcDCbEgs95wAO8//bB0esdbdgLo5ar/bbujz/9cT2WZfOLxkgFsRqT5dJgZcfNWM6qqBnlS12RnxPeJHKM42LmobHpaqcVYP1vghvvdpvvHy0+JHdd/muzeN6JM2JHRihlzUH7xdDqmaYvLjmotuOyWovlaIRrnGg5fHTfN9/8tXfl74ZM3XhE96nQiZregzxIwGg6/AlurydoNeGbAmWzYJNrvqgdAfazGesNbfJMznay9tH+o9FaEr07EEQKxADCEO7YJpAbhcdHl/zjc49OvSQzjMlrtHUcef7an6knawRcLGWT1Yi34/vvrL/hFv/PcRMWEoRJejQvEtwoTxgfQ218Brp1QdAlL4Kug/X3rGgjl1ofiBc6dqf8Y7T+DtjnHPxaJhuzR7ZIn+GGb9h9l+d79KbrDQK6gnHIN5qrKVTTHjFHN0uWq+BZPZVyIidI8V2kMitRfuJdZoMInwJzyTtap3HbVXLLxZu97QtT2c4YyVqNMf8YrRwD7dIWkbZ/Vg9T7LG2NEmE4zwrHgjoVUkXobukIbDy4GyEDP5Me89kHeFxCA5ht4hR/P4tXXLbzTvpG3dPhmhYgrC4vpjjvCM6VnzKofiVPF8/t0+2UFC64EwVXd8XKYgvA7zKl/LHL/TaYqIIiEk4pgrddsmsbkmvksBPO/7kaMg75IEQRhTQ707LPXfo99APAZunYEdZv5n4WqfE/Vl/F5fLnkjSON8Q59hRmN/0NXQlQI4Qt8Elm2M45FA8s5xpt28k03nja8Qp+NpfvPCptH3cwLHILdwEcIqbRj7S+yqSIs6xXnO5GBhTmggEjV11tM4+ELQgMJMTUhuHZB10BtUEFM1jDD5aoWpkSYUYpIcctU1QVHlBhUOZshC1lMVJcbkedV68Rp4G/CbxalwTrcx7Ek8SpjWBVij/WBHzXwi+bqeN1TfP13+OH3eE9gdiVSGCCFVS6rbQJD105R4SNstwndKS1YsJph3+DLMQatlkjEeRDtEdn6yjFVeoCZD/3HNAqbaSIYF15VuV59nqXh1gW8qr4OvEB8ANNsLJ5v+GGY4+YagIWnXLBJsv+zN0NXCKlIF4u6ZzK89bcAC4+lY0NtuYGLoAkQvXJXf401ZatxVEKwYOiIxs1lDNnVjqHgcqL1CYEA1fmoJPNKQMuyia1Y4Q09faURVLWrv2qm6efsSwAovrPVaFycBj6JNy2qi4CvTDMbCcDC4zfR5bemWle15u5BMbcaidJhgsXyLf2fRLXaJtAOtTlr1jGzt8PQJQlYMJXY9FMcoSJ2gLngixGneP8K/6RX9S9syjLd/J9w00TZzd39lbNNP1nAq/ILY20vjHS8wlCDjSWfLyZasJAzjwdaosuxNmFVFcJiI5b0jaUNK5abVNbDHDwrPvYpdBMgXG9puIUNEJGpgYATowzTgj4zJVJ3JdetenzRg3DeZMt/aQY7JOxmuNamm355akavOrdXny+r9zhasFjWx10HLBEs5nw4P9xHVW9MnQgz1L9iEzSZT0ITMA8Qpk7RtB0whftHiAQMVEsSYDFrXp1o/QNST2WawAJWIAtw4buSO94VFVhY9MOS+LBqKoIKIe0TyZ9IAfVfXrf0QMY+m0WuVl0t3wQucTgQfr4TrY8HtV7Dabvxtv9mSVefkJzVsCMEQwqUGBVYsJDE0Kk14gptEp+vj0TQEIX+PFv4DMK00jYBxgIk0bI5fASJXJohLXQecr07DD2fLrCUEYwcLCyWxWI3TO4OdkXuHCrsc7E1wVg6F7RE+AwF62rvbLVR+iaA15tlHHDV/xZgcq3YdkjcgyCmWp9tPtYArD+XCjE7hHGirBDWH7OHeQb1u94nrvN5/iOjTE0AB8+s+j5mchk73w5TsKevjDmflnouCKyowOqbEfLNcX4cXjJaiAohSQGpCtB8Sx/OVCj6XZHWnFLRI18TwJMkprucwzyoLBISUtDFvNv+IBJY0oD1e1HNPF1sl6RCbHnkH47Ygy5LRFqzAm3X13uQTfEQuoaLMoQgclRYPA4TTAJLSrCwipKlGBwblCbGhJVhLGMEz87zlZUvZuj+IDnax2yuIkNhqI2loyDKdlyVFtwjarY4a4SkDOSonAl8KArWw3mrjtmtWCEkLDCLbSFM5F0IE2GhgZJth6DvnPpeMdn1vKAjHRIBWH7BcstQCawIwTomPo4BTnCVgZOwQogAsg1eak1CnbLFVdS3pCyEiZRsu0WTS1xr2vo7/+QTZASwgONy8WACK3Kw2DSN7XQlbYVeLrezdG9ucRU126d0TdquWvU+CwMLQethE3vzRNsfhVTgpp+dOXwoB5Zo96yw6DviJwqzRyE+J9ryt6VJ/PzW1Qoi/46ZXEgGR74U0ogRHkY8JHQUj8A6DSybzZaZmZmamqrVavGnRqNhexdiM7AAsGC2v9fglKlC/xCfVsDOD/xSv9eq7bBOiz3FAHkHWGnNFv+cUXxEC1Z+fr5er8cWmDExMezP0dHRoBoL1k9kD+cMp0Jwr98gLt3ckGHxL2UN2w7LE3wmF1JNfMMigbWKoRBbNSUmJvLibqvYpAk7geGdALAiq0r4FXpdfL7qrtOTcNa87fp1GTC5TJqtZxofEoDFNlmdmJjgxV3m7HY7tpXLzs5WGCysN0dOX8Mnr+2Qcxf0yQsEViiwsJXc/v37MRoygJhphT3lkpOTFQaLsUW99RkBS6VSbdu2LV08oLTKy8uxjS828O3v7/enCkdkW+PReaadp+1XeNoiZI6DGltqvCugsUjwTPRjEVgkSGCRIIFFggQWgUWCBBYJElgkSGBR25EggUWCBBYJEljUdiRIYJEggUWCBBY1OgkSWCRIYJEggUWNToIEFgkSWCRIYFGjkyCBRYIEFgkSWNToJEhgkSCBRYIEFjU6CRJYJEhgkSCBRY1OggQWCRJYJEhgUaOTIIFFggQWCRJY1OgkSGCRIIFFggQWNToJElgkSGCRIIFFjU5gEVgkqBRYOp0uLS0tJycH+1MQWCQoGVg7duzAZmB14kFgkaA0YEFLYXsmXBiNxry8PAKLBKUBy+VyxcXF4cJkMuXm5hJYJCjZULhz5068YiPM0tJSAosEJQOruLgYGxfu3r0be2EG7FdIBx3hHkE9CxgQQzgdeGUPKvHTWKLS90PHGXIQWHR8AsCqqKiA+YVXxepXVlaGEvEqd0HYqri3t7egoMD3Z3V19ZEjR+Qr0Wq1ZmZmpqamYp7k8/WkpKR4PB6ZSrTZbIcOHYL3G3eKP5ubm9G2WVlZISwfJcCanJxEK+ACNz81NaUAVdPT04mJibjA68zMjKxlDQ4O4gcTExPD/iwqKqqvr5e1RLgJ+/r6gJGv0MOHD2/ZsiUg4CHhgZ/KwMAASty+fbvdbt+zZw9+P1VVVQGecKXB6urqYg6I48eP41oBsNDEmJyirPj4ePma2/9Ai7MLdPbBgwfx+0YHyFqixWJJSkpijkOghvuV9U7n5uZaW1uzs7P9+TYYDGsJlkajYUMSXrVarQLd7HA4kpOToUjw6nQ6lQRr69atKL2jo0PW0RChs4SEhImJCWgRXOAe5QYLAZXCwkKoRvZnd3d3gBt8DcAaHh5mQR5UBT8vBboZ4fCSkhJcHD16lJkFioHFLkZGRvx/3JJbdfv378doyNQV9BYsns2bN/t6XfIDGEFB4mLXrl14haKSz6Q7a1UNAVsHowNeca2MxkLgEl2LV1wrCVZDQwN6GlYI2JKpLGjibdu2pYsH7Ff2pqwaS6/XoyVBM9gFTy+99BKGAplmY/8Pl7O7ukBGoYYAAAAASUVORK5CYII=", + "deprecated": true, + "image": "tb-image:Z2F0ZXdheV9nZW5lcmFsX2NoYXJ0X3N0YXRpc3RpY3Nfc3lzdGVtX3dpZGdldF9pbWFnZS5wbmc=:IkdhdGV3YXkgZ2VuZXJhbCBjaGFydCBzdGF0aXN0aWNzIiBzeXN0ZW0gd2lkZ2V0IGltYWdl;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAXk0lEQVR42u2deXwb1bXH+Y+lfY/utK98WkogZSk7LcujBF4pAT6lLaWvlBZeCrTw4AGFvrbQkPYRwlIWfyCr7WA7XrN4wzZx7DjxIu+2vEteZHmTJdmOd8faR9K838y1FUWWZVmaGQM585mPPmNZR/fOvV+de+455849iw/7OOuss/gojpaWFhI8cwQFVlQqVbp4JCUlzc3NaTSaveKhVqsJLBKMHCx2eDyeuLg4r9ebl5c3NjZGGosEpQEL+qm+vh4XKSkp0F4ZGRkWi4XAIsFowdqzZw/HcbiAurLb7Z2dndnZ2QQWCUYFVnd3d1FRkU914XV2djY5OZnAIsGowIqPj/cNfOXl5bDiY2Nj+/v7/anC0UIHHeEdwZUQxkRY8WS8k6AENhb5sUiQwCJBAosancAisEiQwCJBAosancAisEiQwCJBAosancAisEiQwCJBAosancAisEiQwCJBAosECSwCiwQJLBIksEiQwCKwSJDAIkECiwQJLAKLBAksEiSwSJDAIrBIkMAiQQKLBAksAosECSwSJLBIkMAisEiQwCJBAuuzK5jV7SKwCCyJBd+pd35nz7x6xE1gEViSCb7XIFCF84lCO4FFYEkj+IFaoOqSuPn1cfMX75nvnvQQWARWtIKvFg6CqnWx8+83Op8ptuP6hWN2AovAikowsd3FqIppcDaOuIv7uUtj5y+JnR+a9RBYBFaEgvs6BKow9r1V6wBV7PzdYRve3KJyEFgEViSC6VrXxSJVL+YN+6jC+ZHOBQV2Wfz8uNW75lXVTni2q50/zbLWGN0E1qdAEP4q0AOqXq92ZKi6/cHC+auPBKX1dp1zTarq9vBweWytctyaamETVZy3pFim7V6FwMKOX2yPQmwup9Pp0tLScnJy2J5NBFaII7uHA1Xora3Vwgi4FKz9ncIQeXWC5aTTq1hVwU1OD/fMUfv3PjzF0w1JlieL7HcftOIaF0qA5Xa7ExISfG/t2LED79SJB4EV4ijs4+BWQD+9orIzjJaChfMnWUJfxrY45a6qYc6T1O56pMB2ady8j6cfpllfOOZI1bgaxMpgSnGVSNtbRf2ygzU9Pf3mm29iYybs+wUthe2Z8KbRaMSOmATWcgd6iFH1t3K7j6GgYCWIs8Ub91nsnPRVbWpu0Yx7Pmh03i/iy851cfP3HLS+WuU40s8trc/OJsHTtj52ric8H1vkYLlcLrPZDC21fft2QIZ9VvGmyWTKzc0lsIIeRwc4phX+Umb377OgYOH80X5BSaRpXBJW1eXhXy53XL131scTBtxNH9t2qJ2Vw1zQavjOxw8LPrb7DlkdbjnBws6XBoMBF9hVdXx8fOfOnbjGRpilpaUE1tKjwuD+brzQkc+V2AM6bDmwdoju+NvTrW6PZFVl4UicN6dY4YyFF63O5A7Nk+8Eed9PnIHstmqHjGBhp8Jdu3YdOHAAJjx2kysuLsbWvbt378ZemP5U0X6FOJLKdevjTqJLHs0cAUZhnukV3awjPyjRS1KNA6qudXtOrgMZhYPhV8P/fLe4/5LYk5jMJpTp5N2vEANi0GvSWL4DHqDL9wpKAhoiqCZYTmPhfLvOAcG7D1g93miranV578wQLKo/lTpClBj6hOAWlTAg3rDPsqKbjfxYMgo2jbqvFOdTyFloWL63lutIjFPf3yeIlw5xUVZ1c4XA6J0ZllqzOxqw6s3un4omPywzL4G1JoLNY27mE3rssB390bh6sHD+X6UAxC9ybNFUtXLYjfELqRPZ3dyKJYYGC6/HBrlrE4X7golGYCkniN9xy5h7W42DUYXYXwiqVuxmmMyYuOF7GszuyKoKz+dNycI3/KPSEU6J4VR1T4sTpGI60jnhIbBkF+ydEpxDd2Sccg49nG+rN4XbW8udfy1ziGrPFllVnz0qWEX3HrL6+I4eLJxPiRk+Pz5gtXEEljyCjKd/3zd9KhKyzwJT3ee5jhKsMgN3+V5B5cCrudqq5uo4CF7xoQUe//BLDKeqNSb3hnSLtIkYBJZwwAeNJKr/2G/1j6yBpzRtWDytqpuZ1nm+xL6qqo7Me68RjaF36hyrLTGcqsJiWy+6fIsHuGhatc7sZhH3Mxos/bQHmcQYAnw8wZLFuPDGkYHQtlQ0YBUtJgAitBdmVWHq/bZAyJJArkQEJYZZ1deqhWH6+iTLqCWSePngrOepIjtrRrTemQWW082PznvbTgjxsnsOWf2DIU8ese9rdzGeJOytEBGVVyocYd5jkhhtBPTHBzn5wIJu/kWugO9v8m2nnG1el3CGbNVZh/f1GgdTeBjoETxFVFResMb82JcbLLTFpM0LIwnauEDPJXe4/ppvQOfhZ/TLXBuGuasSTuWQsBNx/scL7QltrgCrXG6wPupdSAA8seiZDHGPUKvMK4vpW8QlhikIcK9LspyWi9H7DF/1Rb7jXn74XX6+GaD5V5Xz8Cka1/WiCO4IjYkoaqO46E1esKC64VeUFawdTc6NB63wPbK8qNAnxiAYT3ekWx4rtMe3LhtckxssnL/OE3TDPxcTAJe7R/QcS1tAnylT1Q/bhGxYhNih13nj+7zq7NPO2gtnau/jRxJ4x3DZEHfXolWK1KDM7lPaVF6wgD8zC+QDyzzvZfFgdkIbIyKLjJEHc23wJm/KHEHqyHv1Tnj/cns4mKVhWk4KgHWgU2gcuMfmHN4Q9xgjLlG8KcW6XMKCHFV9tkQYqV/K+YivPI9XncOP7uPHMviex/m6i/wh6z16TVLu83/Oyo1tnAr4BhnBwsDkM2KqZUuy/psY3IBNUAJoTGvAx3KC7QbdaMfLjSOh0ldYOGVXk3O5e2wdcyPlC5oYzg4lfwPwPjyRqZ4v+4oAkOGN0zy009pDha+XHb7fWv4lH2Heys+fbLzTpHmta6BGbXbICxZSY9m46wtiSA4WZlXQ2Ehqy+91rZXiWU5wuvnXaPHxtqcbR5ZlImkxARBuyaX3iDeZ++OPxxwKK9cW45i1ej3qX1DwcIF+wXJH5hYML2anAvfnik826yrN2q3zDbd7Kz/ng8xd9dWZpgd554gsYCEHbUO60Chv1DpYKAqRV8nB+suCF9u+hiNaUMHOgXphBBEberLlUfYjDnqyDHQopKX3+HcxsIjE4hqToqO22mw/qb4LNR+tvPmKuBOYLxtPemDVsA7FeVfKBOwKf5Em01xP37GRjs2W+ptx457K83mPTRawEtuE3yKcubBpYOWwZEUk0UoIFrwm+N1cFj+tbXt/qvm3zaaZTwxYHH7E6Jjp5l+5q74iXjykHrEF/fwuMUv4tjSLurk1aKQ5q4dT2BycaP096uysubh12PSQOMO4cnFFBiY90LKhS2wbNuj6imUZCi0u741ifgimXWzAZn/uOq6XECwsXX8k5bC5/FqmGGz117cZBj4JYOl7s1AfV/WFYL1zoJGr/gb+nG26v8k8v/TzcB1BJ6Fx3i/p890azHm2WmuLyq7wPGO4813U1lP5hc6BBvxZbuBYqg/0FhRE+E4+WcBiE5n7Mq2+Yt6sFZTWbUlTbq80YA2NaisL72VI2WuvstddLfRlzbfRkWsL1gGVxl57JSoz2B3P3tEONnPV38Q7c+q7m0yzS0XeqRca5/bkKV/bPF8SGGlWBqxefQGvOhdjmb43x/cmVnj/6bjDf066NmDBRckyTDK0tq6BamPnWzp9Yb3RCS8A3sQqvGjB4qZ4/YselWAtWisuMHR9AJug2TQ517hR/Kl90b9RlAerSfV3QX3WXeNvV2mGNK5qYaKOIbLZOLVcAiBSo3B/Bb0c81/DalbSM6IZbGcDt0n7RvQlSg9WjKpn84Hddccecldd4JspQJc01Lx8R0IH1L7LEylYXk7wy9UKv35Pxbl5BY+W6Uf8TE7HifbnxOLOMWtfWxOwwLdDJdx1r/7jgH+1G/odtZcJP4b6H2DOFZgAKJqhD+TYEKi4TpzrvF3nUGwCK04Dxx11l6N6Uy0Ph3aRKAuWtZs3x/KdD3lqvuHvQLPVXTve+hSGqgVvh+qc+iN31jan8B7rqsGaOsqrr2Hf01V+z8bExv85GsT+MHa+Iyrzs1EuNJnCYGFahKIxpVrGqh1ko6St/rrWYXNAAuCV8XPChEv0LyAAFV6JLoyz0d+j2mydb9ggKNTG25rMFkkaJwqwnKP8eLYQSKq/xB+mE6XfUpc+NNi9t214yFdM56AaHh1nxaJLrfqrfM9j/HSpGLZf6bD1CjEEJth45XBfFkt3LB4I/sPS9+ZiQBRsmsaNWapGxcDCbEgs95wAO8//bB0esdbdgLo5ar/bbujz/9cT2WZfOLxkgFsRqT5dJgZcfNWM6qqBnlS12RnxPeJHKM42LmobHpaqcVYP1vghvvdpvvHy0+JHdd/muzeN6JM2JHRihlzUH7xdDqmaYvLjmotuOyWovlaIRrnGg5fHTfN9/8tXfl74ZM3XhE96nQiZregzxIwGg6/AlurydoNeGbAmWzYJNrvqgdAfazGesNbfJMznay9tH+o9FaEr07EEQKxADCEO7YJpAbhcdHl/zjc49OvSQzjMlrtHUcef7an6knawRcLGWT1Yi34/vvrL/hFv/PcRMWEoRJejQvEtwoTxgfQ218Brp1QdAlL4Kug/X3rGgjl1ofiBc6dqf8Y7T+DtjnHPxaJhuzR7ZIn+GGb9h9l+d79KbrDQK6gnHIN5qrKVTTHjFHN0uWq+BZPZVyIidI8V2kMitRfuJdZoMInwJzyTtap3HbVXLLxZu97QtT2c4YyVqNMf8YrRwD7dIWkbZ/Vg9T7LG2NEmE4zwrHgjoVUkXobukIbDy4GyEDP5Me89kHeFxCA5ht4hR/P4tXXLbzTvpG3dPhmhYgrC4vpjjvCM6VnzKofiVPF8/t0+2UFC64EwVXd8XKYgvA7zKl/LHL/TaYqIIiEk4pgrddsmsbkmvksBPO/7kaMg75IEQRhTQ707LPXfo99APAZunYEdZv5n4WqfE/Vl/F5fLnkjSON8Q59hRmN/0NXQlQI4Qt8Elm2M45FA8s5xpt28k03nja8Qp+NpfvPCptH3cwLHILdwEcIqbRj7S+yqSIs6xXnO5GBhTmggEjV11tM4+ELQgMJMTUhuHZB10BtUEFM1jDD5aoWpkSYUYpIcctU1QVHlBhUOZshC1lMVJcbkedV68Rp4G/CbxalwTrcx7Ek8SpjWBVij/WBHzXwi+bqeN1TfP13+OH3eE9gdiVSGCCFVS6rbQJD105R4SNstwndKS1YsJph3+DLMQatlkjEeRDtEdn6yjFVeoCZD/3HNAqbaSIYF15VuV59nqXh1gW8qr4OvEB8ANNsLJ5v+GGY4+YagIWnXLBJsv+zN0NXCKlIF4u6ZzK89bcAC4+lY0NtuYGLoAkQvXJXf401ZatxVEKwYOiIxs1lDNnVjqHgcqL1CYEA1fmoJPNKQMuyia1Y4Q09faURVLWrv2qm6efsSwAovrPVaFycBj6JNy2qi4CvTDMbCcDC4zfR5bemWle15u5BMbcaidJhgsXyLf2fRLXaJtAOtTlr1jGzt8PQJQlYMJXY9FMcoSJ2gLngixGneP8K/6RX9S9syjLd/J9w00TZzd39lbNNP1nAq/ILY20vjHS8wlCDjSWfLyZasJAzjwdaosuxNmFVFcJiI5b0jaUNK5abVNbDHDwrPvYpdBMgXG9puIUNEJGpgYATowzTgj4zJVJ3JdetenzRg3DeZMt/aQY7JOxmuNamm355akavOrdXny+r9zhasFjWx10HLBEs5nw4P9xHVW9MnQgz1L9iEzSZT0ITMA8Qpk7RtB0whftHiAQMVEsSYDFrXp1o/QNST2WawAJWIAtw4buSO94VFVhY9MOS+LBqKoIKIe0TyZ9IAfVfXrf0QMY+m0WuVl0t3wQucTgQfr4TrY8HtV7Dabvxtv9mSVefkJzVsCMEQwqUGBVYsJDE0Kk14gptEp+vj0TQEIX+PFv4DMK00jYBxgIk0bI5fASJXJohLXQecr07DD2fLrCUEYwcLCyWxWI3TO4OdkXuHCrsc7E1wVg6F7RE+AwF62rvbLVR+iaA15tlHHDV/xZgcq3YdkjcgyCmWp9tPtYArD+XCjE7hHGirBDWH7OHeQb1u94nrvN5/iOjTE0AB8+s+j5mchk73w5TsKevjDmflnouCKyowOqbEfLNcX4cXjJaiAohSQGpCtB8Sx/OVCj6XZHWnFLRI18TwJMkprucwzyoLBISUtDFvNv+IBJY0oD1e1HNPF1sl6RCbHnkH47Ygy5LRFqzAm3X13uQTfEQuoaLMoQgclRYPA4TTAJLSrCwipKlGBwblCbGhJVhLGMEz87zlZUvZuj+IDnax2yuIkNhqI2loyDKdlyVFtwjarY4a4SkDOSonAl8KArWw3mrjtmtWCEkLDCLbSFM5F0IE2GhgZJth6DvnPpeMdn1vKAjHRIBWH7BcstQCawIwTomPo4BTnCVgZOwQogAsg1eak1CnbLFVdS3pCyEiZRsu0WTS1xr2vo7/+QTZASwgONy8WACK3Kw2DSN7XQlbYVeLrezdG9ucRU126d0TdquWvU+CwMLQethE3vzRNsfhVTgpp+dOXwoB5Zo96yw6DviJwqzRyE+J9ryt6VJ/PzW1Qoi/46ZXEgGR74U0ogRHkY8JHQUj8A6DSybzZaZmZmamqrVavGnRqNhexdiM7AAsGC2v9fglKlC/xCfVsDOD/xSv9eq7bBOiz3FAHkHWGnNFv+cUXxEC1Z+fr5er8cWmDExMezP0dHRoBoL1k9kD+cMp0Jwr98gLt3ckGHxL2UN2w7LE3wmF1JNfMMigbWKoRBbNSUmJvLibqvYpAk7geGdALAiq0r4FXpdfL7qrtOTcNa87fp1GTC5TJqtZxofEoDFNlmdmJjgxV3m7HY7tpXLzs5WGCysN0dOX8Mnr+2Qcxf0yQsEViiwsJXc/v37MRoygJhphT3lkpOTFQaLsUW99RkBS6VSbdu2LV08oLTKy8uxjS828O3v7/enCkdkW+PReaadp+1XeNoiZI6DGltqvCugsUjwTPRjEVgkSGCRIIFFggQWgUWCBBYJElgkSGBR25EggUWCBBYJEljUdiRIYJEggUWCBBY1OgkSWCRIYJEggUWNToIEFgkSWCRIYFGjkyCBRYIEFgkSWNToJEhgkSCBRYIEFjU6CRJYJEhgkSCBRY1OggQWCRJYJEhgUaOTIIFFggQWCRJY1OgkSGCRIIFFggQWNToJElgkSGCRIIFFjU5gEVgkqBRYOp0uLS0tJycH+1MQWCQoGVg7duzAZmB14kFgkaA0YEFLYXsmXBiNxry8PAKLBKUBy+VyxcXF4cJkMuXm5hJYJCjZULhz5068YiPM0tJSAosEJQOruLgYGxfu3r0be2EG7FdIBx3hHkE9CxgQQzgdeGUPKvHTWKLS90PHGXIQWHR8AsCqqKiA+YVXxepXVlaGEvEqd0HYqri3t7egoMD3Z3V19ZEjR+Qr0Wq1ZmZmpqamYp7k8/WkpKR4PB6ZSrTZbIcOHYL3G3eKP5ubm9G2WVlZISwfJcCanJxEK+ACNz81NaUAVdPT04mJibjA68zMjKxlDQ4O4gcTExPD/iwqKqqvr5e1RLgJ+/r6gJGv0MOHD2/ZsiUg4CHhgZ/KwMAASty+fbvdbt+zZw9+P1VVVQGecKXB6urqYg6I48eP41oBsNDEmJyirPj4ePma2/9Ai7MLdPbBgwfx+0YHyFqixWJJSkpijkOghvuV9U7n5uZaW1uzs7P9+TYYDGsJlkajYUMSXrVarQLd7HA4kpOToUjw6nQ6lQRr69atKL2jo0PW0RChs4SEhImJCWgRXOAe5QYLAZXCwkKoRvZnd3d3gBt8DcAaHh5mQR5UBT8vBboZ4fCSkhJcHD16lJkFioHFLkZGRvx/3JJbdfv378doyNQV9BYsns2bN/t6XfIDGEFB4mLXrl14haKSz6Q7a1UNAVsHowNeca2MxkLgEl2LV1wrCVZDQwN6GlYI2JKpLGjibdu2pYsH7Ff2pqwaS6/XoyVBM9gFTy+99BKGAplmY/8Pl7O7ukBGoYYAAAAASUVORK5CYII=", "description": "Displays changes to time-series data over time—for example, temperature or humidity readings.", "descriptor": { "type": "timeseries", @@ -22,6 +22,5 @@ "basicModeDirective": "tb-flot-basic-config", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"stack\":false,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":null,\"max\":null,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"\"},\"shadowSize\":4,\"smoothLines\":false,\"comparisonEnabled\":false,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"bottom\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false},\"title\":\"Timeseries Line Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\"}" }, - "externalId": null, "tags": null } \ No newline at end of file diff --git a/ui-ngx/src/app/core/api/data-aggregator.ts b/ui-ngx/src/app/core/api/data-aggregator.ts index c11c08ad2b..61cb1f1a85 100644 --- a/ui-ngx/src/app/core/api/data-aggregator.ts +++ b/ui-ngx/src/app/core/api/data-aggregator.ts @@ -14,24 +14,23 @@ /// limitations under the License. /// +import { AggKey, IndexedSubscriptionData, } from '@app/shared/models/telemetry/telemetry.models'; import { - AggKey, - IndexedSubscriptionData, -} from '@app/shared/models/telemetry/telemetry.models'; -import { - AggregationType, calculateAggIntervalWithSubscriptionTimeWindow, + AggregationType, + calculateAggIntervalWithSubscriptionTimeWindow, calculateIntervalComparisonEndTime, calculateIntervalEndTime, calculateIntervalStartEndTime, getCurrentTime, - getTime, IntervalMath, + getTime, + IntervalMath, SubscriptionTimewindow } from '@shared/models/time/time.models'; import { UtilsService } from '@core/services/utils.service'; import { deepClone, isDefinedAndNotNull, isNumber, isNumeric } from '@core/utils'; -import Timeout = NodeJS.Timeout; import { DataEntry, DataSet, IndexedData } from '@shared/models/widget.models'; import BTree from 'sorted-btree'; +import Timeout = NodeJS.Timeout; export declare type onAggregatedData = (data: IndexedData, detectChanges: boolean) => void; @@ -318,7 +317,9 @@ export class DataAggregator { this.startTs += tickTs; this.endTs += tickTs; } - this.updateLastInterval(); + if (this.subsTw.aggregation.type !== AggregationType.NONE) { + this.updateLastInterval(); + } this.data = this.updateData(); this.elapsed = this.elapsed - delta * this.aggregationTimeout; } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html index f6af2d1cad..fe642f3d6c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html @@ -205,6 +205,13 @@
+
+ +
+ {{ 'tooltip.show-date-time-interval' | translate }} +
+
+
{{ 'tooltip.background-color' | translate }}
+
+ +
+ {{ 'tooltip.show-date-time-interval' | translate }} +
+
+
{{ 'tooltip.background-color' | translate }}
, protected widgetConfigComponent: WidgetConfigComponent, private $injector: Injector, @@ -86,6 +90,14 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon return this.timeSeriesChartWidgetConfigForm; } + protected setupConfig(widgetConfig: WidgetConfigComponentData) { + const params = widgetConfig.typeParameters as any; + if (isDefinedAndNotNull(params.chartType)) { + this.chartType = params.chartType; + } + super.setupConfig(widgetConfig); + } + protected defaultDataKeys(configData: WidgetConfigComponentData): DataKey[] { return [{ name: 'temperature', label: 'Temperature', type: DataKeyType.timeseries, units: '°C', decimals: 0 }]; } @@ -309,6 +321,4 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon processor.update(Date.now()); return processor.formatted; } - - protected readonly ValueType = ValueType; } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss index be91ff173c..a44bd255e9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss @@ -68,7 +68,13 @@ .tb-label-field, .tb-units-field, .tb-color-field, .tb-decimals-field { display: none; @media #{$mat-gt-sm} { - display: block; + display: flex; + } + } + + .tb-time-series-type-field { + @media #{$mat-md} { + display: none; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts index b2710db346..55613334ff 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts @@ -322,6 +322,7 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit { if (this.showTimeSeriesType) { this.keyRowFormGroup.get('timeSeriesType').patchValue(this.modelValue.settings?.type, {emitEvent: false}); } + this.keyFormControl.patchValue(deepClone(this.modelValue), {emitEvent: false}); this.updateModel(); this.cd.markForCheck(); } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html index c13c6de357..4eff6b3881 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+
{{ panelTitle }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss index 121ed2de77..94785d58f7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss @@ -15,56 +15,71 @@ */ @import '../../../../../../../../scss/constants'; -.tb-form-table-header-cell { - &.tb-source-header { - width: 108px; - min-width: 108px; - } - &.tb-key-header { - flex: 1; - min-width: 150px; - @media #{$mat-gt-sm} { - flex: 1 1 60%; +.tb-data-keys-panel { + .tb-form-table-header-cell { + &.tb-source-header { + width: 108px; + min-width: 108px; } - } - &.tb-label-header { - flex: 1 1 40%; - min-width: 100px; - } - &.tb-units-header { - width: 80px; - min-width: 80px; - } - &.tb-color-header { - width: 40px; - min-width: 40px; - } - &.tb-decimals-header { - width: 60px; - min-width: 60px; - } - &.tb-time-series-type-header { - width: 60px; - min-width: 60px; - } - &.tb-actions-header { - width: 40px; - min-width: 40px; - @media #{$mat-gt-md} { - width: 120px; - min-width: 120px; + + &.tb-key-header { + flex: 1; + min-width: 150px; + @media #{$mat-gt-sm} { + flex: 1 1 60%; + } } - } - &.tb-label-header, &.tb-units-header, &.tb-color-header, &.tb-decimals-header { - display: none; - @media #{$mat-gt-sm} { - display: block; + + &.tb-label-header { + flex: 1 1 40%; + min-width: 100px; + } + + &.tb-units-header { + width: 80px; + min-width: 80px; + } + + &.tb-color-header { + width: 40px; + min-width: 40px; + } + + &.tb-decimals-header { + width: 60px; + min-width: 60px; + } + + &.tb-time-series-type-header { + width: 60px; + min-width: 60px; + } + + &.tb-actions-header { + width: 40px; + min-width: 40px; + @media #{$mat-gt-md} { + width: 120px; + min-width: 120px; + } + } + + &.tb-label-header, &.tb-units-header, &.tb-color-header, &.tb-decimals-header { + display: none; + @media #{$mat-gt-sm} { + display: block; + } + } + &.tb-time-series-type-header { + @media #{$mat-md} { + display: none; + } } } -} -.tb-form-table-body { - tb-data-key-row { - overflow: hidden; + .tb-form-table-body { + tb-data-key-row { + overflow: hidden; + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html index 37c85e173d..f472325d8d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html @@ -196,6 +196,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts index a4a2e9cc7e..8636db1f77 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts @@ -49,6 +49,7 @@ import { Dashboard } from '@shared/models/dashboard.models'; import { WidgetService } from '@core/http/widget.service'; import { IAliasController } from '@core/api/widget-api.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; @Component({ selector: 'tb-widget-settings', @@ -77,6 +78,9 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On @Input() aliasController: IAliasController; + @Input() + dataKeyCallbacks: DataKeysCallbacks; + @Input() dashboard: Dashboard; @@ -98,7 +102,7 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On private definedSettingsComponent: IWidgetSettingsComponent; private widgetSettingsFormData: JsonFormComponentData; - private propagateChange = (v: any) => { }; + private propagateChange = (_v: any) => { }; constructor(private translate: TranslateService, private cfr: ComponentFactoryResolver, @@ -138,6 +142,11 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On this.definedSettingsComponent.aliasController = this.aliasController; } } + if (propName === 'dataKeyCallbacks') { + if (this.definedSettingsComponent) { + this.definedSettingsComponent.dataKeyCallbacks = this.dataKeyCallbacks; + } + } if (propName === 'widgetConfig') { if (this.definedSettingsComponent) { this.definedSettingsComponent.widgetConfig = this.widgetConfig; @@ -225,6 +234,7 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On this.definedSettingsComponentRef = this.definedSettingsContainer.createComponent(factory); this.definedSettingsComponent = this.definedSettingsComponentRef.instance; this.definedSettingsComponent.aliasController = this.aliasController; + this.definedSettingsComponent.dataKeyCallbacks = this.dataKeyCallbacks; this.definedSettingsComponent.dashboard = this.dashboard; this.definedSettingsComponent.widget = this.widget; this.definedSettingsComponent.widgetConfig = this.widgetConfig; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts index 37ce7a9c7f..4bf89a4cde 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts @@ -119,10 +119,17 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C style.rich = renderCtx.labelOption.rich; } + let borderRadius: number[]; + if (value < 0) { + borderRadius = [0, 0, renderCtx.visualSettings.borderRadius, renderCtx.visualSettings.borderRadius]; + } else { + borderRadius = [renderCtx.visualSettings.borderRadius, renderCtx.visualSettings.borderRadius, 0, 0]; + } + return rectShape && { type: 'rect', id: time + '', - shape: {...rectShape, r: renderCtx.visualSettings.borderRadius}, + shape: {...rectShape, r: borderRadius}, style, focus: 'series', transition: 'all', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index dfcd1aaced..ad2083902c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -43,6 +43,21 @@ import { TbColorScheme } from '@shared/models/color.models'; import { AbstractControl, ValidationErrors } from '@angular/forms'; import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel'; +export enum TimeSeriesChartType { + default = 'default', + line = 'line', + bar = 'bar', + point = 'point' +} + +export const timeSeriesChartTypeTranslations = new Map( + [ + [TimeSeriesChartType.line, 'widgets.time-series-chart.type-line'], + [TimeSeriesChartType.bar, 'widgets.time-series-chart.type-bar'], + [TimeSeriesChartType.point, 'widgets.time-series-chart.type-point'] + ] +); + const timeSeriesChartColorScheme: TbColorScheme = { 'threshold.line': { light: 'rgba(0, 0, 0, 0.76)', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 08761fbe36..4b9bc3ba84 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -21,15 +21,18 @@ import { createTimeSeriesXAxisOption, createTimeSeriesYAxis, generateChartData, - parseThresholdData, SeriesLabelPosition, + parseThresholdData, + SeriesLabelPosition, TimeSeriesChartDataItem, timeSeriesChartDefaultSettings, timeSeriesChartKeyDefaultSettings, TimeSeriesChartKeySettings, TimeSeriesChartSeriesType, TimeSeriesChartSettings, + TimeSeriesChartShape, TimeSeriesChartThresholdItem, TimeSeriesChartThresholdType, + TimeSeriesChartType, TimeSeriesChartYAxis, updateDarkMode } from '@home/components/widget/lib/chart/time-series-chart.models'; @@ -63,11 +66,23 @@ import { DataKeySettingsFunction } from '@home/components/widget/config/data-key export class TbTimeSeriesChart { - public static dataKeySettings(): DataKeySettingsFunction { + public static dataKeySettings(type = TimeSeriesChartType.default): DataKeySettingsFunction { return (key, isLatestDataKey) => { if (!isLatestDataKey) { - return mergeDeep({} as TimeSeriesChartKeySettings, + const settings = mergeDeep({} as TimeSeriesChartKeySettings, timeSeriesChartKeyDefaultSettings); + if (type === TimeSeriesChartType.line) { + settings.type = TimeSeriesChartSeriesType.line; + } else if (type === TimeSeriesChartType.bar) { + settings.type = TimeSeriesChartSeriesType.bar; + } else if (type === TimeSeriesChartType.point) { + settings.type = TimeSeriesChartSeriesType.line; + settings.lineSettings.showLine = false; + settings.lineSettings.showPoints = true; + settings.lineSettings.pointShape = TimeSeriesChartShape.circle; + settings.lineSettings.pointSize = 8; + } + return settings; } return null; }; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html index 443ee4abc5..e40470d75e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html @@ -124,6 +124,13 @@
+
+ +
+ {{ 'tooltip.show-date-time-interval' | translate }} +
+
+
{{ 'tooltip.background-color' | translate }}
+
+ +
+ {{ 'tooltip.show-date-time-interval' | translate }} +
+
+
{{ 'tooltip.background-color' | translate }}
-
+
widgets.time-series-chart.series.series-type
{{ timeSeriesChartSeriesTypeTranslations.get(type) | translate }}
+ +
{{ timeSeriesChartTypeTranslations.get(chartType) | translate }}
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts index 24fb200b0b..4da23f32e0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts @@ -19,13 +19,13 @@ import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.m import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { mergeDeep } from '@core/utils'; +import { isDefinedAndNotNull, mergeDeep } from '@core/utils'; import { timeSeriesChartKeyDefaultSettings, TimeSeriesChartKeySettings, TimeSeriesChartSeriesType, timeSeriesChartSeriesTypes, - timeSeriesChartSeriesTypeTranslations + timeSeriesChartSeriesTypeTranslations, TimeSeriesChartType, timeSeriesChartTypeTranslations } from '@home/components/widget/lib/chart/time-series-chart.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; @@ -36,6 +36,10 @@ import { WidgetConfigComponentData } from '@home/models/widget-component.models' }) export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent { + TimeSeriesChartType = TimeSeriesChartType; + + timeSeriesChartTypeTranslations = timeSeriesChartTypeTranslations; + TimeSeriesChartSeriesType = TimeSeriesChartSeriesType; timeSeriesChartSeriesTypes = timeSeriesChartSeriesTypes; @@ -44,6 +48,8 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent timeSeriesChartKeySettingsForm: UntypedFormGroup; + chartType = TimeSeriesChartType.default; + constructor(protected store: Store, private fb: UntypedFormBuilder) { super(store); @@ -55,7 +61,9 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) { const params = widgetConfig.typeParameters as any; - // const timeSeriesChartType = params.timeSeriesChartType; + if (isDefinedAndNotNull(params.chartType)) { + this.chartType = params.chartType; + } } protected defaultSettings(): WidgetSettings { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html index d9a942053c..78f1129727 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html @@ -16,7 +16,7 @@ --> -
+
widgets.time-series-chart.series.line.line
@@ -57,57 +57,60 @@
-
+
widgets.time-series-chart.series.point.points
{{ 'widgets.time-series-chart.series.point.show-points' | translate }}
-
- -
- {{ 'widgets.time-series-chart.series.point.point-label' | translate }} -
-
-
- - - - {{ seriesLabelPositionTranslations.get(position) | translate }} - - - - - - - + +
+ + + + +
+ +
+ {{ 'widgets.time-series-chart.series.point.point-label' | translate }}
-
-
-
widgets.time-series-chart.series.point.point-shape
- - - - {{ timeSeriesChartShapeTranslations.get(shape) | translate }} + +
+ + + + {{ seriesLabelPositionTranslations.get(position) | translate }} -
-
-
widgets.time-series-chart.series.point.point-size
- - - + + + +
- - - +
+
widgets.time-series-chart.series.point.point-shape
+ + + + {{ timeSeriesChartShapeTranslations.get(shape) | translate }} + + + +
+
+
widgets.time-series-chart.series.point.point-size
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts index 46a75edc08..84533b26e4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts @@ -29,7 +29,7 @@ import { seriesLabelPositions, seriesLabelPositionTranslations, timeSeriesChartShapes, - timeSeriesChartShapeTranslations, + timeSeriesChartShapeTranslations, TimeSeriesChartType, timeSeriesLineTypes, timeSeriesLineTypeTranslations } from '@home/components/widget/lib/chart/time-series-chart.models'; @@ -53,6 +53,8 @@ import { DataKeyConfigComponent } from '@home/components/widget/config/data-key- }) export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValueAccessor { + TimeSeriesChartType = TimeSeriesChartType; + lineSeriesStepTypes = lineSeriesStepTypes; lineSeriesStepTypeTranslations = lineSeriesStepTypeTranslations; @@ -74,6 +76,9 @@ export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValu @Input() disabled: boolean; + @Input() + chartType: TimeSeriesChartType; + private modelValue: LineSeriesSettings; private propagateChange = null; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html new file mode 100644 index 0000000000..2d8294987e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html @@ -0,0 +1,159 @@ + + + + +
+
widgets.time-series-chart.chart-style
+
+ + {{ 'widgets.time-series-chart.data-zoom' | translate }} + +
+
+ +
+ {{ 'widgets.time-series-chart.stack-mode' | translate }} +
+
+
+
+
widgets.time-series-chart.axis.axes
+ + + + +
+
+ + + + + {{ 'widget-config.legend' | translate }} + + + + +
+
{{ 'legend.label' | translate }}
+
+ + + + +
+
+ + +
+
+
+
+ + + + + {{ 'widget-config.tooltip' | translate }} + + + + +
+
{{ 'tooltip.trigger' | translate }}
+ + {{ 'tooltip.trigger-point' | translate }} + {{ 'tooltip.trigger-axis' | translate }} + +
+
+
{{ 'tooltip.value' | translate }}
+
+ + + + +
+
+
+ + {{ 'tooltip.date' | translate }} + +
+ + + + + +
+
+
+ +
+ {{ 'tooltip.show-date-time-interval' | translate }} +
+
+
+
+
{{ 'tooltip.background-color' | translate }}
+ + +
+
+
{{ 'tooltip.background-blur' | translate }}
+ + +
px
+
+
+
+
+
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts new file mode 100644 index 0000000000..0321d07ba8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts @@ -0,0 +1,174 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Injector } from '@angular/core'; +import { + Datasource, + legendPositions, + legendPositionTranslationMap, + WidgetSettings, + WidgetSettingsComponent +} from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { formatValue, mergeDeep } from '@core/utils'; +import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models'; +import { + barChartWithLabelsDefaultSettings +} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models'; +import { EChartsTooltipTrigger } from '../../chart/echarts-widget.models'; +import { + timeSeriesChartWidgetDefaultSettings, TimeSeriesChartWidgetSettings +} from '@home/components/widget/lib/chart/time-series-chart-widget.models'; + +@Component({ + selector: 'tb-time-series-chart-widget-settings', + templateUrl: './time-series-chart-widget-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsComponent { + + public get datasource(): Datasource { + const datasources: Datasource[] = this.widgetConfig.config.datasources; + if (datasources && datasources.length) { + return datasources[0]; + } else { + return null; + } + } + + EChartsTooltipTrigger = EChartsTooltipTrigger; + + legendPositions = legendPositions; + + legendPositionTranslationMap = legendPositionTranslationMap; + + timeSeriesChartWidgetSettingsForm: UntypedFormGroup; + + tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); + + tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this); + + constructor(protected store: Store, + private $injector: Injector, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.timeSeriesChartWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return mergeDeep({} as TimeSeriesChartWidgetSettings, timeSeriesChartWidgetDefaultSettings); + } + + protected onSettingsSet(settings: WidgetSettings) { + this.timeSeriesChartWidgetSettingsForm = this.fb.group({ + + thresholds: [settings.thresholds, []], + + dataZoom: [settings.dataZoom, []], + stack: [settings.stack, []], + + yAxis: [settings.yAxis, []], + xAxis: [settings.xAxis, []], + + showLegend: [settings.showLegend, []], + legendLabelFont: [settings.legendLabelFont, []], + legendLabelColor: [settings.legendLabelColor, []], + legendConfig: [settings.legendConfig, []], + + showTooltip: [settings.showTooltip, []], + tooltipTrigger: [settings.tooltipTrigger, []], + tooltipValueFont: [settings.tooltipValueFont, []], + tooltipValueColor: [settings.tooltipValueColor, []], + tooltipShowDate: [settings.tooltipShowDate, []], + tooltipDateFormat: [settings.tooltipDateFormat, []], + tooltipDateFont: [settings.tooltipDateFont, []], + tooltipDateColor: [settings.tooltipDateColor, []], + tooltipDateInterval: [settings.tooltipDateInterval, []], + + tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], + tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], + + background: [settings.background, []] + }); + } + + protected validatorTriggers(): string[] { + return ['showLegend', 'showTooltip', 'tooltipShowDate']; + } + + protected updateValidators(emitEvent: boolean) { + const showLegend: boolean = this.timeSeriesChartWidgetSettingsForm.get('showLegend').value; + const showTooltip: boolean = this.timeSeriesChartWidgetSettingsForm.get('showTooltip').value; + const tooltipShowDate: boolean = this.timeSeriesChartWidgetSettingsForm.get('tooltipShowDate').value; + + if (showLegend) { + this.timeSeriesChartWidgetSettingsForm.get('legendLabelFont').enable(); + this.timeSeriesChartWidgetSettingsForm.get('legendLabelColor').enable(); + this.timeSeriesChartWidgetSettingsForm.get('legendConfig').enable(); + } else { + this.timeSeriesChartWidgetSettingsForm.get('legendLabelFont').disable(); + this.timeSeriesChartWidgetSettingsForm.get('legendLabelColor').disable(); + this.timeSeriesChartWidgetSettingsForm.get('legendConfig').disable(); + } + + if (showTooltip) { + this.timeSeriesChartWidgetSettingsForm.get('tooltipTrigger').enable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipValueFont').enable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipValueColor').enable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipShowDate').enable({emitEvent: false}); + this.timeSeriesChartWidgetSettingsForm.get('tooltipBackgroundColor').enable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipBackgroundBlur').enable(); + if (tooltipShowDate) { + this.timeSeriesChartWidgetSettingsForm.get('tooltipDateFormat').enable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipDateFont').enable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipDateColor').enable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipDateInterval').enable(); + } else { + this.timeSeriesChartWidgetSettingsForm.get('tooltipDateFormat').disable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipDateFont').disable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipDateColor').disable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipDateInterval').disable(); + } + } else { + this.timeSeriesChartWidgetSettingsForm.get('tooltipValueFont').disable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipValueColor').disable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipShowDate').disable({emitEvent: false}); + this.timeSeriesChartWidgetSettingsForm.get('tooltipDateFormat').disable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipDateFont').disable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipDateColor').disable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipDateInterval').disable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipBackgroundColor').disable(); + this.timeSeriesChartWidgetSettingsForm.get('tooltipBackgroundBlur').disable(); + } + } + + private _tooltipValuePreviewFn(): string { + return formatValue(22, 0, '°C', false); + } + + private _tooltipDatePreviewFn(): string { + const dateFormat: DateFormatSettings = this.timeSeriesChartWidgetSettingsForm.get('tooltipDateFormat').value; + const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat); + processor.update(Date.now()); + return processor.formatted; + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html index a9db62ff89..473afe2608 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html @@ -49,10 +49,12 @@
{{ 'widgets.time-series-chart.threshold.threshold-settings' | translate }}
+
+
widget-config.units-short
+ + +
+
+
widget-config.decimals-short
+ + + +
+
+ + {{ 'widgets.time-series-chart.threshold.label' | translate }} + +
+ + + + {{ timeSeriesThresholdLabelPositionTranslations.get(position) | translate }} + + + + + + + +
+
widgets.time-series-chart.threshold.line-appearance
@@ -46,7 +82,7 @@
widgets.time-series-chart.threshold.start-symbol
- + {{ timeSeriesChartShapeTranslations.get(shape) | translate }} @@ -54,7 +90,7 @@
widgets.time-series-chart.threshold.symbol-size
- + @@ -63,7 +99,7 @@
widgets.time-series-chart.threshold.end-symbol
- + {{ timeSeriesChartShapeTranslations.get(shape) | translate }} @@ -71,36 +107,12 @@
widgets.time-series-chart.threshold.symbol-size
- +
-
- - {{ 'widgets.time-series-chart.threshold.label' | translate }} - -
- - - - {{ timeSeriesThresholdLabelPositionTranslations.get(position) | translate }} - - - - - - - -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.scss index 260b199854..b65fa6ad65 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.scss @@ -28,6 +28,8 @@ flex-direction: column; gap: 16px; overflow: auto; + margin: -10px; + padding: 10px; } .tb-threshold-settings-title { font-size: 16px; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts index a93a1c0181..47e1e93095 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts @@ -74,6 +74,8 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit { ngOnInit(): void { this.thresholdSettingsFormGroup = this.fb.group( { + units: [this.thresholdSettings.units, []], + decimals: [this.thresholdSettings.decimals, [Validators.min(0)]], lineColor: [this.thresholdSettings.lineColor, []], lineType: [this.thresholdSettings.lineType, []], lineWidth: [this.thresholdSettings.lineWidth, [Validators.min(0)]], @@ -130,9 +132,10 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit { } private _labelPreviewFn(): string { - const units = this.thresholdSettings.units && this.thresholdSettings.units.length ? - this.thresholdSettings.units : this.widgetConfig.units; - const decimals = isDefinedAndNotNull(this.thresholdSettings.decimals) ? this.thresholdSettings.decimals : + let units: string = this.thresholdSettingsFormGroup.get('units').value; + units = units && units.length ? units : this.widgetConfig.units; + let decimals: number = this.thresholdSettingsFormGroup.get('decimals').value; + decimals = isDefinedAndNotNull(decimals) ? decimals : (isDefinedAndNotNull(this.widgetConfig.decimals) ? this.widgetConfig.decimals : 2); return formatValue(22, decimals, units, false); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html index 811106fc7b..2054d8a15a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html @@ -27,11 +27,12 @@
-
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.scss index d1f1ec6eed..40f3c5359c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.scss @@ -13,15 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +@import '../../../../../../../../../scss/constants'; + .tb-time-series-thresholds-panel { .tb-form-table-header-cell { &.tb-threshold-source-header { flex: 1; - min-width: 200px; + min-width: 100px; + @media #{$mat-gt-md} { + min-width: 200px; + } } &.tb-threshold-key-value-header { flex: 1; - min-width: 150px; + min-width: 100px; + @media #{$mat-gt-md} { + min-width: 150px; + } } &.tb-units-header { @@ -43,5 +52,23 @@ width: 80px; min-width: 80px; } + + &.tb-units-header, &.tb-color-header, &.tb-decimals-header { + display: none; + @media #{$mat-gt-md} { + display: block; + } + } + } + .tb-form-table-body { + .tb-time-series-threshold-row { + overflow: hidden; + } + .mat-divider { + margin-top: 8px; + @media #{$mat-gt-sm} { + display: none; + } + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.html index 072c637641..94831b530f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.html @@ -15,7 +15,8 @@ limitations under the License. --> - + @@ -74,6 +75,15 @@ (matChipInputTokenEnd)="addKey($event)" /> + + warning + - calculateInterval(subsTw.startTs, endTs, subsTw.aggregation.interval, subsTw.tsOffset, subsTw.timezone, timestamp); + = (subsTw: SubscriptionTimewindow, endTs: number, timestamp: number): [number, number] => { + if (subsTw.aggregation.type === AggregationType.NONE) { + return [timestamp, timestamp]; + } else { + return calculateInterval(subsTw.startTs, endTs, subsTw.aggregation.interval, subsTw.tsOffset, subsTw.timezone, timestamp); + } +}; export const calculateAggIntervalWithWidgetTimeWindow = (widgetTimeWindow: WidgetTimewindow, timestamp: number): [number, number] => diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index a164d8d3ec..3088573d18 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -38,12 +38,12 @@ import { AbstractControl, UntypedFormGroup } from '@angular/forms'; import { Observable } from 'rxjs'; import { Dashboard } from '@shared/models/dashboard.models'; import { IAliasController } from '@core/api/widget-api.models'; -import { isNotEmptyStr } from '@core/utils'; +import { isNotEmptyStr, mergeDeep } from '@core/utils'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { HasTenantId } from '@shared/models/entity.models'; -import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; export enum widgetType { timeseries = 'timeseries', @@ -815,6 +815,7 @@ export interface WidgetSize { export interface IWidgetSettingsComponent { aliasController: IAliasController; + dataKeyCallbacks: DataKeysCallbacks; dashboard: Dashboard; widget: Widget; widgetConfig: WidgetConfigComponentData; @@ -832,6 +833,8 @@ export abstract class WidgetSettingsComponent extends PageComponent implements aliasController: IAliasController; + dataKeyCallbacks: DataKeysCallbacks; + dashboard: Dashboard; widget: Widget; @@ -857,7 +860,7 @@ export abstract class WidgetSettingsComponent extends PageComponent implements if (!value) { this.settingsValue = this.defaultSettings(); } else { - this.settingsValue = {...this.defaultSettings(), ...value}; + this.settingsValue = mergeDeep(this.defaultSettings(), value); } if (!this.settingsSet) { this.settingsSet = true; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 29fbafe83c..99b120b667 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6597,6 +6597,7 @@ }, "time-series-chart": { "chart": "Chart", + "chart-style": "Chart style", "data-zoom": "Data zoom", "stack-mode": "Stack mode", "stack-mode-hint": "Stacks series on the chart. The series with the same unit would be put on top of each other.", @@ -6615,6 +6616,9 @@ "shape-pin": "Pin", "shape-arrow": "Arrow", "shape-none": "None", + "type-line": "Line", + "type-bar": "Bar", + "type-point": "Point", "threshold": { "thresholds": "Thresholds", "source": "Source", @@ -6627,6 +6631,8 @@ "threshold-settings": "Threshold settings", "remove-threshold": "Remove threshold", "threshold-value-required": "Threshold value is required.", + "key-required": "Key is required.", + "entity-key-required": "Entity key is required.", "line-appearance": "Line appearance", "line-color": "Line color", "start-symbol": "Start symbol", From bd34456baad761d583bbfa3022fa57312dcd709a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 7 Mar 2024 11:07:08 +0200 Subject: [PATCH 158/209] UI: Improve threshold line color settings. --- .../common/chart/time-series-chart-threshold-row.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html index 473afe2608..f82a717d78 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html @@ -75,6 +75,7 @@
From 0d27c18efb768c03839eb6348f8a2034a043ad59 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Thu, 7 Mar 2024 11:14:59 +0200 Subject: [PATCH 159/209] UI: refactoring widgetActionTypes --- .../lib/settings/common/action/widget-action.component.ts | 3 ++- ui-ngx/src/app/shared/models/widget.models.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts index b7183a5b80..6c515ad0a7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts @@ -28,6 +28,7 @@ import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@an import { WidgetAction, WidgetActionType, + widgetActionTypes, widgetActionTypeTranslationMap, widgetType } from '@shared/models/widget.models'; @@ -88,7 +89,7 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali @Input() callbacks: WidgetActionCallbacks; - widgetActionTypes = Object.keys(WidgetActionType); + widgetActionTypes = widgetActionTypes; widgetActionTypeTranslations = widgetActionTypeTranslationMap; widgetActionType = WidgetActionType; diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 5f4ca8f7be..de875c02ae 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -553,6 +553,8 @@ export enum WidgetMobileActionType { takeScreenshot = 'takeScreenshot' } +export const widgetActionTypes = Object.keys(WidgetActionType); + export const widgetActionTypeTranslationMap = new Map( [ [ WidgetActionType.openDashboardState, 'widget-action.open-dashboard-state' ], From ef385640ff7d2c921dfb56d06d86ed92fd41a412 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 7 Mar 2024 14:21:01 +0200 Subject: [PATCH 160/209] UI: Implement no aggregated data bar width strategies --- ...e-series-chart-basic-config.component.html | 27 +++++++++++ ...ime-series-chart-basic-config.component.ts | 35 ++++++++++++-- .../lib/chart/time-series-chart-bar.models.ts | 30 ++++++++++-- .../lib/chart/time-series-chart.models.ts | 37 ++++++++++++++- .../widget/lib/chart/time-series-chart.ts | 13 ++++-- ...eries-chart-widget-settings.component.html | 27 +++++++++++ ...-series-chart-widget-settings.component.ts | 46 +++++++++++++++++-- .../components/toggle-select.component.ts | 2 +- .../assets/locale/locale.constant-en_US.json | 5 ++ 9 files changed, 204 insertions(+), 18 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html index ac6c07290c..5b14146d0f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html @@ -114,6 +114,33 @@ axisType="xAxis">
+
+
+
widgets.time-series-chart.no-aggregation-bar-width-strategy
+ + + {{ timeSeriesChartNoAggregationBarWidthStrategyTranslations.get(strategy) | translate }} + + +
+
+
widgets.time-series-chart.bar-group-interval-width
+ + + ms + +
+
+
widgets.time-series-chart.separate-bar-width
+ + + ms + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts index a2d29269d3..79aa52fdb6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts @@ -45,7 +45,12 @@ import { TimeSeriesChartWidgetSettings } from '@home/components/widget/lib/chart/time-series-chart-widget.models'; import { EChartsTooltipTrigger } from '@home/components/widget/lib/chart/echarts-widget.models'; -import { TimeSeriesChartType } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { + timeSeriesChartNoAggregationBarWidthStrategies, + TimeSeriesChartNoAggregationBarWidthStrategy, + timeSeriesChartNoAggregationBarWidthStrategyTranslations, + TimeSeriesChartType +} from '@home/components/widget/lib/chart/time-series-chart.models'; @Component({ selector: 'tb-time-series-chart-basic-config', @@ -71,13 +76,19 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon legendPositionTranslationMap = legendPositionTranslationMap; + TimeSeriesChartNoAggregationBarWidthStrategy = TimeSeriesChartNoAggregationBarWidthStrategy; + + timeSeriesChartNoAggregationBarWidthStrategies = timeSeriesChartNoAggregationBarWidthStrategies; + + timeSeriesChartNoAggregationBarWidthStrategyTranslations = timeSeriesChartNoAggregationBarWidthStrategyTranslations; + timeSeriesChartWidgetConfigForm: UntypedFormGroup; tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this); - chartType = TimeSeriesChartType.default; + chartType: TimeSeriesChartType = TimeSeriesChartType.default; constructor(protected store: Store, protected widgetConfigComponent: WidgetConfigComponent, @@ -129,6 +140,12 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon yAxis: [settings.yAxis, []], xAxis: [settings.xAxis, []], + noAggregationBarWidthSettings: this.fb.group({ + strategy: [settings.noAggregationBarWidthSettings.strategy, []], + groupIntervalWidth: [settings.noAggregationBarWidthSettings.groupIntervalWidth, [Validators.min(100)]], + separateBarWidth: [settings.noAggregationBarWidthSettings.separateBarWidth, [Validators.min(100)]], + }), + showLegend: [settings.showLegend, []], legendLabelFont: [settings.legendLabelFont, []], legendLabelColor: [settings.legendLabelColor, []], @@ -181,6 +198,8 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon this.widgetConfig.config.settings.yAxis = config.yAxis; this.widgetConfig.config.settings.xAxis = config.xAxis; + this.widgetConfig.config.settings.noAggregationBarWidthSettings = config.noAggregationBarWidthSettings; + this.widgetConfig.config.settings.showLegend = config.showLegend; this.widgetConfig.config.settings.legendLabelFont = config.legendLabelFont; this.widgetConfig.config.settings.legendLabelColor = config.legendLabelColor; @@ -208,7 +227,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon } protected validatorTriggers(): string[] { - return ['showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate']; + return ['showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate', 'noAggregationBarWidthSettings.strategy']; } protected updateValidators(emitEvent: boolean, trigger?: string) { @@ -217,6 +236,8 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon const showLegend: boolean = this.timeSeriesChartWidgetConfigForm.get('showLegend').value; const showTooltip: boolean = this.timeSeriesChartWidgetConfigForm.get('showTooltip').value; const tooltipShowDate: boolean = this.timeSeriesChartWidgetConfigForm.get('tooltipShowDate').value; + const noAggregationBarWidthSettingsStrategy: TimeSeriesChartNoAggregationBarWidthStrategy = + this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('strategy').value; if (showTitle) { this.timeSeriesChartWidgetConfigForm.get('title').enable(); @@ -245,6 +266,14 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon this.timeSeriesChartWidgetConfigForm.get('iconColor').disable(); } + if (noAggregationBarWidthSettingsStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.group) { + this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('groupIntervalWidth').enable(); + this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('separateBarWidth').disable(); + } else if (noAggregationBarWidthSettingsStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate) { + this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('groupIntervalWidth').disable(); + this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('separateBarWidth').enable(); + } + if (showLegend) { this.timeSeriesChartWidgetConfigForm.get('legendLabelFont').enable(); this.timeSeriesChartWidgetConfigForm.get('legendLabelColor').enable(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts index 4bf89a4cde..e5dfd6944e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts @@ -17,7 +17,11 @@ import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; import { Interval, IntervalMath } from '@shared/models/time/time.models'; import { LabelFormatterCallback, SeriesLabelOption } from 'echarts/types/src/util/types'; -import { TimeSeriesChartDataItem } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { + TimeSeriesChartDataItem, + TimeSeriesChartNoAggregationBarWidthSettings, + TimeSeriesChartNoAggregationBarWidthStrategy +} from '@home/components/widget/lib/chart/time-series-chart.models'; import { CustomSeriesRenderItemParams } from 'echarts'; import { CustomSeriesRenderItemAPI, CustomSeriesRenderItemReturn } from 'echarts/types/dist/shared'; import { isNumeric } from '@core/utils'; @@ -33,6 +37,8 @@ export interface BarVisualSettings { export interface BarRenderContext { barsCount?: number; barIndex?: number; + noAggregation: boolean; + noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings; timeInterval?: Interval; visualSettings?: BarVisualSettings; labelOption?: SeriesLabelOption; @@ -44,19 +50,35 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C renderCtx: BarRenderContext): CustomSeriesRenderItemReturn => { const time = api.value(0) as number; let start = api.value(2) as number; - const end = api.value(3) as number; + let end = api.value(3) as number; let interval = end - start; const ts = start ? start : time; + + const noAggregationGroup = renderCtx.noAggregation && + renderCtx.noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group; + const separateBar = renderCtx.noAggregation && + renderCtx.noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate; + + if (renderCtx.noAggregation) { + if (noAggregationGroup) { + interval = renderCtx.noAggregationBarWidthSettings.groupIntervalWidth; + } else { + interval = renderCtx.noAggregationBarWidthSettings.separateBarWidth; + } + start = time - interval / 2; + end = time + interval / 2; + } if (!start || !end || !interval) { interval = IntervalMath.numberValue(renderCtx.timeInterval); start = time - interval / 2; } + const gap = 0.3; - const barInterval = interval / (renderCtx.barsCount + gap * (renderCtx.barsCount + 3)); + const barInterval = separateBar ? interval : interval / (renderCtx.barsCount + gap * (renderCtx.barsCount + 3)); const intervalGap = barInterval * gap * 2; const barGap = barInterval * gap; const value = api.value(1); - const startTime = start + intervalGap + (barInterval + barGap) * renderCtx.barIndex; + const startTime = separateBar ? start : start + intervalGap + (barInterval + barGap) * renderCtx.barIndex; const delta = barInterval; let offset = 0; if (renderCtx.currentStackItems?.length) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index ad2083902c..6aac959cbe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -376,6 +376,27 @@ export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold = labelColor: timeSeriesChartColorScheme['threshold.label'].light }; +export enum TimeSeriesChartNoAggregationBarWidthStrategy { + group = 'group', + separate = 'separate' +} + +export const timeSeriesChartNoAggregationBarWidthStrategies = + Object.keys(TimeSeriesChartNoAggregationBarWidthStrategy) as TimeSeriesChartNoAggregationBarWidthStrategy[]; + +export const timeSeriesChartNoAggregationBarWidthStrategyTranslations = new Map( + [ + [TimeSeriesChartNoAggregationBarWidthStrategy.group, 'widgets.time-series-chart.no-aggregation-bar-width-strategy-group'], + [TimeSeriesChartNoAggregationBarWidthStrategy.separate, 'widgets.time-series-chart.no-aggregation-bar-width-strategy-separate'] + ] +); + +export interface TimeSeriesChartNoAggregationBarWidthSettings { + strategy: TimeSeriesChartNoAggregationBarWidthStrategy; + groupIntervalWidth?: number; + separateBarWidth?: number; +} + export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings { thresholds: TimeSeriesChartThreshold[]; darkMode: boolean; @@ -383,6 +404,7 @@ export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings { stack: boolean; yAxis: TimeSeriesChartYAxisSettings; xAxis: TimeSeriesChartAxisSettings; + noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings; } export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { @@ -450,6 +472,11 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { showSplitLines: true, splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light }, + noAggregationBarWidthSettings: { + strategy: TimeSeriesChartNoAggregationBarWidthStrategy.group, + groupIntervalWidth: 1000, + separateBarWidth: 1000 + }, showTooltip: true, tooltipTrigger: EChartsTooltipTrigger.axis, tooltipValueFont: { @@ -730,9 +757,12 @@ export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartAxisSetting export const generateChartData = (dataItems: TimeSeriesChartDataItem[], thresholdItems: TimeSeriesChartThresholdItem[], timeInterval: Interval, + noAggregation: boolean, + noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings, stack: boolean, darkMode: boolean): Array => { - let series = generateChartSeries(dataItems, timeInterval, stack, darkMode); + let series = generateChartSeries(dataItems, timeInterval, + noAggregation, noAggregationBarWidthSettings, stack, darkMode); if (thresholdItems.length) { const thresholds = generateChartThresholds(thresholdItems, darkMode); series = series.concat(thresholds); @@ -844,6 +874,8 @@ const createThresholdData = (val: string | number, item: TimeSeriesChartThreshol const generateChartSeries = (dataItems: TimeSeriesChartDataItem[], timeInterval: Interval, + noAggregation: boolean, + noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings, stack: boolean, darkMode: boolean): Array => { const series: Array = []; @@ -863,8 +895,9 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[], for (const item of enabledDataItems) { if (item.dataKey.settings.type === TimeSeriesChartSeriesType.bar) { if (!item.barRenderContext) { - item.barRenderContext = {}; + item.barRenderContext = {noAggregation, noAggregationBarWidthSettings}; } + item.barRenderContext.noAggregation = noAggregation; item.barRenderContext.barsCount = barsCount; item.barRenderContext.barIndex = stack ? barGroups.indexOf(item.yAxisIndex) : barDataItems.indexOf(item); item.barRenderContext.timeInterval = timeInterval; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 4b9bc3ba84..e5d4cb8f46 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -88,6 +88,10 @@ export class TbTimeSeriesChart { }; } + private get noAggregation(): boolean { + return this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE; + } + private readonly shapeResize$: ResizeObserver; private dataItems: TimeSeriesChartDataItem[] = []; @@ -153,7 +157,7 @@ export class TbTimeSeriesChart { this.timeSeriesChartOptions.xAxis[0].min = this.ctx.defaultSubscription.timeWindow.minTime; this.timeSeriesChartOptions.xAxis[0].max = this.ctx.defaultSubscription.timeWindow.maxTime; this.timeSeriesChartOptions.xAxis[0].tbTimeWindow = this.ctx.defaultSubscription.timeWindow; - if (this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE) { + if (this.noAggregation) { this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'line'; } else { this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'shadow'; @@ -414,7 +418,6 @@ export class TbTimeSeriesChart { this.timeSeriesChart = echarts.init(this.chartElement, null, { renderer: 'canvas' }); - const noAggregation = this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE; this.timeSeriesChartOptions = { darkMode: this.darkMode, backgroundColor: 'transparent', @@ -423,7 +426,7 @@ export class TbTimeSeriesChart { confine: true, appendToBody: true, axisPointer: { - type: noAggregation ? 'line' : 'shadow' + type: this.noAggregation ? 'line' : 'shadow' }, formatter: (params: CallbackDataParams[]) => this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat, @@ -488,7 +491,9 @@ export class TbTimeSeriesChart { private updateSeries(): Array { return generateChartData(this.dataItems, this.thresholdItems, - this.ctx.timeWindow.interval, this.settings.stack, this.darkMode); + this.ctx.timeWindow.interval, + this.noAggregation, + this.settings.noAggregationBarWidthSettings, this.settings.stack, this.darkMode); } private updateAxes() { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html index 2d8294987e..f3a9a610c3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html @@ -48,6 +48,33 @@ axisType="xAxis">
+
+
+
widgets.time-series-chart.no-aggregation-bar-width-strategy
+ + + {{ timeSeriesChartNoAggregationBarWidthStrategyTranslations.get(strategy) | translate }} + + +
+
+
widgets.time-series-chart.bar-group-interval-width
+ + + ms + +
+
+
widgets.time-series-chart.separate-bar-width
+ + + ms + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts index 0321d07ba8..a520933f1b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts @@ -22,10 +22,10 @@ import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { formatValue, mergeDeep } from '@core/utils'; +import { formatValue, isDefinedAndNotNull, mergeDeep } from '@core/utils'; import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models'; import { barChartWithLabelsDefaultSettings @@ -34,6 +34,12 @@ import { EChartsTooltipTrigger } from '../../chart/echarts-widget.models'; import { timeSeriesChartWidgetDefaultSettings, TimeSeriesChartWidgetSettings } from '@home/components/widget/lib/chart/time-series-chart-widget.models'; +import { + timeSeriesChartNoAggregationBarWidthStrategies, + TimeSeriesChartNoAggregationBarWidthStrategy, + timeSeriesChartNoAggregationBarWidthStrategyTranslations, TimeSeriesChartType +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; @Component({ selector: 'tb-time-series-chart-widget-settings', @@ -51,18 +57,28 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon } } + TimeSeriesChartType = TimeSeriesChartType; + EChartsTooltipTrigger = EChartsTooltipTrigger; legendPositions = legendPositions; legendPositionTranslationMap = legendPositionTranslationMap; + TimeSeriesChartNoAggregationBarWidthStrategy = TimeSeriesChartNoAggregationBarWidthStrategy; + + timeSeriesChartNoAggregationBarWidthStrategies = timeSeriesChartNoAggregationBarWidthStrategies; + + timeSeriesChartNoAggregationBarWidthStrategyTranslations = timeSeriesChartNoAggregationBarWidthStrategyTranslations; + timeSeriesChartWidgetSettingsForm: UntypedFormGroup; tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this); + chartType: TimeSeriesChartType = TimeSeriesChartType.default; + constructor(protected store: Store, private $injector: Injector, private fb: UntypedFormBuilder) { @@ -73,6 +89,13 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon return this.timeSeriesChartWidgetSettingsForm; } + protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) { + const params = widgetConfig.typeParameters as any; + if (isDefinedAndNotNull(params.chartType)) { + this.chartType = params.chartType; + } + } + protected defaultSettings(): WidgetSettings { return mergeDeep({} as TimeSeriesChartWidgetSettings, timeSeriesChartWidgetDefaultSettings); } @@ -88,6 +111,12 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon yAxis: [settings.yAxis, []], xAxis: [settings.xAxis, []], + noAggregationBarWidthSettings: this.fb.group({ + strategy: [settings.noAggregationBarWidthSettings.strategy, []], + groupIntervalWidth: [settings.noAggregationBarWidthSettings.groupIntervalWidth, [Validators.min(100)]], + separateBarWidth: [settings.noAggregationBarWidthSettings.separateBarWidth, [Validators.min(100)]], + }), + showLegend: [settings.showLegend, []], legendLabelFont: [settings.legendLabelFont, []], legendLabelColor: [settings.legendLabelColor, []], @@ -111,13 +140,23 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon } protected validatorTriggers(): string[] { - return ['showLegend', 'showTooltip', 'tooltipShowDate']; + return ['showLegend', 'showTooltip', 'tooltipShowDate', 'noAggregationBarWidthSettings.strategy']; } protected updateValidators(emitEvent: boolean) { const showLegend: boolean = this.timeSeriesChartWidgetSettingsForm.get('showLegend').value; const showTooltip: boolean = this.timeSeriesChartWidgetSettingsForm.get('showTooltip').value; const tooltipShowDate: boolean = this.timeSeriesChartWidgetSettingsForm.get('tooltipShowDate').value; + const noAggregationBarWidthSettingsStrategy: TimeSeriesChartNoAggregationBarWidthStrategy = + this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('strategy').value; + + if (noAggregationBarWidthSettingsStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.group) { + this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('groupIntervalWidth').enable(); + this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('separateBarWidth').disable(); + } else if (noAggregationBarWidthSettingsStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate) { + this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('groupIntervalWidth').disable(); + this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('separateBarWidth').enable(); + } if (showLegend) { this.timeSeriesChartWidgetSettingsForm.get('legendLabelFont').enable(); @@ -170,5 +209,4 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon processor.update(Date.now()); return processor.formatted; } - } diff --git a/ui-ngx/src/app/shared/components/toggle-select.component.ts b/ui-ngx/src/app/shared/components/toggle-select.component.ts index 9be788c08d..484ea19b2e 100644 --- a/ui-ngx/src/app/shared/components/toggle-select.component.ts +++ b/ui-ngx/src/app/shared/components/toggle-select.component.ts @@ -43,7 +43,7 @@ export class ToggleSelectComponent extends _ToggleBase implements ControlValueAc disabled: boolean; @Input() - selectMediaBreakpoint; + selectMediaBreakpoint: string; @Input() appearance: ToggleHeaderAppearance = 'stroked'; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 4a58bf239c..b32cfb7d38 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6626,6 +6626,11 @@ "type-line": "Line", "type-bar": "Bar", "type-point": "Point", + "no-aggregation-bar-width-strategy": "Bar width strategy for non-aggregated data", + "no-aggregation-bar-width-strategy-group": "Group", + "no-aggregation-bar-width-strategy-separate": "Separate", + "bar-group-interval-width": "Bar group interval width", + "separate-bar-width": "Separate bar width", "threshold": { "thresholds": "Thresholds", "source": "Source", From 51b70f9618c938bcf13251e693db666aec5abd92 Mon Sep 17 00:00:00 2001 From: kalytka Date: Thu, 7 Mar 2024 16:53:29 +0200 Subject: [PATCH 161/209] Improve Api Usage dashboard --- ui-ngx/src/assets/dashboard/api_usage.json | 12725 ++++++++++------ .../assets/locale/locale.constant-en_US.json | 3 + 2 files changed, 7867 insertions(+), 4861 deletions(-) diff --git a/ui-ngx/src/assets/dashboard/api_usage.json b/ui-ngx/src/assets/dashboard/api_usage.json index b1cea42249..5618494261 100644 --- a/ui-ngx/src/assets/dashboard/api_usage.json +++ b/ui-ngx/src/assets/dashboard/api_usage.json @@ -1,4926 +1,7929 @@ { - "title": "Api Usage", - "image": null, - "mobileHide": false, - "mobileOrder": null, - "configuration": { - "description": "", - "widgets": { - "fd6df872-2ddf-0921-3929-2e7f55062fad": { - "type": "latest", - "sizeX": 7.5, - "sizeY": 3, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "jsExecutionApiState", - "type": "timeseries", - "label": "jsApiState", - "color": "#2196f3", - "settings": {}, - "_hash": 0.8830669138660703, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return value ? value : 'ENABLED';", - "aggregationType": "NONE" - }, - { - "name": "jsExecutionLimit", - "type": "timeseries", - "label": "jsLimit", - "color": "#4caf50", - "settings": {}, - "_hash": 0.5463603803546802, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null, - "aggregationType": "NONE" - }, - { - "name": "jsExecutionCount", - "type": "timeseries", - "label": "jsCount", - "color": "#f44336", - "settings": {}, - "_hash": 0.5564241862015964, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null, - "aggregationType": "NONE" + "title": "Api Usage", + "image": null, + "mobileHide": false, + "mobileOrder": null, + "configuration": { + "description": "", + "widgets": { + "a669cf86-e715-efa4-dd9a-b839abf499e9": { + "type": "timeseries", + "sizeX": 24, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "dataKeys": [ + { + "name": "ruleEngineException", + "type": "timeseries", + "label": "Rule Chain", + "color": "#2196f3", + "settings": { + "useCellStyleFunction": false, + "useCellContentFunction": true, + "cellContentFunction": "return JSON.parse(value).ruleChainName;" + }, + "_hash": 0.9954481282345906 + }, + { + "name": "ruleEngineException", + "type": "timeseries", + "label": "Rule Node", + "color": "#4caf50", + "settings": { + "useCellStyleFunction": false, + "useCellContentFunction": true, + "cellContentFunction": "return JSON.parse(value).ruleNodeName;" + }, + "_hash": 0.18580357036589978 + }, + { + "name": "ruleEngineException", + "type": "timeseries", + "label": "Latest Error", + "color": "#f44336", + "settings": { + "useCellStyleFunction": false, + "useCellContentFunction": true, + "cellContentFunction": "return JSON.parse(value).message;" + }, + "_hash": 0.7255162989552142 + } + ], + "entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f" + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 2592000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "showTimestamp": true, + "displayPagination": true, + "defaultPageSize": 10 + }, + "title": "{i18n:api-usage.exceptions}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "useDashboardTimewindow": false, + "showLegend": false, + "widgetStyle": {}, + "actions": {}, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "displayTimewindow": true, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "" }, - { - "name": "jsExecutionApiState", - "type": "timeseries", - "label": "cardId", - "color": "#607d8b", - "settings": {}, - "_hash": 0.051659774305067296, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return (Math.random()*100000).toFixed(0);" + "id": "a669cf86-e715-efa4-dd9a-b839abf499e9", + "typeFullFqn": "system.cards.timeseries_table" + }, + "aab68ab5-8e40-8694-c55c-8eb1c89b88fb": { + "typeFullFqn": "system.cards.markdown_card", + "type": "latest", + "sizeX": 5, + "sizeY": 3.5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "transportMsgLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "transportMsgCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;\n", + "aggregationType": "NONE" + }, + { + "name": "transportDataPointsLimit", + "type": "timeseries", + "label": "pointsLimit", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.22082255831864894, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "transportDataPointsCount", + "type": "timeseries", + "label": "pointsCount", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.6340356364819146, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "transportApiState", + "type": "timeseries", + "label": "title", + "color": "#3f51b5", + "settings": {}, + "_hash": 0.6894070537030252, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.transport}\";" + }, + { + "name": "transportApiState", + "type": "timeseries", + "label": "apiStatus", + "color": "#3f51b5", + "settings": {}, + "_hash": 0.430957831457494, + "aggregationType": "NONE", + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : 'enabled';" + }, + { + "name": "transportApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.662147926074595, + "aggregationType": "NONE", + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return '{i18n:api-usage.messages}';" + }, + { + "name": "transportApiState", + "type": "timeseries", + "label": "pointsUnit", + "color": "#3f51b5", + "settings": {}, + "_hash": 0.44620898738917947, + "aggregationType": "NONE", + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return '{i18n:api-usage.data-points}';" + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "displayValue": "", + "selectedTab": 0, + "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1708518962586, + "endTimeMs": 1708605362586 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "useMarkdownTextFunction": true, + "markdownTextPattern": "", + "markdownTextFunction": "function toShortNumber(number) {\n const rounder = Math.pow(10, 1);\n const powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n let key = '';\n for (const power of powers) {\n const reduced = number / power.value;\n for (const power of powers) {\n let reduced = number / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = power.key;\n break;\n }\n }\n }\n \n return number + key;\n}\n\nfunction calculateBarValues(count, limit) {\n let apiUsageBar = '0%';\n let apiUsagePercent = '';\n let apiUsageValue = `${toShortNumber(count)} / ∞`;\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n apiUsageBar = `${percent}%`\n apiUsagePercent = `${percent.toFixed(2)}%`;\n apiUsageValue = `${toShortNumber(count)} / ${toShortNumber(limit)}`;\n }\n \n return [apiUsageBar, apiUsagePercent, apiUsageValue]\n}\n\nconst [apiUsageBar, apiUsagePercent, apiUsageValue] = calculateBarValues(data[0].count, data[0].limit);\nconst [apiUsageBar2, apiUsagePercent2, apiUsageValue2] = calculateBarValues(data[0].pointsCount, data[0].pointsLimit);\n\n\nreturn `
` +\n '
' +\n '
' +\n '
' +\n '
' +\n `${data[0].title}` +\n '
' +\n `
${data[0].apiStatus.toUpperCase()}
` +\n '
' +\n '
' +\n '
' +\n '
' +\n `
${data[0].unit}
` +\n '
' +\n `
` +\n '
' +\n '
' +\n `
${apiUsagePercent}
` +\n '
' +\n `
${apiUsageValue}
` +\n '
' +\n '
' +\n '
' +\n '
' +\n '
' +\n `
${data[0].pointsUnit}
` +\n '
' +\n `
` +\n '
' +\n '
' +\n `
${apiUsagePercent2}
` +\n '
' +\n `
${apiUsageValue2}
` +\n '
' +\n '
' + \n '
' +\n '
' +\n '
' +\n '
' +\n '' +\n '
'+\n '' +\n '
' +\n '
'\n", + "applyDefaultMarkdownStyle": false, + "markdownCss": "\n" + }, + "title": "Transport", + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "showLegend": false, + "useDashboardTimewindow": true, + "displayTimewindow": true, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "", + "actions": { + "elementClick": [ + { + "name": "transport_details", + "icon": "insert_chart", + "useShowWidgetActionFunction": null, + "showWidgetActionFunction": "return true;", + "type": "openDashboardState", + "targetDashboardStateId": "transport", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "openInSeparateDialog": false, + "openInPopover": false, + "id": "a60e09be-1bea-dfc3-6abb-f87e73256899" + } + ] + } }, - { - "name": "jsExecutionApiState", - "type": "timeseries", - "label": "title", - "color": "#9c27b0", - "settings": {}, - "_hash": 0.7673280949238444, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return \"{i18n:api-usage.scripts}\";", - "aggregationType": "NONE" + "row": 0, + "col": 0, + "id": "aab68ab5-8e40-8694-c55c-8eb1c89b88fb" + }, + "a84fa70a-ddfa-3b24-9aa4-cf9ce91f919a": { + "typeFullFqn": "system.cards.markdown_card", + "type": "latest", + "sizeX": 5, + "sizeY": 3.5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "apiStatus", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value : 'enabled';", + "aggregationType": "NONE" + }, + { + "name": "ruleEngineExecutionLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "ruleEngineExecutionCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.3551317421302518, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.rule-engine}\";" + }, + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.5100381746798048, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.executions}\";" + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "displayValue": "", + "selectedTab": 0, + "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1708518962586, + "endTimeMs": 1708605362586 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "useMarkdownTextFunction": true, + "markdownTextPattern": "", + "markdownTextFunction": "function toShortNumber(number) {\n const rounder = Math.pow(10, 1);\n const powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n let key = '';\n for (const power of powers) {\n let reduced = number / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = power.key;\n break;\n }\n }\n \n return number + key;\n}\n\nfunction calculateBarValues(count, limit) {\n let apiUsageBar = '0%';\n let apiUsagePercent = '';\n let apiUsageValue = `${toShortNumber(count)} / ∞`;\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n apiUsageBar = `${percent}%`\n apiUsagePercent = `${percent.toFixed(2)}%`;\n apiUsageValue = `${toShortNumber(count)} / ${toShortNumber(limit)}`;\n }\n \n return [apiUsageBar, apiUsagePercent, apiUsageValue]\n}\n\nconst [apiUsageBar, apiUsagePercent, apiUsageValue] = calculateBarValues(data[0].count, data[0].limit);\n\n\nreturn `
` +\n '
' +\n '
' +\n '
' +\n '
${title}
' +\n `
${data[0].apiStatus.toUpperCase()}
` +\n '
' +\n '
' +\n `
${data[0].unit}
` +\n '
' +\n `
` +\n '
' +\n '
' +\n `
${apiUsagePercent}
` +\n '
' +\n `
${apiUsageValue}
` +\n '
' +\n '
' +\n '
' +\n '
' +\n '' +\n '
'+\n '' +\n '' +\n '
' +\n '
'\n", + "applyDefaultMarkdownStyle": false, + "markdownCss": "\n" + }, + "title": "Rule Engine execution", + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "showLegend": false, + "useDashboardTimewindow": true, + "displayTimewindow": true, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "", + "actions": { + "elementClick": [ + { + "name": "rule_engine_execution_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "rule_engine_execution", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "3c30248f-0cd8-fb97-a917-bc1e09984a79" + }, + { + "name": "rule_engine_statistics_details", + "icon": "show_chart", + "type": "openDashboardState", + "targetDashboardStateId": "rule_engine_statistics", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "04e4565a-9e24-23df-f376-f2ec70a8165f" + } + ] + } }, - { - "name": "jsExecutionApiState", - "type": "timeseries", - "label": "jsUnit", - "color": "#8bc34a", - "settings": {}, - "_hash": 0.7926918686567068, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return \"{i18n:api-usage.javascript}\";", - "aggregationType": "NONE" + "row": 0, + "col": 0, + "id": "a84fa70a-ddfa-3b24-9aa4-cf9ce91f919a" + }, + "d70d26d4-e22d-4ca9-9ea7-f9c87c093321": { + "typeFullFqn": "system.cards.markdown_card", + "type": "latest", + "sizeX": 5, + "sizeY": 3.5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "jsExecutionApiState", + "type": "timeseries", + "label": "jsApiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value : 'ENABLED';", + "aggregationType": "NONE" + }, + { + "name": "jsExecutionLimit", + "type": "timeseries", + "label": "jsLimit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "jsExecutionCount", + "type": "timeseries", + "label": "jsCount", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "jsExecutionApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.7673280949238444, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.scripts}\";", + "aggregationType": "NONE" + }, + { + "name": "jsExecutionApiState", + "type": "timeseries", + "label": "jsUnit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.7926918686567068, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.javascript}\";", + "aggregationType": "NONE" + }, + { + "name": "tbelExecutionApiState", + "type": "timeseries", + "label": "tbelApiState", + "color": "#3f51b5", + "settings": {}, + "_hash": 0.2002981454581909, + "aggregationType": "NONE", + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value : 'ENABLED';" + }, + { + "name": "tbelExecutionLimit", + "type": "timeseries", + "label": "tbelLimit", + "color": "#ffeb3b", + "settings": {}, + "_hash": 0.5039854873031677, + "aggregationType": "NONE", + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;" + }, + { + "name": "tbelExecutionCount", + "type": "timeseries", + "label": "tbelCount", + "color": "#e91e63", + "settings": {}, + "_hash": 0.9506731992087107, + "aggregationType": "NONE", + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;" + }, + { + "name": "tbelExecutionApiState", + "type": "timeseries", + "label": "tbelUnit", + "color": "#ffeb3b", + "settings": {}, + "_hash": 0.3673530683177082, + "aggregationType": "NONE", + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.tbel}\";" + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "displayValue": "", + "selectedTab": 0, + "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1708518962586, + "endTimeMs": 1708605362586 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "useMarkdownTextFunction": true, + "markdownTextPattern": "", + "markdownTextFunction": "function toShortNumber(number) {\n const rounder = Math.pow(10, 1);\n const powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n let key = '';\n for (const power of powers) {\n const reduced = number / power.value;\n for (const power of powers) {\n let reduced = number / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = power.key;\n break;\n }\n }\n }\n \n return number + key;\n}\n\nfunction calculateBarValues(count, limit) {\n let apiUsageBar = '0%';\n let apiUsagePercent = '';\n let apiUsageValue = `${toShortNumber(count)} / ∞`;\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n apiUsageBar = `${percent}%`\n apiUsagePercent = `${percent.toFixed(2)}%`;\n apiUsageValue = `${toShortNumber(count)} / ${toShortNumber(limit)}`;\n }\n \n return [apiUsageBar, apiUsagePercent, apiUsageValue]\n}\n\nconst [jsUsageBar, jsUsagePercent, jsUsageValue] = calculateBarValues(data[0].jsCount, data[0].jsLimit);\nconst [tbelUsageBar, tbelUsagePercent, tbelUsageValue] = calculateBarValues(data[0].tbelCount, data[0].tbelLimit);\n\nconst jsApiState = data[0].jsApiState;\nconst tbelApiState = data[0].tbelApiState;\nlet currentState;\nif (jsApiState === 'DISABLED' || tbelApiState === 'DISABLED') {\n currentState = 'DISABLED';\n} else if (jsApiState === 'WARNING' || tbelApiState === 'WARNING') {\n currentState = 'WARNING';\n} else {\n currentState = 'ENABLED';\n}\nconst cardClass = currentState.toLowerCase()\n\nreturn `
` +\n '
' +\n '
' +\n '
' +\n '
' +\n `${data[0].title}` +\n '
' +\n `
${currentState}
` +\n '
' +\n '
' +\n '
' +\n '
' +\n `
${data[0].jsUnit}
` +\n '
' +\n `
` +\n '
' +\n '
' +\n `
${jsUsagePercent}
` +\n '
' +\n `
${jsUsageValue}
` +\n '
' +\n '
' +\n '
' +\n '
' +\n '
' +\n `
${data[0].tbelUnit}
` +\n '
' +\n `
` +\n '
' +\n '
' +\n `
${tbelUsagePercent}
` +\n '
' +\n `
${tbelUsageValue}
` +\n '
' +\n '
' + \n '
' +\n '
' +\n '
' +\n '
' +\n '' +\n '
'+\n '' +\n '
' +\n '
'\n", + "applyDefaultMarkdownStyle": false, + "markdownCss": "\n" + }, + "title": "JavaScript functions", + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "showLegend": false, + "useDashboardTimewindow": true, + "displayTimewindow": true, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "", + "actions": { + "elementClick": [ + { + "name": "script_functions_details", + "icon": "insert_chart", + "useShowWidgetActionFunction": null, + "showWidgetActionFunction": "return true;", + "type": "openDashboardState", + "targetDashboardStateId": "script_functions", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "openInSeparateDialog": false, + "openInPopover": false, + "id": "d4961bea-84de-e1af-e50f-666b98d34cd5" + } + ] + } }, - { - "name": "tbelExecutionApiState", - "type": "timeseries", - "label": "tbelApiState", - "color": "#3f51b5", - "settings": {}, - "_hash": 0.2002981454581909, - "aggregationType": "NONE", - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return value ? value : 'ENABLED';" + "row": 0, + "col": 0, + "id": "d70d26d4-e22d-4ca9-9ea7-f9c87c093321" + }, + "4d3ea95c-3188-9872-1817-2f989c7729e0": { + "typeFullFqn": "system.cards.markdown_card", + "type": "latest", + "sizeX": 5, + "sizeY": 3.5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "storageDataPointsLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "storageDataPointsCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "dbApiState", + "type": "timeseries", + "label": "apiStatus", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value : 'enabled';", + "aggregationType": "NONE" + }, + { + "name": "dbApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.6301889725474652, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.telemetry}\";" + }, + { + "name": "dbApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.0027742924142306613, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.data-points-storage-days}\";" + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "displayValue": "", + "selectedTab": 0, + "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1708518962586, + "endTimeMs": 1708605362586 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "useMarkdownTextFunction": true, + "markdownTextPattern": "", + "markdownTextFunction": "function toShortNumber(number) {\n const rounder = Math.pow(10, 1);\n const powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n let key = '';\n for (const power of powers) {\n let reduced = number / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = power.key;\n break;\n }\n }\n \n return number + key;\n}\n\nfunction calculateBarValues(count, limit) {\n let apiUsageBar = '0%';\n let apiUsagePercent = '';\n let apiUsageValue = `${toShortNumber(count)} / ∞`;\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n apiUsageBar = `${percent}%`\n apiUsagePercent = `${percent.toFixed(2)}%`;\n apiUsageValue = `${toShortNumber(count)} / ${toShortNumber(limit)}`;\n }\n \n return [apiUsageBar, apiUsagePercent, apiUsageValue]\n}\n\nconst [apiUsageBar, apiUsagePercent, apiUsageValue] = calculateBarValues(data[0].count, data[0].limit);\n\n\nreturn `
` +\n '
' +\n '
' +\n '
' +\n '
${title}
' +\n `
${data[0].apiStatus.toUpperCase()}
` +\n '
' +\n '
' +\n `
${data[0].unit}
` +\n '
' +\n `
` +\n '
' +\n '
' +\n `
${apiUsagePercent}
` +\n '
' +\n `
${apiUsageValue}
` +\n '
' +\n '
' +\n '
' +\n '
' +\n '' +\n '
'+\n '' +\n '
' +\n '
'\n", + "applyDefaultMarkdownStyle": false, + "markdownCss": "\n" + }, + "title": "Telemetry persistence", + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "showLegend": false, + "useDashboardTimewindow": true, + "displayTimewindow": true, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "", + "actions": { + "elementClick": [ + { + "name": "telemetry_persistence_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "telemetry_persistence", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "6248831c-5b3f-8879-8548-afcf43f10610" + } + ] + } }, - { - "name": "tbelExecutionLimit", - "type": "timeseries", - "label": "tbelLimit", - "color": "#ffeb3b", - "settings": {}, - "_hash": 0.5039854873031677, - "aggregationType": "NONE", - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "row": 0, + "col": 0, + "id": "4d3ea95c-3188-9872-1817-2f989c7729e0" + }, + "2d0d6ff6-cd59-51d4-b916-38e22cdd0702": { + "typeFullFqn": "system.cards.markdown_card", + "type": "latest", + "sizeX": 5, + "sizeY": 3.5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "createdAlarmsLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "createdAlarmsCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "alarmApiState", + "type": "timeseries", + "label": "apiStatus", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value : 'enabled';", + "aggregationType": "NONE" + }, + { + "name": "alarmApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.43439375716502227, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.alarm}\";" + }, + { + "name": "alarmApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.9964061963495883, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.alarms-created}\";" + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "displayValue": "", + "selectedTab": 0, + "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1708518962586, + "endTimeMs": 1708605362586 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "useMarkdownTextFunction": true, + "markdownTextPattern": "", + "markdownTextFunction": "function toShortNumber(number) {\n const rounder = Math.pow(10, 1);\n const powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n let key = '';\n for (const power of powers) {\n let reduced = number / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = power.key;\n break;\n }\n }\n \n return number + key;\n}\n\nfunction calculateBarValues(count, limit) {\n let apiUsageBar = '0%';\n let apiUsagePercent = '';\n let apiUsageValue = `${toShortNumber(count)} / ∞`;\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n apiUsageBar = `${percent}%`\n apiUsagePercent = `${percent.toFixed(2)}%`;\n apiUsageValue = `${toShortNumber(count)} / ${toShortNumber(limit)}`;\n }\n \n return [apiUsageBar, apiUsagePercent, apiUsageValue]\n}\n\nconst [apiUsageBar, apiUsagePercent, apiUsageValue] = calculateBarValues(data[0].count, data[0].limit);\n\n\nreturn `
` +\n '
' +\n '
' +\n '
' +\n '
${title}
' +\n `
${data[0].apiStatus.toUpperCase()}
` +\n '
' +\n '
' +\n `
${data[0].unit}
` +\n '
' +\n `
` +\n '
' +\n '
' +\n `
${apiUsagePercent}
` +\n '
' +\n `
${apiUsageValue}
` +\n '
' +\n '
' +\n '
' +\n '
' +\n '' +\n '
'+\n '' +\n '
' +\n '
'\n", + "applyDefaultMarkdownStyle": false, + "markdownCss": "\n" + }, + "title": "Alarm created", + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "showLegend": false, + "useDashboardTimewindow": true, + "displayTimewindow": true, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "", + "actions": { + "elementClick": [ + { + "name": "email_messages_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "alarms_created", + "setEntityId": false, + "stateEntityParamName": null, + "openInSeparateDialog": null, + "dialogTitle": null, + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "946ba769-84ac-1507-6baa-94701de8967b" + } + ] + } }, - { - "name": "tbelExecutionCount", - "type": "timeseries", - "label": "tbelCount", - "color": "#e91e63", - "settings": {}, - "_hash": 0.9506731992087107, - "aggregationType": "NONE", - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "row": 0, + "col": 0, + "id": "2d0d6ff6-cd59-51d4-b916-38e22cdd0702" + }, + "120573cc-e246-eb49-7d80-68e5d3b3c0cc": { + "typeFullFqn": "system.cards.markdown_card", + "type": "latest", + "sizeX": 5, + "sizeY": 3.5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "emailApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "emailLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "emailCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "smsApiState", + "type": "timeseries", + "label": "apiStatePoint", + "color": "#e91e63", + "settings": {}, + "_hash": 0.2969682764607864, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "smsLimit", + "type": "timeseries", + "label": "pointsLimit", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.22082255831864894, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "smsCount", + "type": "timeseries", + "label": "pointsCount", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.6340356364819146, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value !== '' ? parseInt(value, 10) : 0;", + "aggregationType": "NONE" + }, + { + "name": "notificationApiState", + "type": "timeseries", + "label": "title", + "color": "#3f51b5", + "settings": {}, + "_hash": 0.6894070537030252, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.notifications}\";", + "aggregationType": "NONE" + }, + { + "name": "notificationApiState", + "type": "timeseries", + "label": "unit", + "color": "#3f51b5", + "settings": {}, + "_hash": 0.0005447336528170421, + "aggregationType": "NONE", + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return '{i18n:api-usage.email}';" + }, + { + "name": "notificationApiState", + "type": "timeseries", + "label": "pointsUnit", + "color": "#e91e63", + "settings": {}, + "_hash": 0.12117146988088967, + "aggregationType": "NONE", + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return '{i18n:api-usage.sms}';" + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "displayValue": "", + "selectedTab": 0, + "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1708518962586, + "endTimeMs": 1708605362586 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "useMarkdownTextFunction": true, + "markdownTextPattern": "", + "markdownTextFunction": "function toShortNumber(number) {\n const rounder = Math.pow(10, 1);\n const powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n let key = '';\n for (const power of powers) {\n const reduced = number / power.value;\n for (const power of powers) {\n let reduced = number / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = power.key;\n break;\n }\n }\n }\n \n return number + key;\n}\n\nfunction calculateBarValues(count, limit) {\n let apiUsageBar = '0%';\n let apiUsagePercent = '';\n let apiUsageValue = `${toShortNumber(count)} / ∞`;\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n apiUsageBar = `${percent}%`\n apiUsagePercent = `${percent.toFixed(2)}%`;\n apiUsageValue = `${toShortNumber(count)} / ${toShortNumber(limit)}`;\n }\n \n return [apiUsageBar, apiUsagePercent, apiUsageValue]\n}\n\nconst [apiUsageBar, apiUsagePercent, apiUsageValue] = calculateBarValues(data[0].count, data[0].limit);\nconst [apiUsageBar2, apiUsagePercent2, apiUsageValue2] = calculateBarValues(data[0].pointsCount, data[0].pointsLimit);\n\nconst apiState = data[0].apiState;\nconst apiStatePoint = data[0].apiStatePoint;\nlet currentState;\nif (apiState === 'DISABLED' || apiStatePoint === 'DISABLED') {\n currentState = 'DISABLED';\n} else if (apiState === 'WARNING' || apiStatePoint === 'WARNING') {\n currentState = 'WARNING';\n} else {\n currentState = 'ENABLED';\n}\n\n\n\nreturn `
` +\n '
' +\n '
' +\n '
' +\n '
' +\n `${data[0].title}` +\n '
' +\n `
${currentState}
` +\n '
' +\n '
' +\n '
' +\n '
' +\n `
${data[0].unit}
` +\n '
' +\n `
` +\n '
' +\n '
' +\n `
${apiUsagePercent}
` +\n '
' +\n `
${apiUsageValue}
` +\n '
' +\n '
' +\n '
' +\n '
' +\n '
' +\n `
${data[0].pointsUnit}
` +\n '
' +\n `
` +\n '
' +\n '
' +\n `
${apiUsagePercent2}
` +\n '
' +\n `
${apiUsageValue2}
` +\n '
' +\n '
' + \n '
' +\n '
' +\n '
' +\n '
' +\n '' +\n '
'+\n '' +\n '
' +\n '
'\n", + "applyDefaultMarkdownStyle": false, + "markdownCss": "\n" + }, + "title": "Notifications (Email/SMS)", + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "showLegend": false, + "useDashboardTimewindow": true, + "displayTimewindow": true, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "", + "actions": { + "elementClick": [ + { + "name": "transport_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "notifications", + "setEntityId": false, + "stateEntityParamName": null, + "openInSeparateDialog": null, + "dialogTitle": null, + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "46b7cefe-e1f2-67c1-4055-3a214520f869" + } + ] + } }, - { - "name": "tbelExecutionApiState", - "type": "timeseries", - "label": "tbelUnit", - "color": "#ffeb3b", - "settings": {}, - "_hash": 0.3673530683177082, - "aggregationType": "NONE", - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return \"{i18n:api-usage.tbel}\";" - } - ] - } - ], - "timewindow": { - "displayValue": "", - "selectedTab": 0, - "realtime": { - "realtimeType": 1, - "interval": 1000, - "timewindowMs": 60000, - "quickInterval": "CURRENT_DAY" - }, - "history": { - "historyType": 0, - "interval": 1000, - "timewindowMs": 60000, - "fixedTimewindow": { - "startTimeMs": 1694083375825, - "endTimeMs": 1694169775825 - }, - "quickInterval": "CURRENT_DAY" + "row": 0, + "col": 0, + "id": "120573cc-e246-eb49-7d80-68e5d3b3c0cc" }, - "aggregation": { - "type": "AVG", - "limit": 25000 - } - }, - "showTitle": false, - "backgroundColor": "#fff", - "color": "#666666", - "padding": "0", - "settings": { - "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
\n
\n
\n
\n
\n
${jsUnit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
${tbelUnit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 6px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} " - }, - "title": "JavaScript functions", - "dropShadow": true, - "enableFullscreen": false, - "widgetStyle": { - "cursor": "default" - }, - "titleStyle": { - "fontSize": "20px", - "fontWeight": 400 - }, - "useDashboardTimewindow": true, - "showLegend": false, - "actions": { - "elementClick": [ - { - "name": "script_functions_details", - "icon": "insert_chart", - "useShowWidgetActionFunction": null, - "showWidgetActionFunction": "return true;", - "type": "openDashboardState", - "targetDashboardStateId": "script_functions", - "setEntityId": false, - "stateEntityParamName": null, - "openRightLayout": false, - "openInSeparateDialog": false, - "openInPopover": false, - "id": "d4961bea-84de-e1af-e50f-666b98d34cd5" - } - ] - }, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "", - "displayTimewindow": true - }, - "row": 0, - "col": 0, - "id": "fd6df872-2ddf-0921-3929-2e7f55062fad", - "typeFullFqn": "system.cards.html_value_card" - }, - "7e235874-461b-e7c2-2fdd-d8762a020773": { - "type": "latest", - "sizeX": 7.5, - "sizeY": 3, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "dbApiState", - "type": "timeseries", - "label": "apiState", - "color": "#2196f3", - "settings": {}, - "_hash": 0.8830669138660703, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return value ? value : 'ENABLED';" - }, - { - "name": "storageDataPointsLimit", - "type": "timeseries", - "label": "limit", - "color": "#4caf50", - "settings": {}, - "_hash": 0.5463603803546802, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "storageDataPointsCount", - "type": "timeseries", - "label": "count", - "color": "#f44336", - "settings": {}, - "_hash": 0.5564241862015964, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "dbApiState", - "type": "timeseries", - "label": "apiStateClass", - "color": "#ffc107", - "settings": {}, - "_hash": 0.8737107059960671, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return value ? value.toLowerCase() : 'enabled';" - }, - { - "name": "dbApiState", - "type": "timeseries", - "label": "cardId", - "color": "#607d8b", - "settings": {}, - "_hash": 0.051659774305067296, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return (Math.random()*100000).toFixed(0);" - }, - { - "name": "dbApiState", - "type": "timeseries", - "label": "title", - "color": "#9c27b0", - "settings": {}, - "_hash": 0.6301889725474652, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return \"{i18n:api-usage.telemetry}\";" + "63f99d90-23ab-f8c2-3290-1e693ded5a2e": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "transportMsgCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.transport-messages}", + "color": "#2196f3", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": false, + "postFuncBody": null, + "aggregationType": null + }, + { + "name": "transportDataPointsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.transport-data-points}", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.46849996721308895, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY", + "interval": 300000 + }, + "aggregation": { + "type": "NONE", + "limit": 50000 + }, + "timezone": null + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 2400000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.transport-hourly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": { + "headerButton": [ + { + "name": "{i18n:api-usage.view-details}", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "transport", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "6ef12f6a-0266-25cf-6ca5-5dcb772252c6" + } + ] + }, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "dbApiState", - "type": "timeseries", - "label": "unit", - "color": "#8bc34a", - "settings": {}, - "_hash": 0.0027742924142306613, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return \"{i18n:api-usage.data-points-storage-days}\";" - } - ] - } - ], - "timewindow": { - "displayValue": "", - "selectedTab": 0, - "realtime": { - "realtimeType": 1, - "interval": 1000, - "timewindowMs": 60000, - "quickInterval": "CURRENT_DAY" - }, - "history": { - "historyType": 0, - "interval": 1000, - "timewindowMs": 60000, - "fixedTimewindow": { - "startTimeMs": 1694083375826, - "endTimeMs": 1694169775826 - }, - "quickInterval": "CURRENT_DAY" + "row": 0, + "col": 0, + "id": "63f99d90-23ab-f8c2-3290-1e693ded5a2e" }, - "aggregation": { - "type": "AVG", - "limit": 25000 - } - }, - "showTitle": false, - "backgroundColor": "#fff", - "color": "#666666", - "padding": "0", - "settings": { - "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" - }, - "title": "Telemetry persistence", - "dropShadow": true, - "enableFullscreen": false, - "widgetStyle": { - "cursor": "default" - }, - "titleStyle": { - "fontSize": "20px", - "fontWeight": 400 - }, - "useDashboardTimewindow": true, - "showLegend": false, - "actions": { - "elementClick": [ - { - "name": "telemetry_persistence_details", - "icon": "insert_chart", - "type": "openDashboardState", - "targetDashboardStateId": "telemetry_persistence", - "setEntityId": false, - "stateEntityParamName": null, - "openRightLayout": false, - "id": "6248831c-5b3f-8879-8548-afcf43f10610" - } - ] - }, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "7e235874-461b-e7c2-2fdd-d8762a020773", - "typeFullFqn": "system.cards.html_value_card" - }, - "08545554-a0e8-05c7-66df-6000cfeff8a4": { - "type": "latest", - "sizeX": 7.5, - "sizeY": 3, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "ruleEngineApiState", - "type": "timeseries", - "label": "apiState", - "color": "#2196f3", - "settings": {}, - "_hash": 0.8830669138660703, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return value ? value : 'ENABLED';" - }, - { - "name": "ruleEngineExecutionLimit", - "type": "timeseries", - "label": "limit", - "color": "#4caf50", - "settings": {}, - "_hash": 0.5463603803546802, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "ruleEngineExecutionCount", - "type": "timeseries", - "label": "count", - "color": "#f44336", - "settings": {}, - "_hash": 0.5564241862015964, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "a2b7e906-2d8a-41a8-99a6-409531bfa743": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "ruleEngineExecutionCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.rule-engine-executions}", + "color": "#ab00ff", + "settings": { + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "bar", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "emptyCircle", + "pointSize": 4, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null, + "aggregationType": null + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_YEAR", + "interval": 7200000 + }, + "aggregation": { + "type": "NONE", + "limit": 50000 + }, + "timezone": null + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 2400000, + "separateBarWidth": 2400000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.rule-engine-hourly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": { + "headerButton": [ + { + "name": "{i18n:api-usage.view-statistics}", + "icon": "show_chart", + "type": "openDashboardState", + "targetDashboardStateId": "rule_engine_statistics", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "f9f08190-9ed9-d802-5b7a-c57ff84b5648" + }, + { + "name": "{i18n:api-usage.view-details}", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "rule_engine_execution", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "1aec196b-44ba-ddf4-c4dc-c3f60c1eb6fc" + } + ] + }, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "ruleEngineApiState", - "type": "timeseries", - "label": "apiStateClass", - "color": "#ffc107", - "settings": {}, - "_hash": 0.8737107059960671, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return value ? value.toLowerCase() : 'enabled';" + "row": 0, + "col": 0, + "id": "a2b7e906-2d8a-41a8-99a6-409531bfa743" + }, + "ca996b66-ab7e-f977-152c-98e4ebf2a901": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "jsExecutionCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.javascript-executions}", + "color": "#ff9900", + "settings": { + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "bar", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "emptyCircle", + "pointSize": 4, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null, + "aggregationType": null + }, + { + "name": "tbelExecutionCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.tbel-executions}", + "color": "#4caf50", + "settings": { + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "bar", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "emptyCircle", + "pointSize": 4, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.6818645685001823, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY", + "interval": 300000 + }, + "aggregation": { + "type": "NONE", + "limit": 50000 + }, + "timezone": null + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 2400000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.scripts-hourly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": { + "headerButton": [ + { + "name": "{i18n:api-usage.view-details}", + "icon": "insert_chart", + "useShowWidgetActionFunction": null, + "showWidgetActionFunction": "return true;", + "type": "openDashboardState", + "targetDashboardStateId": "script_functions", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "openInSeparateDialog": false, + "openInPopover": false, + "id": "4687d3f6-8800-a3b6-26e5-0d33f3b828a9" + } + ] + }, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "ruleEngineApiState", - "type": "timeseries", - "label": "cardId", - "color": "#607d8b", - "settings": {}, - "_hash": 0.051659774305067296, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return (Math.random()*100000).toFixed(0);" + "row": 0, + "col": 0, + "id": "ca996b66-ab7e-f977-152c-98e4ebf2a901" + }, + "a3c2f1bb-7d3a-f11c-7b3d-28cd84fdfe34": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "storageDataPointsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.data-points-storage-days}", + "color": "#1039ee", + "settings": { + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "bar", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "emptyCircle", + "pointSize": 4, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null, + "aggregationType": null + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY", + "interval": 300000 + }, + "aggregation": { + "type": "NONE", + "limit": 50000 + }, + "timezone": null + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 2400000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.telemetry-persistence-hourly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": { + "headerButton": [ + { + "name": "{i18n:api-usage.view-details}", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "telemetry_persistence", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "16707efb-e572-bd02-c219-55fc1b0f672a" + } + ] + }, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "ruleEngineApiState", - "type": "timeseries", - "label": "title", - "color": "#9c27b0", - "settings": {}, - "_hash": 0.3551317421302518, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return \"{i18n:api-usage.rule-engine}\";" + "row": 0, + "col": 0, + "id": "a3c2f1bb-7d3a-f11c-7b3d-28cd84fdfe34" + }, + "5cebd4f1-ff6e-62f9-025c-8e7583c3d66a": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "createdAlarmsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.alarms-created}", + "color": "#d35a00", + "settings": { + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "bar", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "emptyCircle", + "pointSize": 4, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null, + "aggregationType": null + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY", + "interval": 300000 + }, + "aggregation": { + "type": "NONE", + "limit": 50000 + }, + "timezone": null + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 2400000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.alarms-created-hourly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": { + "headerButton": [ + { + "name": "{i18n:api-usage.view-details}", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "alarms_created", + "setEntityId": false, + "stateEntityParamName": null, + "openInSeparateDialog": null, + "dialogTitle": null, + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "371882f9-ea23-3abc-fca8-9449c5dfdd6b" + } + ] + }, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "ruleEngineApiState", - "type": "timeseries", - "label": "unit", - "color": "#8bc34a", - "settings": {}, - "_hash": 0.5100381746798048, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return \"{i18n:api-usage.executions}\";" - } - ] - } - ], - "timewindow": { - "displayValue": "", - "selectedTab": 0, - "realtime": { - "realtimeType": 1, - "interval": 1000, - "timewindowMs": 60000, - "quickInterval": "CURRENT_DAY" + "row": 0, + "col": 0, + "id": "5cebd4f1-ff6e-62f9-025c-8e7583c3d66a" }, - "history": { - "historyType": 0, - "interval": 1000, - "timewindowMs": 60000, - "fixedTimewindow": { - "startTimeMs": 1694083375826, - "endTimeMs": 1694169775826 - }, - "quickInterval": "CURRENT_DAY" + "bc0c8840-a9b5-5583-de7b-9e9450f5d8fe": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "emailCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.email-messages}", + "color": "#4caf50", + "settings": { + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "bar", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "emptyCircle", + "pointSize": 4, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.1348755140779876, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null, + "aggregationType": null + }, + { + "name": "smsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.sms-messages}", + "color": "#f36021", + "settings": { + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "bar", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "emptyCircle", + "pointSize": 4, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null, + "aggregationType": null + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY", + "interval": 300000 + }, + "aggregation": { + "type": "NONE", + "limit": 50000 + }, + "timezone": null + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 2400000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.notifications-hourly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": { + "headerButton": [ + { + "name": "{i18n:api-usage.view-details}", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "notifications", + "setEntityId": false, + "stateEntityParamName": null, + "openInSeparateDialog": null, + "dialogTitle": null, + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "49aefac0-ec5e-d6f3-f39c-8744759f4b19" + } + ] + }, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" + }, + "row": 0, + "col": 0, + "id": "bc0c8840-a9b5-5583-de7b-9e9450f5d8fe" }, - "aggregation": { - "type": "AVG", - "limit": 25000 - } - }, - "showTitle": false, - "backgroundColor": "#fff", - "color": "#666666", - "padding": "0", - "settings": { - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n", - "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n
\n
" - }, - "title": "Rule Engine execution", - "dropShadow": true, - "enableFullscreen": false, - "widgetStyle": { - "cursor": "default" - }, - "titleStyle": { - "fontSize": "20px", - "fontWeight": 400 - }, - "useDashboardTimewindow": true, - "showLegend": false, - "actions": { - "elementClick": [ - { - "name": "rule_engine_execution_details", - "icon": "insert_chart", - "type": "openDashboardState", - "targetDashboardStateId": "rule_engine_execution", - "setEntityId": false, - "stateEntityParamName": null, - "openRightLayout": false, - "id": "3c30248f-0cd8-fb97-a917-bc1e09984a79" - }, - { - "name": "rule_engine_statistics_details", - "icon": "show_chart", - "type": "openDashboardState", - "targetDashboardStateId": "rule_engine_statistics", - "setEntityId": false, - "stateEntityParamName": null, - "openRightLayout": false, - "id": "04e4565a-9e24-23df-f376-f2ec70a8165f" - } - ] - }, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "08545554-a0e8-05c7-66df-6000cfeff8a4", - "typeFullFqn": "system.cards.html_value_card" - }, - "a245c67e-53ec-d299-fa89-69fe2062ccb2": { - "type": "latest", - "sizeX": 7.5, - "sizeY": 3, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "transportApiState", - "type": "timeseries", - "label": "apiState", - "color": "#2196f3", - "settings": {}, - "_hash": 0.8830669138660703, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return value ? value : 'ENABLED';" + "0b091dc3-eec3-847e-d0ad-fdf12d474e7a": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "transportMsgCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.transport-messages}", + "color": "#2196f3", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportDataPointsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.transport-data-points}", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.46849996721308895, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000, + "fixedTimewindow": { + "startTimeMs": 1709729389667, + "endTimeMs": 1709815789667 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "SUM", + "limit": 25000 + }, + "timezone": null + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 1800000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.transport-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "transportMsgLimit", - "type": "timeseries", - "label": "limit", - "color": "#4caf50", - "settings": {}, - "_hash": 0.5463603803546802, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "row": 0, + "col": 0, + "id": "0b091dc3-eec3-847e-d0ad-fdf12d474e7a" + }, + "536d7104-49f8-fde6-5827-61b8419f15ec": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "transportMsgCount", + "type": "timeseries", + "label": "{i18n:api-usage.transport-messages}", + "color": "#2196f3", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null, + "aggregationType": null + }, + { + "name": "transportDataPointsCount", + "type": "timeseries", + "label": "{i18n:api-usage.transport-data-points}", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.46849996721308895, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null, + "aggregationType": null + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 86400000, + "fixedTimewindow": { + "startTimeMs": 1709729389667, + "endTimeMs": 1709815789667 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + }, + "timezone": null + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 1800000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.transport-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "transportMsgCount", - "type": "timeseries", - "label": "count", - "color": "#f44336", - "settings": {}, - "_hash": 0.5564241862015964, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "row": 0, + "col": 0, + "id": "536d7104-49f8-fde6-5827-61b8419f15ec" + }, + "c77e417c-ad9d-8e23-3ea1-c75edd653bc0": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "ruleEngineExecutionCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.rule-engine-executions}", + "color": "#ab00ff", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000, + "fixedTimewindow": { + "startTimeMs": 1709729900300, + "endTimeMs": 1709816300300 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "SUM", + "limit": 25000 + }, + "timezone": null + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 1800000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.rule-engine-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "transportApiState", - "type": "timeseries", - "label": "apiStateClass", - "color": "#ffc107", - "settings": {}, - "_hash": 0.8737107059960671, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return value ? value.toLowerCase() : 'enabled';" + "row": 0, + "col": 0, + "id": "c77e417c-ad9d-8e23-3ea1-c75edd653bc0" + }, + "870904d2-d2e1-a1b9-ce56-b03fd47259b5": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "ruleEngineExecutionCount", + "type": "timeseries", + "label": "{i18n:api-usage.rule-engine-executions}", + "color": "#ab00ff", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 900000000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.rule-engine-monthly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "transportApiState", - "type": "timeseries", - "label": "cardId", - "color": "#607d8b", - "settings": {}, - "_hash": 0.051659774305067296, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return (Math.random()*100000).toFixed(0);" + "row": 0, + "col": 0, + "id": "870904d2-d2e1-a1b9-ce56-b03fd47259b5" + }, + "c66e5060-57fd-11e7-6616-65b82c294ac2": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "jsExecutionCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.javascript-executions}", + "color": "#ff9900", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "tbelExecutionCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.tbel-executions}", + "color": "#4caf50", + "settings": { + "type": "bar" + }, + "_hash": 0.5212969314724616, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 1800000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.scripts-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "transportDataPointsLimit", - "type": "timeseries", - "label": "pointsLimit", - "color": "#9c27b0", - "settings": {}, - "_hash": 0.22082255831864894, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "row": 0, + "col": 0, + "id": "c66e5060-57fd-11e7-6616-65b82c294ac2" + }, + "d0e8603e-5d2e-9287-e2c6-8ccbe9c66806": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "jsExecutionCount", + "type": "timeseries", + "label": "{i18n:api-usage.javascript-executions}", + "color": "#ff9900", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "tbelExecutionCount", + "type": "timeseries", + "label": "{i18n:api-usage.tbel-executions}", + "color": "#4caf50", + "settings": { + "type": "bar" + }, + "_hash": 0.49748239768082403, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 900000000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.scripts-monthly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "basic", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "transportDataPointsCount", - "type": "timeseries", - "label": "pointsCount", - "color": "#8bc34a", - "settings": {}, - "_hash": 0.6340356364819146, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "row": 0, + "col": 0, + "id": "d0e8603e-5d2e-9287-e2c6-8ccbe9c66806" + }, + "7f4100d2-41be-4954-d353-1d45000dbbbb": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "storageDataPointsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.data-points-storage-days}", + "color": "#1039ee", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 1800000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.telemetry-persistence-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "transportApiState", - "type": "timeseries", - "label": "title", - "color": "#3f51b5", - "settings": {}, - "_hash": 0.6894070537030252, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return \"{i18n:api-usage.transport}\";" - } - ] - } - ], - "timewindow": { - "displayValue": "", - "selectedTab": 0, - "realtime": { - "realtimeType": 1, - "interval": 1000, - "timewindowMs": 60000, - "quickInterval": "CURRENT_DAY" + "row": 0, + "col": 0, + "id": "7f4100d2-41be-4954-d353-1d45000dbbbb" }, - "history": { - "historyType": 0, - "interval": 1000, - "timewindowMs": 60000, - "fixedTimewindow": { - "startTimeMs": 1694083375826, - "endTimeMs": 1694169775826 - }, - "quickInterval": "CURRENT_DAY" + "226ef8c9-8488-3664-21ac-0b6217336202": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "storageDataPointsCount", + "type": "timeseries", + "label": "{i18n:api-usage.data-points-storage-days}", + "color": "#1039ee", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 900000000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.telemetry-persistence-monthly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "basic", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" + }, + "row": 0, + "col": 0, + "id": "226ef8c9-8488-3664-21ac-0b6217336202" }, - "aggregation": { - "type": "AVG", - "limit": 25000 - } - }, - "showTitle": false, - "backgroundColor": "#fff", - "color": "#666666", - "padding": "0", - "settings": { - "cardHtml": "
\n \n \n
\n
\n
\n
\n ${title}\n
\n
${apiState}
\n
\n
\n
\n
\n
{i18n:api-usage.messages}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
{i18n:api-usage.data-points}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 6px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n" - }, - "title": "Transport", - "dropShadow": true, - "enableFullscreen": false, - "widgetStyle": { - "cursor": "default" - }, - "titleStyle": { - "fontSize": "20px", - "fontWeight": 400 - }, - "useDashboardTimewindow": true, - "showLegend": false, - "actions": { - "elementClick": [ - { - "name": "transport_details", - "icon": "insert_chart", - "type": "openDashboardState", - "targetDashboardStateId": "transport", - "setEntityId": false, - "stateEntityParamName": null, - "openRightLayout": false, - "id": "46b7cefe-e1f2-67c1-4055-3a214520f869" - } - ] - }, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "a245c67e-53ec-d299-fa89-69fe2062ccb2", - "typeFullFqn": "system.cards.html_value_card" - }, - "a151ae60-0326-6116-d818-9070dda8e9c7": { - "type": "latest", - "sizeX": 7.5, - "sizeY": 3, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "alarmApiState", - "type": "timeseries", - "label": "apiState", - "color": "#2196f3", - "settings": {}, - "_hash": 0.8830669138660703, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return value ? value : 'ENABLED';" + "bef6c27b-9fe7-ee92-40d9-9696c501a1f9": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "createdAlarmsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.alarms-created}", + "color": "#d35a00", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 1800000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.alarms-created-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "basic", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "createdAlarmsLimit", - "type": "timeseries", - "label": "limit", - "color": "#4caf50", - "settings": {}, - "_hash": 0.5463603803546802, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "row": 0, + "col": 0, + "id": "bef6c27b-9fe7-ee92-40d9-9696c501a1f9" + }, + "52305cf8-2258-5745-a0e7-41a171594bb3": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "createdAlarmsCount", + "type": "timeseries", + "label": "{i18n:api-usage.alarms-created}", + "color": "#d35a00", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 900000000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.alarms-created-monthly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "basic", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "createdAlarmsCount", - "type": "timeseries", - "label": "count", - "color": "#f44336", - "settings": {}, - "_hash": 0.5564241862015964, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "row": 0, + "col": 0, + "id": "52305cf8-2258-5745-a0e7-41a171594bb3" + }, + "36fdf999-ca22-9a4c-269d-3f004d792792": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "emailCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.email-messages}", + "color": "#d35a00", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 1800000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.email-messages-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "basic", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "alarmApiState", - "type": "timeseries", - "label": "apiStateClass", - "color": "#ffc107", - "settings": {}, - "_hash": 0.8737107059960671, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return value ? value.toLowerCase() : 'enabled';" + "row": 0, + "col": 0, + "id": "36fdf999-ca22-9a4c-269d-3f004d792792" + }, + "9a191755-499d-535e-86c5-061102729c02": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "smsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.sms-messages}", + "color": "#f36021", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 1800000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.sms-messages-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "basic", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "alarmApiState", - "type": "timeseries", - "label": "cardId", - "color": "#607d8b", - "settings": {}, - "_hash": 0.051659774305067296, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return (Math.random()*100000).toFixed(0);" + "row": 0, + "col": 0, + "id": "9a191755-499d-535e-86c5-061102729c02" + }, + "4b266318-8357-33ef-ca5a-74cbf90e014f": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "emailCount", + "type": "timeseries", + "label": "{i18n:api-usage.email-messages}", + "color": "#d35a00", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 900000000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.email-messages-monthly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "basic", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "alarmApiState", - "type": "timeseries", - "label": "title", - "color": "#9c27b0", - "settings": {}, - "_hash": 0.43439375716502227, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return \"{i18n:api-usage.alarm}\";" + "row": 0, + "col": 0, + "id": "4b266318-8357-33ef-ca5a-74cbf90e014f" + }, + "5aa33b0b-3bd5-7fe7-ee72-f564c2ca79d8": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "smsCount", + "type": "timeseries", + "label": "{i18n:api-usage.sms-messages}", + "color": "#f36021", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + }, + "type": "bar" + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 900000000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.sms-messages-monthly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "basic", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - { - "name": "alarmApiState", - "type": "timeseries", - "label": "unit", - "color": "#8bc34a", - "settings": {}, - "_hash": 0.9964061963495883, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return \"{i18n:api-usage.alarms-created}\";" - } - ] - } - ], - "timewindow": { - "displayValue": "", - "selectedTab": 0, - "realtime": { - "realtimeType": 1, - "interval": 1000, - "timewindowMs": 60000, - "quickInterval": "CURRENT_DAY" + "row": 0, + "col": 0, + "id": "5aa33b0b-3bd5-7fe7-ee72-f564c2ca79d8" }, - "history": { - "historyType": 0, - "interval": 1000, - "timewindowMs": 60000, - "fixedTimewindow": { - "startTimeMs": 1694083375826, - "endTimeMs": 1694169775826 - }, - "quickInterval": "CURRENT_DAY" + "fa938580-33db-f1b3-fafc-bc3e3784ad57": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f", + "dataKeys": [ + { + "name": "successfulMsgs", + "type": "timeseries", + "label": "{i18n:api-usage.successful}", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.15490750967648736, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "failedMsgs", + "type": "timeseries", + "label": "{i18n:api-usage.permanent-failures}", + "color": "#ef5350", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.4186621166514697 + }, + { + "name": "tmpFailed", + "type": "timeseries", + "label": "{i18n:api-usage.processing-failures}", + "color": "#ffc107", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.49891007198715376 + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 3600000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 10000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 1800000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "position": "bottom", + "sortDataKeys": false, + "showMin": true, + "showMax": true, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.queue-stats}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" + }, + "row": 0, + "col": 0, + "id": "fa938580-33db-f1b3-fafc-bc3e3784ad57" }, - "aggregation": { - "type": "AVG", - "limit": 25000 + "2ee89893-4e38-5331-95b7-3fd4f310c5a7": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f", + "dataKeys": [ + { + "name": "timeoutMsgs", + "type": "timeseries", + "label": "{i18n:api-usage.permanent-timeouts}", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.565222981550328 + }, + { + "name": "tmpTimeout", + "type": "timeseries", + "label": "{i18n:api-usage.processing-timeouts}", + "color": "#9c27b0", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.2679547062508352 + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 3600000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 10000 + } + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupIntervalWidth": 1800000, + "separateBarWidth": 1000 + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "position": "bottom", + "sortDataKeys": false, + "showMin": true, + "showMax": true, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "{i18n:api-usage.processing-failures-and-timeouts}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "advanced", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" + }, + "row": 0, + "col": 0, + "id": "2ee89893-4e38-5331-95b7-3fd4f310c5a7" } - }, - "showTitle": false, - "backgroundColor": "#fff", - "color": "#666666", - "padding": "0", - "settings": { - "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" - }, - "title": "Alarm created", - "dropShadow": true, - "enableFullscreen": false, - "widgetStyle": { - "cursor": "default" - }, - "titleStyle": { - "fontSize": "20px", - "fontWeight": 400 - }, - "useDashboardTimewindow": true, - "showLegend": false, - "actions": { - "elementClick": [ - { - "name": "email_messages_details", - "icon": "insert_chart", - "type": "openDashboardState", - "targetDashboardStateId": "alarms_created", - "setEntityId": false, - "stateEntityParamName": null, - "openInSeparateDialog": null, - "dialogTitle": null, - "dialogHideDashboardToolbar": true, - "dialogWidth": null, - "dialogHeight": null, - "openRightLayout": false, - "id": "946ba769-84ac-1507-6baa-94701de8967b" - } - ] - }, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" }, - "row": 0, - "col": 0, - "id": "a151ae60-0326-6116-d818-9070dda8e9c7", - "typeFullFqn": "system.cards.html_value_card" - }, - "68e16e98-0420-f72c-4848-41dedffd3904": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "ruleEngineExecutionCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.rule-engine-executions}", - "color": "#ab00ff", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true + "states": { + "default": { + "name": "{i18n:api-usage.api-usage}", + "root": true, + "layouts": { + "main": { + "widgets": { + "aab68ab5-8e40-8694-c55c-8eb1c89b88fb": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 0 + }, + "a84fa70a-ddfa-3b24-9aa4-cf9ce91f919a": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 4 + }, + "d70d26d4-e22d-4ca9-9ea7-f9c87c093321": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 8 + }, + "4d3ea95c-3188-9872-1817-2f989c7729e0": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 12 + }, + "2d0d6ff6-cd59-51d4-b916-38e22cdd0702": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 16 + }, + "120573cc-e246-eb49-7d80-68e5d3b3c0cc": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 20 + }, + "63f99d90-23ab-f8c2-3290-1e693ded5a2e": { + "sizeX": 8, + "sizeY": 4, + "row": 2, + "col": 0 + }, + "a2b7e906-2d8a-41a8-99a6-409531bfa743": { + "sizeX": 8, + "sizeY": 4, + "row": 2, + "col": 8 + }, + "ca996b66-ab7e-f977-152c-98e4ebf2a901": { + "sizeX": 8, + "sizeY": 4, + "row": 2, + "col": 16 + }, + "a3c2f1bb-7d3a-f11c-7b3d-28cd84fdfe34": { + "sizeX": 8, + "sizeY": 4, + "row": 6, + "col": 0 + }, + "5cebd4f1-ff6e-62f9-025c-8e7583c3d66a": { + "sizeX": 8, + "sizeY": 4, + "row": 6, + "col": 8 + }, + "bc0c8840-a9b5-5583-de7b-9e9450f5d8fe": { + "sizeX": 8, + "sizeY": 4, + "row": 6, + "col": 16 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 5, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70, + "outerMargin": true + } } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 0, - "realtime": { - "timewindowMs": 86400000, - "interval": 3600000 - }, - "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", - "min": 0, - "tickDecimals": 0 - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 2400000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.rule-engine-hourly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": { - "headerButton": [ - { - "name": "{i18n:api-usage.view-statistics}", - "icon": "show_chart", - "type": "openDashboardState", - "targetDashboardStateId": "rule_engine_statistics", - "setEntityId": false, - "stateEntityParamName": null, - "openRightLayout": false, - "id": "f9f08190-9ed9-d802-5b7a-c57ff84b5648" - }, - { - "name": "{i18n:api-usage.view-details}", - "icon": "insert_chart", - "type": "openDashboardState", - "targetDashboardStateId": "rule_engine_execution", - "setEntityId": false, - "stateEntityParamName": null, - "openRightLayout": false, - "id": "1aec196b-44ba-ddf4-c4dc-c3f60c1eb6fc" - } - ] - }, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "68e16e98-0420-f72c-4848-41dedffd3904", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "2aa6b499-6e27-b315-6833-89c4d58485ce": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "transportMsgCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.transport-messages}", - "color": "#2196f3", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "transportDataPointsCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.transport-data-points}", - "color": "#4caf50", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true + "transport": { + "name": "{i18n:api-usage.transport}", + "root": false, + "layouts": { + "main": { + "widgets": { + "0b091dc3-eec3-847e-d0ad-fdf12d474e7a": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "536d7104-49f8-fde6-5827-61b8419f15ec": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70, + "outerMargin": true + } } - }, - "_hash": 0.46849996721308895, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 0, - "realtime": { - "timewindowMs": 86400000, - "interval": 3600000 }, - "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" + "rule_engine_execution": { + "name": "{i18n:api-usage.rule-engine-executions}", + "root": false, + "layouts": { + "main": { + "widgets": { + "c77e417c-ad9d-8e23-3ea1-c75edd653bc0": { + "sizeX": 25, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "870904d2-d2e1-a1b9-ce56-b03fd47259b5": { + "sizeX": 25, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70, + "outerMargin": true + } + } + } }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", - "min": 0, - "tickDecimals": 0 + "telemetry_persistence": { + "name": "{i18n:api-usage.telemetry-persistence}", + "root": false, + "layouts": { + "main": { + "widgets": { + "7f4100d2-41be-4954-d353-1d45000dbbbb": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "226ef8c9-8488-3664-21ac-0b6217336202": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70, + "outerMargin": true + } + } + } }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 + "rule_engine_statistics": { + "name": "{i18n:api-usage.rule-engine-statistics}", + "root": false, + "layouts": { + "main": { + "widgets": { + "a669cf86-e715-efa4-dd9a-b839abf499e9": { + "sizeX": 24, + "sizeY": 5, + "row": 7, + "col": 0 + }, + "fa938580-33db-f1b3-fafc-bc3e3784ad57": { + "sizeX": 12, + "sizeY": 7, + "row": 0, + "col": 0 + }, + "2ee89893-4e38-5331-95b7-3fd4f310c5a7": { + "sizeX": 12, + "sizeY": 7, + "row": 0, + "col": 12 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70, + "outerMargin": true + } + } + } }, - "stack": true, - "tooltipIndividual": false, - "defaultBarWidth": 2400000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true + "notifications": { + "name": "{i18n:api-usage.notifications-email-sms}", + "root": false, + "layouts": { + "main": { + "widgets": { + "36fdf999-ca22-9a4c-269d-3f004d792792": { + "sizeX": 12, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "9a191755-499d-535e-86c5-061102729c02": { + "sizeX": 12, + "sizeY": 6, + "row": 0, + "col": 12 + }, + "4b266318-8357-33ef-ca5a-74cbf90e014f": { + "sizeX": 12, + "sizeY": 6, + "row": 6, + "col": 0 + }, + "5aa33b0b-3bd5-7fe7-ee72-f564c2ca79d8": { + "sizeX": 12, + "sizeY": 6, + "row": 6, + "col": 12 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70, + "outerMargin": true + } + } + } }, - "tooltipCumulative": false, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.transport-hourly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": { - "headerButton": [ - { - "name": "{i18n:api-usage.view-details}", - "icon": "insert_chart", - "type": "openDashboardState", - "targetDashboardStateId": "transport", - "setEntityId": false, - "stateEntityParamName": null, - "openRightLayout": false, - "id": "6ef12f6a-0266-25cf-6ca5-5dcb772252c6" - } - ] - }, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "2aa6b499-6e27-b315-6833-89c4d58485ce", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "d890cea3-fba0-6474-9a21-fa780230dc62": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "jsExecutionCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.javascript-executions}", - "color": "#ff9900", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true + "alarms_created": { + "name": "{i18n:api-usage.alarms-created}", + "root": false, + "layouts": { + "main": { + "widgets": { + "bef6c27b-9fe7-ee92-40d9-9696c501a1f9": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "52305cf8-2258-5745-a0e7-41a171594bb3": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70, + "outerMargin": true + } } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "tbelExecutionCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.tbel-executions}", - "color": "#4caf50", - "settings": {}, - "_hash": 0.6818645685001823, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 0, - "realtime": { - "timewindowMs": 86400000, - "interval": 3600000 }, - "aggregation": { - "type": "NONE", - "limit": 1000 + "script_functions": { + "name": "{i18n:api-usage.scripts}", + "root": false, + "layouts": { + "main": { + "widgets": { + "c66e5060-57fd-11e7-6616-65b82c294ac2": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "d0e8603e-5d2e-9287-e2c6-8ccbe9c66806": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70, + "outerMargin": true + } + } + } } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "stack": false, - "enableSelection": true, - "fontSize": 10, - "fontColor": "#545454", - "showTooltip": true, - "tooltipIndividual": false, - "tooltipCumulative": false, - "hideZeros": false, - "grid": { - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1, - "color": "#545454", - "backgroundColor": null, - "tickColor": "#DDDDDD" - }, - "xaxis": { - "title": null, - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "min": 0, - "max": null, - "title": null, - "showLabels": true, - "color": "#545454", - "tickSize": null, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "defaultBarWidth": 2400000, - "barAlignment": "left", - "comparisonEnabled": false, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - }, - "customLegendEnabled": false - }, - "title": "{i18n:api-usage.scripts-hourly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": { - "headerButton": [ - { - "name": "{i18n:api-usage.view-details}", - "icon": "insert_chart", - "useShowWidgetActionFunction": null, - "showWidgetActionFunction": "return true;", - "type": "openDashboardState", - "targetDashboardStateId": "script_functions", - "setEntityId": false, - "stateEntityParamName": null, - "openRightLayout": false, - "openInSeparateDialog": false, - "openInPopover": false, - "id": "4687d3f6-8800-a3b6-26e5-0d33f3b828a9" - } - ] - }, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "", - "widgetCss": "", - "pageSize": 1024, - "noDataDisplayMessage": "", - "configMode": "advanced" }, - "row": 0, - "col": 0, - "id": "d890cea3-fba0-6474-9a21-fa780230dc62", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "84b6cfa5-1449-e0f2-3560-f810d2dd7ead": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "storageDataPointsCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.data-points-storage-days}", - "color": "#1039ee", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null + "entityAliases": { + "40193437-33ac-3172-eefd-0b08eb849062": { + "id": "40193437-33ac-3172-eefd-0b08eb849062", + "alias": "Api usage state", + "filter": { + "type": "apiUsageState", + "resolveMultiple": false + } + }, + "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f": { + "id": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f", + "alias": "TbServiceQueues", + "filter": { + "type": "assetType", + "resolveMultiple": true, + "assetNameFilter": "", + "assetTypes": [ + "TbServiceQueue" + ] } - ] } - ], - "timewindow": { + }, + "filters": {}, + "timewindow": { "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, "hideAggregation": false, "hideAggInterval": false, + "hideTimezone": false, "selectedTab": 0, "realtime": { - "timewindowMs": 86400000, - "interval": 3600000 + "realtimeType": 0, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY", + "interval": 3600000 }, "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", - "min": 0, - "tickDecimals": 0 - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 2400000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.telemetry-persistence-hourly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": { - "headerButton": [ - { - "name": "{i18n:api-usage.view-details}", - "icon": "insert_chart", - "type": "openDashboardState", - "targetDashboardStateId": "telemetry_persistence", - "setEntityId": false, - "stateEntityParamName": null, - "openRightLayout": false, - "id": "16707efb-e572-bd02-c219-55fc1b0f672a" - } - ] - }, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "84b6cfa5-1449-e0f2-3560-f810d2dd7ead", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "d296b566-a000-7402-ae9d-c815381c5435": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "createdAlarmsCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.alarms-created}", - "color": "#d35a00", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 0, - "realtime": { - "timewindowMs": 86400000, - "interval": 3600000 - }, - "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", - "min": 0, - "tickDecimals": 0 - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 2400000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.alarms-created-hourly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": { - "headerButton": [ - { - "name": "{i18n:api-usage.view-details}", - "icon": "insert_chart", - "type": "openDashboardState", - "targetDashboardStateId": "alarms_created", - "setEntityId": false, - "stateEntityParamName": null, - "openInSeparateDialog": null, - "dialogTitle": null, - "dialogHideDashboardToolbar": true, - "dialogWidth": null, - "dialogHeight": null, - "openRightLayout": false, - "id": "371882f9-ea23-3abc-fca8-9449c5dfdd6b" - } - ] - }, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "d296b566-a000-7402-ae9d-c815381c5435", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "00a02464-9509-911b-3b5e-21fb37629822": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "emailCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.email-messages}", - "color": "#4caf50", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.1348755140779876, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "smsCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.sms-messages}", - "color": "#f36021", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 0, - "realtime": { - "timewindowMs": 86400000, - "interval": 3600000 - }, - "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", - "min": 0, - "tickDecimals": 0 - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 2400000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.notifications-hourly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": { - "headerButton": [ - { - "name": "{i18n:api-usage.view-details}", - "icon": "insert_chart", - "type": "openDashboardState", - "targetDashboardStateId": "notifications", - "setEntityId": false, - "stateEntityParamName": null, - "openInSeparateDialog": null, - "dialogTitle": null, - "dialogHideDashboardToolbar": true, - "dialogWidth": null, - "dialogHeight": null, - "openRightLayout": false, - "id": "49aefac0-ec5e-d6f3-f39c-8744759f4b19" - } - ] - }, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "00a02464-9509-911b-3b5e-21fb37629822", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "74199074-7873-6c6a-2a51-3fc614769f03": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "ruleEngineExecutionCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.rule-engine-executions}", - "color": "#ab00ff", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 2592000000, - "interval": 86400000 - }, - "aggregation": { - "type": "SUM", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", - "min": 0, - "tickDecimals": 0 - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 1800000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.rule-engine-daily-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "74199074-7873-6c6a-2a51-3fc614769f03", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "00006bc3-8a8d-b55e-ed39-e318f1bcd090": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "ruleEngineExecutionCount", - "type": "timeseries", - "label": "{i18n:api-usage.rule-engine-executions}", - "color": "#ab00ff", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 31536000000, - "interval": 1000 - }, - "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", - "min": 0, - "tickDecimals": 0 - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 900000000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.rule-engine-monthly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "00006bc3-8a8d-b55e-ed39-e318f1bcd090", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "5f5ca59c-e507-5301-5910-7ad8cd34df40": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "jsExecutionCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.javascript-executions}", - "color": "#ff9900", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "tbelExecutionCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.tbel-executions}", - "color": "#4caf50", - "settings": {}, - "_hash": 0.5212969314724616, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 2592000000, - "interval": 86400000 - }, - "aggregation": { - "type": "SUM", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "min": 0, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 1800000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.scripts-daily-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "", - "widgetCss": "", - "pageSize": 1024, - "noDataDisplayMessage": "" - }, - "row": 0, - "col": 0, - "id": "5f5ca59c-e507-5301-5910-7ad8cd34df40", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "ada32ee9-44ed-48d1-c368-fd0c94b7607f": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "jsExecutionCount", - "type": "timeseries", - "label": "{i18n:api-usage.javascript-executions}", - "color": "#ff9900", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "tbelExecutionCount", - "type": "timeseries", - "label": "{i18n:api-usage.tbel-executions}", - "color": "#4caf50", - "settings": {}, - "_hash": 0.49748239768082403, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 31536000000, - "interval": 1000 - }, - "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "min": 0, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 900000000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.scripts-monthly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "", - "widgetCss": "", - "pageSize": 1024, - "noDataDisplayMessage": "" - }, - "row": 0, - "col": 0, - "id": "ada32ee9-44ed-48d1-c368-fd0c94b7607f", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "98bae68b-0f35-72f2-a428-9b06889f1554": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "transportMsgCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.transport-messages}", - "color": "#2196f3", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "transportDataPointsCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.transport-data-points}", - "color": "#4caf50", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.46849996721308895, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 2592000000, - "interval": 86400000 - }, - "aggregation": { - "type": "SUM", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", - "min": 0, - "tickDecimals": 0 - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": true, - "tooltipIndividual": false, - "defaultBarWidth": 1800000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "tooltipCumulative": false, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.transport-daily-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "98bae68b-0f35-72f2-a428-9b06889f1554", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "e61e5477-5a09-cc25-966b-f613d81da833": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "transportMsgCount", - "type": "timeseries", - "label": "{i18n:api-usage.transport-messages}", - "color": "#2196f3", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "transportDataPointsCount", - "type": "timeseries", - "label": "{i18n:api-usage.transport-data-points}", - "color": "#4caf50", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.46849996721308895, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 31536000000, - "interval": 2592000000 - }, - "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", - "min": 0, - "tickDecimals": 0 - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": true, - "tooltipIndividual": false, - "defaultBarWidth": 900000000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "tooltipCumulative": false, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.transport-monthly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "e61e5477-5a09-cc25-966b-f613d81da833", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "85fe0738-5326-f069-ab3f-30594bde5fed": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "storageDataPointsCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.data-points-storage-days}", - "color": "#1039ee", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 2592000000, - "interval": 86400000 - }, - "aggregation": { - "type": "SUM", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "min": 0, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 1800000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.telemetry-persistence-daily-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "85fe0738-5326-f069-ab3f-30594bde5fed", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "eaeb381a-437e-f6e9-60c9-6bc8826fdd44": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "storageDataPointsCount", - "type": "timeseries", - "label": "{i18n:api-usage.data-points-storage-days}", - "color": "#1039ee", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 31536000000, - "interval": 1000 - }, - "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "min": 0, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 900000000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.telemetry-persistence-monthly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "eaeb381a-437e-f6e9-60c9-6bc8826fdd44", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "4b798823-b97d-9d6a-59dc-fcafd897fc23": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "createdAlarmsCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.alarms-created}", - "color": "#d35a00", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 2592000000, - "interval": 86400000 - }, - "aggregation": { - "type": "SUM", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "min": 0, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 1800000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.alarms-created-daily-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "4b798823-b97d-9d6a-59dc-fcafd897fc23", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "6a981580-7490-19dd-f937-b64cbf67a982": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "createdAlarmsCount", - "type": "timeseries", - "label": "{i18n:api-usage.alarms-created}", - "color": "#d35a00", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 31536000000, - "interval": 1000 - }, - "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "min": 0, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 900000000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.alarms-created-monthly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "6a981580-7490-19dd-f937-b64cbf67a982", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "7302df65-1b0c-579e-bbdb-145126ae3392": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "smsCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.sms-messages}", - "color": "#f36021", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 2592000000, - "interval": 86400000 - }, - "aggregation": { - "type": "SUM", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "min": 0, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 1800000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.sms-messages-daily-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "7302df65-1b0c-579e-bbdb-145126ae3392", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "fdb385e7-14fe-fc9f-ebdc-b400f26fc66b": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "smsCount", - "type": "timeseries", - "label": "{i18n:api-usage.sms-messages}", - "color": "#f36021", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 31536000000, - "interval": 1000 - }, - "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "min": 0, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 900000000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.sms-messages-monthly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "fdb385e7-14fe-fc9f-ebdc-b400f26fc66b", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "2408ad30-163e-8221-08e1-a82b638be564": { - "type": "timeseries", - "sizeX": 12, - "sizeY": 7, - "config": { - "datasources": [ - { - "type": "entity", - "dataKeys": [ - { - "name": "successfulMsgs", - "type": "timeseries", - "label": "{i18n:api-usage.successful}", - "color": "#4caf50", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.15490750967648736 - }, - { - "name": "failedMsgs", - "type": "timeseries", - "label": "{i18n:api-usage.permanent-failures}", - "color": "#ef5350", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.4186621166514697 - }, - { - "name": "tmpFailed", - "type": "timeseries", - "label": "{i18n:api-usage.processing-failures}", - "color": "#ffc107", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.49891007198715376 - } - ], - "entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f" - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 0, - "realtime": { - "timewindowMs": 3600000, - "interval": 1000 - }, - "aggregation": { - "type": "NONE", - "limit": 10000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "min": 0, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "showMin": true, - "showMax": true, - "showAvg": false, - "showTotal": true - } - }, - "title": "Queue Stats", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "mobileHeight": null, - "showTitleIcon": false, - "titleIcon": null, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "", - "widgetStyle": {}, - "useDashboardTimewindow": false, - "displayTimewindow": true, - "actions": {} - }, - "id": "2408ad30-163e-8221-08e1-a82b638be564", - "typeFullFqn": "system.charts.basic_timeseries" - }, - "e43dcfe1-b970-6a11-ce0e-5769f3eb5e88": { - "type": "timeseries", - "sizeX": 12, - "sizeY": 7, - "config": { - "datasources": [ - { - "type": "entity", - "dataKeys": [ - { - "name": "timeoutMsgs", - "type": "timeseries", - "label": "{i18n:api-usage.permanent-timeouts}", - "color": "#4caf50", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.565222981550328 - }, - { - "name": "tmpTimeout", - "type": "timeseries", - "label": "{i18n:api-usage.processing-timeouts}", - "color": "#9c27b0", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.2679547062508352 - } - ], - "entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f" - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 0, - "realtime": { - "timewindowMs": 3600000, - "interval": 1000 - }, - "aggregation": { - "type": "NONE", - "limit": 10000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "min": 0, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "showMin": true, - "showMax": true, - "showAvg": false, - "showTotal": true - } - }, - "title": "Processing Failures and Timeouts", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "mobileHeight": null, - "showTitleIcon": false, - "titleIcon": null, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "", - "widgetStyle": {}, - "useDashboardTimewindow": false, - "displayTimewindow": true, - "actions": {} - }, - "id": "e43dcfe1-b970-6a11-ce0e-5769f3eb5e88", - "typeFullFqn": "system.charts.basic_timeseries" - }, - "a669cf86-e715-efa4-dd9a-b839abf499e9": { - "type": "timeseries", - "sizeX": 24, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "dataKeys": [ - { - "name": "ruleEngineException", - "type": "timeseries", - "label": "Rule Chain", - "color": "#2196f3", - "settings": { - "useCellStyleFunction": false, - "useCellContentFunction": true, - "cellContentFunction": "return JSON.parse(value).ruleChainName;" - }, - "_hash": 0.9954481282345906 - }, - { - "name": "ruleEngineException", - "type": "timeseries", - "label": "Rule Node", - "color": "#4caf50", - "settings": { - "useCellStyleFunction": false, - "useCellContentFunction": true, - "cellContentFunction": "return JSON.parse(value).ruleNodeName;" - }, - "_hash": 0.18580357036589978 - }, - { - "name": "ruleEngineException", - "type": "timeseries", - "label": "Latest Error", - "color": "#f44336", - "settings": { - "useCellStyleFunction": false, - "useCellContentFunction": true, - "cellContentFunction": "return JSON.parse(value).message;" - }, - "_hash": 0.7255162989552142 - } - ], - "entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f" - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 0, - "realtime": { - "timewindowMs": 2592000000, - "interval": 1000 - }, - "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "rgb(255, 255, 255)", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "showTimestamp": true, - "displayPagination": true, - "defaultPageSize": 10 - }, - "title": "Exceptions", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "useDashboardTimewindow": false, - "showLegend": false, - "widgetStyle": {}, - "actions": {}, - "showTitleIcon": false, - "titleIcon": null, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "", - "displayTimewindow": true - }, - "id": "a669cf86-e715-efa4-dd9a-b839abf499e9", - "typeFullFqn": "system.cards.timeseries_table" - }, - "292eaded-4775-36f7-c896-98d57bdda936": { - "type": "latest", - "sizeX": 7.5, - "sizeY": 3, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "emailApiState", - "type": "timeseries", - "label": "apiState", - "color": "#2196f3", - "settings": {}, - "_hash": 0.8830669138660703, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "emailLimit", - "type": "timeseries", - "label": "limit", - "color": "#4caf50", - "settings": {}, - "_hash": 0.5463603803546802, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "emailCount", - "type": "timeseries", - "label": "count", - "color": "#f44336", - "settings": {}, - "_hash": 0.5564241862015964, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "transportApiState", - "type": "timeseries", - "label": "cardId", - "color": "#607d8b", - "settings": {}, - "_hash": 0.051659774305067296, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return (Math.random()*100000).toFixed(0);" - }, - { - "name": "smsApiState", - "type": "timeseries", - "label": "apiStatePoint", - "color": "#e91e63", - "settings": {}, - "_hash": 0.2969682764607864, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "smsLimit", - "type": "timeseries", - "label": "pointsLimit", - "color": "#9c27b0", - "settings": {}, - "_hash": 0.22082255831864894, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "smsCount", - "type": "timeseries", - "label": "pointsCount", - "color": "#8bc34a", - "settings": {}, - "_hash": 0.6340356364819146, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "transportApiState", - "type": "timeseries", - "label": "title", - "color": "#3f51b5", - "settings": {}, - "_hash": 0.6894070537030252, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": true, - "postFuncBody": "return \"{i18n:api-usage.notifications}\";" - } - ] - } - ], - "timewindow": { - "displayValue": "", - "selectedTab": 0, - "realtime": { - "realtimeType": 1, - "interval": 1000, - "timewindowMs": 60000, - "quickInterval": "CURRENT_DAY" - }, - "history": { - "historyType": 0, - "interval": 1000, - "timewindowMs": 60000, - "fixedTimewindow": { - "startTimeMs": 1694083375827, - "endTimeMs": 1694169775827 - }, - "quickInterval": "CURRENT_DAY" - }, - "aggregation": { - "type": "AVG", - "limit": 25000 - } - }, - "showTitle": false, - "backgroundColor": "#fff", - "color": "#666666", - "padding": "0", - "settings": { - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 6px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n", - "cardHtml": "
\n \n \n
\n
\n
\n
\n ${title}\n
\n
\n
\n
\n
\n
\n
{i18n:api-usage.email}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
{i18n:api-usage.sms}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
" - }, - "title": "Notifications (Email/SMS)", - "dropShadow": true, - "enableFullscreen": false, - "widgetStyle": { - "cursor": "default" - }, - "titleStyle": { - "fontSize": "20px", - "fontWeight": 400 - }, - "useDashboardTimewindow": true, - "showLegend": false, - "actions": { - "elementClick": [ - { - "name": "transport_details", - "icon": "insert_chart", - "type": "openDashboardState", - "targetDashboardStateId": "notifications", - "setEntityId": false, - "stateEntityParamName": null, - "openInSeparateDialog": null, - "dialogTitle": null, - "dialogHideDashboardToolbar": true, - "dialogWidth": null, - "dialogHeight": null, - "openRightLayout": false, - "id": "46b7cefe-e1f2-67c1-4055-3a214520f869" - } - ] - }, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "292eaded-4775-36f7-c896-98d57bdda936", - "typeFullFqn": "system.cards.html_value_card" - }, - "b3571a36-2106-1122-7d58-0d38bbb0e9c8": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "emailCountHourly", - "type": "timeseries", - "label": "{i18n:api-usage.email-messages}", - "color": "#d35a00", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 2592000000, - "interval": 86400000 - }, - "aggregation": { - "type": "SUM", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "min": 0, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 1800000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.email-messages-daily-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" - }, - "row": 0, - "col": 0, - "id": "b3571a36-2106-1122-7d58-0d38bbb0e9c8", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, - "aa875b7f-e7c8-7529-1ae7-f456211b59cc": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", - "filterId": null, - "dataKeys": [ - { - "name": "emailCount", - "type": "timeseries", - "label": "{i18n:api-usage.email-messages}", - "color": "#d35a00", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": false, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.0661644137210089, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ] - } - ], - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 31536000000, - "interval": 1000 - }, - "aggregation": { - "type": "NONE", - "limit": 1000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454", - "min": 0, - "tickDecimals": 0, - "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 + "type": "NONE", + "limit": 50000 }, - "stack": false, - "tooltipIndividual": false, - "defaultBarWidth": 900000000, - "barAlignment": "left", - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": true - } - }, - "title": "{i18n:api-usage.email-messages-monthly-activity}", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": {}, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "" + "timezone": null }, - "row": 0, - "col": 0, - "id": "aa875b7f-e7c8-7529-1ae7-f456211b59cc", - "typeFullFqn": "system.charts.timeseries_bars_flot" - } - }, - "states": { - "default": { - "name": "{i18n:api-usage.api-usage}", - "root": true, - "layouts": { - "main": { - "widgets": { - "fd6df872-2ddf-0921-3929-2e7f55062fad": { - "sizeX": 4, - "sizeY": 2, - "row": 0, - "col": 8, - "mobileHeight": 3 - }, - "7e235874-461b-e7c2-2fdd-d8762a020773": { - "sizeX": 4, - "sizeY": 2, - "row": 0, - "col": 12, - "mobileHeight": 3 - }, - "08545554-a0e8-05c7-66df-6000cfeff8a4": { - "sizeX": 4, - "sizeY": 2, - "row": 0, - "col": 4, - "mobileHeight": 3 - }, - "a245c67e-53ec-d299-fa89-69fe2062ccb2": { - "sizeX": 4, - "sizeY": 2, - "row": 0, - "col": 0, - "mobileHeight": 3 - }, - "a151ae60-0326-6116-d818-9070dda8e9c7": { - "sizeX": 4, - "sizeY": 2, - "row": 0, - "col": 16, - "mobileHeight": 3 - }, - "292eaded-4775-36f7-c896-98d57bdda936": { - "sizeX": 4, - "sizeY": 2, - "row": 0, - "col": 20, - "mobileHeight": 3 - }, - "68e16e98-0420-f72c-4848-41dedffd3904": { - "sizeX": 8, - "sizeY": 4, - "row": 2, - "col": 8 - }, - "2aa6b499-6e27-b315-6833-89c4d58485ce": { - "sizeX": 8, - "sizeY": 4, - "row": 2, - "col": 0 - }, - "d890cea3-fba0-6474-9a21-fa780230dc62": { - "sizeX": 8, - "sizeY": 4, - "row": 2, - "col": 16 - }, - "84b6cfa5-1449-e0f2-3560-f810d2dd7ead": { - "sizeX": 8, - "sizeY": 4, - "row": 6, - "col": 0 - }, - "d296b566-a000-7402-ae9d-c815381c5435": { - "sizeX": 8, - "sizeY": 4, - "row": 6, - "col": 8 - }, - "00a02464-9509-911b-3b5e-21fb37629822": { - "sizeX": 8, - "sizeY": 4, - "row": 6, - "col": 16 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "color": "rgba(0,0,0,0.870588)", - "columns": 24, - "margin": 5, - "backgroundSizeMode": "100%", - "autoFillHeight": true, - "backgroundImageUrl": null, - "mobileAutoFillHeight": false, - "mobileRowHeight": 70, - "outerMargin": true - } - } - } - }, - "transport": { - "name": "{i18n:api-usage.transport}", - "root": false, - "layouts": { - "main": { - "widgets": { - "98bae68b-0f35-72f2-a428-9b06889f1554": { - "sizeX": 24, - "sizeY": 6, - "row": 0, - "col": 0 - }, - "e61e5477-5a09-cc25-966b-f613d81da833": { - "sizeX": 24, - "sizeY": 6, - "row": 6, - "col": 0 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "color": "rgba(0,0,0,0.870588)", - "columns": 24, - "margin": 10, - "backgroundSizeMode": "100%", - "autoFillHeight": true, - "backgroundImageUrl": null, - "mobileAutoFillHeight": false, - "mobileRowHeight": 70, - "outerMargin": true - } - } - } - }, - "rule_engine_execution": { - "name": "{i18n:api-usage.rule-engine-executions}", - "root": false, - "layouts": { - "main": { - "widgets": { - "74199074-7873-6c6a-2a51-3fc614769f03": { - "sizeX": 24, - "sizeY": 6, - "row": 0, - "col": 0 - }, - "00006bc3-8a8d-b55e-ed39-e318f1bcd090": { - "sizeX": 24, - "sizeY": 6, - "row": 6, - "col": 0 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "color": "rgba(0,0,0,0.870588)", - "columns": 24, - "margin": 10, - "backgroundSizeMode": "100%", - "autoFillHeight": true, - "backgroundImageUrl": null, - "mobileAutoFillHeight": false, - "mobileRowHeight": 70, - "outerMargin": true - } - } + "settings": { + "stateControllerId": "entity", + "showTitle": false, + "showDashboardsSelect": false, + "showEntitiesSelect": false, + "showDashboardTimewindow": false, + "showDashboardExport": false, + "toolbarAlwaysOpen": true, + "titleColor": "rgba(0,0,0,0.870588)", + "showFilters": false, + "showDashboardLogo": false, + "dashboardLogoUrl": null, + "hideToolbar": false, + "showUpdateDashboardImage": false, + "dashboardCss": ".tb-time-series-chart-panel {\n padding: 13px;\n}\n\n.tb-time-series-chart-panel {\n gap: 0; \n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n\n.card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n.card .unit {\n color: #666666;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} " } - }, - "telemetry_persistence": { - "name": "{i18n:api-usage.telemetry-persistence}", - "root": false, - "layouts": { - "main": { - "widgets": { - "85fe0738-5326-f069-ab3f-30594bde5fed": { - "sizeX": 24, - "sizeY": 6, - "row": 0, - "col": 0 - }, - "eaeb381a-437e-f6e9-60c9-6bc8826fdd44": { - "sizeX": 24, - "sizeY": 6, - "row": 6, - "col": 0 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "color": "rgba(0,0,0,0.870588)", - "columns": 24, - "margin": 10, - "backgroundSizeMode": "100%", - "autoFillHeight": true, - "backgroundImageUrl": null, - "mobileAutoFillHeight": false, - "mobileRowHeight": 70, - "outerMargin": true - } - } - } - }, - "rule_engine_statistics": { - "name": "{i18n:api-usage.rule-engine-statistics}", - "root": false, - "layouts": { - "main": { - "widgets": { - "2408ad30-163e-8221-08e1-a82b638be564": { - "sizeX": 12, - "sizeY": 7, - "mobileHeight": null, - "row": 0, - "col": 0 - }, - "e43dcfe1-b970-6a11-ce0e-5769f3eb5e88": { - "sizeX": 12, - "sizeY": 7, - "mobileHeight": null, - "row": 0, - "col": 12 - }, - "a669cf86-e715-efa4-dd9a-b839abf499e9": { - "sizeX": 24, - "sizeY": 5, - "row": 7, - "col": 0 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "color": "rgba(0,0,0,0.870588)", - "columns": 24, - "margin": 10, - "backgroundSizeMode": "100%", - "autoFillHeight": true, - "backgroundImageUrl": null, - "mobileAutoFillHeight": false, - "mobileRowHeight": 70, - "outerMargin": true - } - } - } - }, - "notifications": { - "name": "{i18n:api-usage.notifications-email-sms}", - "root": false, - "layouts": { - "main": { - "widgets": { - "7302df65-1b0c-579e-bbdb-145126ae3392": { - "sizeX": 12, - "sizeY": 6, - "row": 0, - "col": 12 - }, - "fdb385e7-14fe-fc9f-ebdc-b400f26fc66b": { - "sizeX": 12, - "sizeY": 6, - "row": 6, - "col": 12 - }, - "b3571a36-2106-1122-7d58-0d38bbb0e9c8": { - "sizeX": 12, - "sizeY": 6, - "row": 0, - "col": 0 - }, - "aa875b7f-e7c8-7529-1ae7-f456211b59cc": { - "sizeX": 12, - "sizeY": 6, - "row": 6, - "col": 0 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "color": "rgba(0,0,0,0.870588)", - "columns": 24, - "margin": 10, - "backgroundSizeMode": "100%", - "autoFillHeight": true, - "backgroundImageUrl": null, - "mobileAutoFillHeight": false, - "mobileRowHeight": 70, - "outerMargin": true - } - } - } - }, - "alarms_created": { - "name": "{i18n:api-usage.alarms-created}", - "root": false, - "layouts": { - "main": { - "widgets": { - "4b798823-b97d-9d6a-59dc-fcafd897fc23": { - "sizeX": 24, - "sizeY": 6, - "row": 0, - "col": 0 - }, - "6a981580-7490-19dd-f937-b64cbf67a982": { - "sizeX": 24, - "sizeY": 6, - "row": 6, - "col": 0 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "color": "rgba(0,0,0,0.870588)", - "columns": 24, - "margin": 10, - "backgroundSizeMode": "100%", - "autoFillHeight": true, - "backgroundImageUrl": null, - "mobileAutoFillHeight": false, - "mobileRowHeight": 70, - "outerMargin": true - } - } - } - }, - "script_functions": { - "name": "{i18n:api-usage.scripts}", - "root": false, - "layouts": { - "main": { - "widgets": { - "5f5ca59c-e507-5301-5910-7ad8cd34df40": { - "sizeX": 24, - "sizeY": 6, - "row": 0, - "col": 0 - }, - "ada32ee9-44ed-48d1-c368-fd0c94b7607f": { - "sizeX": 24, - "sizeY": 6, - "row": 6, - "col": 0 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "color": "rgba(0,0,0,0.870588)", - "columns": 24, - "margin": 10, - "backgroundSizeMode": "100%", - "autoFillHeight": true, - "backgroundImageUrl": null, - "mobileAutoFillHeight": false, - "mobileRowHeight": 70, - "outerMargin": true - } - } - } - } - }, - "entityAliases": { - "40193437-33ac-3172-eefd-0b08eb849062": { - "id": "40193437-33ac-3172-eefd-0b08eb849062", - "alias": "Api usage state", - "filter": { - "type": "apiUsageState", - "resolveMultiple": false - } - }, - "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f": { - "id": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f", - "alias": "TbServiceQueues", - "filter": { - "type": "assetType", - "resolveMultiple": true, - "assetNameFilter": "", - "assetTypes": [ - "TbServiceQueue" - ] - } - } - }, - "filters": {}, - "timewindow": { - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "selectedTab": 0, - "realtime": { - "timewindowMs": 86400000, - "interval": 3600000 - }, - "aggregation": { - "type": "NONE", - "limit": 50000 - } }, - "settings": { - "stateControllerId": "entity", - "showTitle": false, - "showDashboardsSelect": false, - "showEntitiesSelect": false, - "showDashboardTimewindow": false, - "showDashboardExport": false, - "toolbarAlwaysOpen": true, - "titleColor": "rgba(0,0,0,0.870588)", - "showFilters": false, - "showDashboardLogo": false - } - }, - "externalId": null, - "name": "Api Usage" + "name": "Api Usage" } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index b32cfb7d38..8b46687fa2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -791,6 +791,9 @@ "api-usage": "API usage", "alarm": "Alarm", "alarms-created": "Alarms created", + "queue-stats": "Queue Stats", + "processing-failures-and-timeouts": "Processing Failures and Timeouts", + "exceptions": "Exceptions", "alarms-created-daily-activity": "Alarms created daily activity", "alarms-created-hourly-activity": "Alarms created hourly activity", "alarms-created-monthly-activity": "Alarms created monthly activity", From 00e8bda08b9531dda5287f35635a62477506cbce Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 7 Mar 2024 17:01:17 +0200 Subject: [PATCH 162/209] UI: Add icon to configure mobile app notification --- ...tion-template-configuration.component.html | 16 ++++++++- ...cation-template-configuration.component.ts | 33 +++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html index 54db7ece72..8297985a16 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html @@ -120,7 +120,21 @@ }} -
+
+
+ + {{ 'icon.icon' | translate }} + +
+ + + + +
+
) { + const settings = deepClone(value); + if (isDefinedAndNotNull(settings)) { + for (const method of Object.values(NotificationDeliveryMethod)) { + if (isDefinedAndNotNull(settings[method]?.enabled)) { + delete settings[method].enabled; + } + } + } + this.templateConfigurationForm.patchValue(settings, {emitEvent: false}); } registerOnChange(fn: any): void { @@ -160,6 +168,9 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co case NotificationDeliveryMethod.WEB: form.get('additionalConfig.icon.enabled').updateValueAndValidity({onlySelf: true}); break; + case NotificationDeliveryMethod.MOBILE_APP: + form.get('additionalConfig.icon.enabled').updateValueAndValidity({onlySelf: true}); + break; } } }); @@ -225,9 +236,25 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co subject: ['', [Validators.required, Validators.maxLength(50)]], body: ['', [Validators.required, Validators.maxLength(150)]], additionalConfig: this.fb.group({ + icon: this.fb.group({ + enabled: [false], + icon: [{value: 'notifications', disabled: true}, Validators.required], + color: [{value: '#757575', disabled: true}] + }), onClick: [null] }) }); + deliveryMethodForm.get('additionalConfig.icon.enabled').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + if (value) { + deliveryMethodForm.get('additionalConfig.icon.icon').enable({emitEvent: false}); + deliveryMethodForm.get('additionalConfig.icon.color').enable({emitEvent: false}); + } else { + deliveryMethodForm.get('additionalConfig.icon.icon').disable({emitEvent: false}); + deliveryMethodForm.get('additionalConfig.icon.color').disable({emitEvent: false}); + } + }); break; case NotificationDeliveryMethod.MICROSOFT_TEAMS: deliveryMethodForm = this.fb.group({ From d39be089744ab694114130d23441f51a24097a10 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 7 Mar 2024 17:50:28 +0200 Subject: [PATCH 163/209] UI: Improve time series bar width strategy: calculate width as percentage of time window. --- ...e-series-chart-basic-config.component.html | 31 +--- ...ime-series-chart-basic-config.component.ts | 31 +--- .../lib/chart/time-series-chart-bar.models.ts | 16 +- .../lib/chart/time-series-chart.models.ts | 40 ++++- .../widget/lib/chart/time-series-chart.ts | 3 +- ...eries-chart-widget-settings.component.html | 31 +--- ...-series-chart-widget-settings.component.ts | 38 +---- ...me-series-chart-axis-settings.component.ts | 5 +- ...regation-bar-width-settings.component.html | 75 +++++++++ ...ggregation-bar-width-settings.component.ts | 147 ++++++++++++++++++ .../common/widget-settings-common.module.ts | 5 + .../assets/locale/locale.constant-en_US.json | 6 +- ui-ngx/src/form.scss | 6 +- 13 files changed, 295 insertions(+), 139 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html index 5b14146d0f..3d099c2645 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html @@ -114,33 +114,10 @@ axisType="xAxis">
-
-
-
widgets.time-series-chart.no-aggregation-bar-width-strategy
- - - {{ timeSeriesChartNoAggregationBarWidthStrategyTranslations.get(strategy) | translate }} - - -
-
-
widgets.time-series-chart.bar-group-interval-width
- - - ms - -
-
-
widgets.time-series-chart.separate-bar-width
- - - ms - -
-
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts index 79aa52fdb6..5307147b0e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts @@ -45,12 +45,7 @@ import { TimeSeriesChartWidgetSettings } from '@home/components/widget/lib/chart/time-series-chart-widget.models'; import { EChartsTooltipTrigger } from '@home/components/widget/lib/chart/echarts-widget.models'; -import { - timeSeriesChartNoAggregationBarWidthStrategies, - TimeSeriesChartNoAggregationBarWidthStrategy, - timeSeriesChartNoAggregationBarWidthStrategyTranslations, - TimeSeriesChartType -} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { TimeSeriesChartType } from '@home/components/widget/lib/chart/time-series-chart.models'; @Component({ selector: 'tb-time-series-chart-basic-config', @@ -76,12 +71,6 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon legendPositionTranslationMap = legendPositionTranslationMap; - TimeSeriesChartNoAggregationBarWidthStrategy = TimeSeriesChartNoAggregationBarWidthStrategy; - - timeSeriesChartNoAggregationBarWidthStrategies = timeSeriesChartNoAggregationBarWidthStrategies; - - timeSeriesChartNoAggregationBarWidthStrategyTranslations = timeSeriesChartNoAggregationBarWidthStrategyTranslations; - timeSeriesChartWidgetConfigForm: UntypedFormGroup; tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); @@ -140,11 +129,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon yAxis: [settings.yAxis, []], xAxis: [settings.xAxis, []], - noAggregationBarWidthSettings: this.fb.group({ - strategy: [settings.noAggregationBarWidthSettings.strategy, []], - groupIntervalWidth: [settings.noAggregationBarWidthSettings.groupIntervalWidth, [Validators.min(100)]], - separateBarWidth: [settings.noAggregationBarWidthSettings.separateBarWidth, [Validators.min(100)]], - }), + noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []], showLegend: [settings.showLegend, []], legendLabelFont: [settings.legendLabelFont, []], @@ -227,7 +212,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon } protected validatorTriggers(): string[] { - return ['showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate', 'noAggregationBarWidthSettings.strategy']; + return ['showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate']; } protected updateValidators(emitEvent: boolean, trigger?: string) { @@ -236,8 +221,6 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon const showLegend: boolean = this.timeSeriesChartWidgetConfigForm.get('showLegend').value; const showTooltip: boolean = this.timeSeriesChartWidgetConfigForm.get('showTooltip').value; const tooltipShowDate: boolean = this.timeSeriesChartWidgetConfigForm.get('tooltipShowDate').value; - const noAggregationBarWidthSettingsStrategy: TimeSeriesChartNoAggregationBarWidthStrategy = - this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('strategy').value; if (showTitle) { this.timeSeriesChartWidgetConfigForm.get('title').enable(); @@ -266,14 +249,6 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon this.timeSeriesChartWidgetConfigForm.get('iconColor').disable(); } - if (noAggregationBarWidthSettingsStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.group) { - this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('groupIntervalWidth').enable(); - this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('separateBarWidth').disable(); - } else if (noAggregationBarWidthSettingsStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate) { - this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('groupIntervalWidth').disable(); - this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('separateBarWidth').enable(); - } - if (showLegend) { this.timeSeriesChartWidgetConfigForm.get('legendLabelFont').enable(); this.timeSeriesChartWidgetConfigForm.get('legendLabelColor').enable(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts index e5dfd6944e..bf7f6cf764 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts @@ -19,7 +19,6 @@ import { Interval, IntervalMath } from '@shared/models/time/time.models'; import { LabelFormatterCallback, SeriesLabelOption } from 'echarts/types/src/util/types'; import { TimeSeriesChartDataItem, - TimeSeriesChartNoAggregationBarWidthSettings, TimeSeriesChartNoAggregationBarWidthStrategy } from '@home/components/widget/lib/chart/time-series-chart.models'; import { CustomSeriesRenderItemParams } from 'echarts'; @@ -38,7 +37,9 @@ export interface BarRenderContext { barsCount?: number; barIndex?: number; noAggregation: boolean; - noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings; + noAggregationBarWidthStrategy: TimeSeriesChartNoAggregationBarWidthStrategy; + noAggregationWidthRelative?: boolean; + noAggregationWidth?: number; timeInterval?: Interval; visualSettings?: BarVisualSettings; labelOption?: SeriesLabelOption; @@ -54,16 +55,15 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C let interval = end - start; const ts = start ? start : time; - const noAggregationGroup = renderCtx.noAggregation && - renderCtx.noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group; const separateBar = renderCtx.noAggregation && - renderCtx.noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate; + renderCtx.noAggregationBarWidthStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate; if (renderCtx.noAggregation) { - if (noAggregationGroup) { - interval = renderCtx.noAggregationBarWidthSettings.groupIntervalWidth; + if (renderCtx.noAggregationWidthRelative) { + const scaleWidth = api.getWidth() / api.size([1,0])[0]; + interval = scaleWidth * (renderCtx.noAggregationWidth / 100); } else { - interval = renderCtx.noAggregationBarWidthSettings.separateBarWidth; + interval = renderCtx.noAggregationWidth; } start = time - interval / 2; end = time + interval / 2; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index 6aac959cbe..fa349252b7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -391,10 +391,16 @@ export const timeSeriesChartNoAggregationBarWidthStrategyTranslations = new Map< ] ); +export interface TimeSeriesChartBarWidth { + relative?: boolean; + relativeWidth?: number; + absoluteWidth?: number; +} + export interface TimeSeriesChartNoAggregationBarWidthSettings { strategy: TimeSeriesChartNoAggregationBarWidthStrategy; - groupIntervalWidth?: number; - separateBarWidth?: number; + groupWidth?: TimeSeriesChartBarWidth; + barWidth?: TimeSeriesChartBarWidth; } export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings { @@ -474,8 +480,16 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { }, noAggregationBarWidthSettings: { strategy: TimeSeriesChartNoAggregationBarWidthStrategy.group, - groupIntervalWidth: 1000, - separateBarWidth: 1000 + groupWidth: { + relative: true, + relativeWidth: 2, + absoluteWidth: 1000 + }, + barWidth: { + relative: true, + relativeWidth: 2, + absoluteWidth: 1000 + } }, showTooltip: true, tooltipTrigger: EChartsTooltipTrigger.axis, @@ -757,12 +771,12 @@ export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartAxisSetting export const generateChartData = (dataItems: TimeSeriesChartDataItem[], thresholdItems: TimeSeriesChartThresholdItem[], timeInterval: Interval, + stack: boolean, noAggregation: boolean, noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings, - stack: boolean, darkMode: boolean): Array => { let series = generateChartSeries(dataItems, timeInterval, - noAggregation, noAggregationBarWidthSettings, stack, darkMode); + stack, noAggregation, noAggregationBarWidthSettings, darkMode); if (thresholdItems.length) { const thresholds = generateChartThresholds(thresholdItems, darkMode); series = series.concat(thresholds); @@ -874,9 +888,9 @@ const createThresholdData = (val: string | number, item: TimeSeriesChartThreshol const generateChartSeries = (dataItems: TimeSeriesChartDataItem[], timeInterval: Interval, + stack: boolean, noAggregation: boolean, noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings, - stack: boolean, darkMode: boolean): Array => { const series: Array = []; const enabledDataItems = dataItems.filter(d => d.enabled); @@ -895,7 +909,17 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[], for (const item of enabledDataItems) { if (item.dataKey.settings.type === TimeSeriesChartSeriesType.bar) { if (!item.barRenderContext) { - item.barRenderContext = {noAggregation, noAggregationBarWidthSettings}; + item.barRenderContext = {noAggregation, + noAggregationBarWidthStrategy: noAggregationBarWidthSettings.strategy}; + const targetWidth = noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group ? + noAggregationBarWidthSettings.groupWidth : noAggregationBarWidthSettings.barWidth; + if (targetWidth.relative) { + item.barRenderContext.noAggregationWidthRelative = true; + item.barRenderContext.noAggregationWidth = targetWidth.relativeWidth; + } else { + item.barRenderContext.noAggregationWidthRelative = false; + item.barRenderContext.noAggregationWidth = targetWidth.absoluteWidth; + } } item.barRenderContext.noAggregation = noAggregation; item.barRenderContext.barsCount = barsCount; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index e5d4cb8f46..6d790d5439 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -492,8 +492,9 @@ export class TbTimeSeriesChart { private updateSeries(): Array { return generateChartData(this.dataItems, this.thresholdItems, this.ctx.timeWindow.interval, + this.settings.stack, this.noAggregation, - this.settings.noAggregationBarWidthSettings, this.settings.stack, this.darkMode); + this.settings.noAggregationBarWidthSettings, this.darkMode); } private updateAxes() { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html index f3a9a610c3..a754dec0b4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html @@ -48,33 +48,10 @@ axisType="xAxis">
-
-
-
widgets.time-series-chart.no-aggregation-bar-width-strategy
- - - {{ timeSeriesChartNoAggregationBarWidthStrategyTranslations.get(strategy) | translate }} - - -
-
-
widgets.time-series-chart.bar-group-interval-width
- - - ms - -
-
-
widgets.time-series-chart.separate-bar-width
- - - ms - -
-
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts index a520933f1b..b793e8c3eb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts @@ -22,23 +22,17 @@ import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; -import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { formatValue, isDefinedAndNotNull, mergeDeep } from '@core/utils'; import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models'; -import { - barChartWithLabelsDefaultSettings -} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models'; import { EChartsTooltipTrigger } from '../../chart/echarts-widget.models'; import { - timeSeriesChartWidgetDefaultSettings, TimeSeriesChartWidgetSettings + timeSeriesChartWidgetDefaultSettings, + TimeSeriesChartWidgetSettings } from '@home/components/widget/lib/chart/time-series-chart-widget.models'; -import { - timeSeriesChartNoAggregationBarWidthStrategies, - TimeSeriesChartNoAggregationBarWidthStrategy, - timeSeriesChartNoAggregationBarWidthStrategyTranslations, TimeSeriesChartType -} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { TimeSeriesChartType } from '@home/components/widget/lib/chart/time-series-chart.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; @Component({ @@ -65,12 +59,6 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon legendPositionTranslationMap = legendPositionTranslationMap; - TimeSeriesChartNoAggregationBarWidthStrategy = TimeSeriesChartNoAggregationBarWidthStrategy; - - timeSeriesChartNoAggregationBarWidthStrategies = timeSeriesChartNoAggregationBarWidthStrategies; - - timeSeriesChartNoAggregationBarWidthStrategyTranslations = timeSeriesChartNoAggregationBarWidthStrategyTranslations; - timeSeriesChartWidgetSettingsForm: UntypedFormGroup; tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); @@ -111,11 +99,7 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon yAxis: [settings.yAxis, []], xAxis: [settings.xAxis, []], - noAggregationBarWidthSettings: this.fb.group({ - strategy: [settings.noAggregationBarWidthSettings.strategy, []], - groupIntervalWidth: [settings.noAggregationBarWidthSettings.groupIntervalWidth, [Validators.min(100)]], - separateBarWidth: [settings.noAggregationBarWidthSettings.separateBarWidth, [Validators.min(100)]], - }), + noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []], showLegend: [settings.showLegend, []], legendLabelFont: [settings.legendLabelFont, []], @@ -140,23 +124,13 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon } protected validatorTriggers(): string[] { - return ['showLegend', 'showTooltip', 'tooltipShowDate', 'noAggregationBarWidthSettings.strategy']; + return ['showLegend', 'showTooltip', 'tooltipShowDate']; } protected updateValidators(emitEvent: boolean) { const showLegend: boolean = this.timeSeriesChartWidgetSettingsForm.get('showLegend').value; const showTooltip: boolean = this.timeSeriesChartWidgetSettingsForm.get('showTooltip').value; const tooltipShowDate: boolean = this.timeSeriesChartWidgetSettingsForm.get('tooltipShowDate').value; - const noAggregationBarWidthSettingsStrategy: TimeSeriesChartNoAggregationBarWidthStrategy = - this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('strategy').value; - - if (noAggregationBarWidthSettingsStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.group) { - this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('groupIntervalWidth').enable(); - this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('separateBarWidth').disable(); - } else if (noAggregationBarWidthSettingsStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate) { - this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('groupIntervalWidth').disable(); - this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('separateBarWidth').enable(); - } if (showLegend) { this.timeSeriesChartWidgetSettingsForm.get('legendLabelFont').enable(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts index 2ae330c276..ab20727bdf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts @@ -22,8 +22,6 @@ import { TimeSeriesChartAxisSettings, TimeSeriesChartYAxisSettings } from '@home/components/widget/lib/chart/time-series-chart.models'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; import { merge } from 'rxjs'; @Component({ @@ -60,8 +58,7 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu public axisSettingsFormGroup: UntypedFormGroup; - constructor(protected store: Store, - private fb: UntypedFormBuilder) { + constructor(private fb: UntypedFormBuilder) { } ngOnInit(): void { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.html new file mode 100644 index 0000000000..239c6ea18c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.html @@ -0,0 +1,75 @@ + +
+
+
widgets.time-series-chart.no-aggregation-bar-width-strategy
+ + + {{ timeSeriesChartNoAggregationBarWidthStrategyTranslations.get(strategy) | translate }} + + +
+ + + + + + + + +
+ +
+
{{ label }}
+
+ + + + {{ (formGroup.get('relative').value ? + 'widgets.time-series-chart.bar-width-relative' : + 'widgets.time-series-chart.bar-width-absolute') | translate }} + + + {{ 'widgets.time-series-chart.bar-width-relative' | translate }} + + + {{ 'widgets.time-series-chart.bar-width-absolute' | translate }} + + + + + + % + + + + ms + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.ts new file mode 100644 index 0000000000..36d8cb917a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.ts @@ -0,0 +1,147 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + Validators +} from '@angular/forms'; +import { + TimeSeriesChartNoAggregationBarWidthSettings, + timeSeriesChartNoAggregationBarWidthStrategies, + TimeSeriesChartNoAggregationBarWidthStrategy, + timeSeriesChartNoAggregationBarWidthStrategyTranslations +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { merge } from 'rxjs'; + +@Component({ + selector: 'tb-time-series-no-aggregation-bar-width-settings', + templateUrl: './time-series-no-aggregation-bar-width-settings.component.html', + styleUrls: ['./../../widget-settings.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeSeriesNoAggregationBarWidthSettingsComponent), + multi: true + } + ] +}) +export class TimeSeriesNoAggregationBarWidthSettingsComponent implements OnInit, ControlValueAccessor { + + TimeSeriesChartNoAggregationBarWidthStrategy = TimeSeriesChartNoAggregationBarWidthStrategy; + + timeSeriesChartNoAggregationBarWidthStrategies = timeSeriesChartNoAggregationBarWidthStrategies; + + timeSeriesChartNoAggregationBarWidthStrategyTranslations = timeSeriesChartNoAggregationBarWidthStrategyTranslations; + + @Input() + disabled: boolean; + + private modelValue: TimeSeriesChartNoAggregationBarWidthSettings; + + private propagateChange = null; + + public barWidthSettingsFormGroup: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + } + + ngOnInit(): void { + this.barWidthSettingsFormGroup = this.fb.group({ + strategy: [null, []], + groupWidth: this.fb.group({ + relative: [null, []], + relativeWidth: [null, [Validators.required, Validators.min(0.1), Validators.max(100)]], + absoluteWidth: [null, [Validators.required, Validators.min(100)]] + }), + barWidth: this.fb.group({ + relative: [null, []], + relativeWidth: [null, [Validators.required, Validators.min(0.1), Validators.max(100)]], + absoluteWidth: [null, [Validators.required, Validators.min(100)]] + }) + }); + this.barWidthSettingsFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + merge(this.barWidthSettingsFormGroup.get('strategy').valueChanges, + this.barWidthSettingsFormGroup.get('groupWidth.relative').valueChanges, + this.barWidthSettingsFormGroup.get('barWidth.relative').valueChanges) + .subscribe(() => { + this.updateValidators(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.barWidthSettingsFormGroup.disable({emitEvent: false}); + } else { + this.barWidthSettingsFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + writeValue(value: TimeSeriesChartNoAggregationBarWidthSettings): void { + this.modelValue = value; + this.barWidthSettingsFormGroup.patchValue( + value, {emitEvent: false} + ); + this.updateValidators(); + } + + private updateValidators() { + const strategy: TimeSeriesChartNoAggregationBarWidthStrategy = + this.barWidthSettingsFormGroup.get('strategy').value; + const groupWidthRelative: boolean = this.barWidthSettingsFormGroup.get('groupWidth.relative').value; + const barWidthRelative: boolean = this.barWidthSettingsFormGroup.get('barWidth.relative').value; + if (strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group) { + this.barWidthSettingsFormGroup.get('groupWidth').enable({emitEvent: false}); + this.barWidthSettingsFormGroup.get('barWidth').disable({emitEvent: false}); + if (groupWidthRelative) { + this.barWidthSettingsFormGroup.get('groupWidth').get('relativeWidth').enable({emitEvent: false}); + this.barWidthSettingsFormGroup.get('groupWidth').get('absoluteWidth').disable({emitEvent: false}); + } else { + this.barWidthSettingsFormGroup.get('groupWidth').get('relativeWidth').disable({emitEvent: false}); + this.barWidthSettingsFormGroup.get('groupWidth').get('absoluteWidth').enable({emitEvent: false}); + } + } else if (strategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate) { + this.barWidthSettingsFormGroup.get('groupWidth').disable({emitEvent: false}); + this.barWidthSettingsFormGroup.get('barWidth').enable({emitEvent: false}); + if (barWidthRelative) { + this.barWidthSettingsFormGroup.get('barWidth').get('relativeWidth').enable({emitEvent: false}); + this.barWidthSettingsFormGroup.get('barWidth').get('absoluteWidth').disable({emitEvent: false}); + } else { + this.barWidthSettingsFormGroup.get('barWidth').get('relativeWidth').disable({emitEvent: false}); + this.barWidthSettingsFormGroup.get('barWidth').get('absoluteWidth').enable({emitEvent: false}); + } + } + } + + private updateModel() { + this.modelValue = this.barWidthSettingsFormGroup.getRawValue(); + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index e741314315..b741a8e358 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -106,6 +106,9 @@ import { EntityAliasInputComponent } from '@home/components/widget/lib/settings/ import { TimeSeriesChartThresholdSettingsPanelComponent } from '@home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component'; +import { + TimeSeriesNoAggregationBarWidthSettingsComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component'; @NgModule({ declarations: [ @@ -146,6 +149,7 @@ import { TimeSeriesChartThresholdsPanelComponent, TimeSeriesChartThresholdRowComponent, TimeSeriesChartThresholdSettingsPanelComponent, + TimeSeriesNoAggregationBarWidthSettingsComponent, DataKeyInputComponent, EntityAliasInputComponent ], @@ -192,6 +196,7 @@ import { TimeSeriesChartThresholdsPanelComponent, TimeSeriesChartThresholdRowComponent, TimeSeriesChartThresholdSettingsPanelComponent, + TimeSeriesNoAggregationBarWidthSettingsComponent, DataKeyInputComponent, EntityAliasInputComponent ], diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index b32cfb7d38..1cef32ac6b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6629,8 +6629,10 @@ "no-aggregation-bar-width-strategy": "Bar width strategy for non-aggregated data", "no-aggregation-bar-width-strategy-group": "Group", "no-aggregation-bar-width-strategy-separate": "Separate", - "bar-group-interval-width": "Bar group interval width", - "separate-bar-width": "Separate bar width", + "bar-group-width": "Bar group width", + "bar-width": "Bar width", + "bar-width-relative": "Percentage of time window", + "bar-width-absolute": "Absolute (ms)", "threshold": { "thresholds": "Thresholds", "source": "Source", diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index dfe6667afd..e2390c795d 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -29,9 +29,11 @@ @media #{$breakpoint} { @include form-row-column; .mat-mdc-form-field, tb-unit-input { - width: auto; - &.medium-width { + &:not(.fixed-width) { width: auto; + &.medium-width { + width: auto; + } } } } From fa14a5ffdcce82676070ee25a381319a68810ce9 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 7 Mar 2024 17:55:09 +0200 Subject: [PATCH 164/209] Update widget.models.ts --- ui-ngx/src/app/shared/models/widget.models.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index de875c02ae..c1824be6f7 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -553,7 +553,7 @@ export enum WidgetMobileActionType { takeScreenshot = 'takeScreenshot' } -export const widgetActionTypes = Object.keys(WidgetActionType); +export const widgetActionTypes = Object.keys(WidgetActionType) as WidgetActionType[]; export const widgetActionTypeTranslationMap = new Map( [ From ad9944e9f99523b8d7d0ad5066d8b8167d20206d Mon Sep 17 00:00:00 2001 From: kalytka Date: Thu, 7 Mar 2024 18:38:52 +0200 Subject: [PATCH 165/209] Refactoring --- ui-ngx/src/assets/dashboard/api_usage.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/assets/dashboard/api_usage.json b/ui-ngx/src/assets/dashboard/api_usage.json index 5618494261..00793db02c 100644 --- a/ui-ngx/src/assets/dashboard/api_usage.json +++ b/ui-ngx/src/assets/dashboard/api_usage.json @@ -2063,7 +2063,7 @@ "realtimeType": 0, "timewindowMs": 86400000, "quickInterval": "CURRENT_DAY", - "interval": 300000 + "interval": 3600000 }, "aggregation": { "type": "NONE", @@ -7922,7 +7922,7 @@ "dashboardLogoUrl": null, "hideToolbar": false, "showUpdateDashboardImage": false, - "dashboardCss": ".tb-time-series-chart-panel {\n padding: 13px;\n}\n\n.tb-time-series-chart-panel {\n gap: 0; \n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n\n.card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n.card .unit {\n color: #666666;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} " + "dashboardCss": ".tb-time-series-chart-panel {\n padding: 13px;\n}\n\n.tb-time-series-chart-content {\n gap: 0; \n}\n\n.tb-time-series-chart-panel {\n gap: 8px; \n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n\n.card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n.card .unit {\n color: #666666;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} " } }, "name": "Api Usage" From 71583c5a292b33ef73700718d1386f7cf2df7bb3 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 7 Mar 2024 17:41:51 +0100 Subject: [PATCH 166/209] TbAbstractGetAttributesNode: get latest moved to the last step because SqlTimeseriesLatestDao.findLatest() effectively do blocking DB call, so getting attr async will do the job without awaiting the latest response. --- .../rule/engine/metadata/TbAbstractGetAttributesNode.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java index fab1893702..7cf144ec66 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java @@ -99,10 +99,10 @@ public abstract class TbAbstractGetAttributesNode>> failuresPairSet = ConcurrentHashMap.newKeySet(); var getKvEntryPairFutures = Futures.allAsList( - getLatestTelemetry(ctx, entityId, TbNodeUtils.processPatterns(config.getLatestTsKeyNames(), msg), failuresPairSet), getAttrAsync(ctx, entityId, CLIENT_SCOPE, TbNodeUtils.processPatterns(config.getClientAttributeNames(), msg), failuresPairSet), getAttrAsync(ctx, entityId, SHARED_SCOPE, TbNodeUtils.processPatterns(config.getSharedAttributeNames(), msg), failuresPairSet), - getAttrAsync(ctx, entityId, SERVER_SCOPE, TbNodeUtils.processPatterns(config.getServerAttributeNames(), msg), failuresPairSet) + getAttrAsync(ctx, entityId, SERVER_SCOPE, TbNodeUtils.processPatterns(config.getServerAttributeNames(), msg), failuresPairSet), + getLatestTelemetry(ctx, entityId, TbNodeUtils.processPatterns(config.getLatestTsKeyNames(), msg), failuresPairSet) ); withCallback(getKvEntryPairFutures, futuresList -> { var msgMetaData = msg.getMetaData().copy(); From db36fb2c476e415dae93ce44f139a4aac7c272a1 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 7 Mar 2024 18:53:31 +0200 Subject: [PATCH 167/209] UI: Improve time series chart widget layout. --- .../widget/lib/chart/time-series-chart-widget.component.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss index 9d990c50a7..7da3d8e4b5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss @@ -23,7 +23,7 @@ $maxLegendHeight: 35%; position: relative; display: flex; flex-direction: column; - gap: 16px; + gap: 8px; padding: 20px 24px 24px 24px; > div:not(.tb-time-series-chart-overlay) { z-index: 1; @@ -44,7 +44,7 @@ $maxLegendHeight: 35%; min-height: 0; display: flex; flex-direction: column; - gap: 16px; + gap: 8px; &.legend-top { flex-direction: column-reverse; } From e364d15da57c6b3b38b25f91038bee6a5ba712fc Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 7 Mar 2024 19:57:19 +0200 Subject: [PATCH 168/209] UI: Time series chart: Add Y axis ticks formatter function. --- .../lib/chart/time-series-chart.models.ts | 22 +++++++++- ...eries-chart-widget-settings.component.html | 2 + ...-series-chart-axis-settings.component.html | 41 ++++++++++++------- ...me-series-chart-axis-settings.component.ts | 18 +++++++- .../assets/locale/locale.constant-en_US.json | 1 + 5 files changed, 66 insertions(+), 18 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index fa349252b7..edfaa3692d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -25,7 +25,7 @@ import { import { ComponentStyle, Font, simpleDateFormat, textStyle } from '@shared/models/widget-settings.models'; import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared'; import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts'; -import { formatValue, isDefinedAndNotNull, isUndefinedOrNull, parseFunction } from '@core/utils'; +import { formatValue, isDefinedAndNotNull, isUndefined, isUndefinedOrNull, parseFunction } from '@core/utils'; import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; import tinycolor from 'tinycolor2'; import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D'; @@ -293,6 +293,7 @@ export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSetting min?: number | string; max?: number | string; intervalCalculator?: string; + ticksFormatter?: string; } export interface TimeSeriesChartThreshold { @@ -441,6 +442,7 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { lineHeight: '1' }, tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light, + ticksFormatter: null, showTicks: true, ticksColor: timeSeriesChartColorScheme['axis.ticks'].light, showLine: true, @@ -647,6 +649,7 @@ export interface TimeSeriesChartYAxis { units: string; option: YAXisOption & ValueAxisBaseOption; intervalCalculator?: (axis: Axis2D) => number; + ticksFormatter?: (value: any) => string; } export const createTimeSeriesYAxis = (axisId: string, units: string, @@ -656,6 +659,10 @@ export const createTimeSeriesYAxis = (axisId: string, units: string, settings.tickLabelColor, darkMode, 'axis.tickLabel'); const yAxisNameStyle = createChartTextStyle(settings.labelFont, settings.labelColor, darkMode, 'axis.label'); + let ticksFormatter: (value: any) => string; + if (settings.ticksFormatter && settings.ticksFormatter.length) { + ticksFormatter = parseFunction(settings.ticksFormatter, ['value']); + } const yAxis: TimeSeriesChartYAxis = { id: axisId, units, @@ -699,7 +706,18 @@ export const createTimeSeriesYAxis = (axisId: string, units: string, fontWeight: yAxisTickLabelStyle.fontWeight, fontFamily: yAxisTickLabelStyle.fontFamily, fontSize: yAxisTickLabelStyle.fontSize, - formatter: (value: any) => formatValue(value, decimals, units, false) + formatter: (value: any) => { + let result: string; + if (ticksFormatter) { + try { + result = ticksFormatter(value); + } catch (_e) {} + } + if (isUndefined(result)) { + result = formatValue(value, decimals, units, false); + } + return result; + } }, splitLine: { show: settings.showSplitLines, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html index a754dec0b4..35f0d147ac 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html @@ -41,10 +41,12 @@
widgets.time-series-chart.axis.axes
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html index b12e322b17..24f3dc0883 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html @@ -56,21 +56,32 @@
-
- -
widgets.time-series-chart.axis.tick-labels
-
-
- - - - +
+
+ +
widgets.time-series-chart.axis.tick-labels
+
+
+ + + + +
+
+
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts index ab20727bdf..328574d648 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts @@ -23,6 +23,8 @@ import { TimeSeriesChartYAxisSettings } from '@home/components/widget/lib/chart/time-series-chart.models'; import { merge } from 'rxjs'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { WidgetService } from '@core/http/widget.service'; @Component({ selector: 'tb-time-series-chart-axis-settings', @@ -46,19 +48,26 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu timeSeriesAxisPositionTranslations = timeSeriesAxisPositionTranslations; + functionScopeVariables = this.widgetService.getWidgetScopeVariables(); + @Input() disabled: boolean; @Input() axisType: 'xAxis' | 'yAxis' = 'xAxis'; + @Input() + @coerceBoolean() + advanced = false; + private modelValue: TimeSeriesChartAxisSettings | TimeSeriesChartYAxisSettings; private propagateChange = null; public axisSettingsFormGroup: UntypedFormGroup; - constructor(private fb: UntypedFormBuilder) { + constructor(private fb: UntypedFormBuilder, + private widgetService: WidgetService,) { } ngOnInit(): void { @@ -85,6 +94,7 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu splitLinesColor: [null, []] }); if (this.axisType === 'yAxis') { + this.axisSettingsFormGroup.addControl('ticksFormatter', this.fb.control(null, [])); this.axisSettingsFormGroup.addControl('min', this.fb.control(null, [])); this.axisSettingsFormGroup.addControl('max', this.fb.control(null, [])); } @@ -140,9 +150,15 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu if (showTickLabels) { this.axisSettingsFormGroup.get('tickLabelFont').enable({emitEvent: false}); this.axisSettingsFormGroup.get('tickLabelColor').enable({emitEvent: false}); + if (this.axisType === 'yAxis') { + this.axisSettingsFormGroup.get('ticksFormatter').enable({emitEvent: false}); + } } else { this.axisSettingsFormGroup.get('tickLabelFont').disable({emitEvent: false}); this.axisSettingsFormGroup.get('tickLabelColor').disable({emitEvent: false}); + if (this.axisType === 'yAxis') { + this.axisSettingsFormGroup.get('ticksFormatter').disable({emitEvent: false}); + } } if (showTicks) { this.axisSettingsFormGroup.get('ticksColor').enable({emitEvent: false}); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 1cef32ac6b..216ed878b2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6677,6 +6677,7 @@ "position-top": "Top", "position-bottom": "Bottom", "tick-labels": "Tick labels", + "ticks-formatter-function": "Ticks formatter function", "show-ticks": "Show ticks", "show-line": "Show line", "show-split-lines": "Show split lines", From c8abb9ed8eefa3b2bd9df16db1f754d014d2d0f7 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 7 Mar 2024 19:01:10 +0100 Subject: [PATCH 169/209] Validator.validateString implementation with function --- .../thingsboard/server/dao/service/Validator.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java index 77f81d639f..16454e5914 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java @@ -27,6 +27,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import java.util.List; import java.util.UUID; +import java.util.function.Function; import java.util.regex.Pattern; public class Validator { @@ -59,6 +60,18 @@ public class Validator { } } + /* + * This method validate String string. If string is invalid than throw + * IncorrectParameterException exception + * + * @param val the value + * @param errorMessageFunction the error message function that apply value + */ + public static void validateString(String val, Function errorMessageFunction) { + if (val == null || val.isEmpty()) { + throw new IncorrectParameterException(errorMessageFunction.apply(val)); + } + } /** * This method validate long value. If value isn't positive than throw From 8c513bbf82b0c023acfa2789ee4fb012fe4dcc33 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 7 Mar 2024 19:03:14 +0100 Subject: [PATCH 170/209] BaseTimeseriesService reduce memory footprint at findLatest --- .../dao/timeseries/BaseTimeseriesService.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index 1f77fa3c1d..8d04d1f570 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -43,6 +43,7 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.Validator; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -126,18 +127,22 @@ public class BaseTimeseriesService implements TimeseriesService { @Override public ListenableFuture> findLatest(TenantId tenantId, EntityId entityId, Collection keys) { validate(entityId); - List> futures = Lists.newArrayListWithExpectedSize(keys.size()); - keys.forEach(key -> Validator.validateString(key, "Incorrect key " + key)); - keys.forEach(key -> futures.add(timeseriesLatestDao.findLatest(tenantId, entityId, key))); + List> futures = new ArrayList<>(keys.size()); + keys.forEach(key -> Validator.validateString(key, k -> "Incorrect key " + k)); + for (String key : keys) { + futures.add(timeseriesLatestDao.findLatest(tenantId, entityId, key)); + } return Futures.allAsList(futures); } @Override public List findLatestSync(TenantId tenantId, EntityId entityId, Collection keys) { validate(entityId); - List latestEntries = Lists.newArrayListWithExpectedSize(keys.size()); - keys.forEach(key -> Validator.validateString(key, "Incorrect key " + key)); - keys.forEach(key -> latestEntries.add(timeseriesLatestDao.findLatestSync(tenantId, entityId, key))); + List latestEntries = new ArrayList(keys.size()); + keys.forEach(key -> Validator.validateString(key, k -> "Incorrect key " + k)); + for (String key : keys) { + latestEntries.add(timeseriesLatestDao.findLatestSync(tenantId, entityId, key)); + } return latestEntries; } From 45e9a9f642b9c5167738ba73437fbab533e49fb1 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 7 Mar 2024 20:26:59 +0100 Subject: [PATCH 171/209] Validator.validateEntityId with function added to reduce an overhead --- .../thingsboard/server/dao/service/Validator.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java index 16454e5914..b3a121baf6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java @@ -47,6 +47,19 @@ public class Validator { } } + /** + * This method validate EntityId entity id. If entity id is invalid than throw + * IncorrectParameterException exception + * + * @param entityId the entityId + * @param errorMessageFunction the error message for exception that apply entityId + */ + public static void validateEntityId(EntityId entityId, Function errorMessageFunction) { + if (entityId == null || entityId.getId() == null) { + throw new IncorrectParameterException(errorMessageFunction.apply(entityId)); + } + } + /** * This method validate String string. If string is invalid than throw * IncorrectParameterException exception From c97cbbefa3199c4401d191e2803310a6efb59468 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 7 Mar 2024 20:27:29 +0100 Subject: [PATCH 172/209] BaseTimeseriesService reduce memory footprint at validateEntityId --- .../server/dao/timeseries/BaseTimeseriesService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index 8d04d1f570..50c884f86e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -286,7 +286,7 @@ public class BaseTimeseriesService implements TimeseriesService { } private static void validate(EntityId entityId) { - Validator.validateEntityId(entityId, "Incorrect entityId " + entityId); + Validator.validateEntityId(entityId, id -> "Incorrect entityId " + id); } private void validate(ReadTsKvQuery query) { From 6b482d1cb5d5f90f9d9faa31cf9e87aa3578a111 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 8 Mar 2024 11:04:50 +0200 Subject: [PATCH 173/209] UI: Improved margin and config new widgets --- ui-ngx/src/assets/dashboard/api_usage.json | 359 +++++++++++++++------ 1 file changed, 268 insertions(+), 91 deletions(-) diff --git a/ui-ngx/src/assets/dashboard/api_usage.json b/ui-ngx/src/assets/dashboard/api_usage.json index 00793db02c..d5ed8d5304 100644 --- a/ui-ngx/src/assets/dashboard/api_usage.json +++ b/ui-ngx/src/assets/dashboard/api_usage.json @@ -97,7 +97,8 @@ "displayTimewindow": true, "widgetCss": "", "pageSize": 1024, - "noDataDisplayMessage": "" + "noDataDisplayMessage": "", + "configMode": "basic" }, "id": "a669cf86-e715-efa4-dd9a-b839abf499e9", "typeFullFqn": "system.cards.timeseries_table" @@ -1368,7 +1369,7 @@ "timezone": null }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -1439,8 +1440,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 2400000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 2400000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -1505,7 +1514,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": { "headerButton": [ { @@ -1679,7 +1688,7 @@ "timezone": null }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -1750,8 +1759,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 2400000, - "separateBarWidth": 2400000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 2400000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -1816,7 +1833,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": { "headerButton": [ { @@ -2072,7 +2089,7 @@ "timezone": null }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -2143,8 +2160,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 2400000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 2400000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -2209,7 +2234,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": { "headerButton": [ { @@ -2387,7 +2412,7 @@ "timezone": null }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -2458,8 +2483,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 2400000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 2400000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -2524,7 +2557,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": { "headerButton": [ { @@ -2698,7 +2731,7 @@ "timezone": null }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -2769,8 +2802,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 2400000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 2400000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -2835,7 +2876,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": { "headerButton": [ { @@ -3086,7 +3127,7 @@ "timezone": null }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -3157,8 +3198,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 2400000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 2400000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -3223,7 +3272,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": { "headerButton": [ { @@ -3401,7 +3450,7 @@ "timezone": null }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -3470,8 +3519,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 1800000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 1800000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -3536,7 +3593,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": {}, "showTitleIcon": false, "titleIcon": "thermostat", @@ -3703,7 +3760,7 @@ "timezone": null }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -3772,8 +3829,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 1800000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 1800000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -3838,7 +3903,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": {}, "showTitleIcon": false, "titleIcon": "thermostat", @@ -3962,7 +4027,7 @@ "timezone": null }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -4031,8 +4096,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 1800000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 1800000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -4097,7 +4170,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": {}, "showTitleIcon": false, "titleIcon": "thermostat", @@ -4212,7 +4285,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -4281,8 +4354,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 900000000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 900000000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -4347,7 +4428,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": {}, "showTitleIcon": false, "titleIcon": "thermostat", @@ -4478,7 +4559,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -4547,8 +4628,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 1800000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 1800000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -4613,7 +4702,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": {}, "showTitleIcon": false, "titleIcon": "thermostat", @@ -4744,7 +4833,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -4813,8 +4902,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 900000000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 900000000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -4994,7 +5091,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -5063,8 +5160,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 1800000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 1800000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -5129,7 +5234,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": {}, "showTitleIcon": false, "titleIcon": "thermostat", @@ -5244,7 +5349,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -5313,8 +5418,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 900000000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 900000000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -5494,7 +5607,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -5563,8 +5676,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 1800000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 1800000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -5744,7 +5865,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -5813,8 +5934,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 900000000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 900000000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -5994,7 +6123,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -6063,8 +6192,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 1800000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 1800000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -6244,7 +6381,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -6313,8 +6450,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 1800000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 1800000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -6494,7 +6639,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -6563,8 +6708,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 900000000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 900000000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -6744,7 +6897,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -6813,8 +6966,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 900000000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 900000000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -7056,7 +7217,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -7125,8 +7286,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 1800000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 1800000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -7190,7 +7359,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": {}, "showTitleIcon": false, "titleIcon": "thermostat", @@ -7331,7 +7500,7 @@ } }, "showTitle": true, - "backgroundColor": "rgba(0, 0, 0, 0)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { @@ -7400,8 +7569,16 @@ }, "noAggregationBarWidthSettings": { "strategy": "group", - "groupIntervalWidth": 1800000, - "separateBarWidth": 1000 + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 1800000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, "legendLabelFont": { @@ -7465,7 +7642,7 @@ "dropShadow": true, "enableFullscreen": true, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": {}, "showTitleIcon": false, "titleIcon": "thermostat", @@ -7602,7 +7779,7 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70, + "mobileRowHeight": 100, "outerMargin": true } } @@ -7631,7 +7808,7 @@ "backgroundColor": "#eeeeee", "color": "rgba(0,0,0,0.870588)", "columns": 24, - "margin": 10, + "margin": 5, "backgroundSizeMode": "100%", "autoFillHeight": true, "backgroundImageUrl": null, @@ -7649,13 +7826,13 @@ "main": { "widgets": { "c77e417c-ad9d-8e23-3ea1-c75edd653bc0": { - "sizeX": 25, + "sizeX": 24, "sizeY": 6, "row": 0, "col": 0 }, "870904d2-d2e1-a1b9-ce56-b03fd47259b5": { - "sizeX": 25, + "sizeX": 24, "sizeY": 6, "row": 6, "col": 0 @@ -7665,7 +7842,7 @@ "backgroundColor": "#eeeeee", "color": "rgba(0,0,0,0.870588)", "columns": 24, - "margin": 10, + "margin": 5, "backgroundSizeMode": "100%", "autoFillHeight": true, "backgroundImageUrl": null, @@ -7699,7 +7876,7 @@ "backgroundColor": "#eeeeee", "color": "rgba(0,0,0,0.870588)", "columns": 24, - "margin": 10, + "margin": 5, "backgroundSizeMode": "100%", "autoFillHeight": true, "backgroundImageUrl": null, @@ -7739,7 +7916,7 @@ "backgroundColor": "#eeeeee", "color": "rgba(0,0,0,0.870588)", "columns": 24, - "margin": 10, + "margin": 5, "backgroundSizeMode": "100%", "autoFillHeight": true, "backgroundImageUrl": null, @@ -7785,7 +7962,7 @@ "backgroundColor": "#eeeeee", "color": "rgba(0,0,0,0.870588)", "columns": 24, - "margin": 10, + "margin": 5, "backgroundSizeMode": "100%", "autoFillHeight": true, "backgroundImageUrl": null, @@ -7819,7 +7996,7 @@ "backgroundColor": "#eeeeee", "color": "rgba(0,0,0,0.870588)", "columns": 24, - "margin": 10, + "margin": 5, "backgroundSizeMode": "100%", "autoFillHeight": true, "backgroundImageUrl": null, @@ -7853,7 +8030,7 @@ "backgroundColor": "#eeeeee", "color": "rgba(0,0,0,0.870588)", "columns": 24, - "margin": 10, + "margin": 5, "backgroundSizeMode": "100%", "autoFillHeight": true, "backgroundImageUrl": null, From c2183be6f551021a24aab2e4742261e3b8161504 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 8 Mar 2024 10:39:56 +0100 Subject: [PATCH 174/209] BaseTimeseriesService Memory footprint reduced. replaced Lists.newArrayListWithExpectedSize with new ArrayList(size) as we are always use fixed size array and never exceed the initial size. The newArrayListWithExpectedSize implementation adds some additional space to grow beyond initial size. --- .../dao/timeseries/BaseTimeseriesService.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index 50c884f86e..cfba6786f3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -16,7 +16,6 @@ package org.thingsboard.server.dao.timeseries; import com.google.common.base.Function; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; @@ -138,7 +137,7 @@ public class BaseTimeseriesService implements TimeseriesService { @Override public List findLatestSync(TenantId tenantId, EntityId entityId, Collection keys) { validate(entityId); - List latestEntries = new ArrayList(keys.size()); + List latestEntries = new ArrayList<>(keys.size()); keys.forEach(key -> Validator.validateString(key, k -> "Incorrect key " + k)); for (String key : keys) { latestEntries.add(timeseriesLatestDao.findLatestSync(tenantId, entityId, key)); @@ -170,7 +169,7 @@ public class BaseTimeseriesService implements TimeseriesService { @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { validate(entityId); - List> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); + List> futures = new ArrayList<>(INSERTS_PER_ENTRY); saveAndRegisterFutures(tenantId, futures, entityId, tsKvEntry, 0L); return Futures.transform(Futures.allAsList(futures), SUM_ALL_INTEGERS, MoreExecutors.directExecutor()); } @@ -187,7 +186,7 @@ public class BaseTimeseriesService implements TimeseriesService { private ListenableFuture doSave(TenantId tenantId, EntityId entityId, List tsKvEntries, long ttl, boolean saveLatest) { int inserts = saveLatest ? INSERTS_PER_ENTRY : INSERTS_PER_ENTRY_WITHOUT_LATEST; - List> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * inserts); + List> futures = new ArrayList<>(tsKvEntries.size() * inserts); for (TsKvEntry tsKvEntry : tsKvEntries) { if (saveLatest) { saveAndRegisterFutures(tenantId, futures, entityId, tsKvEntry, ttl); @@ -200,7 +199,7 @@ public class BaseTimeseriesService implements TimeseriesService { @Override public ListenableFuture> saveLatest(TenantId tenantId, EntityId entityId, List tsKvEntries) { - List> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size()); + List> futures = new ArrayList<>(tsKvEntries.size()); for (TsKvEntry tsKvEntry : tsKvEntries) { futures.add(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry)); } @@ -247,7 +246,7 @@ public class BaseTimeseriesService implements TimeseriesService { public ListenableFuture> remove(TenantId tenantId, EntityId entityId, List deleteTsKvQueries) { validate(entityId); deleteTsKvQueries.forEach(BaseTimeseriesService::validate); - List> futures = Lists.newArrayListWithExpectedSize(deleteTsKvQueries.size() * DELETES_PER_ENTRY); + List> futures = new ArrayList<>(deleteTsKvQueries.size() * DELETES_PER_ENTRY); for (DeleteTsKvQuery tsKvQuery : deleteTsKvQueries) { deleteAndRegisterFutures(tenantId, futures, entityId, tsKvQuery); } @@ -257,7 +256,7 @@ public class BaseTimeseriesService implements TimeseriesService { @Override public ListenableFuture> removeLatest(TenantId tenantId, EntityId entityId, Collection keys) { validate(entityId); - List> futures = Lists.newArrayListWithExpectedSize(keys.size()); + List> futures = new ArrayList<>(keys.size()); for (String key : keys) { DeleteTsKvQuery query = new BaseDeleteTsKvQuery(key, 0, System.currentTimeMillis(), false); futures.add(timeseriesLatestDao.removeLatest(tenantId, entityId, query)); From 59154be6c8d5936a12e839043739de674f3b79b9 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 8 Mar 2024 12:37:23 +0200 Subject: [PATCH 175/209] UI: Fixed not updated action source in widget action --- .../action/widget-action-dialog.component.ts | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts index b10085c294..2c7f94b8d5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts @@ -20,11 +20,11 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { + FormBuilder, + FormControl, + FormGroup, FormGroupDirective, NgForm, - UntypedFormBuilder, - UntypedFormControl, - UntypedFormGroup, ValidatorFn, Validators } from '@angular/forms'; @@ -32,13 +32,15 @@ import { Subject } from 'rxjs'; import { Router } from '@angular/router'; import { DialogComponent } from '@app/shared/components/dialog.component'; import { + toWidgetActionDescriptor, WidgetActionCallbacks, WidgetActionDescriptorInfo, WidgetActionsData } from '@home/components/widget/action/manage-widget-actions.component.models'; import { UtilsService } from '@core/services/utils.service'; import { - actionDescriptorToAction, defaultWidgetAction, + actionDescriptorToAction, + defaultWidgetAction, WidgetActionSource, widgetType } from '@shared/models/widget.models'; @@ -65,7 +67,7 @@ export class WidgetActionDialogComponent extends DialogComponent(); - widgetActionFormGroup: UntypedFormGroup; + widgetActionFormGroup: FormGroup; isAdd: boolean; action: WidgetActionDescriptorInfo; @@ -83,7 +85,7 @@ export class WidgetActionDialogComponent extends DialogComponent, - public fb: UntypedFormBuilder) { + public fb: FormBuilder) { super(store, router, dialogRef); this.isAdd = data.isAdd; if (this.isAdd) { @@ -100,19 +102,14 @@ export class WidgetActionDialogComponent extends DialogComponent { + return (c: FormControl) => { const newName = c.value; - const valid = this.checkActionName(newName, this.widgetActionFormGroup.get('actionSourceId').value); + const valid = this.checkActionName(newName, c.parent?.get('actionSourceId').value); return !valid ? { actionNameNotUnique: true } : null; @@ -182,7 +179,7 @@ export class WidgetActionDialogComponent extends DialogComponent Date: Fri, 8 Mar 2024 15:35:36 +0200 Subject: [PATCH 176/209] UI: API usage dashboard add tick format function --- ui-ngx/src/assets/dashboard/api_usage.json | 138 +++++++++++++++------ 1 file changed, 97 insertions(+), 41 deletions(-) diff --git a/ui-ngx/src/assets/dashboard/api_usage.json b/ui-ngx/src/assets/dashboard/api_usage.json index d5ed8d5304..7d9eefd18d 100644 --- a/ui-ngx/src/assets/dashboard/api_usage.json +++ b/ui-ngx/src/assets/dashboard/api_usage.json @@ -1405,6 +1405,7 @@ "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "min": null, "max": null }, @@ -1430,7 +1431,7 @@ "weight": "400", "lineHeight": "1" }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "tickLabelColor": null, "showTicks": true, "ticksColor": "rgba(0, 0, 0, 0.54)", "showLine": true, @@ -1443,7 +1444,7 @@ "groupWidth": { "relative": false, "relativeWidth": 2, - "absoluteWidth": 2400000 + "absoluteWidth": 3600000 }, "barWidth": { "relative": true, @@ -1724,6 +1725,7 @@ "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "min": null, "max": null }, @@ -1762,7 +1764,7 @@ "groupWidth": { "relative": false, "relativeWidth": 2, - "absoluteWidth": 2400000 + "absoluteWidth": 3600000 }, "barWidth": { "relative": true, @@ -2125,6 +2127,7 @@ "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "min": null, "max": null }, @@ -2163,7 +2166,7 @@ "groupWidth": { "relative": false, "relativeWidth": 2, - "absoluteWidth": 2400000 + "absoluteWidth": 3600000 }, "barWidth": { "relative": true, @@ -2448,6 +2451,7 @@ "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "min": null, "max": null }, @@ -2486,7 +2490,7 @@ "groupWidth": { "relative": false, "relativeWidth": 2, - "absoluteWidth": 2400000 + "absoluteWidth": 3600000 }, "barWidth": { "relative": true, @@ -2767,6 +2771,7 @@ "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "min": null, "max": null }, @@ -2805,7 +2810,7 @@ "groupWidth": { "relative": false, "relativeWidth": 2, - "absoluteWidth": 2400000 + "absoluteWidth": 3600000 }, "barWidth": { "relative": true, @@ -3163,6 +3168,7 @@ "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "min": null, "max": null }, @@ -3201,7 +3207,7 @@ "groupWidth": { "relative": false, "relativeWidth": 2, - "absoluteWidth": 2400000 + "absoluteWidth": 3600000 }, "barWidth": { "relative": true, @@ -3485,7 +3491,10 @@ "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -3790,12 +3799,15 @@ "lineHeight": "1" }, "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "showTicks": true, "ticksColor": "rgba(0, 0, 0, 0.54)", "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -3830,9 +3842,9 @@ "noAggregationBarWidthSettings": { "strategy": "group", "groupWidth": { - "relative": false, - "relativeWidth": 2, - "absoluteWidth": 1800000 + "relative": true, + "relativeWidth": 6, + "absoluteWidth": 900000000 }, "barWidth": { "relative": true, @@ -4062,7 +4074,10 @@ "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -4315,12 +4330,15 @@ "lineHeight": "1" }, "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "showTicks": true, "ticksColor": "rgba(0, 0, 0, 0.54)", "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -4355,8 +4373,8 @@ "noAggregationBarWidthSettings": { "strategy": "group", "groupWidth": { - "relative": false, - "relativeWidth": 2, + "relative": true, + "relativeWidth": 6, "absoluteWidth": 900000000 }, "barWidth": { @@ -4594,7 +4612,10 @@ "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -4863,12 +4884,15 @@ "lineHeight": "1" }, "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "showTicks": true, "ticksColor": "rgba(0, 0, 0, 0.54)", "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -4903,14 +4927,14 @@ "noAggregationBarWidthSettings": { "strategy": "group", "groupWidth": { - "relative": false, - "relativeWidth": 2, + "relative": true, + "relativeWidth": 6, "absoluteWidth": 900000000 }, "barWidth": { - "relative": true, + "relative": false, "relativeWidth": 2, - "absoluteWidth": 1000 + "absoluteWidth": 900000000 } }, "showLegend": true, @@ -5126,7 +5150,10 @@ "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -5379,12 +5406,15 @@ "lineHeight": "1" }, "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "showTicks": true, "ticksColor": "rgba(0, 0, 0, 0.54)", "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -5419,8 +5449,8 @@ "noAggregationBarWidthSettings": { "strategy": "group", "groupWidth": { - "relative": false, - "relativeWidth": 2, + "relative": true, + "relativeWidth": 6, "absoluteWidth": 900000000 }, "barWidth": { @@ -5642,7 +5672,10 @@ "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -5895,12 +5928,15 @@ "lineHeight": "1" }, "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "showTicks": true, "ticksColor": "rgba(0, 0, 0, 0.54)", "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -5935,8 +5971,8 @@ "noAggregationBarWidthSettings": { "strategy": "group", "groupWidth": { - "relative": false, - "relativeWidth": 2, + "relative": true, + "relativeWidth": 6, "absoluteWidth": 900000000 }, "barWidth": { @@ -6158,7 +6194,10 @@ "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -6416,7 +6455,10 @@ "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -6669,12 +6711,15 @@ "lineHeight": "1" }, "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "showTicks": true, "ticksColor": "rgba(0, 0, 0, 0.54)", "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -6709,8 +6754,8 @@ "noAggregationBarWidthSettings": { "strategy": "group", "groupWidth": { - "relative": false, - "relativeWidth": 2, + "relative": true, + "relativeWidth": 6, "absoluteWidth": 900000000 }, "barWidth": { @@ -6927,12 +6972,15 @@ "lineHeight": "1" }, "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", "showTicks": true, "ticksColor": "rgba(0, 0, 0, 0.54)", "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -6967,8 +7015,8 @@ "noAggregationBarWidthSettings": { "strategy": "group", "groupWidth": { - "relative": false, - "relativeWidth": 2, + "relative": true, + "relativeWidth": 6, "absoluteWidth": 900000000 }, "barWidth": { @@ -7252,7 +7300,10 @@ "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -7308,6 +7359,7 @@ }, "legendLabelColor": "rgba(0, 0, 0, 0.76)", "legendConfig": { + "direction": "column", "position": "bottom", "sortDataKeys": false, "showMin": true, @@ -7535,7 +7587,10 @@ "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", + "min": null, + "max": null }, "xAxis": { "show": true, @@ -7591,6 +7646,7 @@ }, "legendLabelColor": "rgba(0, 0, 0, 0.76)", "legendConfig": { + "direction": "column", "position": "bottom", "sortDataKeys": false, "showMin": true, @@ -8099,7 +8155,7 @@ "dashboardLogoUrl": null, "hideToolbar": false, "showUpdateDashboardImage": false, - "dashboardCss": ".tb-time-series-chart-panel {\n padding: 13px;\n}\n\n.tb-time-series-chart-content {\n gap: 0; \n}\n\n.tb-time-series-chart-panel {\n gap: 8px; \n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n\n.card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n.card .unit {\n color: #666666;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} " + "dashboardCss": ".tb-time-series-chart-panel {\n padding: 13px;\n}\n\n.tb-time-series-chart-content {\n gap: 0; \n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n\n.card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n.card .unit {\n color: #666666;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} " } }, "name": "Api Usage" From 59d0f36697c0e7bad424670b4b8c97a7ce3f3652 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 8 Mar 2024 15:22:16 +0100 Subject: [PATCH 177/209] CachedAttributesService: replaced blocking calls and immediate futures with the true async --- .../attributes/CachedAttributesService.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java index 1666015a48..8058b0cee5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java @@ -113,15 +113,15 @@ public class CachedAttributesService implements AttributesService { validate(entityId, scope); Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey); - AttributeCacheKey attributeCacheKey = new AttributeCacheKey(scope, entityId, attributeKey); - TbCacheValueWrapper cachedAttributeValue = cache.get(attributeCacheKey); - if (cachedAttributeValue != null) { - hitCounter.increment(); - AttributeKvEntry cachedAttributeKvEntry = cachedAttributeValue.get(); - return Futures.immediateFuture(Optional.ofNullable(cachedAttributeKvEntry)); - } else { - missCounter.increment(); - return cacheExecutor.submit(() -> { + return cacheExecutor.submit(() -> { + AttributeCacheKey attributeCacheKey = new AttributeCacheKey(scope, entityId, attributeKey); + TbCacheValueWrapper cachedAttributeValue = cache.get(attributeCacheKey); + if (cachedAttributeValue != null) { + hitCounter.increment(); + AttributeKvEntry cachedAttributeKvEntry = cachedAttributeValue.get(); + return Optional.ofNullable(cachedAttributeKvEntry); + } else { + missCounter.increment(); var cacheTransaction = cache.newTransactionForKey(attributeCacheKey); try { Optional result = attributesDao.find(tenantId, entityId, scope, attributeKey); @@ -133,8 +133,8 @@ public class CachedAttributesService implements AttributesService { log.debug("Could not find attribute from cache: [{}] [{}] [{}]", entityId, scope, attributeKey, e); throw e; } - }); - } + } + }); } @Override @@ -207,7 +207,8 @@ public class CachedAttributesService implements AttributesService { @Override public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String scope) { validate(entityId, scope); - return Futures.immediateFuture(attributesDao.findAll(tenantId, entityId, scope)); + // We can`t watch on cache because the keys are unknown. + return jpaExecutorService.submit(() -> attributesDao.findAll(tenantId, entityId, scope)); } @Override From c4b243ef10f7e5b3c6ea534f864c8f13e92e4221 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 8 Mar 2024 18:20:13 +0100 Subject: [PATCH 178/209] SqlTimeseriesLatestDao async refactoring: replaced Futures.immediateFuture() blocking call with true async using service.submit() and Futures.transformAsync() --- .../dao/sqlts/SqlTimeseriesLatestDao.java | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java index 112daf0637..f1a58aa417 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java @@ -151,12 +151,12 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme @Override public ListenableFuture> findLatestOpt(TenantId tenantId, EntityId entityId, String key) { - return Futures.immediateFuture(Optional.ofNullable(doFindLatest(entityId, key))); + return service.submit(() -> Optional.ofNullable(doFindLatest(entityId, key))); } @Override public ListenableFuture findLatest(TenantId tenantId, EntityId entityId, String key) { - return Futures.immediateFuture(getLatestTsKvEntry(entityId, key)); + return service.submit(() -> getLatestTsKvEntry(entityId, key)); } @Override @@ -221,36 +221,29 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme } protected ListenableFuture getRemoveLatestFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - TsKvEntry latest = doFindLatest(entityId, query.getKey()); - - if (latest == null) { - return Futures.immediateFuture(new TsKvLatestRemovingResult(query.getKey(), false)); - } - - long ts = latest.getTs(); - ListenableFuture removedLatestFuture; - if (ts >= query.getStartTs() && ts < query.getEndTs()) { - TsKvLatestEntity latestEntity = new TsKvLatestEntity(); - latestEntity.setEntityId(entityId.getId()); - latestEntity.setKey(getOrSaveKeyId(query.getKey())); - removedLatestFuture = service.submit(() -> { + ListenableFuture latestFuture = service.submit(() -> doFindLatest(entityId, query.getKey())); + return Futures.transformAsync(latestFuture, latest -> { + if (latest == null) { + return Futures.immediateFuture(new TsKvLatestRemovingResult(query.getKey(), false)); + } + boolean isRemoved = false; + long ts = latest.getTs(); + if (ts >= query.getStartTs() && ts < query.getEndTs()) { + TsKvLatestEntity latestEntity = new TsKvLatestEntity(); + latestEntity.setEntityId(entityId.getId()); + latestEntity.setKey(getOrSaveKeyId(query.getKey())); tsKvLatestRepository.delete(latestEntity); - return true; - }); - } else { - removedLatestFuture = Futures.immediateFuture(false); - } - - return Futures.transformAsync(removedLatestFuture, isRemoved -> { - if (isRemoved && query.getRewriteLatestIfDeleted()) { - return getNewLatestEntryFuture(tenantId, entityId, query); + isRemoved = true; + if (query.getRewriteLatestIfDeleted()) { + return getNewLatestEntryFuture(tenantId, entityId, query); + } } return Futures.immediateFuture(new TsKvLatestRemovingResult(query.getKey(), isRemoved)); }, MoreExecutors.directExecutor()); } protected ListenableFuture> getFindAllLatestFuture(EntityId entityId) { - return Futures.immediateFuture( + return service.submit(() -> DaoUtil.convertDataList(Lists.newArrayList( searchTsKvLatestRepository.findAllByEntityId(entityId.getId())))); } From c688f516c7d7e73869211673b018f1defa4d0765 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 11 Mar 2024 15:44:56 +0200 Subject: [PATCH 179/209] UI: Multiple Y axes configuration support for time series chart. --- ...e-series-chart-basic-config.component.html | 15 +- ...ime-series-chart-basic-config.component.ts | 39 +++- .../basic/common/data-key-row.component.html | 39 ++-- .../basic/common/data-key-row.component.scss | 17 ++ .../basic/common/data-key-row.component.ts | 46 +++- .../common/data-keys-panel.component.html | 5 +- .../common/data-keys-panel.component.scss | 15 ++ .../basic/common/data-keys-panel.component.ts | 8 + .../aggregated-value-card-widget.component.ts | 30 +-- .../value-chart-card-widget.component.ts | 11 +- .../lib/chart/time-series-chart.models.ts | 216 ++++++++++++++---- .../widget/lib/chart/time-series-chart.ts | 102 +++++++-- ...e-series-chart-key-settings.component.html | 12 + ...ime-series-chart-key-settings.component.ts | 12 +- ...eries-chart-widget-settings.component.html | 15 +- ...-series-chart-widget-settings.component.ts | 37 ++- ...-series-chart-axis-settings.component.html | 18 +- ...me-series-chart-axis-settings.component.ts | 18 +- ...-series-chart-threshold-row.component.html | 28 ++- ...-series-chart-threshold-row.component.scss | 14 ++ ...me-series-chart-threshold-row.component.ts | 32 ++- ...rt-threshold-settings-panel.component.html | 10 + ...hart-threshold-settings-panel.component.ts | 6 +- ...ries-chart-thresholds-panel.component.html | 2 + ...ries-chart-thresholds-panel.component.scss | 11 + ...series-chart-thresholds-panel.component.ts | 5 +- ...e-series-chart-y-axes-panel.component.html | 63 +++++ ...e-series-chart-y-axes-panel.component.scss | 87 +++++++ ...ime-series-chart-y-axes-panel.component.ts | 180 +++++++++++++++ ...ime-series-chart-y-axis-row.component.html | 72 ++++++ ...ime-series-chart-y-axis-row.component.scss | 95 ++++++++ .../time-series-chart-y-axis-row.component.ts | 201 ++++++++++++++++ ...chart-y-axis-settings-panel.component.html | 43 ++++ ...chart-y-axis-settings-panel.component.scss | 49 ++++ ...s-chart-y-axis-settings-panel.component.ts | 66 ++++++ .../common/widget-settings-common.module.ts | 15 ++ ui-ngx/src/app/shared/models/common.ts | 19 ++ .../assets/locale/locale.constant-en_US.json | 10 +- 38 files changed, 1502 insertions(+), 161 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.ts create mode 100644 ui-ngx/src/app/shared/models/common.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html index 3d099c2645..1f62a13c4e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html @@ -25,6 +25,10 @@ forceSingleDatasource formControlName="datasources"> + + + [widgetConfig]="widgetConfig?.config" + [yAxisIds]="yAxisIds">
widget-config.appearance
@@ -104,11 +111,7 @@
-
widgets.time-series-chart.axis.axes
- - +
widgets.time-series-chart.axis.x-axis
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts index 5307147b0e..f9ee35da23 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts @@ -45,7 +45,11 @@ import { TimeSeriesChartWidgetSettings } from '@home/components/widget/lib/chart/time-series-chart-widget.models'; import { EChartsTooltipTrigger } from '@home/components/widget/lib/chart/echarts-widget.models'; -import { TimeSeriesChartType } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { + TimeSeriesChartKeySettings, TimeSeriesChartThreshold, + TimeSeriesChartType, TimeSeriesChartYAxes, + TimeSeriesChartYAxisId +} from '@home/components/widget/lib/chart/time-series-chart.models'; @Component({ selector: 'tb-time-series-chart-basic-config', @@ -63,6 +67,11 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon } } + public get yAxisIds(): TimeSeriesChartYAxisId[] { + const yAxes: TimeSeriesChartYAxes = this.timeSeriesChartWidgetConfigForm.get('yAxes').value; + return Object.keys(yAxes); + } + TimeSeriesChartType = TimeSeriesChartType; EChartsTooltipTrigger = EChartsTooltipTrigger; @@ -86,6 +95,15 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon super(store, widgetConfigComponent); } + public yAxisRemoved(yAxisId: TimeSeriesChartYAxisId): void { + if (this.widgetConfig.config.datasources && this.widgetConfig.config.datasources.length > 1) { + for (let i = 1; i < this.widgetConfig.config.datasources.length; i++) { + const datasource = this.widgetConfig.config.datasources[i]; + this.removeYaxisId(datasource.dataKeys, yAxisId); + } + } + } + protected configForm(): UntypedFormGroup { return this.timeSeriesChartWidgetConfigForm; } @@ -109,6 +127,8 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon this.timeSeriesChartWidgetConfigForm = this.fb.group({ timewindowConfig: [getTimewindowConfig(configData.config), []], datasources: [configData.config.datasources, []], + + yAxes: [settings.yAxes, []], series: [this.getSeries(configData.config.datasources), []], thresholds: [settings.thresholds, []], @@ -126,7 +146,6 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon dataZoom: [settings.dataZoom, []], stack: [settings.stack, []], - yAxis: [settings.yAxis, []], xAxis: [settings.xAxis, []], noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []], @@ -180,7 +199,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon this.widgetConfig.config.settings.dataZoom = config.dataZoom; this.widgetConfig.config.settings.stack = config.stack; - this.widgetConfig.config.settings.yAxis = config.yAxis; + this.widgetConfig.config.settings.yAxes = config.yAxes; this.widgetConfig.config.settings.xAxis = config.xAxis; this.widgetConfig.config.settings.noAggregationBarWidthSettings = config.noAggregationBarWidthSettings; @@ -303,6 +322,20 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon } } + private removeYaxisId(series: DataKey[], yAxisId: TimeSeriesChartYAxisId): boolean { + let changed = false; + if (series) { + series.forEach(key => { + const keySettings = ((key.settings || {}) as TimeSeriesChartKeySettings); + if (keySettings.yAxisId === yAxisId) { + keySettings.yAxisId = 'default'; + changed = true; + } + }); + } + return changed; + } + private getCardButtons(config: WidgetConfig): string[] { const buttons: string[] = []; if (isUndefined(config.enableFullscreen) || config.enableFullscreen) { diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html index 3ef6891808..0c8be80bf6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html @@ -38,6 +38,30 @@ +
+ + + + + {{ timeSeriesChartSeriesTypeIcons.get(keyRowFormGroup.get('timeSeriesType').value) }} + + + + {{ timeSeriesChartSeriesTypeIcons.get(type) }} + {{ timeSeriesChartSeriesTypeTranslations.get(type) | translate }} + + + +
+
+ + + + {{ yAxis }} + + + +
@@ -54,21 +78,6 @@
widget-config.decimals-suffix
-
- - - - - {{ timeSeriesChartSeriesTypeIcons.get(keyRowFormGroup.get('timeSeriesType').value) }} - - - - {{ timeSeriesChartSeriesTypeIcons.get(type) }} - {{ timeSeriesChartSeriesTypeTranslations.get(type) | translate }} - - - -
= { dataZoom: false, xAxis: { show: false }, - yAxis: { - show: true, - showLine: false, - showTicks: false, - showTickLabels: false, - showSplitLines: true, - min: 'dataMin', - max: 'dataMax', - intervalCalculator: - 'var scale = axis.scale; return !scale.isBlank() ? ((scale.getExtent()[1] - scale.getExtent()[0]) / 2) : undefined;' + yAxes: { + default: { + show: true, + showLine: false, + showTicks: false, + showTickLabels: false, + showSplitLines: true, + min: 'dataMin', + max: 'dataMax', + intervalCalculator: + 'var scale = axis.scale; return !scale.isBlank() ? ((scale.getExtent()[1] - scale.getExtent()[0]) / 2) : undefined;' + } }, tooltipDateInterval: false, tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss') - } as TimeSeriesChartSettings; + }; this.lineChart = new TbTimeSeriesChart(this.ctx, settings, this.chartElement.nativeElement, this.renderer, true); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts index f5a305a247..a7981b3acf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts @@ -57,6 +57,7 @@ import { TimeSeriesChartSeriesType, TimeSeriesChartSettings } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { DeepPartial } from '@shared/models/common'; const layoutHeight = 56; const valueRelativeWidth = 0.35; @@ -162,17 +163,19 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD } public ngAfterViewInit() { - const settings: TimeSeriesChartSettings = { + const settings: DeepPartial = { dataZoom: false, xAxis: { show: false }, - yAxis: { - show: false + yAxes: { + default: { + show: false, + } }, tooltipDateInterval: false, tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss') - } as TimeSeriesChartSettings; + }; this.lineChart = new TbTimeSeriesChart(this.ctx, settings, this.chartElement.nativeElement, this.renderer, false); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index edfaa3692d..9af41fa378 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -25,7 +25,15 @@ import { import { ComponentStyle, Font, simpleDateFormat, textStyle } from '@shared/models/widget-settings.models'; import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared'; import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts'; -import { formatValue, isDefinedAndNotNull, isUndefined, isUndefinedOrNull, parseFunction } from '@core/utils'; +import { + formatValue, + isDefinedAndNotNull, + isNumeric, + isUndefined, + isUndefinedOrNull, + mergeDeep, + parseFunction +} from '@core/utils'; import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; import tinycolor from 'tinycolor2'; import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D'; @@ -289,13 +297,82 @@ export interface TimeSeriesChartAxisSettings { splitLinesColor: string; } +export type TimeSeriesChartYAxisId = 'default' | string; + export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSettings { + id?: TimeSeriesChartYAxisId; + order?: number; + units?: string; + decimals?: number; min?: number | string; max?: number | string; intervalCalculator?: string; ticksFormatter?: string; } +export const timeSeriesChartYAxisValid = (axis: TimeSeriesChartYAxisSettings): boolean => + !(!axis.id || isUndefinedOrNull(axis.order)); + +export const timeSeriesChartYAxisValidator = (control: AbstractControl): ValidationErrors | null => { + const axis: TimeSeriesChartYAxisSettings = control.value; + if (!timeSeriesChartYAxisValid(axis)) { + return { + axis: true + }; + } + return null; +}; + +export const getNextTimeSeriesYAxisId = (axes: TimeSeriesChartYAxisSettings[]): TimeSeriesChartYAxisId => { + let id = 0; + for (const axis of axes) { + const existingId = axis.id; + if (existingId.startsWith('axis')) { + const idSuffix = existingId.substring('axis'.length); + if (isNumeric(idSuffix)) { + id = Math.max(id, Number(idSuffix)); + } + } + } + return 'axis' + (id+1); +}; + +export const defaultTimeSeriesChartYAxisSettings: TimeSeriesChartYAxisSettings = { + units: null, + decimals: 0, + show: true, + label: '', + labelFont: { + family: 'Roboto', + size: 12, + sizeUnit: 'px', + style: 'normal', + weight: '600', + lineHeight: '1' + }, + labelColor: timeSeriesChartColorScheme['axis.label'].light, + position: AxisPosition.left, + showTickLabels: true, + tickLabelFont: { + family: 'Roboto', + size: 12, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '1' + }, + tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light, + ticksFormatter: null, + showTicks: true, + ticksColor: timeSeriesChartColorScheme['axis.ticks'].light, + showLine: true, + lineColor: timeSeriesChartColorScheme['axis.line'].light, + showSplitLines: true, + splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light +}; + +export type TimeSeriesChartYAxes = {[id: TimeSeriesChartYAxisId]: TimeSeriesChartYAxisSettings}; + export interface TimeSeriesChartThreshold { type: TimeSeriesChartThresholdType; value?: number; @@ -304,6 +381,7 @@ export interface TimeSeriesChartThreshold { entityAlias?: string; entityKey?: string; entityKeyType?: DataKeyType.attribute | DataKeyType.timeseries; + yAxisId: TimeSeriesChartYAxisId; units?: string; decimals?: number; lineColor: string; @@ -320,7 +398,7 @@ export interface TimeSeriesChartThreshold { } export const timeSeriesChartThresholdValid = (threshold: TimeSeriesChartThreshold): boolean => { - if (!threshold.type) { + if (!threshold.type || !threshold.yAxisId) { return false; } switch (threshold.type) { @@ -355,7 +433,8 @@ export const timeSeriesChartThresholdValidator = (control: AbstractControl): Val export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold = { type: TimeSeriesChartThresholdType.constant, - units: '', + yAxisId: 'default', + units: null, decimals: 0, lineColor: timeSeriesChartColorScheme['threshold.line'].light, lineType: TimeSeriesChartLineType.solid, @@ -404,13 +483,61 @@ export interface TimeSeriesChartNoAggregationBarWidthSettings { barWidth?: TimeSeriesChartBarWidth; } +export enum TimeSeriesChartAnimationEasing { + linear = 'linear', + quadraticIn = 'quadraticIn', + quadraticOut = 'quadraticOut', + quadraticInOut = 'quadraticInOut', + cubicIn = 'cubicIn', + cubicOut = 'cubicOut', + cubicInOut = 'cubicInOut', + quarticIn = 'quarticIn', + quarticOut = 'quarticOut', + quarticInOut = 'quarticInOut', + quinticIn = 'quinticIn', + quinticOut = 'quinticOut', + quinticInOut = 'quinticInOut', + sinusoidalIn = 'sinusoidalIn', + sinusoidalOut = 'sinusoidalOut', + sinusoidalInOut = 'sinusoidalInOut', + exponentialIn = 'exponentialIn', + exponentialOut = 'exponentialOut', + exponentialInOut = 'exponentialInOut', + circularIn = 'circularIn', + circularOut = 'circularOut', + circularInOut = 'circularInOut', + elasticIn = 'elasticIn', + elasticOut = 'elasticOut', + elasticInOut = 'elasticInOut', + backIn = 'backIn', + backOut = 'backOut', + backInOut = 'backInOut', + bounceIn = 'bounceIn', + bounceOut = 'bounceOut', + bounceInOut = 'bounceInOut' +} + +export const timeSeriesChartAnimationEasings = Object.keys(TimeSeriesChartAnimationEasing) as TimeSeriesChartAnimationEasing[]; + +export interface TimeSeriesChartAnimationSettings { + animation: boolean; + animationThreshold: number; + animationDuration: number; + animationEasing: TimeSeriesChartAnimationEasing; + animationDelay: number; + animationDurationUpdate: number; + animationEasingUpdate: TimeSeriesChartAnimationEasing; + animationDelayUpdate: number; +} + export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings { thresholds: TimeSeriesChartThreshold[]; darkMode: boolean; dataZoom: boolean; stack: boolean; - yAxis: TimeSeriesChartYAxisSettings; + yAxes: TimeSeriesChartYAxes; xAxis: TimeSeriesChartAxisSettings; + animation: TimeSeriesChartAnimationSettings; noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings; } @@ -419,36 +546,10 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { darkMode: false, dataZoom: true, stack: false, - yAxis: { - show: true, - label: '', - labelFont: { - family: 'Roboto', - size: 12, - sizeUnit: 'px', - style: 'normal', - weight: '600', - lineHeight: '1' - }, - labelColor: timeSeriesChartColorScheme['axis.label'].light, - position: AxisPosition.left, - showTickLabels: true, - tickLabelFont: { - family: 'Roboto', - size: 12, - sizeUnit: 'px', - style: 'normal', - weight: '400', - lineHeight: '1' - }, - tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light, - ticksFormatter: null, - showTicks: true, - ticksColor: timeSeriesChartColorScheme['axis.ticks'].light, - showLine: true, - lineColor: timeSeriesChartColorScheme['axis.line'].light, - showSplitLines: true, - splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light + yAxes: { + default: mergeDeep({} as TimeSeriesChartYAxisSettings, + defaultTimeSeriesChartYAxisSettings, + { id: 'default', order: 0 } as TimeSeriesChartYAxisSettings) }, xAxis: { show: true, @@ -480,6 +581,16 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { showSplitLines: true, splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light }, + animation: { + animation: true, + animationThreshold: 2000, + animationDuration: 1000, + animationEasing: TimeSeriesChartAnimationEasing.cubicOut, + animationDelay: 0, + animationDurationUpdate: 300, + animationEasingUpdate: TimeSeriesChartAnimationEasing.cubicOut, + animationDelayUpdate: 0 + }, noAggregationBarWidthSettings: { strategy: TimeSeriesChartNoAggregationBarWidthStrategy.group, groupWidth: { @@ -558,6 +669,7 @@ export interface BarSeriesSettings { } export interface TimeSeriesChartKeySettings { + yAxisId: TimeSeriesChartYAxisId; showInLegend: boolean; dataHiddenByDefault: boolean; type: TimeSeriesChartSeriesType; @@ -566,6 +678,7 @@ export interface TimeSeriesChartKeySettings { } export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = { + yAxisId: 'default', showInLegend: true, dataHiddenByDefault: false, type: TimeSeriesChartSeriesType.line, @@ -626,6 +739,7 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = { }; export interface TimeSeriesChartDataItem extends EChartsSeriesItem { + yAxisId: TimeSeriesChartYAxisId; yAxisIndex: number; option?: LineSeriesOption | CustomSeriesOption; barRenderContext?: BarRenderContext; @@ -635,6 +749,7 @@ type TimeSeriesChartThresholdValue = number | string | (number | string)[]; export interface TimeSeriesChartThresholdItem { id: string; + yAxisId: TimeSeriesChartYAxisId; yAxisIndex: number; latestDataKey?: DataKey; units?: string; @@ -646,14 +761,16 @@ export interface TimeSeriesChartThresholdItem { export interface TimeSeriesChartYAxis { id: string; - units: string; + decimals: number; + settings: TimeSeriesChartYAxisSettings; option: YAXisOption & ValueAxisBaseOption; intervalCalculator?: (axis: Axis2D) => number; ticksFormatter?: (value: any) => string; } -export const createTimeSeriesYAxis = (axisId: string, units: string, - decimals: number, settings: TimeSeriesChartYAxisSettings, +export const createTimeSeriesYAxis = (units: string, + decimals: number, + settings: TimeSeriesChartYAxisSettings, darkMode: boolean): TimeSeriesChartYAxis => { const yAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont, settings.tickLabelColor, darkMode, 'axis.tickLabel'); @@ -664,13 +781,14 @@ export const createTimeSeriesYAxis = (axisId: string, units: string, ticksFormatter = parseFunction(settings.ticksFormatter, ['value']); } const yAxis: TimeSeriesChartYAxis = { - id: axisId, - units, + id: settings.id, + decimals, + settings, option: { show: settings.show, type: 'value', position: settings.position, - id: axisId, + id: settings.id, offset: 0, alignTicks: true, scale: true, @@ -843,13 +961,11 @@ const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[], id: item.id, dataGroupId: item.id, yAxisIndex: item.yAxisIndex, - animation: true, data: [], tooltip: { show: false }, markLine: { - animation: true, lineStyle: { width: item.settings.lineWidth, color: prepareChartThemeColor(item.settings.lineColor, darkMode, 'threshold.line'), @@ -956,16 +1072,19 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[], }; export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings, + yAxisList: TimeSeriesChartYAxis[], dataItems: TimeSeriesChartDataItem[], thresholdDataItems: TimeSeriesChartThresholdItem[], darkMode: boolean): EChartsOption => { options.darkMode = darkMode; if (Array.isArray(options.yAxis)) { - for (const yAxis of options.yAxis) { - yAxis.nameTextStyle.color = prepareChartThemeColor(settings.yAxis.labelColor, darkMode, 'axis.label'); - yAxis.axisLabel.color = prepareChartThemeColor(settings.yAxis.tickLabelColor, darkMode, 'axis.tickLabel'); - yAxis.axisLine.lineStyle.color = prepareChartThemeColor(settings.yAxis.lineColor, darkMode, 'axis.line'); - yAxis.axisTick.lineStyle.color = prepareChartThemeColor(settings.yAxis.ticksColor, darkMode, 'axis.ticks'); - yAxis.splitLine.lineStyle.color = prepareChartThemeColor(settings.yAxis.splitLinesColor, darkMode, 'axis.splitLine'); + for (let i = 0; i < options.yAxis.length; i++) { + const yAxis = options.yAxis[i]; + const yAxisSettings = yAxisList[i].settings; + yAxis.nameTextStyle.color = prepareChartThemeColor(yAxisSettings.labelColor, darkMode, 'axis.label'); + yAxis.axisLabel.color = prepareChartThemeColor(yAxisSettings.tickLabelColor, darkMode, 'axis.tickLabel'); + yAxis.axisLine.lineStyle.color = prepareChartThemeColor(yAxisSettings.lineColor, darkMode, 'axis.line'); + yAxis.axisTick.lineStyle.color = prepareChartThemeColor(yAxisSettings.ticksColor, darkMode, 'axis.ticks'); + yAxis.splitLine.lineStyle.color = prepareChartThemeColor(yAxisSettings.splitLinesColor, darkMode, 'axis.splitLine'); } } if (Array.isArray(options.xAxis)) { @@ -1032,7 +1151,6 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem, emphasis: { focus: 'series' }, - animation: true, dimensions: [ {name: 'intervalStart', type: 'number'}, {name: 'intervalEnd', type: 'number'} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 6d790d5439..abad3f79e8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -19,7 +19,7 @@ import { AxisPosition, calculateThresholdsOffset, createTimeSeriesXAxisOption, - createTimeSeriesYAxis, + createTimeSeriesYAxis, defaultTimeSeriesChartYAxisSettings, generateChartData, parseThresholdData, SeriesLabelPosition, @@ -29,11 +29,11 @@ import { TimeSeriesChartKeySettings, TimeSeriesChartSeriesType, TimeSeriesChartSettings, - TimeSeriesChartShape, + TimeSeriesChartShape, TimeSeriesChartThreshold, timeSeriesChartThresholdDefaultSettings, TimeSeriesChartThresholdItem, TimeSeriesChartThresholdType, TimeSeriesChartType, - TimeSeriesChartYAxis, + TimeSeriesChartYAxis, TimeSeriesChartYAxisId, TimeSeriesChartYAxisSettings, updateDarkMode } from '@home/components/widget/lib/chart/time-series-chart.models'; import { ResizeObserver } from '@juggle/resize-observer'; @@ -63,6 +63,7 @@ import { AggregationType } from '@shared/models/time/time.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { WidgetSubscriptionOptions } from '@core/api/widget-api.models'; import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; +import { DeepPartial } from '@shared/models/common'; export class TbTimeSeriesChart { @@ -94,9 +95,11 @@ export class TbTimeSeriesChart { private readonly shapeResize$: ResizeObserver; + private readonly settings: TimeSeriesChartSettings; + + private yAxisList: TimeSeriesChartYAxis[] = []; private dataItems: TimeSeriesChartDataItem[] = []; private thresholdItems: TimeSeriesChartThresholdItem[] = []; - private yAxisList: TimeSeriesChartYAxis[] = []; private timeSeriesChart: ECharts; private timeSeriesChartOptions: EChartsOption; @@ -120,13 +123,16 @@ export class TbTimeSeriesChart { yMax$ = this.yMaxSubject.asObservable(); constructor(private ctx: WidgetContext, - private readonly settings: TimeSeriesChartSettings, + private readonly inputSettings: DeepPartial, private chartElement: HTMLElement, private renderer: Renderer2, private autoResize = true) { - this.settings = mergeDeep({} as TimeSeriesChartSettings, timeSeriesChartDefaultSettings, this.settings); + this.settings = mergeDeep({} as TimeSeriesChartSettings, + timeSeriesChartDefaultSettings, + this.inputSettings as TimeSeriesChartSettings); this.darkMode = this.settings.darkMode; + this.setupYAxes(); this.setupData(); this.setupThresholds(); if (this.settings.showTooltip && this.settings.tooltipShowDate) { @@ -258,7 +264,8 @@ export class TbTimeSeriesChart { if (this.darkMode !== darkMode) { this.darkMode = darkMode; if (this.timeSeriesChart) { - this.timeSeriesChartOptions = updateDarkMode(this.timeSeriesChartOptions, this.settings, this.dataItems, + this.timeSeriesChartOptions = updateDarkMode(this.timeSeriesChartOptions, + this.settings, this.yAxisList, this.dataItems, this.thresholdItems, darkMode); this.timeSeriesChart.setOption(this.timeSeriesChartOptions); } @@ -287,11 +294,16 @@ export class TbTimeSeriesChart { const units = dataKey.units && dataKey.units.length ? dataKey.units : this.ctx.units; const decimals = isDefinedAndNotNull(dataKey.decimals) ? dataKey.decimals : (isDefinedAndNotNull(this.ctx.decimals) ? this.ctx.decimals : 2); + let yAxisId = keySettings.yAxisId; + if (!Object.keys(this.settings.yAxes).includes(yAxisId)) { + yAxisId = 'default'; + } this.dataItems.push({ id: this.nextComponentId(), units, decimals, - yAxisIndex: this.getYAxis(units, decimals), + yAxisId, + yAxisIndex: this.getYAxisIndex(yAxisId), dataKey, data: namedData, enabled: !keySettings.dataHiddenByDefault @@ -303,7 +315,9 @@ export class TbTimeSeriesChart { private setupThresholds(): void { const thresholdDatasources: Datasource[] = []; - for (const threshold of this.settings.thresholds) { + for (const thresholdSettings of this.settings.thresholds) { + const threshold = mergeDeep({} as TimeSeriesChartThreshold, + timeSeriesChartThresholdDefaultSettings, thresholdSettings); let latestDataKey: DataKey = null; let entityDataKey: DataKey = null; let value = null; @@ -351,11 +365,16 @@ export class TbTimeSeriesChart { const units = threshold.units && threshold.units.length ? threshold.units : this.ctx.units; const decimals = isDefinedAndNotNull(threshold.decimals) ? threshold.decimals : (isDefinedAndNotNull(this.ctx.decimals) ? this.ctx.decimals : 2); + let yAxisId = threshold.yAxisId; + if (!Object.keys(this.settings.yAxes).includes(yAxisId)) { + yAxisId = 'default'; + } const thresholdItem: TimeSeriesChartThresholdItem = { id: this.nextComponentId(), units, decimals, - yAxisIndex: this.getYAxis(units, decimals), + yAxisId, + yAxisIndex: this.getYAxisIndex(yAxisId), value, latestDataKey, settings: threshold @@ -368,17 +387,32 @@ export class TbTimeSeriesChart { this.subscribeForEntityThresholds(thresholdDatasources); } + private setupYAxes(): void { + const yAxisSettingsList = Object.values(this.settings.yAxes); + yAxisSettingsList.sort((a1, a2) => a1.order - a2.order); + for (const yAxisSettings of yAxisSettingsList) { + const axisSettings = mergeDeep({} as TimeSeriesChartYAxisSettings, + defaultTimeSeriesChartYAxisSettings, yAxisSettings); + const units = axisSettings.units && axisSettings.units.length ? axisSettings.units : this.ctx.units; + const decimals = isDefinedAndNotNull(axisSettings.decimals) ? axisSettings.decimals : + (isDefinedAndNotNull(this.ctx.decimals) ? this.ctx.decimals : 2); + const yAxis = createTimeSeriesYAxis(units, decimals, axisSettings, this.darkMode); + this.yAxisList.push(yAxis); + } + } + + private nextComponentId(): string { return (this.componentIndexCounter++) + ''; } - private getYAxis(units: string, decimals: number): number { - let yAxisIndex = this.yAxisList.findIndex(axis => axis.units === units); + private getYAxisIndex(id: TimeSeriesChartYAxisId): number { + let yAxisIndex = this.yAxisList.findIndex(axis => axis.id === id); if (yAxisIndex === -1) { - const yAxisId = this.yAxisList.length + ''; - const yAxis = createTimeSeriesYAxis(yAxisId, units, decimals, this.settings.yAxis, this.darkMode); - this.yAxisList.push(yAxis); - yAxisIndex = this.yAxisList.length - 1; + yAxisIndex = this.yAxisList.findIndex(axis => axis.id === 'default'); + if (yAxisIndex === -1 && this.yAxisList.length) { + yAxisIndex = 0; + } } return yAxisIndex; } @@ -460,7 +494,15 @@ export class TbTimeSeriesChart { realtime: true, bottom: 10 } - ] + ], + animation: this.settings.animation.animation, + animationThreshold: this.settings.animation.animationThreshold, + animationDuration: this.settings.animation.animationDuration, + animationEasing: this.settings.animation.animationEasing, + animationDelay: this.settings.animation.animationDelay, + animationDurationUpdate: this.settings.animation.animationDurationUpdate, + animationEasingUpdate: this.settings.animation.animationEasingUpdate, + animationDelayUpdate: this.settings.animation.animationDelayUpdate }; this.timeSeriesChartOptions.xAxis[0].tbTimeWindow = this.ctx.defaultSubscription.timeWindow; @@ -588,7 +630,7 @@ export class TbTimeSeriesChart { result.offset += 5; } width = newWidth; - const showLine = !!width && this.settings.yAxis.showLine; + const showLine = !!width && yAxis.settings.showLine; if (yAxis.option.axisLine.show !== showLine) { yAxis.option.axisLine.show = showLine; result.changed = true; @@ -597,7 +639,7 @@ export class TbTimeSeriesChart { yAxis.option.offset = result.offset; result.changed = true; } - if (this.settings.yAxis.label) { + if (yAxis.settings.label) { if (!width) { if (yAxis.option.name) { yAxis.option.name = null; @@ -605,7 +647,7 @@ export class TbTimeSeriesChart { } } else { if (!yAxis.option.name) { - yAxis.option.name = this.settings.yAxis.label; + yAxis.option.name = yAxis.settings.label; result.changed = true; } const nameGap = width; @@ -613,7 +655,7 @@ export class TbTimeSeriesChart { yAxis.option.nameGap = nameGap; result.changed = true; } - const nameWidth = measureYAxisNameWidth(this.timeSeriesChart, yAxis.id, this.settings.yAxis.label); + const nameWidth = measureYAxisNameWidth(this.timeSeriesChart, yAxis.id, yAxis.settings.label); result.offset += nameWidth; } } @@ -623,15 +665,25 @@ export class TbTimeSeriesChart { } private scaleYAxis(yAxis: TimeSeriesChartYAxis): boolean { - const yAxisIndex = this.yAxisList.indexOf(yAxis); - const axisBarDataItems = this.dataItems.filter(d => d.yAxisIndex === yAxisIndex && d.enabled && + const axisBarDataItems = this.dataItems.filter(d => d.yAxisId === yAxis.id && d.enabled && d.data.length && d.dataKey.settings.type === TimeSeriesChartSeriesType.bar); return !axisBarDataItems.length; } + private yAxisEmpty(yAxis: TimeSeriesChartYAxis): boolean { + const axisDataItems = this.dataItems.filter(d => d.yAxisId === yAxis.id && d.enabled && + d.data.length); + return !axisDataItems.length; + } + private calculateYAxisInterval(axisList: TimeSeriesChartYAxis[]): boolean { let changed = false; for (const yAxis of axisList) { + const minInterval = this.yAxisEmpty(yAxis) ? undefined : (1 / Math.pow(10, yAxis.decimals)); + if (yAxis.option.minInterval !== minInterval) { + yAxis.option.minInterval = minInterval; + changed = true; + } if (yAxis.intervalCalculator) { const axis = getYAxis(this.timeSeriesChart, yAxis.id); if (axis) { @@ -649,8 +701,10 @@ export class TbTimeSeriesChart { } private minTopOffset(): number { + const showTickLabels = + !!this.yAxisList.find(yAxis => yAxis.settings.show && yAxis.settings.showTickLabels); return (this.topPointLabels) ? 20 : - ((this.settings.yAxis.show && this.settings.yAxis.showTickLabels) ? 10 : 5); + (showTickLabels ? 10 : 5); } private minBottomOffset(): number { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html index 0d470703b6..5f0fbb9e37 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html @@ -29,6 +29,18 @@
+
+
+
widgets.time-series-chart.axis.y-axis
+ + + + {{ yAxis }} + + + +
+
widgets.time-series-chart.series.series-type
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts index 4da23f32e0..d1ac428396 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts @@ -25,9 +25,10 @@ import { TimeSeriesChartKeySettings, TimeSeriesChartSeriesType, timeSeriesChartSeriesTypes, - timeSeriesChartSeriesTypeTranslations, TimeSeriesChartType, timeSeriesChartTypeTranslations + timeSeriesChartSeriesTypeTranslations, TimeSeriesChartType, timeSeriesChartTypeTranslations, TimeSeriesChartYAxisId } from '@home/components/widget/lib/chart/time-series-chart.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { TimeSeriesChartWidgetSettings } from '@home/components/widget/lib/chart/time-series-chart-widget.models'; @Component({ selector: 'tb-time-series-chart-key-settings', @@ -50,6 +51,8 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent chartType = TimeSeriesChartType.default; + yAxisIds: TimeSeriesChartYAxisId[]; + constructor(protected store: Store, private fb: UntypedFormBuilder) { super(store); @@ -64,6 +67,8 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent if (isDefinedAndNotNull(params.chartType)) { this.chartType = params.chartType; } + const widgetSettings = (widgetConfig.config?.settings || {}) as TimeSeriesChartWidgetSettings; + this.yAxisIds = widgetSettings.yAxes ? Object.keys(widgetSettings.yAxes) : ['default']; } protected defaultSettings(): WidgetSettings { @@ -73,7 +78,12 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent protected onSettingsSet(settings: WidgetSettings) { const seriesSettings = settings as TimeSeriesChartKeySettings; + let yAxisId = seriesSettings.yAxisId; + if (!this.yAxisIds.includes(yAxisId)) { + yAxisId = 'default'; + } this.timeSeriesChartKeySettingsForm = this.fb.group({ + yAxisId: [yAxisId, []], showInLegend: [seriesSettings.showInLegend, []], dataHiddenByDefault: [seriesSettings.dataHiddenByDefault, []], type: [seriesSettings.type, []], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html index 35f0d147ac..933961a7ae 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html @@ -16,12 +16,18 @@ --> + + + [widgetConfig]="widgetConfig?.config" + [yAxisIds]="yAxisIds">
widgets.time-series-chart.chart-style
@@ -38,12 +44,7 @@
-
widgets.time-series-chart.axis.axes
- - +
widgets.time-series-chart.axis.x-axis
1) { + for (let i = 1; i < this.widgetConfig.config.datasources.length; i++) { + const datasource = this.widgetConfig.config.datasources[i]; + this.removeYaxisId(datasource.dataKeys, yAxisId); + } + } + } + protected settingsForm(): UntypedFormGroup { return this.timeSeriesChartWidgetSettingsForm; } @@ -91,12 +110,12 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon protected onSettingsSet(settings: WidgetSettings) { this.timeSeriesChartWidgetSettingsForm = this.fb.group({ + yAxes: [settings.yAxes, []], thresholds: [settings.thresholds, []], dataZoom: [settings.dataZoom, []], stack: [settings.stack, []], - yAxis: [settings.yAxis, []], xAxis: [settings.xAxis, []], noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []], @@ -173,6 +192,20 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon } } + private removeYaxisId(series: DataKey[], yAxisId: TimeSeriesChartYAxisId): boolean { + let changed = false; + if (series) { + series.forEach(key => { + const keySettings = ((key.settings || {}) as TimeSeriesChartKeySettings); + if (keySettings.yAxisId === yAxisId) { + keySettings.yAxisId = 'default'; + changed = true; + } + }); + } + return changed; + } + private _tooltipValuePreviewFn(): string { return formatValue(22, 0, '°C', false); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html index 24f3dc0883..9511baf121 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html @@ -17,13 +17,13 @@ -->
- + - {{ axisTitle | translate }} + {{ 'widgets.time-series-chart.axis.show' | translate }} @@ -56,6 +56,18 @@
+
+
widget-config.units-short
+ + +
+
+
widget-config.decimals-short
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts index 328574d648..a88fc2e123 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts @@ -15,7 +15,13 @@ /// import { Component, forwardRef, Input, OnInit } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + Validators +} from '@angular/forms'; import { AxisPosition, timeSeriesAxisPositionTranslations, @@ -40,9 +46,11 @@ import { WidgetService } from '@core/http/widget.service'; }) export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValueAccessor { - settingsExpanded = false; + @Input() + @coerceBoolean() + alwaysExpanded = false; - axisTitle: string; + settingsExpanded = false; axisPositions: AxisPosition[]; @@ -72,8 +80,6 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu ngOnInit(): void { - this.axisTitle = this.axisType === 'xAxis' ? 'widgets.time-series-chart.axis.x-axis' : 'widgets.time-series-chart.axis.y-axis'; - this.axisPositions = this.axisType === 'xAxis' ? [AxisPosition.top, AxisPosition.bottom] : [AxisPosition.left, AxisPosition.right]; @@ -94,6 +100,8 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu splitLinesColor: [null, []] }); if (this.axisType === 'yAxis') { + this.axisSettingsFormGroup.addControl('units', this.fb.control(null, [])); + this.axisSettingsFormGroup.addControl('decimals', this.fb.control(null, [Validators.min(0)])); this.axisSettingsFormGroup.addControl('ticksFormatter', this.fb.control(null, [])); this.axisSettingsFormGroup.addControl('min', this.fb.control(null, [])); this.axisSettingsFormGroup.addControl('max', this.fb.control(null, [])); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html index f82a717d78..7e2c9f74c2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html @@ -73,6 +73,15 @@ [formControl]="entityKeyFormControl">
+
+ + + + {{ yAxis }} + + + +
-
- -
+ +
+
+
+
+
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.scss new file mode 100644 index 0000000000..52aa69f59d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.scss @@ -0,0 +1,87 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../../../../../../scss/constants'; + +.tb-y-axes-panel { + .tb-form-table-header-cell { + &.tb-axis-id-header { + width: 60px; + min-width: 60px; + } + + &.tb-label-header { + flex: 1 1 60%; + min-width: 100px; + } + + &.tb-position-header { + flex: 1; + min-width: 60px; + @media #{$mat-gt-md} { + flex: 1 1 40%; + } + } + + &.tb-units-header { + width: 80px; + min-width: 80px; + } + + &.tb-decimals-header { + width: 60px; + min-width: 60px; + } + + &.tb-min-header, &.tb-max-header { + width: 80px; + min-width: 80px; + } + + &.tb-show-header { + width: 40px; + min-width: 40px; + } + + &.tb-actions-header { + width: 80px; + min-width: 80px; + @media #{$mat-gt-md} { + width: 120px; + min-width: 120px; + } + } + + &.tb-label-header, &.tb-units-header, &.tb-decimals-header { + display: none; + @media #{$mat-gt-md} { + display: block; + } + } + + &.tb-min-header, &.tb-max-header { + display: none; + @media #{$mat-gt-xs} { + display: block; + } + } + } + + .tb-form-table-body { + tb-time-series-chart-y-axis-row { + overflow: hidden; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts new file mode 100644 index 0000000000..9879ffe877 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts @@ -0,0 +1,180 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, forwardRef, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validator +} from '@angular/forms'; +import { + defaultTimeSeriesChartYAxisSettings, + getNextTimeSeriesYAxisId, + TimeSeriesChartYAxes, TimeSeriesChartYAxisId, + TimeSeriesChartYAxisSettings, + timeSeriesChartYAxisValid, + timeSeriesChartYAxisValidator +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { mergeDeep } from '@core/utils'; +import { CdkDragDrop } from '@angular/cdk/drag-drop'; +import { coerceBoolean } from '@shared/decorators/coercion'; + +@Component({ + selector: 'tb-time-series-chart-y-axes-panel', + templateUrl: './time-series-chart-y-axes-panel.component.html', + styleUrls: ['./time-series-chart-y-axes-panel.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeSeriesChartYAxesPanelComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => TimeSeriesChartYAxesPanelComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class TimeSeriesChartYAxesPanelComponent implements ControlValueAccessor, OnInit, Validator { + + @Input() + disabled: boolean; + + @Input() + @coerceBoolean() + advanced = false; + + @Output() + axisRemoved = new EventEmitter(); + + yAxesFormGroup: UntypedFormGroup; + + get dragEnabled(): boolean { + return this.axesFormArray().controls.length > 1; + } + + private propagateChange = (_val: any) => {}; + + constructor(private fb: UntypedFormBuilder) { + } + + ngOnInit() { + this.yAxesFormGroup = this.fb.group({ + axes: [this.fb.array([]), []] + }); + this.yAxesFormGroup.valueChanges.subscribe( + () => { + let axes: TimeSeriesChartYAxisSettings[] = this.yAxesFormGroup.get('axes').value; + for (let i = 0; i < axes.length; i++) { + axes[i].order = i; + } + if (axes) { + axes = axes.filter(axis => timeSeriesChartYAxisValid(axis)); + } + const yAxes: TimeSeriesChartYAxes = {}; + for (const axis of axes) { + yAxes[axis.id] = axis; + } + this.propagateChange(yAxes); + } + ); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.yAxesFormGroup.disable({emitEvent: false}); + } else { + this.yAxesFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: TimeSeriesChartYAxes | undefined): void { + const yAxes: TimeSeriesChartYAxes = value || {}; + if (!yAxes.default) { + yAxes.default = mergeDeep({} as TimeSeriesChartYAxisSettings, defaultTimeSeriesChartYAxisSettings, + {id: 'default', order: 0} as TimeSeriesChartYAxisSettings); + } + const yAxisSettingsList = Object.values(yAxes); + yAxisSettingsList.sort((a1, a2) => a1.order - a2.order); + this.yAxesFormGroup.setControl('axes', this.prepareAxesFormArray(yAxisSettingsList), {emitEvent: false}); + } + + public validate(c: UntypedFormControl) { + const valid = this.yAxesFormGroup.valid; + return valid ? null : { + yAxes: { + valid: false, + }, + }; + } + + axisDrop(event: CdkDragDrop) { + const axesArray = this.yAxesFormGroup.get('axes') as UntypedFormArray; + const axis = axesArray.at(event.previousIndex); + axesArray.removeAt(event.previousIndex); + axesArray.insert(event.currentIndex, axis); + } + + axesFormArray(): UntypedFormArray { + return this.yAxesFormGroup.get('axes') as UntypedFormArray; + } + + trackByAxis(index: number, axisControl: AbstractControl): any { + return axisControl; + } + + removeAxis(index: number) { + const axis = + (this.yAxesFormGroup.get('axes') as UntypedFormArray).at(index).value as TimeSeriesChartYAxisSettings; + (this.yAxesFormGroup.get('axes') as UntypedFormArray).removeAt(index); + this.axisRemoved.emit(axis.id); + } + + addAxis() { + const axis = mergeDeep({} as TimeSeriesChartYAxisSettings, + defaultTimeSeriesChartYAxisSettings); + const axes: TimeSeriesChartYAxisSettings[] = this.yAxesFormGroup.get('axes').value; + axis.id = getNextTimeSeriesYAxisId(axes); + axis.order = axes.length; + const axesArray = this.yAxesFormGroup.get('axes') as UntypedFormArray; + const axisControl = this.fb.control(axis, [timeSeriesChartYAxisValidator]); + axesArray.push(axisControl); + } + + private prepareAxesFormArray(axes: TimeSeriesChartYAxisSettings[]): UntypedFormArray { + const axesControls: Array = []; + axes.forEach((axis) => { + axesControls.push(this.fb.control(axis, [timeSeriesChartYAxisValidator])); + }); + return this.fb.array(axesControls); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html new file mode 100644 index 0000000000..0b1907033f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html @@ -0,0 +1,72 @@ + +
+
+ {{ modelValue.id }} +
+ + + + + + + + {{ timeSeriesAxisPositionTranslations.get(position) | translate }} + + + +
+ + + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ +
+ +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.scss new file mode 100644 index 0000000000..12db0d9228 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.scss @@ -0,0 +1,95 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../../../../../../scss/constants'; + +.tb-form-table-row.tb-axis-row { + + .tb-axis-id-field { + color: rgba(0, 0, 0, 0.76); + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.2px; + width: 60px; + min-width: 60px; + } + + .tb-label-field { + flex: 1 1 60%; + min-width: 100px; + } + + .tb-position-field { + flex: 1; + min-width: 60px; + @media #{$mat-gt-md} { + flex: 1 1 40%; + } + } + + .tb-units-field, .tb-decimals-field, .tb-min-field, .tb-max-field { + display: flex; + flex-direction: row; + place-content: center; + align-items: center; + } + + .tb-units-field { + width: 80px; + min-width: 80px; + } + + .tb-decimals-field { + width: 60px; + min-width: 60px; + } + + .tb-min-field, .tb-max-field { + width: 80px; + min-width: 80px; + } + + .tb-show-field { + width: 40px; + min-width: 40px; + } + + .tb-label-field { + display: none; + @media #{$mat-gt-md} { + display: block; + } + } + + .tb-units-field, .tb-decimals-field { + display: none; + @media #{$mat-gt-md} { + display: flex; + } + } + + .tb-min-field, .tb-max-field { + display: none; + @media #{$mat-gt-xs} { + display: flex; + } + } + + .tb-remove-button { + width: 40px; + min-width: 40px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts new file mode 100644 index 0000000000..a4824fd51e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts @@ -0,0 +1,201 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectorRef, + Component, + EventEmitter, + forwardRef, + Input, + OnInit, + Output, + Renderer2, + ViewContainerRef, + ViewEncapsulation +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { + AxisPosition, + timeSeriesAxisPositionTranslations, + TimeSeriesChartYAxisSettings +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { + TimeSeriesChartYAxisSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component'; +import { deepClone } from '@core/utils'; + +@Component({ + selector: 'tb-time-series-chart-y-axis-row', + templateUrl: './time-series-chart-y-axis-row.component.html', + styleUrls: ['./time-series-chart-y-axis-row.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeSeriesChartYAxisRowComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, OnInit { + + axisPositions = [AxisPosition.left, AxisPosition.right]; + + timeSeriesAxisPositionTranslations = timeSeriesAxisPositionTranslations; + + @Input() + disabled: boolean; + + @Input() + @coerceBoolean() + advanced = false; + + @Output() + axisRemoved = new EventEmitter(); + + axisFormGroup: UntypedFormGroup; + + modelValue: TimeSeriesChartYAxisSettings; + + private propagateChange = (_val: any) => {}; + + constructor(private fb: UntypedFormBuilder, + private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef, + private cd: ChangeDetectorRef) { + } + + ngOnInit() { + this.axisFormGroup = this.fb.group({ + label: [null, []], + position: [null, []], + units: [null, []], + decimals: [null, []], + min: [null, []], + max: [null, []], + show: [null, []] + }); + this.axisFormGroup.valueChanges.subscribe( + () => this.updateModel() + ); + this.axisFormGroup.get('show').valueChanges.subscribe(() => { + this.updateValidators(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.axisFormGroup.disable({emitEvent: false}); + } else { + this.axisFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + writeValue(value: TimeSeriesChartYAxisSettings): void { + this.modelValue = value; + this.axisFormGroup.patchValue( + { + label: value.label, + position: value.position, + units: value.units, + decimals: value.decimals, + min: value.min, + max: value.max, + show: value.show, + }, {emitEvent: false} + ); + this.updateValidators(); + this.cd.markForCheck(); + } + + editAxis($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + yAxisSettings: deepClone(this.modelValue), + advanced: this.advanced + }; + const yAxisSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, TimeSeriesChartYAxisSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null, + ctx, + {}, + {}, {}, true); + yAxisSettingsPanelPopover.tbComponentRef.instance.popover = yAxisSettingsPanelPopover; + yAxisSettingsPanelPopover.tbComponentRef.instance.yAxisSettingsApplied.subscribe((yAxisSettings) => { + yAxisSettingsPanelPopover.hide(); + this.modelValue = {...this.modelValue, ...yAxisSettings}; + this.axisFormGroup.patchValue( + { + label: this.modelValue.label, + position: this.modelValue.position, + units: this.modelValue.units, + decimals: this.modelValue.decimals, + min: this.modelValue.min, + max: this.modelValue.max, + show: this.modelValue.show + }, + {emitEvent: false}); + this.updateValidators(); + this.propagateChange(this.modelValue); + }); + } + } + + private updateValidators() { + const show: boolean = this.axisFormGroup.get('show').value; + if (show) { + this.axisFormGroup.get('label').enable({emitEvent: false}); + this.axisFormGroup.get('position').enable({emitEvent: false}); + this.axisFormGroup.get('units').enable({emitEvent: false}); + this.axisFormGroup.get('decimals').enable({emitEvent: false}); + } else { + this.axisFormGroup.get('label').disable({emitEvent: false}); + this.axisFormGroup.get('position').disable({emitEvent: false}); + this.axisFormGroup.get('units').disable({emitEvent: false}); + this.axisFormGroup.get('decimals').disable({emitEvent: false}); + } + } + + private updateModel() { + const value = this.axisFormGroup.value; + this.modelValue.label = value.label; + this.modelValue.position = value.position; + this.modelValue.units = value.units; + this.modelValue.decimals = value.decimals; + this.modelValue.min = value.min; + this.modelValue.max = value.max; + this.modelValue.show = value.show; + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.html new file mode 100644 index 0000000000..759074b4be --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.html @@ -0,0 +1,43 @@ + +
+
{{ 'widgets.time-series-chart.axis.y-axis-settings' | translate }}
+
+ + +
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.scss new file mode 100644 index 0000000000..095c38b7f7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.scss @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../../../../../../scss/constants'; + +.tb-y-axis-settings-panel { + width: 530px; + display: flex; + flex-direction: column; + gap: 16px; + @media #{$mat-lt-md} { + width: 90vw; + } + .tb-y-axis-settings-panel-content { + display: flex; + flex-direction: column; + gap: 16px; + overflow: auto; + margin: -10px; + padding: 10px; + } + .tb-y-axis-settings-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-y-axis-settings-panel-buttons { + height: 40px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.ts new file mode 100644 index 0000000000..8046aafd29 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.ts @@ -0,0 +1,66 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { TimeSeriesChartYAxisSettings } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; + +@Component({ + selector: 'tb-time-series-chart-y-axis-settings-panel', + templateUrl: './time-series-chart-y-axis-settings-panel.component.html', + providers: [], + styleUrls: ['./time-series-chart-y-axis-settings-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class TimeSeriesChartYAxisSettingsPanelComponent implements OnInit { + + @Input() + yAxisSettings: TimeSeriesChartYAxisSettings; + + @Input() + @coerceBoolean() + advanced = false; + + @Input() + popover: TbPopoverComponent; + + @Output() + yAxisSettingsApplied = new EventEmitter(); + + yAxisSettingsFormGroup: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + } + + ngOnInit(): void { + this.yAxisSettingsFormGroup = this.fb.group( + { + yAxis: [this.yAxisSettings, []] + } + ); + } + + cancel() { + this.popover?.hide(); + } + + applyYAxisSettings() { + const yAxisSettings = this.yAxisSettingsFormGroup.get('yAxis').getRawValue(); + this.yAxisSettingsApplied.emit(yAxisSettings); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index b741a8e358..714da2a0c7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -109,6 +109,15 @@ import { import { TimeSeriesNoAggregationBarWidthSettingsComponent } from '@home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component'; +import { + TimeSeriesChartYAxesPanelComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component'; +import { + TimeSeriesChartYAxisRowComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component'; +import { + TimeSeriesChartYAxisSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component'; @NgModule({ declarations: [ @@ -150,6 +159,9 @@ import { TimeSeriesChartThresholdRowComponent, TimeSeriesChartThresholdSettingsPanelComponent, TimeSeriesNoAggregationBarWidthSettingsComponent, + TimeSeriesChartYAxesPanelComponent, + TimeSeriesChartYAxisRowComponent, + TimeSeriesChartYAxisSettingsPanelComponent, DataKeyInputComponent, EntityAliasInputComponent ], @@ -197,6 +209,9 @@ import { TimeSeriesChartThresholdRowComponent, TimeSeriesChartThresholdSettingsPanelComponent, TimeSeriesNoAggregationBarWidthSettingsComponent, + TimeSeriesChartYAxesPanelComponent, + TimeSeriesChartYAxisRowComponent, + TimeSeriesChartYAxisSettingsPanelComponent, DataKeyInputComponent, EntityAliasInputComponent ], diff --git a/ui-ngx/src/app/shared/models/common.ts b/ui-ngx/src/app/shared/models/common.ts new file mode 100644 index 0000000000..c331cc5127 --- /dev/null +++ b/ui-ngx/src/app/shared/models/common.ts @@ -0,0 +1,19 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +export type DeepPartial = T extends object ? { + [P in keyof T]?: DeepPartial; +} : T; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 216ed878b2..830a54f88e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6609,6 +6609,7 @@ "stack-mode": "Stack mode", "stack-mode-hint": "Stacks series on the chart. The series with the same unit would be put on top of each other.", "axes": "Axes", + "y-axes": "Y axes", "line-type": "Line type", "line-type-solid": "Solid", "line-type-dashed": "Dashed", @@ -6670,6 +6671,9 @@ "axes": "Axes", "x-axis": "X axis", "y-axis": "Y axis", + "y-axis-settings": "Y axis settings", + "remove-y-axis": "Remove Y axis", + "id": "Id", "label": "Label", "position": "Position", "position-left": "Left", @@ -6686,7 +6690,11 @@ "scale": "Scale", "scale-min": "min", "scale-max": "max", - "scale-auto": "Auto" + "scale-auto": "Auto", + "min": "Min", + "max": "Max", + "show": "Show", + "add-y-axis": "Add Y axis" }, "series": { "legend-settings": "Legend settings", From 7823f1533c92eba581cb0fb0484b0a0e280bac4b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 11 Mar 2024 16:12:44 +0200 Subject: [PATCH 180/209] UI: Time series chart animation settings. --- ...e-series-chart-basic-config.component.html | 3 + ...ime-series-chart-basic-config.component.ts | 4 + ...eries-chart-widget-settings.component.html | 3 + ...-series-chart-widget-settings.component.ts | 2 + ...es-chart-animation-settings.component.html | 89 +++++++++++++ ...ries-chart-animation-settings.component.ts | 124 ++++++++++++++++++ .../common/widget-settings-common.module.ts | 5 + .../assets/locale/locale.constant-en_US.json | 10 ++ 8 files changed, 240 insertions(+) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-animation-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-animation-settings.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html index 1f62a13c4e..adaeb78acb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html @@ -223,6 +223,9 @@
+ +
widget-config.card-appearance
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts index f9ee35da23..ba4b4c6371 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts @@ -168,6 +168,8 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], + animation: [settings.animation, []], + background: [settings.background, []], cardButtons: [this.getCardButtons(configData.config), []], @@ -221,6 +223,8 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon this.widgetConfig.config.settings.tooltipBackgroundColor = config.tooltipBackgroundColor; this.widgetConfig.config.settings.tooltipBackgroundBlur = config.tooltipBackgroundBlur; + this.widgetConfig.config.settings.animation = config.animation; + this.widgetConfig.config.settings.background = config.background; this.setCardButtons(config.cardButtons, this.widgetConfig.config); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html index 933961a7ae..42249a67cd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html @@ -157,6 +157,9 @@
+ +
{{ 'widgets.background.background' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts index 003b5005aa..e270b97137 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts @@ -138,6 +138,8 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], + animation: [settings.animation, []], + background: [settings.background, []] }); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-animation-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-animation-settings.component.html new file mode 100644 index 0000000000..5e0ad8e2ec --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-animation-settings.component.html @@ -0,0 +1,89 @@ + + +
+ + + + + {{ 'widgets.time-series-chart.animation.animation' | translate }} + + + + +
+
{{ 'widgets.time-series-chart.animation.animation-threshold' | translate }}
+ + +
ms
+
+
+
+
{{ 'widgets.time-series-chart.animation.animation-duration' | translate }}
+ + +
ms
+
+
+
+
widgets.time-series-chart.animation.animation-easing
+ + + + {{ easing }} + + + +
+
+
{{ 'widgets.time-series-chart.animation.animation-delay' | translate }}
+ + +
ms
+
+
+
+
{{ 'widgets.time-series-chart.animation.update-animation-duration' | translate }}
+ + +
ms
+
+
+
+
widgets.time-series-chart.animation.update-animation-easing
+ + + + {{ easing }} + + + +
+
+
{{ 'widgets.time-series-chart.animation.update-animation-delay' | translate }}
+ + +
ms
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-animation-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-animation-settings.component.ts new file mode 100644 index 0000000000..5da5aa2fe9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-animation-settings.component.ts @@ -0,0 +1,124 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + Validators +} from '@angular/forms'; +import { + timeSeriesChartAnimationEasings, + TimeSeriesChartAnimationSettings +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { WidgetService } from '@core/http/widget.service'; + +@Component({ + selector: 'tb-time-series-chart-animation-settings', + templateUrl: './time-series-chart-animation-settings.component.html', + styleUrls: ['./../../widget-settings.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeSeriesChartAnimationSettingsComponent), + multi: true + } + ] +}) +export class TimeSeriesChartAnimationSettingsComponent implements OnInit, ControlValueAccessor { + + settingsExpanded = false; + + timeSeriesChartAnimationEasings = timeSeriesChartAnimationEasings; + + @Input() + disabled: boolean; + + private modelValue: TimeSeriesChartAnimationSettings; + + private propagateChange = null; + + public animationSettingsFormGroup: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder, + private widgetService: WidgetService,) { + } + + ngOnInit(): void { + this.animationSettingsFormGroup = this.fb.group({ + animation: [null, []], + animationThreshold: [null, [Validators.min(0)]], + animationDuration: [null, [Validators.min(0)]], + animationEasing: [null, []], + animationDelay: [null, [Validators.min(0)]], + animationDurationUpdate: [null, [Validators.min(0)]], + animationEasingUpdate: [null, []], + animationDelayUpdate: [null, [Validators.min(0)]] + }); + this.animationSettingsFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + this.animationSettingsFormGroup.get('animation').valueChanges + .subscribe(() => { + this.updateValidators(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.animationSettingsFormGroup.disable({emitEvent: false}); + } else { + this.animationSettingsFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + writeValue(value: TimeSeriesChartAnimationSettings): void { + this.modelValue = value; + this.animationSettingsFormGroup.patchValue( + value, {emitEvent: false} + ); + this.updateValidators(); + this.animationSettingsFormGroup.get('animation').valueChanges.subscribe((animation) => { + this.settingsExpanded = animation; + }); + } + + private updateValidators() { + const animation: boolean = this.animationSettingsFormGroup.get('animation').value; + if (animation) { + this.animationSettingsFormGroup.enable({emitEvent: false}); + } else { + this.animationSettingsFormGroup.disable({emitEvent: false}); + this.animationSettingsFormGroup.get('animation').enable({emitEvent: false}); + } + } + + private updateModel() { + this.modelValue = this.animationSettingsFormGroup.getRawValue(); + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index 714da2a0c7..b80b20f402 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -118,6 +118,9 @@ import { import { TimeSeriesChartYAxisSettingsPanelComponent } from '@home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component'; +import { + TimeSeriesChartAnimationSettingsComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-animation-settings.component'; @NgModule({ declarations: [ @@ -162,6 +165,7 @@ import { TimeSeriesChartYAxesPanelComponent, TimeSeriesChartYAxisRowComponent, TimeSeriesChartYAxisSettingsPanelComponent, + TimeSeriesChartAnimationSettingsComponent, DataKeyInputComponent, EntityAliasInputComponent ], @@ -212,6 +216,7 @@ import { TimeSeriesChartYAxesPanelComponent, TimeSeriesChartYAxisRowComponent, TimeSeriesChartYAxisSettingsPanelComponent, + TimeSeriesChartAnimationSettingsComponent, DataKeyInputComponent, EntityAliasInputComponent ], diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 830a54f88e..decb97384e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6696,6 +6696,16 @@ "show": "Show", "add-y-axis": "Add Y axis" }, + "animation": { + "animation": "Animation", + "animation-threshold": "Animation threshold", + "animation-duration": "Animation duration", + "animation-easing": "Animation easing", + "animation-delay": "Animation delay", + "update-animation-duration": "Update animation duration", + "update-animation-easing": "Update animation easing", + "update-animation-delay": "Update animation delay" + }, "series": { "legend-settings": "Legend settings", "show-in-legend": "Show in legend", From e2a77779ad15fa1686250ad0b7fb801b950e2845 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 11 Mar 2024 17:45:09 +0200 Subject: [PATCH 181/209] UI: Update home dashboards --- .../lib/chart/time-series-chart.models.ts | 11 +- .../assets/dashboard/sys_admin_home_page.json | 1166 +++++++++++------ .../dashboard/tenant_admin_home_page.json | 976 +++++++++----- 3 files changed, 1492 insertions(+), 661 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index 9af41fa378..02cbdb2ff8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -884,7 +884,16 @@ export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartAxisSetting fontWeight: xAxisTickLabelStyle.fontWeight, fontFamily: xAxisTickLabelStyle.fontFamily, fontSize: xAxisTickLabelStyle.fontSize, - hideOverlap: true + hideOverlap: true, + /* formatter: { + year: '{yyyy}', + month: '{MMM}', + day: '{d}', + hour: '{HH}:{mm}', + minute: '{HH}:{mm}', + second: '{HH}:{mm}:{ss}', + millisecond: '{hh}:{mm}:{ss} {SSS}' + } */ }, axisLine: { show: settings.showLine, diff --git a/ui-ngx/src/assets/dashboard/sys_admin_home_page.json b/ui-ngx/src/assets/dashboard/sys_admin_home_page.json index f93de01596..b308bc98d6 100644 --- a/ui-ngx/src/assets/dashboard/sys_admin_home_page.json +++ b/ui-ngx/src/assets/dashboard/sys_admin_home_page.json @@ -69,142 +69,6 @@ "id": "d70cc256-4c7b-ee06-9905-b8c5e546605f", "typeFullFqn": "system.cards.markdown_card" }, - "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079", - "filterId": null, - "dataKeys": [ - { - "name": "transportMsgCountHourly", - "type": "timeseries", - "label": "{i18n:widgets.transport-messages.title}", - "color": "#305680", - "settings": {}, - "_hash": 0.2880464219129071, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ], - "latestDataKeys": [] - } - ], - "timewindow": { - "hideInterval": false, - "hideLastInterval": false, - "hideQuickInterval": false, - "hideAggregation": true, - "hideAggInterval": false, - "hideTimezone": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 2592000000, - "interval": 86400000, - "fixedTimewindow": { - "startTimeMs": 1680443065451, - "endTimeMs": 1680529465451 - }, - "quickInterval": "CURRENT_DAY" - }, - "aggregation": { - "type": "SUM", - "limit": 50000 - } - }, - "showTitle": false, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "0px", - "settings": { - "stack": true, - "fontSize": 10, - "fontColor": "#545454", - "showTooltip": true, - "tooltipIndividual": false, - "tooltipCumulative": false, - "hideZeros": false, - "grid": { - "verticalLines": false, - "horizontalLines": false, - "outlineWidth": 0, - "color": "#545454", - "backgroundColor": null, - "tickColor": "#DDDDDD" - }, - "xaxis": { - "title": null, - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "min": 0, - "max": null, - "title": null, - "showLabels": true, - "color": "#545454", - "tickSize": null, - "tickDecimals": 0, - "ticksFormatter": "return value % 1000 === 0 ? ((value / 1000) + 'k') : '';" - }, - "defaultBarWidth": 1800000, - "barAlignment": "left", - "comparisonEnabled": false, - "timeForComparison": "previousInterval", - "comparisonCustomIntervalValue": 7200000, - "xaxisSecond": { - "axisPosition": "top", - "title": null, - "showLabels": true - }, - "customLegendEnabled": false, - "dataKeysListForLabels": [], - "showLegend": false, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": true, - "showTotal": false, - "showLatest": false - } - }, - "title": "Transport messages", - "dropShadow": false, - "enableFullscreen": false, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": { - "padding": "0" - }, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "titleTooltip": "", - "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-widget-container > .tb-widget .flot-base {\n opacity: 0.48;\n}\n", - "pageSize": 1024, - "noDataDisplayMessage": "" - }, - "row": 0, - "col": 0, - "id": "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, "4b5e47ed-c197-a937-d727-041ba8decec2": { "type": "latest", "sizeX": 5, @@ -294,227 +158,6 @@ "id": "4b5e47ed-c197-a937-d727-041ba8decec2", "typeFullFqn": "system.cards.markdown_card" }, - "eace9148-b02a-48fe-1a95-acd9928aa8c5": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079", - "filterId": null, - "dataKeys": [ - { - "name": "cpuUsage", - "type": "timeseries", - "label": "{i18n:widgets.system-info.cpu}", - "color": "#305680", - "settings": { - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "excludeFromStacking": false, - "showLines": true, - "lineWidth": 3, - "fillLines": false, - "showPoints": false, - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showSeparateAxis": false, - "axisPosition": "left", - "comparisonSettings": { - "showValuesForComparison": true, - "comparisonValuesLabel": "", - "color": "" - }, - "thresholds": [] - }, - "_hash": 0.9347575372081658, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "memoryUsage", - "type": "timeseries", - "label": "{i18n:widgets.system-info.ram}", - "color": "#ac3bc9", - "settings": { - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "excludeFromStacking": false, - "showLines": true, - "lineWidth": 3, - "fillLines": false, - "showPoints": false, - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showSeparateAxis": false, - "axisPosition": "left", - "comparisonSettings": { - "showValuesForComparison": true, - "comparisonValuesLabel": "", - "color": "" - }, - "thresholds": [] - }, - "_hash": 0.31887216598848855, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - }, - { - "name": "discUsage", - "type": "timeseries", - "label": "{i18n:widgets.system-info.disk}", - "color": "#40d0ae", - "settings": { - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "excludeFromStacking": false, - "showLines": true, - "lineWidth": 3, - "fillLines": false, - "showPoints": false, - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showSeparateAxis": false, - "axisPosition": "left", - "comparisonSettings": { - "showValuesForComparison": true, - "comparisonValuesLabel": "", - "color": "" - }, - "thresholds": [] - }, - "_hash": 0.26499182606431004, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ], - "latestDataKeys": null - } - ], - "timewindow": { - "hideInterval": false, - "hideLastInterval": false, - "hideQuickInterval": false, - "hideAggregation": false, - "hideAggInterval": false, - "hideTimezone": false, - "selectedTab": 0, - "realtime": { - "realtimeType": 0, - "timewindowMs": 3600000, - "quickInterval": "CURRENT_DAY", - "interval": 10000 - }, - "aggregation": { - "type": "AVG", - "limit": 25000 - } - }, - "showTitle": false, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "stack": false, - "fontSize": 10, - "fontColor": "#545454", - "showTooltip": true, - "tooltipIndividual": false, - "tooltipCumulative": false, - "hideZeros": false, - "grid": { - "verticalLines": false, - "horizontalLines": false, - "outlineWidth": 0, - "color": "#545454", - "backgroundColor": null, - "tickColor": "#DDDDDD" - }, - "xaxis": { - "title": null, - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "min": 0, - "max": 100, - "title": null, - "showLabels": true, - "color": "#545454", - "tickSize": null, - "tickDecimals": 0, - "ticksFormatter": "" - }, - "shadowSize": 0, - "smoothLines": true, - "comparisonEnabled": false, - "timeForComparison": "previousInterval", - "comparisonCustomIntervalValue": 7200000, - "xaxisSecond": { - "axisPosition": "top", - "title": null, - "showLabels": true - }, - "customLegendEnabled": false, - "dataKeysListForLabels": [], - "showLegend": true, - "legendConfig": { - "direction": "row", - "position": "top", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": false, - "showLatest": false - } - }, - "title": "System Info Chart", - "dropShadow": false, - "enableFullscreen": false, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "useDashboardTimewindow": false, - "showTitleIcon": false, - "titleTooltip": "", - "widgetStyle": {}, - "widgetCss": "\n.tb-legend-keys td .tb-legend-line {\n height: 5px !important;\n vertical-align: middle;\n}\n", - "pageSize": 1024, - "units": "%", - "noDataDisplayMessage": "", - "displayTimewindow": true - }, - "row": 0, - "col": 0, - "id": "eace9148-b02a-48fe-1a95-acd9928aa8c5", - "typeFullFqn": "system.charts.basic_timeseries" - }, "8acbf5df-f9fc-114d-216f-86f081aa4779": { "type": "latest", "sizeX": 5, @@ -2197,6 +1840,798 @@ "col": 0, "id": "163025d8-3ca4-dd1e-c17b-e40d8e03e8a8", "typeFullFqn": "system.cards.markdown_card" + }, + "98bce47d-76df-5fc1-f8b9-8584d73a2439": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": "", + "entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079", + "dataKeys": [ + { + "name": "transportMsgCountHourly", + "type": "timeseries", + "label": "{i18n:widgets.transport-messages.title}", + "color": "#305680", + "settings": { + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "bar", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "emptyCircle", + "pointSize": 4, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.39181957822569946, + "decimals": 0 + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + }, + "latestDataKeys": [] + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": true, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000, + "fixedTimewindow": { + "startTimeMs": 1709807941444, + "endTimeMs": 1709894341444 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "SUM", + "limit": 25000 + }, + "timezone": null + }, + "showTitle": false, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "return value === 0 ? value : ((value / 1000) + 'k');", + "showTicks": false, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": false, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": false, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": 0, + "max": null + } + }, + "thresholds": [], + "dataZoom": false, + "stack": false, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": false, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": false, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": false, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } + }, + "showLegend": false, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "top", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": true, + "showTotal": false, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "MMMM dd, yyyy", + "lastUpdateAgo": false, + "custom": true + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": false, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "Time series chart", + "dropShadow": false, + "enableFullscreen": false, + "titleStyle": null, + "configMode": "advanced", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-time-series-chart-panel {\n padding: 0;\n}\n\n.tb-time-series-chart-panel div.tb-widget-title {\n padding-top: 5px;\n padding-left: 5px;\n}", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": true, + "iconSize": "24px", + "icon": "query_builder", + "iconPosition": "left", + "font": { + "size": 14, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.54)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" + }, + "row": 0, + "col": 0, + "id": "98bce47d-76df-5fc1-f8b9-8584d73a2439" + }, + "b9174a99-2a00-3dbe-0eae-927fd8506345": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": "", + "entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079", + "dataKeys": [ + { + "name": "cpuUsage", + "type": "timeseries", + "label": "{i18n:widgets.system-info.cpu}", + "color": "#305680", + "settings": { + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": true, + "lineType": "solid", + "lineWidth": 3, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 14, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.39181957822569946, + "decimals": null, + "aggregationType": null, + "units": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "memoryUsage", + "type": "timeseries", + "label": "{i18n:widgets.system-info.ram}", + "color": "#AC3BC9", + "settings": { + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": true, + "lineType": "solid", + "lineWidth": 3, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 14, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.9392996197028385, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "discUsage", + "type": "timeseries", + "label": "{i18n:widgets.system-info.disk}", + "color": "#40D0AE", + "settings": { + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": true, + "lineType": "solid", + "lineWidth": 3, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 14, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.027335636460212864, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + }, + "latestDataKeys": [] + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "timewindowMs": 3600000, + "quickInterval": "CURRENT_DAY", + "interval": 10000 + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + }, + "timezone": null + }, + "showTitle": false, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "", + "showTicks": false, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": false, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": false, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": 0, + "max": 100 + } + }, + "thresholds": [], + "dataZoom": false, + "stack": false, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": false, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": false, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": false, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "top", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": false, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "dd MMM yyyy HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": false, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 300, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "Time series chart", + "dropShadow": false, + "enableFullscreen": false, + "titleStyle": null, + "configMode": "basic", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": ".tb-time-series-chart-panel {\n padding: 0;\n}\n\n.tb-time-series-chart-panel div.tb-widget-title {\n padding-top: 5px;\n padding-left: 5px;\n}", + "pageSize": 1024, + "units": "%", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": true, + "iconSize": "24px", + "icon": "query_builder", + "iconPosition": "left", + "font": { + "size": 14, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.54)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "", + "iconSize": "0px" + }, + "row": 0, + "col": 0, + "id": "b9174a99-2a00-3dbe-0eae-927fd8506345" } }, "states": { @@ -2288,7 +2723,7 @@ "layouts": { "main": { "widgets": { - "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e": { + "98bce47d-76df-5fc1-f8b9-8584d73a2439": { "sizeX": 24, "sizeY": 11, "row": 0, @@ -2315,14 +2750,6 @@ "layouts": { "main": { "widgets": { - "eace9148-b02a-48fe-1a95-acd9928aa8c5": { - "sizeX": 48, - "sizeY": 20, - "row": 8, - "col": 0, - "mobileOrder": 1, - "mobileHeight": 10 - }, "f0479b00-ed47-0a30-0c36-bde7847aae00": { "sizeX": 48, "sizeY": 8, @@ -2330,6 +2757,12 @@ "mobileHeight": 4, "row": 0, "col": 0 + }, + "b9174a99-2a00-3dbe-0eae-927fd8506345": { + "sizeX": 48, + "sizeY": 20, + "row": 8, + "col": 0 } }, "gridSettings": { @@ -2609,6 +3042,5 @@ "dashboardCss": ".tb-widget-container > .tb-widget {\n border: 1px solid rgba(0, 0, 0, 0.05);\n box-shadow: 0px 5px 16px rgba(0, 0, 0, 0.04);\n border-radius: 12px;\n}\n\n.tb-widget-container > .tb-widget:not([style*=\"padding: 0\"]) {\n padding: 16px !important;\n}\n\n.tb-card-title {\n display: grid;\n}\n\n.tb-home-widget-title {\n font-style: normal;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n color: rgba(0, 0, 0, 0.54);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.tb-home-widget-link {\n position: relative;\n border-bottom: none;\n}\n\n.tb-home-widget-link:hover {\n border-bottom: none;\n}\n\n.tb-home-widget-link:focus {\n border-bottom: none;\n}\n\n.tb-home-widget-link::after {\n content: 'arrow_forward';\n display: inline-block;\n transform: rotate(315deg);\n font-family: 'Material Icons';\n font-weight: normal;\n font-style: normal;\n font-size: 18px;\n color: rgba(0, 0, 0, 0.12);\n vertical-align: bottom;\n margin-left: 6px; \n}\n\n.tb-home-widget-link:hover::after {\n color: inherit;\n}\n\n.tb-home-widget-info-icon {\n color: rgba(0, 0, 0, 0.12);\n font-size: 16px;\n width: 16px;\n height: 16px;\n line-height: 15px;\n vertical-align: middle;\n}\n\n.tb-widget-container > .tb-widget .tb-timewindow {\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n color: rgba(0, 0, 0, 0.54);\n padding: 0;\n}\n\n.tb-widget-container > .tb-widget .tb-legend-keys .tb-legend-label {\n cursor: pointer;\n user-select: none;\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.2px;\n color: rgba(0, 0, 0, 0.54);\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .tb-widget-container > .tb-widget {\n border-radius: 4px;\n }\n .tb-widget-container > .tb-widget:not([style*=\"padding: 0\"]) {\n padding: 2px !important;\n }\n .tb-hide-md {\n display: none;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1819px) {\n .tb-widget-container > .tb-widget:not([style*=\"padding: 0\"]) {\n padding: 8px !important;\n }\n .tb-hide-lg {\n display: none;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-hide-md-lg {\n display: none;\n }\n\n .tb-home-widget-title {\n font-size: 12px;\n line-height: 16px;\n }\n \n .tb-widget-container > .tb-widget .tb-widget-title {\n padding: 0;\n }\n\n .tb-widget-container > .tb-widget .tb-timewindow {\n font-size: 12px;\n line-height: 16px;\n min-height: 24px;\n padding: 0;\n }\n\n .tb-widget-container > .tb-widget .tb-timewindow .mat-mdc-icon-button.tb-mat-32 {\n width: 24px;\n height: 24px;\n line-height: 24px;\n }\n\n .tb-widget-container > .tb-widget .tb-timewindow .mat-mdc-icon-button.tb-mat-32 .mat-icon {\n width: 18px;\n height: 18px;\n font-size: 18px;\n }\n \n .tb-widget-container > .tb-widget tb-legend {\n padding-bottom: 0 !important;\n }\n \n .tb-widget-container > .tb-widget .tb-legend-keys .tb-legend-label {\n font-size: 11px;\n line-height: 16px;\n letter-spacing: 0.25px;\n }\n}\n\n@media screen and (max-width: 959px), screen and (min-width: 1820px) {\n .tb-hide-not-md-lg {\n display: none;\n }\n}\n" } }, - "externalId": null, "name": "System Administrator Home Page" } \ No newline at end of file diff --git a/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json index 37e8d82e45..b9a996b8a5 100644 --- a/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json +++ b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json @@ -69,142 +69,6 @@ "id": "d70cc256-4c7b-ee06-9905-b8c5e546605f", "typeFullFqn": "system.cards.markdown_card" }, - "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079", - "filterId": null, - "dataKeys": [ - { - "name": "transportMsgCountHourly", - "type": "timeseries", - "label": "{i18n:widgets.transport-messages.title}", - "color": "#305680", - "settings": {}, - "_hash": 0.2880464219129071, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ], - "latestDataKeys": [] - } - ], - "timewindow": { - "hideInterval": false, - "hideLastInterval": false, - "hideQuickInterval": false, - "hideAggregation": true, - "hideAggInterval": false, - "hideTimezone": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 2592000000, - "interval": 86400000, - "fixedTimewindow": { - "startTimeMs": 1680443065451, - "endTimeMs": 1680529465451 - }, - "quickInterval": "CURRENT_DAY" - }, - "aggregation": { - "type": "SUM", - "limit": 50000 - } - }, - "showTitle": false, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "0px", - "settings": { - "stack": true, - "fontSize": 10, - "fontColor": "#545454", - "showTooltip": true, - "tooltipIndividual": false, - "tooltipCumulative": false, - "hideZeros": false, - "grid": { - "verticalLines": false, - "horizontalLines": false, - "outlineWidth": 0, - "color": "#545454", - "backgroundColor": null, - "tickColor": "#DDDDDD" - }, - "xaxis": { - "title": null, - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "min": 0, - "max": null, - "title": null, - "showLabels": true, - "color": "#545454", - "tickSize": null, - "tickDecimals": 0, - "ticksFormatter": "return value % 1000 === 0 ? ((value / 1000) + 'k') : '';" - }, - "defaultBarWidth": 1800000, - "barAlignment": "left", - "comparisonEnabled": false, - "timeForComparison": "previousInterval", - "comparisonCustomIntervalValue": 7200000, - "xaxisSecond": { - "axisPosition": "top", - "title": null, - "showLabels": true - }, - "customLegendEnabled": false, - "dataKeysListForLabels": [], - "showLegend": false, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": true, - "showTotal": false, - "showLatest": false - } - }, - "title": "Transport messages", - "dropShadow": false, - "enableFullscreen": false, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "widgetStyle": { - "padding": "0" - }, - "useDashboardTimewindow": false, - "actions": {}, - "displayTimewindow": true, - "showTitleIcon": false, - "titleTooltip": "", - "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-widget-container > .tb-widget .flot-base {\n opacity: 0.48;\n}\n", - "pageSize": 1024, - "noDataDisplayMessage": "" - }, - "row": 0, - "col": 0, - "id": "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e", - "typeFullFqn": "system.charts.timeseries_bars_flot" - }, "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4": { "type": "static", "sizeX": 7.5, @@ -269,159 +133,6 @@ "id": "a23185ad-dc46-806c-0e50-5b21fb080ace", "typeFullFqn": "system.home_page_widgets.getting_started" }, - "d26e5cd7-75ef-d475-00c7-1a2d1114efe8": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079", - "filterId": null, - "dataKeys": [ - { - "name": "activeDevicesCountHourly", - "type": "timeseries", - "label": "{i18n:device.devices}", - "color": "#305680", - "settings": { - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "excludeFromStacking": false, - "showLines": true, - "lineWidth": 3, - "fillLines": true, - "fillLinesOpacity": 0.04, - "showPoints": false, - "showSeparateAxis": false, - "axisPosition": "left", - "comparisonSettings": { - "showValuesForComparison": true, - "comparisonValuesLabel": "", - "color": "" - }, - "thresholds": [] - }, - "_hash": 0.9688095820365725, - "aggregationType": null, - "units": null, - "decimals": null, - "funcBody": null, - "usePostProcessing": null, - "postFuncBody": null - } - ], - "latestDataKeys": [] - } - ], - "timewindow": { - "hideInterval": false, - "hideLastInterval": false, - "hideQuickInterval": false, - "hideAggregation": true, - "hideAggInterval": true, - "hideTimezone": false, - "selectedTab": 1, - "history": { - "historyType": 0, - "timewindowMs": 2592000000, - "interval": 7200000, - "fixedTimewindow": { - "startTimeMs": 1681400576338, - "endTimeMs": 1681486976338 - }, - "quickInterval": "CURRENT_DAY" - }, - "aggregation": { - "type": "NONE", - "limit": 25000 - } - }, - "showTitle": false, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "0px", - "settings": { - "stack": false, - "fontSize": 10, - "fontColor": "#545454", - "showTooltip": true, - "tooltipIndividual": false, - "tooltipCumulative": false, - "hideZeros": false, - "grid": { - "verticalLines": false, - "horizontalLines": false, - "outlineWidth": 0, - "color": "#545454", - "backgroundColor": null, - "tickColor": "#DDDDDD" - }, - "xaxis": { - "title": null, - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "min": null, - "max": null, - "title": null, - "showLabels": true, - "color": "#545454", - "tickSize": null, - "tickDecimals": 0, - "ticksFormatter": "" - }, - "shadowSize": 0, - "smoothLines": true, - "comparisonEnabled": false, - "timeForComparison": "previousInterval", - "comparisonCustomIntervalValue": 7200000, - "xaxisSecond": { - "axisPosition": "top", - "title": null, - "showLabels": true - }, - "customLegendEnabled": false, - "dataKeysListForLabels": [], - "showLegend": false, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": true, - "showTotal": false, - "showLatest": false - } - }, - "title": "Devices activity", - "dropShadow": false, - "enableFullscreen": false, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "useDashboardTimewindow": false, - "displayTimewindow": true, - "showTitleIcon": false, - "titleTooltip": "", - "widgetStyle": { - "padding": "0" - }, - "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n", - "pageSize": 1024, - "noDataDisplayMessage": "" - }, - "row": 0, - "col": 0, - "id": "d26e5cd7-75ef-d475-00c7-1a2d1114efe8", - "typeFullFqn": "system.charts.basic_timeseries" - }, "ebbd0a6e-8a47-e770-5086-7f4974250f2d": { "type": "static", "sizeX": 7.5, @@ -777,6 +488,686 @@ "col": 0, "id": "7ac20b6a-dc40-b18e-9f5f-bca20bc693bb", "typeFullFqn": "system.cards.markdown_card" + }, + "bf7f6efa-7614-3bc0-c4d0-6665d67510a8": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": "", + "entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079", + "dataKeys": [ + { + "name": "transportMsgCountHourly", + "type": "timeseries", + "label": "{i18n:widgets.transport-messages.title}", + "color": "#305680", + "settings": { + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "bar", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "emptyCircle", + "pointSize": 4, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.39181957822569946, + "decimals": 0 + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + }, + "latestDataKeys": [] + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": true, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000, + "fixedTimewindow": { + "startTimeMs": 1709807941444, + "endTimeMs": 1709894341444 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "SUM", + "limit": 25000 + }, + "timezone": null + }, + "showTitle": false, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "return value === 0 ? value : ((value / 1000) + 'k');", + "showTicks": false, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": false, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": false, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": 0, + "max": null + } + }, + "thresholds": [], + "dataZoom": false, + "stack": false, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": false, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": false, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": false, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } + }, + "showLegend": false, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "top", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": true, + "showTotal": false, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "MMMM dd, yyyy", + "lastUpdateAgo": false, + "custom": true + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": false, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + } + }, + "title": "Time series chart", + "dropShadow": false, + "enableFullscreen": false, + "titleStyle": null, + "configMode": "advanced", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-time-series-chart-panel {\n padding: 0;\n}\n\n.tb-time-series-chart-panel div.tb-widget-title {\n padding-top: 5px;\n padding-left: 5px;\n}", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": true, + "iconSize": "24px", + "icon": "query_builder", + "iconPosition": "left", + "font": { + "size": 14, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.54)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" + }, + "row": 0, + "col": 0, + "id": "bf7f6efa-7614-3bc0-c4d0-6665d67510a8" + }, + "8e71a398-caf5-540d-cec5-6e5dc264343e": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": "", + "entityAliasId": "d9229b29-3f46-de8d-7fe8-eb0c43c75079", + "dataKeys": [ + { + "name": "activeDevicesCountHourly", + "type": "timeseries", + "label": "{i18n:device.devices}", + "color": "#305680", + "settings": { + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": true, + "lineType": "solid", + "lineWidth": 3, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 14, + "fillAreaSettings": { + "type": "opacity", + "opacity": 0.04, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.39181957822569946, + "decimals": 0, + "aggregationType": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + }, + "latestDataKeys": [] + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": true, + "hideAggInterval": true, + "hideTimezone": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000, + "fixedTimewindow": { + "startTimeMs": 1709807941444, + "endTimeMs": 1709894341444 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "NONE", + "limit": 25000 + }, + "timezone": null + }, + "showTitle": false, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "thresholds": [], + "dataZoom": false, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "", + "showTicks": false, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": false, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": false, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "min": 0, + "max": null + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": false, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": false, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": false, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } + }, + "showLegend": false, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "top", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": true, + "showTotal": false, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "MMMM dd, yyyy", + "lastUpdateAgo": false, + "custom": true + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": false, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + }, + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": null, + "showTicks": false, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": false, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": false, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": 0 + } + }, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 200, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + } + }, + "title": "Time series chart", + "dropShadow": false, + "enableFullscreen": false, + "titleStyle": null, + "configMode": "basic", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-time-series-chart-panel {\n padding: 0;\n}\n\n.tb-time-series-chart-panel div.tb-widget-title {\n padding-top: 5px;\n padding-left: 5px;\n}", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": true, + "iconSize": "24px", + "icon": "query_builder", + "iconPosition": "left", + "font": { + "size": 14, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.54)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" + }, + "row": 0, + "col": 0, + "id": "8e71a398-caf5-540d-cec5-6e5dc264343e" } }, "states": { @@ -868,7 +1259,7 @@ "layouts": { "main": { "widgets": { - "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e": { + "bf7f6efa-7614-3bc0-c4d0-6665d67510a8": { "sizeX": 24, "sizeY": 11, "row": 0, @@ -895,7 +1286,7 @@ "layouts": { "main": { "widgets": { - "d26e5cd7-75ef-d475-00c7-1a2d1114efe8": { + "8e71a398-caf5-540d-cec5-6e5dc264343e": { "sizeX": 24, "sizeY": 11, "row": 0, @@ -1049,6 +1440,5 @@ "dashboardCss": ".tb-widget-container > .tb-widget {\n border: 1px solid rgba(0, 0, 0, 0.05);\n box-shadow: 0px 5px 16px rgba(0, 0, 0, 0.04);\n border-radius: 12px;\n}\n\n.tb-widget-container > .tb-widget:not([style*=\"padding: 0\"]) {\n padding: 16px !important;\n}\n\n.tb-card-title {\n display: grid;\n}\n\n.tb-home-widget-title {\n font-style: normal;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n color: rgba(0, 0, 0, 0.54);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.tb-home-widget-link {\n position: relative;\n border-bottom: none;\n}\n\n.tb-home-widget-link:hover {\n border-bottom: none;\n}\n\n.tb-home-widget-link:focus {\n border-bottom: none;\n}\n\n.tb-home-widget-link::after {\n content: 'arrow_forward';\n display: inline-block;\n transform: rotate(315deg);\n font-family: 'Material Icons';\n font-weight: normal;\n font-style: normal;\n font-size: 18px;\n color: rgba(0, 0, 0, 0.12);\n vertical-align: bottom;\n margin-left: 6px; \n}\n\n.tb-home-widget-link:hover::after {\n color: inherit;\n}\n\n.tb-home-widget-info-icon {\n color: rgba(0, 0, 0, 0.12);\n font-size: 16px;\n width: 16px;\n height: 16px;\n line-height: 15px;\n vertical-align: middle;\n}\n\n.tb-widget-container > .tb-widget .tb-timewindow {\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n color: rgba(0, 0, 0, 0.54);\n padding: 0;\n}\n\n.tb-widget-container > .tb-widget .tb-legend-keys .tb-legend-label {\n cursor: pointer;\n user-select: none;\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.2px;\n color: rgba(0, 0, 0, 0.54);\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .tb-widget-container > .tb-widget {\n border-radius: 4px;\n }\n .tb-widget-container > .tb-widget:not([style*=\"padding: 0\"]) {\n padding: 2px !important;\n }\n .tb-hide-md {\n display: none;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1819px) {\n .tb-widget-container > .tb-widget:not([style*=\"padding: 0\"]) {\n padding: 8px !important;\n }\n .tb-hide-lg {\n display: none;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-hide-md-lg {\n display: none;\n }\n\n .tb-home-widget-title {\n font-size: 12px;\n line-height: 16px;\n }\n \n .tb-widget-container > .tb-widget .tb-widget-title {\n padding: 0;\n }\n\n .tb-widget-container > .tb-widget .tb-timewindow {\n font-size: 12px;\n line-height: 16px;\n min-height: 24px;\n padding: 0;\n }\n\n .tb-widget-container > .tb-widget .tb-timewindow .mat-mdc-icon-button.tb-mat-32 {\n width: 24px;\n height: 24px;\n line-height: 24px;\n }\n\n .tb-widget-container > .tb-widget .tb-timewindow .mat-mdc-icon-button.tb-mat-32 .mat-icon {\n width: 18px;\n height: 18px;\n font-size: 18px;\n }\n \n .tb-widget-container > .tb-widget tb-legend {\n padding-bottom: 0 !important;\n }\n \n .tb-widget-container > .tb-widget .tb-legend-keys .tb-legend-label {\n font-size: 11px;\n line-height: 16px;\n letter-spacing: 0.25px;\n }\n}\n\n@media screen and (max-width: 959px), screen and (min-width: 1820px) {\n .tb-hide-not-md-lg {\n display: none;\n }\n}\n" } }, - "externalId": null, "name": "Tenant Administrator Home Page" -} +} \ No newline at end of file From 9779b80d94398565031b6004a333f34b21a69ea6 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 11 Mar 2024 17:53:15 +0200 Subject: [PATCH 182/209] UI: API usage dashboard added y-axis settings --- ui-ngx/src/assets/dashboard/api_usage.json | 2229 ++++++++++++-------- 1 file changed, 1395 insertions(+), 834 deletions(-) diff --git a/ui-ngx/src/assets/dashboard/api_usage.json b/ui-ngx/src/assets/dashboard/api_usage.json index 7d9eefd18d..8b285b2b0b 100644 --- a/ui-ngx/src/assets/dashboard/api_usage.json +++ b/ui-ngx/src/assets/dashboard/api_usage.json @@ -1373,42 +1373,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -1501,6 +1507,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -1693,42 +1709,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -1821,6 +1843,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -2095,42 +2127,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -2223,6 +2261,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -2419,42 +2467,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -2547,6 +2601,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -2739,42 +2803,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -2867,6 +2937,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -3136,42 +3216,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -3264,6 +3350,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -3460,42 +3556,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -3588,6 +3690,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -3773,42 +3885,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -3901,6 +4019,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -4043,42 +4171,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -4171,6 +4305,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -4304,42 +4448,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -4432,6 +4582,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -4581,42 +4741,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -4709,6 +4875,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -4858,42 +5034,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -4986,6 +5168,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -5119,42 +5311,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -5247,6 +5445,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -5380,42 +5588,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -5508,6 +5722,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -5641,42 +5865,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -5769,6 +5999,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -5902,42 +6142,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -6030,6 +6276,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -6163,42 +6419,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -6291,6 +6553,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -6424,42 +6696,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -6552,6 +6830,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -6685,42 +6973,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -6813,6 +7107,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -6946,42 +7250,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -7074,6 +7384,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 1000, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -7152,26 +7472,63 @@ "label": "{i18n:api-usage.successful}", "color": "#4caf50", "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 3, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 14, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } } - ], - "comparisonSettings": { - "showValuesForComparison": true } }, "_hash": 0.15490750967648736, @@ -7188,59 +7545,145 @@ "label": "{i18n:api-usage.permanent-failures}", "color": "#ef5350", "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 3, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 14, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.4186621166514697 - }, - { - "name": "tmpFailed", - "type": "timeseries", - "label": "{i18n:api-usage.processing-failures}", - "color": "#ffc107", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.4186621166514697, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "tmpFailed", + "type": "timeseries", + "label": "{i18n:api-usage.processing-failures}", + "color": "#ffc107", + "settings": { + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 3, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 14, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } } - ], - "comparisonSettings": { - "showValuesForComparison": true } }, - "_hash": 0.49891007198715376 + "_hash": 0.49891007198715376, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null } ], "alarmFilterConfig": { @@ -7269,42 +7712,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -7397,6 +7846,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 300, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", @@ -7475,29 +7934,72 @@ "label": "{i18n:api-usage.permanent-timeouts}", "color": "#4caf50", "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 3, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 14, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } } - ], - "comparisonSettings": { - "showValuesForComparison": true } }, - "_hash": 0.565222981550328 + "_hash": 0.565222981550328, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null }, { "name": "tmpTimeout", @@ -7505,29 +8007,72 @@ "label": "{i18n:api-usage.processing-timeouts}", "color": "#9c27b0", "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 1, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 14, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } } - ], - "comparisonSettings": { - "showValuesForComparison": true } }, - "_hash": 0.2679547062508352 + "_hash": 0.2679547062508352, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null } ], "alarmFilterConfig": { @@ -7556,42 +8101,48 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "showTicks": true, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": true, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "ticksFormatter": "const rounder = Math.pow(10, 1);\nconst powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nlet formattedValue = value;\nlet key = '';\nfor (let power of powers) {\n let reduced = value / power.value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n formattedValue = reduced;\n key = power.key;\n break;\n }\n}\nreturn formattedValue + key;", - "min": null, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -7684,6 +8235,16 @@ "tooltipDateInterval": true, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 300, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, "background": { "type": "color", "color": "#fff", From b3d536a5670f757ad05801c144f49d9e8b3ed8f8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 12 Mar 2024 14:32:27 +0200 Subject: [PATCH 183/209] UI: Time series chart widgets performance and layout improvements. Update echarts version. --- ui-ngx/package.json | 2 +- ...charts+5.4.3.patch => echarts+5.5.0.patch} | 66 ++++----- ...e-series-chart-basic-config.component.html | 6 + ...ime-series-chart-basic-config.component.ts | 2 + .../bar-chart-with-labels-widget.component.ts | 7 +- .../lib/chart/doughnut-widget.component.ts | 2 +- .../widget/lib/chart/echarts-widget.models.ts | 1 + .../lib/chart/range-chart-widget.component.ts | 2 +- .../lib/chart/time-series-chart-bar.models.ts | 25 ++-- .../time-series-chart-widget.component.html | 3 +- .../time-series-chart-widget.component.scss | 20 +-- .../time-series-chart-widget.component.ts | 4 + .../chart/time-series-chart-widget.models.ts | 4 +- .../lib/chart/time-series-chart.models.ts | 24 +-- .../widget/lib/chart/time-series-chart.ts | 60 ++++++-- ...eries-chart-widget-settings.component.html | 17 ++- ...-series-chart-widget-settings.component.ts | 3 +- .../assets/dashboard/sys_admin_home_page.json | 12 +- .../dashboard/tenant_admin_home_page.json | 139 +++++++----------- .../assets/locale/locale.constant-en_US.json | 1 + ui-ngx/yarn.lock | 18 +-- 21 files changed, 211 insertions(+), 207 deletions(-) rename ui-ngx/patches/{echarts+5.4.3.patch => echarts+5.5.0.patch} (89%) diff --git a/ui-ngx/package.json b/ui-ngx/package.json index ab60ebd525..c39817749f 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -55,7 +55,7 @@ "core-js": "^3.29.1", "date-fns": "2.0.0-alpha.27", "dayjs": "1.11.4", - "echarts": "^5.4.3", + "echarts": "^5.5.0", "flot": "https://github.com/thingsboard/flot.git#0.9-work", "flot.curvedlines": "https://github.com/MichaelZinsmaier/CurvedLines.git#master", "font-awesome": "^4.7.0", diff --git a/ui-ngx/patches/echarts+5.4.3.patch b/ui-ngx/patches/echarts+5.5.0.patch similarity index 89% rename from ui-ngx/patches/echarts+5.4.3.patch rename to ui-ngx/patches/echarts+5.5.0.patch index bca89c616c..b25599afd8 100644 --- a/ui-ngx/patches/echarts+5.4.3.patch +++ b/ui-ngx/patches/echarts+5.5.0.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/echarts/lib/component/dataZoom/DataZoomModel.js b/node_modules/echarts/lib/component/dataZoom/DataZoomModel.js -index 112ebe3..9afc9b7 100644 +index d6c05f3..d09baed 100644 --- a/node_modules/echarts/lib/component/dataZoom/DataZoomModel.js +++ b/node_modules/echarts/lib/component/dataZoom/DataZoomModel.js -@@ -422,7 +422,10 @@ function (_super) { +@@ -362,7 +362,10 @@ var DataZoomModel = /** @class */function (_super) { return axisProxy.getDataValueWindow(); } } else { @@ -14,32 +14,30 @@ index 112ebe3..9afc9b7 100644 } }; /** -@@ -449,11 +452,11 @@ function (_super) { +@@ -381,10 +384,10 @@ var DataZoomModel = /** @class */function (_super) { + var axisInfo = this._targetAxisInfoMap.get(axisDim); for (var j = 0; j < axisInfo.indexList.length; j++) { var proxy = this.getAxisProxy(axisDim, axisInfo.indexList[j]); - - if (proxy.hostedBy(this)) { + if (proxy && proxy.hostedBy(this)) { return proxy; } - - if (!firstProxy) { + if (proxy && !firstProxy) { firstProxy = proxy; } } diff --git a/node_modules/echarts/lib/component/dataZoom/InsideZoomView.js b/node_modules/echarts/lib/component/dataZoom/InsideZoomView.js -index 7163279..c37f9c2 100644 +index 06469b2..ccfbfa6 100644 --- a/node_modules/echarts/lib/component/dataZoom/InsideZoomView.js +++ b/node_modules/echarts/lib/component/dataZoom/InsideZoomView.js -@@ -112,12 +112,15 @@ var getRangeHandlers = { +@@ -96,11 +96,14 @@ var getRangeHandlers = { range[0] = (range[0] - percentPoint) * scale + percentPoint; - range[1] = (range[1] - percentPoint) * scale + percentPoint; // Restrict range. - + range[1] = (range[1] - percentPoint) * scale + percentPoint; + // Restrict range. - var minMaxSpan = this.dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan(); - sliderMove(0, range, [0, 100], 0, minMaxSpan.minSpan, minMaxSpan.maxSpan); - this.range = range; -- - if (lastRange[0] !== range[0] || lastRange[1] !== range[1]) { - return range; + var proxy = this.dataZoomModel.findRepresentativeAxisProxy(); @@ -47,7 +45,6 @@ index 7163279..c37f9c2 100644 + var minMaxSpan = proxy.getMinMaxSpan(); + sliderMove(0, range, [0, 100], 0, minMaxSpan.minSpan, minMaxSpan.maxSpan); + this.range = range; -+ + if (lastRange[0] !== range[0] || lastRange[1] !== range[1]) { + return range; + } @@ -55,7 +52,7 @@ index 7163279..c37f9c2 100644 }, pan: makeMover(function (range, axisModel, coordSysInfo, coordSysMainType, controller, e) { diff --git a/node_modules/echarts/lib/component/dataZoom/SliderZoomView.js b/node_modules/echarts/lib/component/dataZoom/SliderZoomView.js -index 590ef51..aff8a0a 100644 +index 98912e0..2e809af 100644 --- a/node_modules/echarts/lib/component/dataZoom/SliderZoomView.js +++ b/node_modules/echarts/lib/component/dataZoom/SliderZoomView.js @@ -64,7 +64,7 @@ var DEFAULT_MOVE_HANDLE_SIZE = 7; @@ -67,7 +64,7 @@ index 590ef51..aff8a0a 100644 var REALTIME_ANIMATION_CONFIG = { easing: 'cubicOut', duration: 100, -@@ -406,34 +406,37 @@ function (_super) { +@@ -359,30 +359,33 @@ var SliderZoomView = /** @class */function (_super) { var result; var ecModel = this.ecModel; dataZoomModel.eachTargetAxis(function (axisDim, axisIndex) { @@ -76,20 +73,16 @@ index 590ef51..aff8a0a 100644 - if (result) { - return; - } -- - if (showDataShadow !== true && indexOf(SHOW_DATA_SHADOW_SERIES_TYPE, seriesModel.get('type')) < 0) { - return; - } -- - var thisAxis = ecModel.getComponent(getAxisMainType(axisDim), axisIndex).axis; - var otherDim = getOtherDim(axisDim); - var otherAxisInverse; - var coordSys = seriesModel.coordinateSystem; -- - if (otherDim != null && coordSys.getOtherAxis) { - otherAxisInverse = coordSys.getOtherAxis(thisAxis).inverse; - } -- - otherDim = seriesModel.getData().mapDimension(otherDim); - result = { - thisAxis: thisAxis, @@ -106,20 +99,16 @@ index 590ef51..aff8a0a 100644 + if (result) { + return; + } -+ + if (showDataShadow !== true && indexOf(SHOW_DATA_SHADOW_SERIES_TYPE, seriesModel.get('type')) < 0) { + return; + } -+ + var thisAxis = ecModel.getComponent(getAxisMainType(axisDim), axisIndex).axis; + var otherDim = getOtherDim(axisDim); + var otherAxisInverse; + var coordSys = seriesModel.coordinateSystem; -+ + if (otherDim != null && coordSys.getOtherAxis) { + otherAxisInverse = coordSys.getOtherAxis(thisAxis).inverse; + } -+ + otherDim = seriesModel.getData().mapDimension(otherDim); + result = { + thisAxis: thisAxis, @@ -133,10 +122,10 @@ index 590ef51..aff8a0a 100644 }, this); return result; }; -@@ -595,12 +598,17 @@ function (_super) { - +@@ -530,12 +533,17 @@ var SliderZoomView = /** @class */function (_super) { + var dataZoomModel = this.dataZoomModel; + var handleEnds = this._handleEnds; var viewExtend = this._getViewExtent(); - - var minMaxSpan = dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan(); - var percentExtent = [0, 100]; - sliderMove(delta, handleEnds, viewExtend, dataZoomModel.get('zoomLock') ? 'all' : handleIndex, minMaxSpan.minSpan != null ? linearMap(minMaxSpan.minSpan, percentExtent, viewExtend, true) : null, minMaxSpan.maxSpan != null ? linearMap(minMaxSpan.maxSpan, percentExtent, viewExtend, true) : null); @@ -155,13 +144,13 @@ index 590ef51..aff8a0a 100644 + return false; + } }; - SliderZoomView.prototype._updateView = function (nonRealtime) { + var displaybles = this._displayables; diff --git a/node_modules/echarts/lib/component/dataZoom/dataZoomProcessor.js b/node_modules/echarts/lib/component/dataZoom/dataZoomProcessor.js -index 3871784..9bab428 100644 +index ce98fed..361b138 100644 --- a/node_modules/echarts/lib/component/dataZoom/dataZoomProcessor.js +++ b/node_modules/echarts/lib/component/dataZoom/dataZoomProcessor.js -@@ -91,7 +91,10 @@ var dataZoomProcessor = { +@@ -90,7 +90,10 @@ var dataZoomProcessor = { // init stage and not after action dispatch handler, because // reset should be called after seriesData.restoreData. dataZoomModel.eachTargetAxis(function (axisDim, axisIndex) { @@ -170,12 +159,12 @@ index 3871784..9bab428 100644 + if (axisProxy) { + axisProxy.reset(dataZoomModel); + } - }); // Caution: data zoom filtering is order sensitive when using + }); + // Caution: data zoom filtering is order sensitive when using // percent range and no min/max/scale set on axis. - // For example, we have dataZoom definition: -@@ -108,7 +111,10 @@ var dataZoomProcessor = { +@@ -107,7 +110,10 @@ var dataZoomProcessor = { + // So we should filter x-axis after reset x-axis immediately, // and then reset y-axis and filter y-axis. - dataZoomModel.eachTargetAxis(function (axisDim, axisIndex) { - dataZoomModel.getAxisProxy(axisDim, axisIndex).filterData(dataZoomModel, api); + var axisProxy = dataZoomModel.getAxisProxy(axisDim, axisIndex); @@ -186,23 +175,22 @@ index 3871784..9bab428 100644 }); ecModel.eachComponent('dataZoom', function (dataZoomModel) { diff --git a/node_modules/echarts/lib/component/toolbox/feature/DataZoom.js b/node_modules/echarts/lib/component/toolbox/feature/DataZoom.js -index c5ee405..957ffd2 100644 +index cf8d6bc..9b30ec1 100644 --- a/node_modules/echarts/lib/component/toolbox/feature/DataZoom.js +++ b/node_modules/echarts/lib/component/toolbox/feature/DataZoom.js -@@ -129,10 +129,13 @@ function (_super) { +@@ -109,9 +109,12 @@ var DataZoomFeature = /** @class */function (_super) { var axisModel = axis.model; - var dataZoomModel = findDataZoom(dimName, axisModel, ecModel); // Restrict range. - + var dataZoomModel = findDataZoom(dimName, axisModel, ecModel); + // Restrict range. - var minMaxSpan = dataZoomModel.findRepresentativeAxisProxy(axisModel).getMinMaxSpan(); +- if (minMaxSpan.minValueSpan != null || minMaxSpan.maxValueSpan != null) { +- minMax = sliderMove(0, minMax.slice(), axis.scale.getExtent(), 0, minMaxSpan.minValueSpan, minMaxSpan.maxValueSpan); + var proxy = dataZoomModel.findRepresentativeAxisProxy(axisModel); + if (proxy) { + var minMaxSpan = proxy.getMinMaxSpan(); - -- if (minMaxSpan.minValueSpan != null || minMaxSpan.maxValueSpan != null) { -- minMax = sliderMove(0, minMax.slice(), axis.scale.getExtent(), 0, minMaxSpan.minValueSpan, minMaxSpan.maxValueSpan); + if (minMaxSpan.minValueSpan != null || minMaxSpan.maxValueSpan != null) { + minMax = sliderMove(0, minMax.slice(), axis.scale.getExtent(), 0, minMaxSpan.minValueSpan, minMaxSpan.maxValueSpan); + } } - dataZoomModel && (snapshot[dataZoomModel.id] = { + dataZoomId: dataZoomModel.id, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html index adaeb78acb..b254b4df68 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html @@ -245,6 +245,12 @@
+
+
{{ 'widget-config.card-padding' | translate }}
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts index ba4b4c6371..31dca75c7e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts @@ -174,6 +174,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon cardButtons: [this.getCardButtons(configData.config), []], borderRadius: [configData.config.borderRadius, []], + padding: [settings.padding, []], actions: [configData.config.actions || {}, []] }); @@ -229,6 +230,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon this.setCardButtons(config.cardButtons, this.widgetConfig.config); this.widgetConfig.config.borderRadius = config.borderRadius; + this.widgetConfig.config.settings.padding = config.padding; this.widgetConfig.config.actions = config.actions; return this.widgetConfig; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts index c523bf7927..4784a65c88 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts @@ -37,7 +37,6 @@ import { } from '@shared/models/widget-settings.models'; import { ResizeObserver } from '@juggle/resize-observer'; import { formatValue } from '@core/utils'; -import { DataKey } from '@shared/models/widget.models'; import { Observable } from 'rxjs'; import { ImagePipe } from '@shared/pipe/image.pipe'; import { DomSanitizer } from '@angular/platform-browser'; @@ -53,9 +52,9 @@ import { CallbackDataParams, CustomSeriesRenderItem, LabelLayoutOptionCallback } import { ECharts, echartsModule, - EChartsOption, EChartsSeriesItem, + EChartsOption, + EChartsSeriesItem, echartsTooltipFormatter, - NamedDataSet, toNamedData } from '@home/components/widget/lib/chart/echarts-widget.models'; import { IntervalMath } from '@shared/models/time/time.models'; @@ -381,7 +380,7 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft tooltip: { trigger: 'axis', confine: true, - appendToBody: true, + appendTo: 'body', axisPointer: { type: 'shadow' }, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/doughnut-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/doughnut-widget.component.ts index 723b581fc5..d215f37a32 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/doughnut-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/doughnut-widget.component.ts @@ -387,7 +387,7 @@ export class DoughnutWidgetComponent implements OnInit, OnDestroy, AfterViewInit tooltip: { trigger: this.settings.showTooltip ? 'item' : 'none', confine: false, - appendToBody: true + appendTo: 'body', }, series: [ { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts index 06e8e98fd0..c446fcbda1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts @@ -153,6 +153,7 @@ export type EChartsSeriesItem = { id: string; dataKey: DataKey; data: NamedDataSet; + dataSet?: DataSet; enabled: boolean; units?: string; decimals?: number; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts index 0a8c15b247..9909be6532 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts @@ -318,7 +318,7 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn tooltip: { trigger: 'axis', confine: true, - appendToBody: true, + appendTo: 'body', axisPointer: { type: 'shadow' }, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts index bf7f6cf764..c00a078b2f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts @@ -33,14 +33,18 @@ export interface BarVisualSettings { borderRadius: number; } +export interface BarRenderSharedContext { + timeInterval: Interval; + noAggregationBarWidthStrategy: TimeSeriesChartNoAggregationBarWidthStrategy; + noAggregationWidthRelative: boolean; + noAggregationWidth: number; +} + export interface BarRenderContext { + shared: BarRenderSharedContext; barsCount?: number; barIndex?: number; - noAggregation: boolean; - noAggregationBarWidthStrategy: TimeSeriesChartNoAggregationBarWidthStrategy; - noAggregationWidthRelative?: boolean; - noAggregationWidth?: number; - timeInterval?: Interval; + noAggregation?: boolean; visualSettings?: BarVisualSettings; labelOption?: SeriesLabelOption; barStackIndex?: number; @@ -56,20 +60,20 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C const ts = start ? start : time; const separateBar = renderCtx.noAggregation && - renderCtx.noAggregationBarWidthStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate; + renderCtx.shared.noAggregationBarWidthStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate; if (renderCtx.noAggregation) { - if (renderCtx.noAggregationWidthRelative) { + if (renderCtx.shared.noAggregationWidthRelative) { const scaleWidth = api.getWidth() / api.size([1,0])[0]; - interval = scaleWidth * (renderCtx.noAggregationWidth / 100); + interval = scaleWidth * (renderCtx.shared.noAggregationWidth / 100); } else { - interval = renderCtx.noAggregationWidth; + interval = renderCtx.shared.noAggregationWidth; } start = time - interval / 2; end = time + interval / 2; } if (!start || !end || !interval) { - interval = IntervalMath.numberValue(renderCtx.timeInterval); + interval = IntervalMath.numberValue(renderCtx.shared.timeInterval); start = time - interval / 2; } @@ -147,7 +151,6 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C } else { borderRadius = [renderCtx.visualSettings.borderRadius, renderCtx.visualSettings.borderRadius, 0, 0]; } - return rectShape && { type: 'rect', id: time + '', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html index 1b96aac669..4430579899 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html @@ -15,7 +15,8 @@ limitations under the License. --> -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss index 7da3d8e4b5..66428d9d91 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss @@ -24,7 +24,9 @@ $maxLegendHeight: 35%; display: flex; flex-direction: column; gap: 8px; - padding: 20px 24px 24px 24px; + &.overlay { + padding: 20px 24px 24px 24px; + } > div:not(.tb-time-series-chart-overlay) { z-index: 1; } @@ -101,22 +103,6 @@ $maxLegendHeight: 35%; width: 100%; table-layout: auto; } - - thead th, tbody th { - position: sticky; - z-index: 1; - backdrop-filter: blur(5000px); - } - thead th { - top: 0; - &:first-child { - left: 0; - z-index: 2; - } - } - tbody th { - left: 0; - } th, td { &:not(:last-child) { padding-right: 8px; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts index 560309b08b..fbb23c6c84 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts @@ -70,6 +70,8 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV backgroundStyle$: Observable; overlayStyle: ComponentStyle = {}; + overlayEnabled: boolean; + padding: string; legendLabelStyle: ComponentStyle; disabledLegendLabelStyle: ComponentStyle; @@ -90,6 +92,8 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.overlayStyle = overlayStyle(this.settings.background.overlay); + this.overlayEnabled = this.settings.background.overlay.enabled; + this.padding = this.overlayEnabled ? undefined : this.settings.padding; this.showLegend = this.settings.showLegend; if (this.showLegend) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.models.ts index ccfca7c4d0..6edf87251c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.models.ts @@ -25,6 +25,7 @@ export interface TimeSeriesChartWidgetSettings extends TimeSeriesChartSettings { legendLabelColor: string; legendConfig: LegendConfig; background: BackgroundSettings; + padding: string; } export const timeSeriesChartWidgetDefaultSettings: TimeSeriesChartWidgetSettings = @@ -48,5 +49,6 @@ export const timeSeriesChartWidgetDefaultSettings: TimeSeriesChartWidgetSettings color: 'rgba(255,255,255,0.72)', blur: 3 } - } + }, + padding: '12px' } as TimeSeriesChartWidgetSettings); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index 02cbdb2ff8..b5d5968565 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -37,11 +37,11 @@ import { import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; import tinycolor from 'tinycolor2'; import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D'; -import { Interval } from '@shared/models/time/time.models'; import { ValueAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes'; import { SeriesLabelOption } from 'echarts/types/src/util/types'; import { BarRenderContext, + BarRenderSharedContext, BarVisualSettings, renderTimeSeriesBar } from '@home/components/widget/lib/chart/time-series-chart-bar.models'; @@ -915,13 +915,12 @@ export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartAxisSetting export const generateChartData = (dataItems: TimeSeriesChartDataItem[], thresholdItems: TimeSeriesChartThresholdItem[], - timeInterval: Interval, stack: boolean, noAggregation: boolean, - noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings, + barRenderSharedContext: BarRenderSharedContext, darkMode: boolean): Array => { - let series = generateChartSeries(dataItems, timeInterval, - stack, noAggregation, noAggregationBarWidthSettings, darkMode); + let series = generateChartSeries(dataItems, + stack, noAggregation, barRenderSharedContext, darkMode); if (thresholdItems.length) { const thresholds = generateChartThresholds(thresholdItems, darkMode); series = series.concat(thresholds); @@ -1030,10 +1029,9 @@ const createThresholdData = (val: string | number, item: TimeSeriesChartThreshol ]; const generateChartSeries = (dataItems: TimeSeriesChartDataItem[], - timeInterval: Interval, stack: boolean, noAggregation: boolean, - noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings, + barRenderSharedContext: BarRenderSharedContext, darkMode: boolean): Array => { const series: Array = []; const enabledDataItems = dataItems.filter(d => d.enabled); @@ -1053,21 +1051,11 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[], if (item.dataKey.settings.type === TimeSeriesChartSeriesType.bar) { if (!item.barRenderContext) { item.barRenderContext = {noAggregation, - noAggregationBarWidthStrategy: noAggregationBarWidthSettings.strategy}; - const targetWidth = noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group ? - noAggregationBarWidthSettings.groupWidth : noAggregationBarWidthSettings.barWidth; - if (targetWidth.relative) { - item.barRenderContext.noAggregationWidthRelative = true; - item.barRenderContext.noAggregationWidth = targetWidth.relativeWidth; - } else { - item.barRenderContext.noAggregationWidthRelative = false; - item.barRenderContext.noAggregationWidth = targetWidth.absoluteWidth; - } + shared: barRenderSharedContext}; } item.barRenderContext.noAggregation = noAggregation; item.barRenderContext.barsCount = barsCount; item.barRenderContext.barIndex = stack ? barGroups.indexOf(item.yAxisIndex) : barDataItems.indexOf(item); - item.barRenderContext.timeInterval = timeInterval; if (stack) { const stackItems = enabledDataItems.filter(d => d.yAxisIndex === item.yAxisIndex); item.barRenderContext.currentStackItems = stackItems; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index abad3f79e8..57e52f5f5d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -19,21 +19,26 @@ import { AxisPosition, calculateThresholdsOffset, createTimeSeriesXAxisOption, - createTimeSeriesYAxis, defaultTimeSeriesChartYAxisSettings, + createTimeSeriesYAxis, + defaultTimeSeriesChartYAxisSettings, generateChartData, parseThresholdData, SeriesLabelPosition, TimeSeriesChartDataItem, timeSeriesChartDefaultSettings, timeSeriesChartKeyDefaultSettings, - TimeSeriesChartKeySettings, + TimeSeriesChartKeySettings, TimeSeriesChartNoAggregationBarWidthStrategy, TimeSeriesChartSeriesType, TimeSeriesChartSettings, - TimeSeriesChartShape, TimeSeriesChartThreshold, timeSeriesChartThresholdDefaultSettings, + TimeSeriesChartShape, + TimeSeriesChartThreshold, + timeSeriesChartThresholdDefaultSettings, TimeSeriesChartThresholdItem, TimeSeriesChartThresholdType, TimeSeriesChartType, - TimeSeriesChartYAxis, TimeSeriesChartYAxisId, TimeSeriesChartYAxisSettings, + TimeSeriesChartYAxis, + TimeSeriesChartYAxisId, + TimeSeriesChartYAxisSettings, updateDarkMode } from '@home/components/widget/lib/chart/time-series-chart.models'; import { ResizeObserver } from '@juggle/resize-observer'; @@ -52,7 +57,7 @@ import { toNamedData } from '@home/components/widget/lib/chart/echarts-widget.models'; import { DateFormatProcessor } from '@shared/models/widget-settings.models'; -import { isDefinedAndNotNull, mergeDeep } from '@core/utils'; +import { isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils'; import { DataKey, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; import * as echarts from 'echarts/core'; import { CallbackDataParams } from 'echarts/types/dist/shared'; @@ -64,6 +69,7 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { WidgetSubscriptionOptions } from '@core/api/widget-api.models'; import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; import { DeepPartial } from '@shared/models/common'; +import { BarRenderSharedContext } from '@home/components/widget/lib/chart/time-series-chart-bar.models'; export class TbTimeSeriesChart { @@ -119,6 +125,8 @@ export class TbTimeSeriesChart { private highlightedDataKey: DataKey; + private barRenderSharedContext: BarRenderSharedContext; + yMin$ = this.yMinSubject.asObservable(); yMax$ = this.yMaxSubject.asObservable(); @@ -156,7 +164,10 @@ export class TbTimeSeriesChart { public update(): void { for (const item of this.dataItems) { const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === item.dataKey) : null; - item.data = datasourceData?.data ? toNamedData(datasourceData.data) : []; + if (!isEqual(item.dataSet, datasourceData?.data)) { + item.dataSet = datasourceData?.data; + item.data = datasourceData?.data ? toNamedData(datasourceData.data) : []; + } } this.onResize(); if (this.timeSeriesChart) { @@ -168,6 +179,7 @@ export class TbTimeSeriesChart { } else { this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'shadow'; } + this.barRenderSharedContext.timeInterval = this.ctx.timeWindow.interval; this.updateSeriesData(true); if (this.highlightedDataKey) { this.keyEnter(this.highlightedDataKey); @@ -277,6 +289,15 @@ export class TbTimeSeriesChart { } private setupData(): void { + const noAggregationBarWidthSettings = this.settings.noAggregationBarWidthSettings; + const targetBarWidth = noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group ? + noAggregationBarWidthSettings.groupWidth : noAggregationBarWidthSettings.barWidth; + this.barRenderSharedContext = { + timeInterval: this.ctx.timeWindow?.interval, + noAggregationBarWidthStrategy: noAggregationBarWidthSettings.strategy, + noAggregationWidthRelative: targetBarWidth.relative, + noAggregationWidth: targetBarWidth.relative ? targetBarWidth.relativeWidth : targetBarWidth.absoluteWidth + }; if (this.ctx.datasources.length) { for (const datasource of this.ctx.datasources) { const dataKeys = datasource.dataKeys; @@ -458,7 +479,7 @@ export class TbTimeSeriesChart { tooltip: [{ trigger: this.settings.tooltipTrigger === EChartsTooltipTrigger.axis ? 'axis' : 'item', confine: true, - appendToBody: true, + appendTo: 'body', axisPointer: { type: this.noAggregation ? 'line' : 'shadow' }, @@ -533,10 +554,9 @@ export class TbTimeSeriesChart { private updateSeries(): Array { return generateChartData(this.dataItems, this.thresholdItems, - this.ctx.timeWindow.interval, this.settings.stack, this.noAggregation, - this.settings.noAggregationBarWidthSettings, this.darkMode); + this.barRenderSharedContext, this.darkMode); } private updateAxes() { @@ -721,10 +741,32 @@ export class TbTimeSeriesChart { const width = this.timeSeriesChart.getWidth(); const height = this.timeSeriesChart.getHeight(); if (width !== shapeWidth || height !== shapeHeight) { + let barItems: TimeSeriesChartDataItem[]; + if (this.animationEnabled()) { + barItems = + this.dataItems.filter(d => d.enabled && d.data.length && + d.dataKey.settings.type === TimeSeriesChartSeriesType.bar); + this.updateBarsAnimation(barItems, false); + } this.timeSeriesChart.resize(); + if (this.animationEnabled()) { + this.updateBarsAnimation(barItems, true); + } } } } } + private animationEnabled(): boolean { + return this.settings.animation.animation; + } + + private updateBarsAnimation(barItems: TimeSeriesChartDataItem[], animation: boolean) { + if (barItems.length) { + barItems.forEach(item => { + item.option.animation = animation; + }); + this.timeSeriesChart.setOption(this.timeSeriesChartOptions); + } + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html index 42249a67cd..cd6f1255f8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html @@ -160,10 +160,19 @@ -
-
{{ 'widgets.background.background' | translate }}
- - +
+
widget-config.card-appearance
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
{{ 'widget-config.card-padding' | translate }}
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts index e270b97137..6edab7ecf2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts @@ -140,7 +140,8 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon animation: [settings.animation, []], - background: [settings.background, []] + background: [settings.background, []], + padding: [settings.padding, []] }); } diff --git a/ui-ngx/src/assets/dashboard/sys_admin_home_page.json b/ui-ngx/src/assets/dashboard/sys_admin_home_page.json index b308bc98d6..29c692ad87 100644 --- a/ui-ngx/src/assets/dashboard/sys_admin_home_page.json +++ b/ui-ngx/src/assets/dashboard/sys_admin_home_page.json @@ -2110,13 +2110,14 @@ "color": "rgba(255,255,255,0.72)", "blur": 3 } - } + }, + "padding": "0" }, "title": "Time series chart", "dropShadow": false, "enableFullscreen": false, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": {}, "showTitleIcon": false, "titleIcon": "thermostat", @@ -2134,7 +2135,7 @@ "titleColor": "rgba(0, 0, 0, 0.87)", "titleTooltip": "", "widgetStyle": {}, - "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-time-series-chart-panel {\n padding: 0;\n}\n\n.tb-time-series-chart-panel div.tb-widget-title {\n padding-top: 5px;\n padding-left: 5px;\n}", + "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-time-series-chart-panel div.tb-widget-title {\n padding-top: 5px;\n padding-left: 5px;\n}", "pageSize": 1024, "units": "", "decimals": null, @@ -2580,7 +2581,8 @@ "color": "rgba(255,255,255,0.72)", "blur": 3 } - } + }, + "padding": "0" }, "title": "Time series chart", "dropShadow": false, @@ -2604,7 +2606,7 @@ "titleColor": "rgba(0, 0, 0, 0.87)", "titleTooltip": "", "widgetStyle": {}, - "widgetCss": ".tb-time-series-chart-panel {\n padding: 0;\n}\n\n.tb-time-series-chart-panel div.tb-widget-title {\n padding-top: 5px;\n padding-left: 5px;\n}", + "widgetCss": ".tb-time-series-chart-panel div.tb-widget-title {\n padding-top: 5px;\n padding-left: 5px;\n}", "pageSize": 1024, "units": "%", "decimals": null, diff --git a/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json index b9a996b8a5..063cb0def2 100644 --- a/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json +++ b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json @@ -758,13 +758,14 @@ "color": "rgba(255,255,255,0.72)", "blur": 3 } - } + }, + "padding": "0" }, "title": "Time series chart", "dropShadow": false, "enableFullscreen": false, "titleStyle": null, - "configMode": "advanced", + "configMode": "basic", "actions": {}, "showTitleIcon": false, "titleIcon": "thermostat", @@ -782,7 +783,7 @@ "titleColor": "rgba(0, 0, 0, 0.87)", "titleTooltip": "", "widgetStyle": {}, - "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-time-series-chart-panel {\n padding: 0;\n}\n\n.tb-time-series-chart-panel div.tb-widget-title {\n padding-top: 5px;\n padding-left: 5px;\n}", + "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-time-series-chart-panel div.tb-widget-title {\n padding-top: 5px;\n padding-left: 5px;\n}", "pageSize": 1024, "units": "", "decimals": null, @@ -932,42 +933,47 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "0px", "settings": { + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": null, + "showTicks": false, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": false, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": false, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": 0 + } + }, "thresholds": [], "dataZoom": false, "stack": false, - "yAxis": { - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "ticksFormatter": "", - "showTicks": false, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": false, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": false, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "min": 0, - "max": null - }, "xAxis": { "show": true, "label": "", @@ -1060,53 +1066,6 @@ "tooltipDateInterval": false, "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", "tooltipBackgroundBlur": 4, - "background": { - "type": "color", - "color": "#fff", - "overlay": { - "enabled": false, - "color": "rgba(255,255,255,0.72)", - "blur": 3 - } - }, - "yAxes": { - "default": { - "units": null, - "decimals": 0, - "show": true, - "label": "", - "labelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "600", - "lineHeight": "1" - }, - "labelColor": "rgba(0, 0, 0, 0.54)", - "position": "left", - "showTickLabels": true, - "tickLabelFont": { - "family": "Roboto", - "size": 12, - "sizeUnit": "px", - "style": "normal", - "weight": "400", - "lineHeight": "1" - }, - "tickLabelColor": "rgba(0, 0, 0, 0.54)", - "ticksFormatter": null, - "showTicks": false, - "ticksColor": "rgba(0, 0, 0, 0.54)", - "showLine": false, - "lineColor": "rgba(0, 0, 0, 0.54)", - "showSplitLines": false, - "splitLinesColor": "rgba(0, 0, 0, 0.12)", - "id": "default", - "order": 0, - "min": 0 - } - }, "animation": { "animation": true, "animationThreshold": 2000, @@ -1116,7 +1075,17 @@ "animationDurationUpdate": 300, "animationEasingUpdate": "cubicOut", "animationDelayUpdate": 0 - } + }, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + }, + "padding": "0" }, "title": "Time series chart", "dropShadow": false, @@ -1140,7 +1109,7 @@ "titleColor": "rgba(0, 0, 0, 0.87)", "titleTooltip": "", "widgetStyle": {}, - "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-time-series-chart-panel {\n padding: 0;\n}\n\n.tb-time-series-chart-panel div.tb-widget-title {\n padding-top: 5px;\n padding-left: 5px;\n}", + "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-time-series-chart-panel div.tb-widget-title {\n padding-top: 5px;\n padding-left: 5px;\n}", "pageSize": 1024, "units": "", "decimals": null, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index decb97384e..f9fc2fa2ee 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5213,6 +5213,7 @@ "card-buttons": "Card buttons", "show-card-buttons": "Show card buttons", "card-border-radius": "Card border radius", + "card-padding": "Card padding", "card-appearance": "Card appearance", "color": "Color", "tooltip": "Tooltip", diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index d78a9a52d6..4317883a2b 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -5367,13 +5367,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -echarts@^5.4.3: - version "5.4.3" - resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.4.3.tgz#f5522ef24419164903eedcfd2b506c6fc91fb20c" - integrity sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA== +echarts@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.5.0.tgz#c13945a7f3acdd67c134d8a9ac67e917830113ac" + integrity sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw== dependencies: tslib "2.3.0" - zrender "5.4.4" + zrender "5.5.0" editorconfig@^0.15.3: version "0.15.3" @@ -11101,9 +11101,9 @@ zone.js@~0.13.0: dependencies: tslib "^2.3.0" -zrender@5.4.4: - version "5.4.4" - resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.4.4.tgz#8854f1d95ecc82cf8912f5a11f86657cb8c9e261" - integrity sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw== +zrender@5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.5.0.tgz#54d0d6c4eda81a96d9f60a9cd74dc48ea026bc1e" + integrity sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w== dependencies: tslib "2.3.0" From 018d3af3a741c92d6ebe26ee91ec125e62981981 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 12 Mar 2024 15:03:58 +0200 Subject: [PATCH 184/209] UI: API usage dashboard change line chart style --- ui-ngx/src/assets/dashboard/api_usage.json | 28 ++++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/ui-ngx/src/assets/dashboard/api_usage.json b/ui-ngx/src/assets/dashboard/api_usage.json index 8b285b2b0b..c0f3645156 100644 --- a/ui-ngx/src/assets/dashboard/api_usage.json +++ b/ui-ngx/src/assets/dashboard/api_usage.json @@ -7482,7 +7482,7 @@ "stepType": "start", "smooth": false, "lineType": "solid", - "lineWidth": 3, + "lineWidth": 2.5, "showPoints": false, "showPointLabel": false, "pointLabelPosition": "top", @@ -7496,7 +7496,7 @@ }, "pointLabelColor": "rgba(0, 0, 0, 0.76)", "pointShape": "circle", - "pointSize": 14, + "pointSize": 12, "fillAreaSettings": { "type": "none", "opacity": 0.4, @@ -7555,7 +7555,7 @@ "stepType": "start", "smooth": false, "lineType": "solid", - "lineWidth": 3, + "lineWidth": 2.5, "showPoints": false, "showPointLabel": false, "pointLabelPosition": "top", @@ -7569,7 +7569,7 @@ }, "pointLabelColor": "rgba(0, 0, 0, 0.76)", "pointShape": "circle", - "pointSize": 14, + "pointSize": 12, "fillAreaSettings": { "type": "none", "opacity": 0.4, @@ -7628,7 +7628,7 @@ "stepType": "start", "smooth": false, "lineType": "solid", - "lineWidth": 3, + "lineWidth": 2.5, "showPoints": false, "showPointLabel": false, "pointLabelPosition": "top", @@ -7642,7 +7642,7 @@ }, "pointLabelColor": "rgba(0, 0, 0, 0.76)", "pointShape": "circle", - "pointSize": 14, + "pointSize": 12, "fillAreaSettings": { "type": "none", "opacity": 0.4, @@ -7864,7 +7864,8 @@ "color": "rgba(255,255,255,0.72)", "blur": 3 } - } + }, + "padding": "12px" }, "title": "{i18n:api-usage.queue-stats}", "dropShadow": true, @@ -7944,7 +7945,7 @@ "stepType": "start", "smooth": false, "lineType": "solid", - "lineWidth": 3, + "lineWidth": 2.5, "showPoints": false, "showPointLabel": false, "pointLabelPosition": "top", @@ -7958,7 +7959,7 @@ }, "pointLabelColor": "rgba(0, 0, 0, 0.76)", "pointShape": "circle", - "pointSize": 14, + "pointSize": 12, "fillAreaSettings": { "type": "none", "opacity": 0.4, @@ -8017,7 +8018,7 @@ "stepType": "start", "smooth": false, "lineType": "solid", - "lineWidth": 1, + "lineWidth": 2.5, "showPoints": false, "showPointLabel": false, "pointLabelPosition": "top", @@ -8031,7 +8032,7 @@ }, "pointLabelColor": "rgba(0, 0, 0, 0.76)", "pointShape": "circle", - "pointSize": 14, + "pointSize": 12, "fillAreaSettings": { "type": "none", "opacity": 0.4, @@ -8253,7 +8254,8 @@ "color": "rgba(255,255,255,0.72)", "blur": 3 } - } + }, + "padding": "12px" }, "title": "{i18n:api-usage.processing-failures-and-timeouts}", "dropShadow": true, @@ -8716,7 +8718,7 @@ "dashboardLogoUrl": null, "hideToolbar": false, "showUpdateDashboardImage": false, - "dashboardCss": ".tb-time-series-chart-panel {\n padding: 13px;\n}\n\n.tb-time-series-chart-content {\n gap: 0; \n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n\n.card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n.card .unit {\n color: #666666;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} " + "dashboardCss": ".card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n\n.card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 12px 12px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-mdc-button-base {\n text-transform: uppercase;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.card .mdc-button__label {\n pointer-events: none;\n}\n\n.action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n.card .unit {\n color: #666666;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-mdc-button-base {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-mdc-button-base {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-mdc-button-base {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} " } }, "name": "Api Usage" From 3d656192dde7c4db79f4dfa47576d6a317078c35 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 12 Mar 2024 17:18:42 +0200 Subject: [PATCH 185/209] UI: Update demo dashboards --- .../dashboards/rule_engine_statistics.json | 1086 ++++++++++++----- .../json/demo/dashboards/thermostats.json | 1057 ++++++++++++---- .../time-series-chart-widget.component.scss | 7 +- 3 files changed, 1618 insertions(+), 532 deletions(-) diff --git a/application/src/main/data/json/demo/dashboards/rule_engine_statistics.json b/application/src/main/data/json/demo/dashboards/rule_engine_statistics.json index c84faa359e..83a96c6fe6 100644 --- a/application/src/main/data/json/demo/dashboards/rule_engine_statistics.json +++ b/application/src/main/data/json/demo/dashboards/rule_engine_statistics.json @@ -5,104 +5,50 @@ "mobileOrder": null, "configuration": { "widgets": { - "81987f19-3eac-e4ce-b790-d96e9b54d9a0": { + "5eb79712-5c24-3060-7e4f-6af36b8f842d": { "type": "timeseries", - "sizeX": 12, - "sizeY": 7, + "sizeX": 24, + "sizeY": 5, "config": { "datasources": [ { "type": "entity", "dataKeys": [ { - "name": "successfulMsgs", + "name": "ruleEngineException", "type": "timeseries", - "label": "${entityName} Successful", - "color": "#4caf50", + "label": "Rule Chain", + "color": "#2196f3", "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } + "useCellStyleFunction": false, + "useCellContentFunction": true, + "cellContentFunction": "return JSON.parse(value).ruleChainName;" }, - "_hash": 0.15490750967648736 + "_hash": 0.9954481282345906 }, { - "name": "failedMsgs", + "name": "ruleEngineException", "type": "timeseries", - "label": "${entityName} Permanent Failures", - "color": "#ef5350", + "label": "Rule Node", + "color": "#4caf50", "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } + "useCellStyleFunction": false, + "useCellContentFunction": true, + "cellContentFunction": "return JSON.parse(value).ruleNodeName;" }, - "_hash": 0.4186621166514697 + "_hash": 0.18580357036589978 }, { - "name": "tmpFailed", + "name": "ruleEngineException", "type": "timeseries", - "label": "${entityName} Processing Failures", - "color": "#ffc107", + "label": "Latest Error", + "color": "#f44336", "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } + "useCellStyleFunction": false, + "useCellContentFunction": true, + "cellContentFunction": "return JSON.parse(value).message;" }, - "_hash": 0.49891007198715376 + "_hash": 0.7255162989552142 } ], "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018" @@ -111,312 +57,905 @@ "timewindow": { "realtime": { "interval": 1000, - "timewindowMs": 300000 + "timewindowMs": 86400000 }, "aggregation": { "type": "NONE", - "limit": 8640 - }, - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false + "limit": 200 + } }, "showTitle": true, - "backgroundColor": "#fff", + "backgroundColor": "rgb(255, 255, 255)", "color": "rgba(0, 0, 0, 0.87)", "padding": "8px", "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "showMin": true, - "showMax": true, - "showAvg": false, - "showTotal": true - } + "showTimestamp": true, + "displayPagination": true, + "defaultPageSize": 10 }, - "title": "Queue Stats", + "title": "Exceptions", "dropShadow": true, "enableFullscreen": true, "titleStyle": { "fontSize": "16px", "fontWeight": 400 }, - "mobileHeight": null, + "useDashboardTimewindow": false, + "showLegend": false, + "widgetStyle": {}, + "actions": {}, "showTitleIcon": false, "titleIcon": null, "iconColor": "rgba(0, 0, 0, 0.87)", "iconSize": "24px", "titleTooltip": "", - "widgetStyle": {}, - "useDashboardTimewindow": false, - "displayTimewindow": true, - "actions": {} + "displayTimewindow": true }, - "id": "81987f19-3eac-e4ce-b790-d96e9b54d9a0", - "typeFullFqn": "system.charts.basic_timeseries" + "id": "5eb79712-5c24-3060-7e4f-6af36b8f842d", + "typeFullFqn": "system.cards.timeseries_table" }, - "5eb79712-5c24-3060-7e4f-6af36b8f842d": { + "42face47-730d-f930-fef5-2a1ef6304b16": { + "typeFullFqn": "system.time_series_chart", "type": "timeseries", - "sizeX": 24, + "sizeX": 8, "sizeY": 5, "config": { "datasources": [ { "type": "entity", + "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018", "dataKeys": [ { - "name": "ruleEngineException", + "name": "successfulMsgs", "type": "timeseries", - "label": "Rule Chain", - "color": "#2196f3", + "label": "{i18n:api-usage.successful}", + "color": "#4caf50", "settings": { - "useCellStyleFunction": false, - "useCellContentFunction": true, - "cellContentFunction": "return JSON.parse(value).ruleChainName;" + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2.5, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 12, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } }, - "_hash": 0.9954481282345906 + "_hash": 0.15490750967648736, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null }, { - "name": "ruleEngineException", + "name": "failedMsgs", "type": "timeseries", - "label": "Rule Node", - "color": "#4caf50", + "label": "{i18n:api-usage.permanent-failures}", + "color": "#ef5350", "settings": { - "useCellStyleFunction": false, - "useCellContentFunction": true, - "cellContentFunction": "return JSON.parse(value).ruleNodeName;" + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2.5, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 12, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } }, - "_hash": 0.18580357036589978 + "_hash": 0.4186621166514697, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null }, { - "name": "ruleEngineException", + "name": "tmpFailed", "type": "timeseries", - "label": "Latest Error", - "color": "#f44336", + "label": "{i18n:api-usage.processing-failures}", + "color": "#ffc107", "settings": { - "useCellStyleFunction": false, - "useCellContentFunction": true, - "cellContentFunction": "return JSON.parse(value).message;" + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2.5, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 12, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } }, - "_hash": 0.7255162989552142 + "_hash": 0.49891007198715376, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null } ], - "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018" + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } } ], "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, "realtime": { - "interval": 1000, - "timewindowMs": 86400000 + "realtimeType": 0, + "timewindowMs": 300000, + "quickInterval": "CURRENT_DAY", + "interval": 1000 }, "aggregation": { "type": "NONE", - "limit": 200 + "limit": 8640 } }, "showTitle": true, - "backgroundColor": "rgb(255, 255, 255)", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", + "padding": "0px", "settings": { - "showTimestamp": true, - "displayPagination": true, - "defaultPageSize": 10 + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } + }, + "thresholds": [], + "dataZoom": true, + "stack": false, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 1800000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } + }, + "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": true, + "showMax": true, + "showAvg": false, + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 300, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + }, + "padding": "12px" }, - "title": "Exceptions", + "title": "{i18n:api-usage.queue-stats}", "dropShadow": true, "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "useDashboardTimewindow": false, - "showLegend": false, - "widgetStyle": {}, + "titleStyle": null, + "configMode": "basic", "actions": {}, "showTitleIcon": false, - "titleIcon": null, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", "titleTooltip": "", - "displayTimewindow": true + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - "id": "5eb79712-5c24-3060-7e4f-6af36b8f842d", - "typeFullFqn": "system.cards.timeseries_table" + "row": 0, + "col": 0, + "id": "42face47-730d-f930-fef5-2a1ef6304b16" }, - "ad3f1417-87a8-750e-fc67-49a2de1466d4": { + "6a74ab56-cb36-e75e-c094-a51a896da94a": { + "typeFullFqn": "system.time_series_chart", "type": "timeseries", - "sizeX": 12, - "sizeY": 7, + "sizeX": 8, + "sizeY": 5, "config": { "datasources": [ { "type": "entity", + "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018", "dataKeys": [ { "name": "timeoutMsgs", "type": "timeseries", - "label": "${entityName} Permanent Timeouts", + "label": "{i18n:api-usage.permanent-timeouts}", "color": "#4caf50", "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2.5, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 12, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } } - ], - "comparisonSettings": { - "showValuesForComparison": true } }, - "_hash": 0.565222981550328 + "_hash": 0.565222981550328, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null }, { "name": "tmpTimeout", "type": "timeseries", - "label": "${entityName} Processing Timeouts", + "label": "{i18n:api-usage.processing-timeouts}", "color": "#9c27b0", "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": false, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": false, + "lineType": "solid", + "lineWidth": 2.5, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 12, + "fillAreaSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } } - ], - "comparisonSettings": { - "showValuesForComparison": true } }, - "_hash": 0.2679547062508352 + "_hash": 0.2679547062508352, + "aggregationType": null, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null } ], - "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018" + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + } } ], "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, "realtime": { - "interval": 1000, - "timewindowMs": 300000 + "realtimeType": 0, + "timewindowMs": 300000, + "quickInterval": "CURRENT_DAY", + "interval": 1000 }, "aggregation": { "type": "NONE", "limit": 8640 - }, - "hideInterval": false, - "hideAggregation": false, - "hideAggInterval": false + } }, "showTitle": true, - "backgroundColor": "#fff", + "backgroundColor": "#FFFFFF", "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", + "padding": "0px", "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 + "yAxes": { + "default": { + "units": null, + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": null, + "max": null + } }, + "thresholds": [], + "dataZoom": true, "stack": false, - "tooltipIndividual": false, - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupWidth": { + "relative": false, + "relativeWidth": 2, + "absoluteWidth": 1800000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } }, "showLegend": true, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", "legendConfig": { "direction": "column", "position": "bottom", + "sortDataKeys": false, "showMin": true, "showMax": true, "showAvg": false, - "showTotal": true - } + "showTotal": true, + "showLatest": false + }, + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "yyyy-MM-dd HH:mm:ss", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": true, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 300, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + }, + "padding": "12px" }, - "title": "Processing Failures and Timeouts", + "title": "{i18n:api-usage.processing-failures-and-timeouts}", "dropShadow": true, "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "mobileHeight": null, + "titleStyle": null, + "configMode": "basic", + "actions": {}, "showTitleIcon": false, - "titleIcon": null, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "", - "widgetStyle": {}, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", "useDashboardTimewindow": false, "displayTimewindow": true, - "actions": {} + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" }, - "id": "ad3f1417-87a8-750e-fc67-49a2de1466d4", - "typeFullFqn": "system.charts.basic_timeseries" + "row": 0, + "col": 0, + "id": "6a74ab56-cb36-e75e-c094-a51a896da94a" } }, "states": { @@ -426,23 +965,21 @@ "layouts": { "main": { "widgets": { - "81987f19-3eac-e4ce-b790-d96e9b54d9a0": { - "sizeX": 12, - "sizeY": 7, - "mobileHeight": null, - "row": 0, - "col": 0 - }, "5eb79712-5c24-3060-7e4f-6af36b8f842d": { "sizeX": 24, "sizeY": 5, "row": 7, "col": 0 }, - "ad3f1417-87a8-750e-fc67-49a2de1466d4": { + "42face47-730d-f930-fef5-2a1ef6304b16": { + "sizeX": 12, + "sizeY": 7, + "row": 0, + "col": 0 + }, + "6a74ab56-cb36-e75e-c094-a51a896da94a": { "sizeX": 12, "sizeY": 7, - "mobileHeight": null, "row": 0, "col": 12 } @@ -511,6 +1048,5 @@ }, "filters": {} }, - "externalId": null, "name": "Rule Engine Statistics" } \ No newline at end of file diff --git a/application/src/main/data/json/demo/dashboards/thermostats.json b/application/src/main/data/json/demo/dashboards/thermostats.json index 967c1cb253..654a311386 100644 --- a/application/src/main/data/json/demo/dashboards/thermostats.json +++ b/application/src/main/data/json/demo/dashboards/thermostats.json @@ -325,240 +325,6 @@ "id": "7943196b-eedb-d422-f9c3-b32d379ad172", "typeFullFqn": "system.alarm_widgets.alarms_table" }, - "14a19183-f0b2-d6be-0f62-9863f0a51111": { - "type": "timeseries", - "sizeX": 18, - "sizeY": 6, - "config": { - "datasources": [ - { - "type": "entity", - "dataKeys": [ - { - "name": "temperature", - "type": "timeseries", - "label": "Temperature", - "color": "#ef5350", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": true, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.7852346160709658, - "units": "°C", - "decimals": 1 - } - ], - "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547" - } - ], - "timewindow": { - "realtime": { - "interval": 30000, - "timewindowMs": 3600000 - }, - "aggregation": { - "type": "AVG", - "limit": 25000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "smoothLines": true, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "showMin": true, - "showMax": true, - "showAvg": true, - "showTotal": false - } - }, - "title": "Temperature", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "mobileHeight": null, - "showTitleIcon": false, - "titleIcon": null, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "", - "widgetStyle": {}, - "useDashboardTimewindow": false, - "displayTimewindow": true, - "actions": {} - }, - "id": "14a19183-f0b2-d6be-0f62-9863f0a51111", - "typeFullFqn": "system.charts.basic_timeseries" - }, - "07f49fd5-a73b-d74c-c220-362c20af81f4": { - "type": "timeseries", - "sizeX": 18, - "sizeY": 6, - "config": { - "datasources": [ - { - "type": "entity", - "dataKeys": [ - { - "name": "humidity", - "type": "timeseries", - "label": "Humidity", - "color": "#2196f3", - "settings": { - "excludeFromStacking": false, - "hideDataByDefault": false, - "disableDataHiding": false, - "removeFromLegend": false, - "showLines": true, - "fillLines": true, - "showPoints": false, - "showPointShape": "circle", - "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", - "showPointsLineWidth": 5, - "showPointsRadius": 3, - "showSeparateAxis": false, - "axisPosition": "left", - "thresholds": [ - { - "thresholdValueSource": "predefinedValue" - } - ], - "comparisonSettings": { - "showValuesForComparison": true - } - }, - "_hash": 0.28640715926957183, - "units": "%", - "decimals": 0 - } - ], - "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547" - } - ], - "timewindow": { - "realtime": { - "interval": 30000, - "timewindowMs": 3600000 - }, - "aggregation": { - "type": "AVG", - "limit": 25000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "shadowSize": 4, - "fontColor": "#545454", - "fontSize": 10, - "xaxis": { - "showLabels": true, - "color": "#545454" - }, - "yaxis": { - "showLabels": true, - "color": "#545454" - }, - "grid": { - "color": "#545454", - "tickColor": "#DDDDDD", - "verticalLines": true, - "horizontalLines": true, - "outlineWidth": 1 - }, - "stack": false, - "tooltipIndividual": false, - "timeForComparison": "months", - "xaxisSecond": { - "axisPosition": "top", - "showLabels": true - }, - "smoothLines": true, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "showMin": true, - "showMax": true, - "showAvg": true, - "showTotal": false - } - }, - "title": "Humidity", - "dropShadow": true, - "enableFullscreen": true, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "mobileHeight": null, - "showTitleIcon": false, - "titleIcon": null, - "iconColor": "rgba(0, 0, 0, 0.87)", - "iconSize": "24px", - "titleTooltip": "", - "widgetStyle": {}, - "useDashboardTimewindow": false, - "displayTimewindow": true, - "actions": {} - }, - "id": "07f49fd5-a73b-d74c-c220-362c20af81f4", - "typeFullFqn": "system.charts.basic_timeseries" - }, "c4631f94-2db3-523b-4d09-2a1a0a75d93f": { "type": "latest", "sizeX": 6, @@ -668,7 +434,9 @@ "fieldsAlignment": "column", "fieldsInRow": 2, "groupTitle": "${entityName}", - "widgetTitle": "Termostat settings" + "widgetTitle": "Termostat settings", + "columnGap": 10, + "rowGap": 5 }, "title": "New Update Multiple Attributes", "dropShadow": true, @@ -787,8 +555,8 @@ "markerImageSize": 48, "useColorFunction": false, "markerImages": [ - "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+", - "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg==" + "tb-image:dGhlcm1vc3RhdHNfZGFzaGJvYXJkX3dpZGdldF90aGVybW9zdGF0X21hcHNfbWFya2VyX2ltYWdlXzAuc3Zn:IlRoZXJtb3N0YXRzIiBkYXNoYm9hcmQgd2lkZ2V0ICJUaGVybW9zdGF0IG1hcHMiIG1hcmtlciBpbWFnZSAw;data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+", + "tb-image:dGhlcm1vc3RhdHNfZGFzaGJvYXJkX3dpZGdldF90aGVybW9zdGF0X21hcHNfbWFya2VyX2ltYWdlXzEuc3Zn:IlRoZXJtb3N0YXRzIiBkYXNoYm9hcmQgd2lkZ2V0ICJUaGVybW9zdGF0IG1hcHMiIG1hcmtlciBpbWFnZSAx;data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg==" ], "useMarkerImageFunction": true, "colorFunction": "\n", @@ -942,8 +710,8 @@ "markerImageSize": 34, "useColorFunction": false, "markerImages": [ - "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+", - "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg==" + "tb-image:dGhlcm1vc3RhdHNfZGFzaGJvYXJkX3dpZGdldF90aGVybW9zdGF0X21hcHNfbWFya2VyX2ltYWdlXzAuc3Zn:IlRoZXJtb3N0YXRzIiBkYXNoYm9hcmQgd2lkZ2V0ICJUaGVybW9zdGF0IG1hcHMiIG1hcmtlciBpbWFnZSAw;data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+", + "tb-image:dGhlcm1vc3RhdHNfZGFzaGJvYXJkX3dpZGdldF90aGVybW9zdGF0X21hcHNfbWFya2VyX2ltYWdlXzEuc3Zn:IlRoZXJtb3N0YXRzIiBkYXNoYm9hcmQgd2lkZ2V0ICJUaGVybW9zdGF0IG1hcHMiIG1hcmtlciBpbWFnZSAx;data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg==" ], "useMarkerImageFunction": true, "color": "#fe7569", @@ -1093,8 +861,8 @@ "markerImageSize": 34, "useColorFunction": false, "markerImages": [ - "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+", - "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg==" + "tb-image:dGhlcm1vc3RhdHNfZGFzaGJvYXJkX3dpZGdldF90aGVybW9zdGF0X21hcHNfbWFya2VyX2ltYWdlXzAuc3Zn:IlRoZXJtb3N0YXRzIiBkYXNoYm9hcmQgd2lkZ2V0ICJUaGVybW9zdGF0IG1hcHMiIG1hcmtlciBpbWFnZSAw;data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+", + "tb-image:dGhlcm1vc3RhdHNfZGFzaGJvYXJkX3dpZGdldF90aGVybW9zdGF0X21hcHNfbWFya2VyX2ltYWdlXzEuc3Zn:IlRoZXJtb3N0YXRzIiBkYXNoYm9hcmQgd2lkZ2V0ICJUaGVybW9zdGF0IG1hcHMiIG1hcmtlciBpbWFnZSAx;data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg==" ], "useMarkerImageFunction": true, "color": "#fe7569", @@ -1151,6 +919,786 @@ }, "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf", "typeFullFqn": "system.input_widgets.markers_placement_openstreetmap" + }, + "eda8a397-0959-690c-405c-11e2c9b2bc7e": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": "", + "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547", + "dataKeys": [ + { + "name": "temperature", + "type": "timeseries", + "label": "Temperature", + "color": "#EF5350", + "settings": { + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": true, + "lineType": "solid", + "lineWidth": 2.5, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 14, + "fillAreaSettings": { + "type": "gradient", + "opacity": 0.4, + "gradient": { + "start": 60, + "end": 10 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.5973804076994531, + "units": "°C", + "decimals": 1, + "aggregationType": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + }, + "latestDataKeys": [ + { + "name": "temperatureAlarmThreshold", + "type": "attribute", + "label": "temperatureAlarmThreshold", + "color": "#4caf50", + "settings": { + "__thresholdKey": true + }, + "_hash": 0.7120450032526351 + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "timewindowMs": 3600000, + "quickInterval": "CURRENT_DAY", + "interval": 30000 + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + }, + "timezone": null + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": true, + "showMax": true, + "showAvg": true, + "showTotal": false, + "showLatest": false + }, + "thresholds": [ + { + "type": "latestKey", + "yAxisId": "default", + "units": "°C", + "decimals": 0, + "lineColor": "rgb(233, 30, 99)", + "lineType": "solid", + "lineWidth": 2, + "startSymbol": "none", + "startSymbolSize": 5, + "endSymbol": "arrow", + "endSymbolSize": 8, + "showLabel": true, + "labelPosition": "end", + "labelFont": { + "size": 14, + "family": "Roboto", + "weight": "500", + "style": "normal" + }, + "labelColor": "rgb(233, 30, 99)", + "latestKey": "temperatureAlarmThreshold", + "latestKeyType": "attribute" + } + ], + "dataZoom": true, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "MMM dd yyyy HH:mm", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": false, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + }, + "yAxes": { + "default": { + "units": "°C", + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": null, + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": 0 + } + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } + }, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 500, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, + "padding": "12px" + }, + "title": "Temperature", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "basic", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" + }, + "row": 0, + "col": 0, + "id": "eda8a397-0959-690c-405c-11e2c9b2bc7e" + }, + "ac90f089-197f-b767-82c3-2668844265a2": { + "typeFullFqn": "system.time_series_chart", + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": "", + "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547", + "dataKeys": [ + { + "name": "humidity", + "type": "timeseries", + "label": "Humidity", + "color": "#2196F3", + "settings": { + "yAxisId": "default", + "showInLegend": true, + "dataHiddenByDefault": false, + "type": "line", + "lineSettings": { + "showLine": true, + "step": false, + "stepType": "start", + "smooth": true, + "lineType": "solid", + "lineWidth": 2.5, + "showPoints": false, + "showPointLabel": false, + "pointLabelPosition": "top", + "pointLabelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "pointLabelColor": "rgba(0, 0, 0, 0.76)", + "pointShape": "circle", + "pointSize": 14, + "fillAreaSettings": { + "type": "gradient", + "opacity": 0.4, + "gradient": { + "start": 60, + "end": 10 + } + } + }, + "barSettings": { + "showBorder": false, + "borderWidth": 2, + "borderRadius": 0, + "showLabel": false, + "labelPosition": "top", + "labelFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.76)", + "backgroundSettings": { + "type": "none", + "opacity": 0.4, + "gradient": { + "start": 100, + "end": 0 + } + } + } + }, + "_hash": 0.4481337125330429, + "units": "%", + "decimals": 0, + "aggregationType": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ], + "alarmFilterConfig": { + "statusList": [ + "ACTIVE" + ] + }, + "latestDataKeys": [ + { + "name": "humidityAlarmThreshold", + "type": "attribute", + "label": "humidityAlarmThreshold", + "color": "#4caf50", + "settings": { + "__thresholdKey": true + }, + "_hash": 0.134733085341747 + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideLastInterval": false, + "hideQuickInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "timewindowMs": 3600000, + "quickInterval": "CURRENT_DAY", + "interval": 30000 + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + }, + "timezone": null + }, + "showTitle": true, + "backgroundColor": "rgba(0, 0, 0, 0)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": true, + "showMax": true, + "showAvg": true, + "showTotal": false, + "showLatest": false + }, + "thresholds": [ + { + "type": "latestKey", + "yAxisId": "default", + "units": "%", + "decimals": 0, + "lineColor": "rgb(4, 138, 211)", + "lineType": "solid", + "lineWidth": 2, + "startSymbol": "none", + "startSymbolSize": 5, + "endSymbol": "arrow", + "endSymbolSize": 8, + "showLabel": true, + "labelPosition": "end", + "labelFont": { + "size": 14, + "family": "Roboto", + "weight": "500", + "style": "normal" + }, + "labelColor": "rgb(4, 138, 211)", + "latestKey": "humidityAlarmThreshold", + "latestKeyType": "attribute" + } + ], + "dataZoom": true, + "stack": false, + "yAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "xAxis": { + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "bottom", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 10, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)" + }, + "legendLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "legendLabelColor": "rgba(0, 0, 0, 0.76)", + "showTooltip": true, + "tooltipTrigger": "axis", + "tooltipValueFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "500", + "lineHeight": "16px" + }, + "tooltipValueColor": "rgba(0, 0, 0, 0.76)", + "tooltipShowDate": true, + "tooltipDateFormat": { + "format": "MMM dd yyyy HH:mm", + "lastUpdateAgo": false, + "custom": false + }, + "tooltipDateFont": { + "family": "Roboto", + "size": 11, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "16px" + }, + "tooltipDateColor": "rgba(0, 0, 0, 0.76)", + "tooltipDateInterval": false, + "tooltipBackgroundColor": "rgba(255, 255, 255, 0.76)", + "tooltipBackgroundBlur": 4, + "background": { + "type": "color", + "color": "#fff", + "overlay": { + "enabled": false, + "color": "rgba(255,255,255,0.72)", + "blur": 3 + } + }, + "yAxes": { + "default": { + "units": "°C", + "decimals": 0, + "show": true, + "label": "", + "labelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "600", + "lineHeight": "1" + }, + "labelColor": "rgba(0, 0, 0, 0.54)", + "position": "left", + "showTickLabels": true, + "tickLabelFont": { + "family": "Roboto", + "size": 12, + "sizeUnit": "px", + "style": "normal", + "weight": "400", + "lineHeight": "1" + }, + "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormatter": null, + "showTicks": true, + "ticksColor": "rgba(0, 0, 0, 0.54)", + "showLine": true, + "lineColor": "rgba(0, 0, 0, 0.54)", + "showSplitLines": true, + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "id": "default", + "order": 0, + "min": 0 + } + }, + "noAggregationBarWidthSettings": { + "strategy": "group", + "groupWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + }, + "barWidth": { + "relative": true, + "relativeWidth": 2, + "absoluteWidth": 1000 + } + }, + "animation": { + "animation": true, + "animationThreshold": 2000, + "animationDuration": 500, + "animationEasing": "cubicOut", + "animationDelay": 0, + "animationDurationUpdate": 300, + "animationEasingUpdate": "cubicOut", + "animationDelayUpdate": 0 + }, + "padding": "12px" + }, + "title": "Humidity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": null, + "configMode": "basic", + "actions": {}, + "showTitleIcon": false, + "titleIcon": "thermostat", + "iconColor": "#1F6BDD", + "useDashboardTimewindow": false, + "displayTimewindow": true, + "titleFont": { + "size": 16, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal", + "lineHeight": "24px" + }, + "titleColor": "rgba(0, 0, 0, 0.87)", + "titleTooltip": "", + "widgetStyle": {}, + "widgetCss": "", + "pageSize": 1024, + "units": "", + "decimals": null, + "noDataDisplayMessage": "", + "timewindowStyle": { + "showIcon": false, + "iconSize": "24px", + "icon": null, + "iconPosition": "left", + "font": { + "size": 12, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal", + "lineHeight": "16px" + }, + "color": "rgba(0, 0, 0, 0.38)", + "displayTypePrefix": true + }, + "margin": "0px", + "borderRadius": "0px", + "iconSize": "0px" + }, + "row": 0, + "col": 0, + "id": "ac90f089-197f-b767-82c3-2668844265a2" } }, "states": { @@ -1226,20 +1774,6 @@ "layouts": { "main": { "widgets": { - "14a19183-f0b2-d6be-0f62-9863f0a51111": { - "sizeX": 18, - "sizeY": 6, - "mobileHeight": null, - "row": 0, - "col": 6 - }, - "07f49fd5-a73b-d74c-c220-362c20af81f4": { - "sizeX": 18, - "sizeY": 6, - "mobileHeight": null, - "row": 6, - "col": 6 - }, "c4631f94-2db3-523b-4d09-2a1a0a75d93f": { "sizeX": 6, "sizeY": 6, @@ -1251,6 +1785,18 @@ "sizeY": 6, "row": 6, "col": 0 + }, + "eda8a397-0959-690c-405c-11e2c9b2bc7e": { + "sizeX": 18, + "sizeY": 6, + "row": 0, + "col": 6 + }, + "ac90f089-197f-b767-82c3-2668844265a2": { + "sizeX": 18, + "sizeY": 6, + "row": 6, + "col": 6 } }, "gridSettings": { @@ -1327,6 +1873,5 @@ }, "filters": {} }, - "externalId": null, "name": "Thermostats" } \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss index 66428d9d91..ee0400d96f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss @@ -102,10 +102,15 @@ $maxLegendHeight: 35%; &.vertical { width: 100%; table-layout: auto; + tbody { + th { + width: 95%; + } + } } th, td { &:not(:last-child) { - padding-right: 8px; + padding-right: 16px; } } thead tr, tbody tr:not(:last-child) { From 33525f16eeee3ed7f3ac2d5fea1972e6b606e8aa Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 12 Mar 2024 17:31:02 +0200 Subject: [PATCH 186/209] UI: echarts patch updated. --- ui-ngx/patches/echarts+5.5.0.patch | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ui-ngx/patches/echarts+5.5.0.patch b/ui-ngx/patches/echarts+5.5.0.patch index b25599afd8..39551ac137 100644 --- a/ui-ngx/patches/echarts+5.5.0.patch +++ b/ui-ngx/patches/echarts+5.5.0.patch @@ -194,3 +194,16 @@ index cf8d6bc..9b30ec1 100644 } dataZoomModel && (snapshot[dataZoomModel.id] = { dataZoomId: dataZoomModel.id, +diff --git a/node_modules/echarts/lib/component/tooltip/TooltipView.js b/node_modules/echarts/lib/component/tooltip/TooltipView.js +index b8a9b95..8e4cb2f 100644 +--- a/node_modules/echarts/lib/component/tooltip/TooltipView.js ++++ b/node_modules/echarts/lib/component/tooltip/TooltipView.js +@@ -360,7 +360,7 @@ var TooltipView = /** @class */function (_super) { + each(itemCoordSys.dataByAxis, function (axisItem) { + var axisModel = ecModel.getComponent(axisItem.axisDim + 'Axis', axisItem.axisIndex); + var axisValue = axisItem.value; +- if (!axisModel || axisValue == null) { ++ if (!axisModel || !axisModel.axis || axisValue == null) { + return; + } + var axisValueLabel = axisPointerViewHelper.getValueLabel(axisValue, axisModel.axis, ecModel, axisItem.seriesDataIndices, axisItem.valueLabelOpt); From b2460eb6429963bd37b0a6006d6a754f8ed0f4c6 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 12 Mar 2024 19:40:12 +0100 Subject: [PATCH 187/209] Parallel and multiarch build: yarn install --non-interactive --network-concurrency 4 --network-timeout 100000 --mutex network --- msa/js-executor/docker/Dockerfile | 2 +- msa/js-executor/pom.xml | 2 +- msa/web-ui/docker/Dockerfile | 2 +- msa/web-ui/pom.xml | 2 +- ui-ngx/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/msa/js-executor/docker/Dockerfile b/msa/js-executor/docker/Dockerfile index a7bebc8aaf..117bde8712 100644 --- a/msa/js-executor/docker/Dockerfile +++ b/msa/js-executor/docker/Dockerfile @@ -35,7 +35,7 @@ COPY src/server.js ./ RUN chmod a+x /tmp/*.sh \ && mv /tmp/start-js-executor.sh /usr/bin \ && chown -R node:node ${pkg.installFolder} \ - && yarn install --production && yarn cache clean --all + && yarn install --production --non-interactive --network-concurrency 4 --network-timeout 100000 --mutex network && yarn cache clean --all USER node diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 5eb3a34c49..5880ff513a 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -81,7 +81,7 @@ yarn - install + install --non-interactive --network-concurrency 4 --network-timeout 100000 --mutex network diff --git a/msa/web-ui/docker/Dockerfile b/msa/web-ui/docker/Dockerfile index 8bac82683b..3081d90107 100644 --- a/msa/web-ui/docker/Dockerfile +++ b/msa/web-ui/docker/Dockerfile @@ -34,7 +34,7 @@ COPY src/server.js ./ RUN chmod a+x /tmp/*.sh \ && mv /tmp/start-web-ui.sh /usr/bin \ && chown -R node:node ${pkg.installFolder} \ - && yarn install --production && yarn cache clean --all + && yarn install --production --non-interactive --network-concurrency 4 --network-timeout 100000 --mutex network && yarn cache clean --all USER node diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index f60c73b8d1..c070210f02 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -90,7 +90,7 @@ yarn - install + install --non-interactive --network-concurrency 4 --network-timeout 100000 --mutex network diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index a13d364052..944ca04846 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -66,7 +66,7 @@ yarn - install + install --non-interactive --network-concurrency 4 --network-timeout 100000 --mutex network From 9d632430bafb81edbc6dc23f8f3a5ab38d275344 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 12 Mar 2024 19:40:42 +0100 Subject: [PATCH 188/209] license header format --- .../lwm2m/server/store/util/LwM2MClientSerDesTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java index 8cdd7b5147..3e4d807da0 100644 --- a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 446ff16897f66716332d7950c826b6308710433b Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 13 Mar 2024 11:20:10 +0200 Subject: [PATCH 189/209] License format --- .../lwm2m/server/store/util/LwM2MClientSerDesTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java index 8cdd7b5147..3e4d807da0 100644 --- a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * Copyright © 2016-2024 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 60f40ad6b8f63c082a9b47bb12df77cddc9b0053 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 13 Mar 2024 18:29:35 +0200 Subject: [PATCH 190/209] UI: Improve time series charts X axis and tooltip date formatting: introduce auto date format. --- .../json/demo/dashboards/thermostats.json | 44 +++-- .../json/system/widget_types/bar_chart.json | 2 +- .../widget_types/bar_chart_with_labels.json | 2 +- .../json/system/widget_types/line_chart.json | 2 +- .../json/system/widget_types/point_chart.json | 2 +- .../json/system/widget_types/range_chart.json | 5 +- .../widget_types/time_series_chart.json | 2 +- ...rt-with-labels-basic-config.component.html | 3 +- .../range-chart-basic-config.component.html | 3 +- ...e-series-chart-basic-config.component.html | 11 +- .../aggregated-value-card-widget.component.ts | 3 +- .../value-chart-card-widget.component.ts | 3 +- .../bar-chart-with-labels-widget.component.ts | 9 +- .../widget/lib/chart/echarts-widget.models.ts | 12 +- .../lib/chart/range-chart-widget.component.ts | 8 +- .../lib/chart/time-series-chart.models.ts | 108 ++++++++++-- .../widget/lib/chart/time-series-chart.ts | 30 +--- ...with-labels-widget-settings.component.html | 3 +- ...range-chart-widget-settings.component.html | 3 +- ...eries-chart-widget-settings.component.html | 3 +- ...-date-format-settings-panel.component.html | 49 ++++++ ...-date-format-settings-panel.component.scss | 73 ++++++++ ...to-date-format-settings-panel.component.ts | 92 ++++++++++ .../auto-date-format-settings.component.html | 25 +++ .../auto-date-format-settings.component.ts | 99 +++++++++++ ...-series-chart-axis-settings.component.html | 22 +++ ...me-series-chart-axis-settings.component.ts | 18 +- .../common/date-format-select.component.html | 5 + .../common/date-format-select.component.ts | 49 +++++- .../common/widget-settings-common.module.ts | 10 ++ .../shared/models/widget-settings.models.ts | 157 +++++++++++++++++- .../assets/dashboard/sys_admin_home_page.json | 14 +- .../dashboard/tenant_admin_home_page.json | 14 +- .../assets/locale/locale.constant-en_US.json | 15 +- 34 files changed, 797 insertions(+), 103 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.ts diff --git a/application/src/main/data/json/demo/dashboards/thermostats.json b/application/src/main/data/json/demo/dashboards/thermostats.json index 654a311386..223722fee1 100644 --- a/application/src/main/data/json/demo/dashboards/thermostats.json +++ b/application/src/main/data/json/demo/dashboards/thermostats.json @@ -1147,7 +1147,8 @@ "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormat": {} }, "legendLabelFont": { "family": "Roboto", @@ -1173,7 +1174,9 @@ "tooltipDateFormat": { "format": "MMM dd yyyy HH:mm", "lastUpdateAgo": false, - "custom": false + "custom": false, + "auto": true, + "autoDateFormatSettings": {} }, "tooltipDateFont": { "family": "Roboto", @@ -1537,7 +1540,8 @@ "showLine": true, "lineColor": "rgba(0, 0, 0, 0.54)", "showSplitLines": true, - "splitLinesColor": "rgba(0, 0, 0, 0.12)" + "splitLinesColor": "rgba(0, 0, 0, 0.12)", + "ticksFormat": {} }, "legendLabelFont": { "family": "Roboto", @@ -1561,9 +1565,11 @@ "tooltipValueColor": "rgba(0, 0, 0, 0.76)", "tooltipShowDate": true, "tooltipDateFormat": { - "format": "MMM dd yyyy HH:mm", + "format": null, "lastUpdateAgo": false, - "custom": false + "custom": false, + "auto": true, + "autoDateFormatSettings": {} }, "tooltipDateFont": { "family": "Roboto", @@ -1712,19 +1718,25 @@ "sizeX": 11, "sizeY": 11, "row": 0, - "col": 0 + "col": 0, + "mobileOrder": 1, + "mobileHeight": 5 }, "7943196b-eedb-d422-f9c3-b32d379ad172": { "sizeX": 13, "sizeY": 5, "row": 0, - "col": 11 + "col": 11, + "mobileOrder": 2, + "mobileHeight": 5 }, "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb": { "sizeX": 13, "sizeY": 6, "row": 5, - "col": 11 + "col": 11, + "mobileOrder": 3, + "mobileHeight": 5 } }, "gridSettings": { @@ -1778,25 +1790,33 @@ "sizeX": 6, "sizeY": 6, "row": 0, - "col": 0 + "col": 0, + "mobileOrder": 3, + "mobileHeight": 5 }, "0a430429-9078-9ae6-2b67-e4a15a2bf8bf": { "sizeX": 6, "sizeY": 6, "row": 6, - "col": 0 + "col": 0, + "mobileOrder": 4, + "mobileHeight": 6 }, "eda8a397-0959-690c-405c-11e2c9b2bc7e": { "sizeX": 18, "sizeY": 6, "row": 0, - "col": 6 + "col": 6, + "mobileOrder": 1, + "mobileHeight": 6 }, "ac90f089-197f-b767-82c3-2668844265a2": { "sizeX": 18, "sizeY": 6, "row": 6, - "col": 6 + "col": 6, + "mobileOrder": 2, + "mobileHeight": 6 } }, "gridSettings": { diff --git a/application/src/main/data/json/system/widget_types/bar_chart.json b/application/src/main/data/json/system/widget_types/bar_chart.json index c857922eea..b3791f53a1 100644 --- a/application/src/main/data/json/system/widget_types/bar_chart.json +++ b/application/src/main/data/json/system/widget_types/bar_chart.json @@ -20,7 +20,7 @@ "latestDataKeySettingsDirective": "", "hasBasicMode": true, "basicModeDirective": "tb-time-series-chart-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":\"dd MMM yyyy HH:mm:ss\",\"lastUpdateAgo\":false,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Bar chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":null,\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0}},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":1000,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"padding\":\"12px\"},\"title\":\"Bar chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" }, "tags": [ "chart", diff --git a/application/src/main/data/json/system/widget_types/bar_chart_with_labels.json b/application/src/main/data/json/system/widget_types/bar_chart_with_labels.json index 75814a547a..356e91f90e 100644 --- a/application/src/main/data/json/system/widget_types/bar_chart_with_labels.json +++ b/application/src/main/data/json/system/widget_types/bar_chart_with_labels.json @@ -20,7 +20,7 @@ "latestDataKeySettingsDirective": "", "hasBasicMode": true, "basicModeDirective": "tb-bar-chart-with-labels-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"rgb(125, 142, 255)\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 50) {\\n\\tvalue = 50;\\n} else if (value > 80) {\\n\\tvalue = 80;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Soil moisture\",\"color\":\"rgb(249, 111, 255)\",\"settings\":{},\"_hash\":0.9111685461089025,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 30) {\\n\\tvalue = 30;\\n} else if (value > 90) {\\n\\tvalue = 90;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Leaf wetness\",\"color\":\"rgb(255, 163, 137)\",\"settings\":{},\"_hash\":0.8487533373085416,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 40) {\\n\\tvalue = 40;\\n} else if (value > 70) {\\n\\tvalue = 70;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cloud cover\",\"color\":\"#FFED53\",\"settings\":{},\"_hash\":0.7690144858984289,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 20) {\\n\\tvalue = 20;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":1,\"history\":{\"historyType\":2,\"timewindowMs\":60000,\"interval\":\"MONTH\",\"fixedTimewindow\":{\"startTimeMs\":1704293713163,\"endTimeMs\":1704380113163},\"quickInterval\":\"CURRENT_HALF_YEAR\"},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showBarLabel\":true,\"barLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"12px\"},\"barLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showBarValue\":true,\"barValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"700\",\"lineHeight\":\"12px\"},\"barValueColor\":\"rgba(0, 0, 0, 0.76)\",\"showLegend\":true,\"legendPosition\":\"top\",\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":\"MMMM y\",\"lastUpdateAgo\":false,\"custom\":true},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Bar chart with labels\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"public\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"%\",\"decimals\":0,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"24px\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"rgb(125, 142, 255)\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 50) {\\n\\tvalue = 50;\\n} else if (value > 80) {\\n\\tvalue = 80;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Soil moisture\",\"color\":\"rgb(249, 111, 255)\",\"settings\":{},\"_hash\":0.9111685461089025,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 30) {\\n\\tvalue = 30;\\n} else if (value > 90) {\\n\\tvalue = 90;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Leaf wetness\",\"color\":\"rgb(255, 163, 137)\",\"settings\":{},\"_hash\":0.8487533373085416,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 40) {\\n\\tvalue = 40;\\n} else if (value > 70) {\\n\\tvalue = 70;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cloud cover\",\"color\":\"#FFED53\",\"settings\":{},\"_hash\":0.7690144858984289,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 20) {\\n\\tvalue = 20;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":1,\"history\":{\"historyType\":2,\"timewindowMs\":60000,\"interval\":\"MONTH\",\"fixedTimewindow\":{\"startTimeMs\":1704293713163,\"endTimeMs\":1704380113163},\"quickInterval\":\"CURRENT_HALF_YEAR\"},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showBarLabel\":true,\"barLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"12px\"},\"barLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showBarValue\":true,\"barValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"700\",\"lineHeight\":\"12px\"},\"barValueColor\":\"rgba(0, 0, 0, 0.76)\",\"showLegend\":true,\"legendPosition\":\"top\",\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"tooltipDateInterval\":true},\"title\":\"Bar chart with labels\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"public\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"%\",\"decimals\":0,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"24px\"}" }, "tags": [ "bar chart", diff --git a/application/src/main/data/json/system/widget_types/line_chart.json b/application/src/main/data/json/system/widget_types/line_chart.json index 5fff201c28..273281e17a 100644 --- a/application/src/main/data/json/system/widget_types/line_chart.json +++ b/application/src/main/data/json/system/widget_types/line_chart.json @@ -20,7 +20,7 @@ "latestDataKeySettingsDirective": "", "hasBasicMode": true, "basicModeDirective": "tb-time-series-chart-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":true,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":false,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"emptyCircle\",\"pointSize\":4,\"fillAreaSettings\":{\"type\":\"opacity\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":true,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":false,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"emptyCircle\",\"pointSize\":4,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.3409583261715494,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"decimals\":null,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":\"dd MMM yyyy HH:mm:ss\",\"lastUpdateAgo\":false,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Line chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":true,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":false,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"emptyCircle\",\"pointSize\":4,\"fillAreaSettings\":{\"type\":\"opacity\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":true,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":false,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"emptyCircle\",\"pointSize\":4,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.3409583261715494,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"decimals\":null,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":null,\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0}},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":1000,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"padding\":\"12px\"},\"title\":\"Line chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" }, "tags": [ "chart", diff --git a/application/src/main/data/json/system/widget_types/point_chart.json b/application/src/main/data/json/system/widget_types/point_chart.json index f16d6823ee..d692570136 100644 --- a/application/src/main/data/json/system/widget_types/point_chart.json +++ b/application/src/main/data/json/system/widget_types/point_chart.json @@ -20,7 +20,7 @@ "latestDataKeySettingsDirective": "", "hasBasicMode": true, "basicModeDirective": "tb-time-series-chart-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":false,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":true,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"circle\",\"pointSize\":8,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":false,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":true,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"circle\",\"pointSize\":8,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":\"dd MMM yyyy HH:mm:ss\",\"lastUpdateAgo\":false,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Point chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":false,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":true,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"circle\",\"pointSize\":8,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":false,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":true,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"circle\",\"pointSize\":8,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":null,\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0}},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":1000,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"padding\":\"12px\"},\"title\":\"Point chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" }, "tags": [ "chart", diff --git a/application/src/main/data/json/system/widget_types/range_chart.json b/application/src/main/data/json/system/widget_types/range_chart.json index 981e3582c5..72cbeeacbb 100644 --- a/application/src/main/data/json/system/widget_types/range_chart.json +++ b/application/src/main/data/json/system/widget_types/range_chart.json @@ -2,7 +2,7 @@ "fqn": "range_chart", "name": "Range chart", "deprecated": false, - "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAC/VBMVEX////////8/PwAAAD///////+bsfP+wEzu7u73lkzz8/P1eWTT09PN1emRpeOcsvSxsbGQpeLc3NyXl5epqanjXHPLy8tti+OgoKD/wU26urr29vbts0fnkUf39/fIrYP4l02oqKjJroPl5eWUrPPDw8PCwsLJroSQqfKLpfKDn/HU1NSnu/X/tzD/pwNyku//vD//sB3m5ub2jj98mfB2lfD2hTD/rRPu8v3/3qD1fCD3+f7K1vnnjEf1eh30awSWrvOBnfFkhOLld1n1dRX0cxH/9N/1cVvld1jmjEf2izn/qQry9f2Yr/P/+u/+9u+RpuP82r//yGDdM1D3kEPzWD32gSixwfBWeN//2JDzY0v+vkf/siT+5+RBaNr7x6DbYWXxTC96mPD+7N+VqN35tID2emX3oWD3k0fm6/zc5PvV3vq2xveWrfL+8/Hd3d09ZNr/6b/6vZCPj4/2h3TfQl3bKkjaIUD/ujnxQCL0bwrC0PiWqN5Kbt3/79D6zMqGhob/0n/iUWn0aVH+vUPZGjr/tSuwwvZpiOLR0dH+2I97e3v1dWDkcV3fPVnxSCr/qw+Jo/F/m/GLoum3xOj64uZef+H41NoyW9hdfNbJycnxqLT7x5/4no/nb4P/zXDiVm3hS2TlcV3zXEPyUzj+uTTwRCbkbRB+mOaareN5k+FyjuBHbNw4YNn7xr3/47D70LD5t6zumqjngJH/2I+Ojo7lYnf4oWC+mF7utEj2iDTmfSrxRyrunw/kZwLL1vWWrfS/zPORqfP98fKjt/GGofHv7+/+7erK0umHnuKDm+Lf39/t5d7829bm2dT949Dzt8HqwLFse6r4raDsjJz3lIPopHzGpnT4q3DZVmXSNkvnj0LnjD7mgjbjWzHiSzHwRyrunQXv8/3+9POquOWot+Tu4s3u3b3rzrfUxrHu166boa6coK76uKzTvJvoppv6vpDuyYfdboDmkXH1fGffR2H3oV/kbVnnjkHtrTbOIDTiUiLhUCD/rhTuoROM0RHwAAAABXRSTlMg77MAvxFwlo8AAA5CSURBVHja1JtJaBNRGMfj8scXk8MwSYZxoiFhaC9FEQoRA7YHE6K9iMZ4KIoWcTkpqK3oRVH05HISwX1Db66guF48eHHFBfcFQUUQ933BL6PxZZyZRE2+of4aMhPyWvKb73vzf3nQQL9A/z743+lLGoE+Qlea0JLShVA0mFHFwP9Hn0CgL7QwdERgKnGlmY7QBYgNGwY2lleHBjLSNwAIpdlAFPRIQERBsIjcvDGQEUEimqLq0FFysUR4WLKiG5yQiKGiJGIqIJYCKQEGDhV6wAmJCD3VBDWqCxCmrjeBg+7tK06DkQD8Yd3C2ML1YMM/kY2FWM9GsOGfyLLuWOEg2PBPZMW22JotYMM3kYs9sdjbw2DDN5Eta2KxbQWw4ZtIYRmJrAAbfomsXxgjFq4DF36JXCmQB2eQ+CWybHuMKPAFiU8i61bESqy5Ai58EtnYY4l08wWJTyKHuy2R7d3gwieRnmWkwRok/ogsoc6y4FzI84tYsU4wBwm/iBXrP+i5CC74RcqxTnAu5C0RMypgppQ4lCaIlN6CxnKwUBZZcwhsWN/ZE4CeUA3TiCIiEImjoVCs/6T7MNggkXAk1YIo4lGoCaEDiQQIRRGN4fynaWVu3BRskIjRBJ0sbPtaDRR5fm/1jtWrV+/YsXrH+3uCDRJpDpNI5Me+VlwnMYFG8qYz9IvdZ8AFicQjioKmlC4A0JFeNJSuvBRZuxdcBEAIlGcGkUBDWdIVkuy6DCb4c+TqzpmhED0sipvBBL9IMU8WZToXgAd+kSknQhUs7wQP/CKXd1WK5E88BAv8IvnllSKruO6/7CJzFq0KVbJ2ClhgF9nbFbLRdQ4ssIss6LSLFJ+BBXaRYinWZ9LPT3YyBQmviIx19iCpS2QOanK1GCJ4g6R+kQVLUIvO5SE7+SLqgkWkmEcNptwP/c4i1AeDyIGu3VNqDSn+DyL5zp3VZ66M9Zm9WeTcfVpwVJvvtliXInNQBwwiFHU1UkHGOvcaRYqYKoSiAc0mEs2KwB8wZRFF3apdVa8vuRIzfRRZaiAiVMPUDOiq+KN9rQO7an/jo1h3cIJTxCgJIB5FWMR1wBSoTdGax/ku75LIWJ/pU0WEoRr4y32tS1/GjB4zevSYu9eEJ6/vjh5DY8pPNJ6ePl8SPARIIBqZr0ZKFQHiEUATtUXGHh87dtSosaPu7DkrvDh+ZxQNGVt6EHQsne15IngIgKCKGIaigaCDjppMXxz8SW6G55j2oAu58eDBEkGCZExYmCZqc6Qt+JOOHDzYRGOcZPeDgX/OkczEYLJ8iafCnWQ6SNA4G21T8Y9wiGzKyk/WkYE77bN/OPRmkcyx2s2yn2R7u8h4muqybTpa4casScEKkr1SZFbaPn/Hw4VMa9CNzAz8Exwi09sn2q71MbeSXKCquTGpF4lMbfutXxa7lORFeZAcm+xtInTvtZN2KUlrR4VE76zI1Jw9G4h2R0lkrCftKpNmwZ2qic8i0pp2XOtJrQ7brDSoHJ5MH/H6s+NQBQYRucySzHaUJFlhaxPyFMnMgycsIu9ss9irYXJyHtmF0h6tNa69FZ5wiIyjj+hWkun2hs86FX5WJAlXxufavXuLQ2Rqm3s8XIdExrq9raqsAzAvk/XuLQ6RTIcVCcnfS5IdB4kj1muLJI+lPXuLQ4SWWbUTm2I9+bci6dbZXr3FIkLLLHdm58ZV9Elb0FMk7T71KHfq6i1LZG6LQEJrAehovZA4cm62vffleZssiYx15xJlYsZjrlsLhDoo/9dbSjU0s8mAoqlV9rWut5UFHCoTqSQy1r1wisgaUm/VJ6LGof/Y12pW4zoQ9hJxLLNsRtlfJdmU/WsRus3V2VsBEIZm29fyZFMu6E0HlUTG+l+KpDvKi896RDQDKO9r6YDqua91+/iQKuwpb9btue096NYp4capD9Z7Z+va1zJ1VY0rYSoLoWtzI/AQefRgsIOj8vTjSWHx9KRzkDx7IFx4/ONXvr2sT0TTNBEPt8AirCXgwYSVA4hBA0rIM8mIkSixdfKAKgyDC/u+Wu8NHQ4XGp4j32kvYxWFgSiKVsOkVlnyVrARbJYJM1XID6QJWKQT/Qm7VAFBLLbKH1hZ2/iBq6s4WW/MDle8oCHeIeT47ry85MlW96pKyvy8zlntWcNA9rtfb5nMwXoDSOZ0W4Ik211hs7zoxhS5guQKZatbTRdgvQHEVfp/mSZp8yJ60QXSLC82kS0GZF30Acj9u7K9pF0gdaE1kS0SRIyWW0AQA+Mm8OtzkGOqNZEtDgSiT6qoFWhz63OQrXeAlM5v2HBh1dK1AtmDENkiQdwWk0SoE6SxmsgWB5KlQAAnLMg80ZrIFgdiDlw9EOSI/TD1NpmtOg8EqQstDAaudhlcfLHytnDZsqdAkNIE3St6ISBy8Gu5bM2TDEBCxiwuZfIMxNiz+VK21imAhIxZLBOAwF5n+9Z+FQiysp0DCfMkWW169jqbLVN9B4FkTeBtC5yBjyAL11oqTLbyRIeBWMO2KRSClDv9YrYy1wKZDM6f6HKcdIxZ0Hx7qiD35Pn/ua8iprpafLZK40GieBaf39PH0fBjOIUxC6YsXrtSPShZepfrW27rQT4HaqhmIxVHavoF67oKIFz/NY8gdeNn/tBs4evM6Q4SqdHlOAzovcKOKQACl6eytXB/QeLLMR7/kGoFK27DQDQXixzKTgVljxswKo6NwVBsCDgH5wMM/QbfNt18QU495Zz8RdkfyLEsLXT30OveSy+99Bs6ltWVlfHKMnoklhTNyPM0T5ITEtx8uNh7tYbt841vJyL0h4ep2qLfAj7/J/Lm7Y1Mxnv869a18bvWz3O4CBFRtJCIojAKQyyxjkVb7fqlTYTG2IVX7A/bZgc0wzq2n6O5icVza412yhvvc/4zn4Izuv+dBQrX7+aqlDw0kS9PV/xK4yO+OOftZ5xjo6tiwVWdKztdtl0vHY98buK+HUXZ8M7h6XEKjx/36PZ79BxZZRWTAEZBesaxFIGB24KOU2dlYMHWbB4SdLkbJXK3kfewRu1D5NNpYNR8H1ggSrO5dCKSVO7Bu1iK5GKivjOKWFg1su43y4y5EFnn03IBY927JCDzSbzq3KKtQ3YwUtpGCKNEkmU/CphIhHrAJZFGSDtpqCv52hJSbAyRHhkinY0mhAJ8clKZRH5lQwOARVvbhmWrPi/hQiSJjbmSF2dIB5KRJiDC0IBxbaUxO/XytSqYA5FtQe7hvXtlZO8kQ5naosfBMe1pZuNCRMSvSgd0050hECLVctiRaEsHbsoTYkLEkhAfgNG4JHISxMGureSBsf4iaXZAiVA5GsGAKvxOeOPUvs3IGFRbdG43697SH8/IqvCJWGvQbBcC8VVd4vw131hY5lYvkn3SOXybWRNCFzjVip0INVkqPMjrbsAEtLbo3NZyD2/656nEzJaQyvvJBEhiRmHVVir3KL1IyqymRKgTjdV/HwbHzwe1VSa7rnOzV0skZ5QI3bAnhEfU502tLsrBvRcky1QdQ0dKxJ4Qd4CfF32Wp09+epH8q+56WqSG4Wj98yCHlQ1JiCGBQhk6KONpQBD04pxH8DZ7moN4WfbgwF5d8Kh4c0XWb+Dfo2cRVARvfgdP+iH8pdlprKGtioudRztN8vq68zYv/ZPD9OKF1Ej+S4dUvbj9j68jsd4zqmhNs7V3uT7C5SshNVSMRgouHTOqBOe05ZKFB6qOR0KiThDbjWylNxpxkFx7VGeZjLAJrFjPaxUMogwPVPGovbeNrcZI8IeR206ylV7XfHdtN67Y62hJKAZZleKw+pP4n8BASrN16UPkbtwK/+1ohCuAjxpG8nCX34u/vW1Mm1IyZqvlNHp5dz1xEXvEiurXwiRCeaxpe/3LlzA/4xcqERqzNjdpJaZiq6Jv99TxZ2B9MWkKCgJRtTBUaLlZ0YF/+Zj9hMcviKlnmF48Di3rb+DHiFRqyykuQHBcWRD2vn8799/x/Ond27dp8bh79+mnJkctX2M9w4AxP7h/vsbOrMkdnT9/NI/1QRvBbCcaWS2a3MGTJ9Ha0I0slrWPJwdo4nDn4LCuDN3IfFln69UCTbxbrt7WlaEbidmarXZ/NXm0ipXBG6mz9epZwv08+gdvpM7W692EO3wfy4M3ss7WzrOUercXy8M3sliFDnmbUnNEDN8IZSs996YYvpGQreUC3dgAIz5bs94O2QAjPluvDtGDDTBC2Zod7aIHm2BksaJzbx82wch8+XoPfdgEI5jN0IuNMLJYoB/BCJNghpcYG+RGFQ6Dwhz9ODZSSIwFJLh1xTTMa93Z+m1o3cWWJyR98KBZ90a0Xs9r5fTxx/j4uYt9cELSq3mznnHFbDJBlx5y8MgAYe29CUXLgMAFhE72yjF4ZPBQcMcvdGSqMHmgcm/ojqAPR5RgDV1OznNrJlTUI6BEA3csMOGFI6kvpVJq1O1SoZRulzI+SqRdp1/B3wC2sN7dwwmsQM6Q534hWK6hRZVEte/8dsowBVglHVN9jK3SS8tKGtRuLUUuVYdUAO1SmSORdhkJ40VURsy+s4IZWzizfqWo1utdlOa0LTTXtI+K0ikXJFWGWTEqtGxKuVDtUlsY1ioV+7xMpL9rRAluBZ+CT41TiEYs84dkXPi/5YzjWkTpFh95KTPHUh6lYJapdinLmWmVagvjEunvGgGXgjNYIZSORuwEBMWclE6CGZgiSicY8Upa3hNcoJgKE6Vjy/dHHVLIVqkoyVkqzU6hFYrW0dgb4XBvxMgKRVtWH3LrnlJUo1VIioNkGPP4bbhvAFNUEUxyYh66WloRrdKy4JN2qeEGqTQ7i3+EPGnoZLvJvItlSMnTWbb57wJH9RLtM9lpbDpOnc3O/ACb/i1NA1ABvwAAAABJRU5ErkJggg==", + "image": "tb-image:cmFuZ2VfY2hhcnRfc3lzdGVtX3dpZGdldF9pbWFnZS5wbmc=:IlJhbmdlIGNoYXJ0IiBzeXN0ZW0gd2lkZ2V0IGltYWdl;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAC/VBMVEX////////8/PwAAAD///////+bsfP+wEzu7u73lkzz8/P1eWTT09PN1emRpeOcsvSxsbGQpeLc3NyXl5epqanjXHPLy8tti+OgoKD/wU26urr29vbts0fnkUf39/fIrYP4l02oqKjJroPl5eWUrPPDw8PCwsLJroSQqfKLpfKDn/HU1NSnu/X/tzD/pwNyku//vD//sB3m5ub2jj98mfB2lfD2hTD/rRPu8v3/3qD1fCD3+f7K1vnnjEf1eh30awSWrvOBnfFkhOLld1n1dRX0cxH/9N/1cVvld1jmjEf2izn/qQry9f2Yr/P/+u/+9u+RpuP82r//yGDdM1D3kEPzWD32gSixwfBWeN//2JDzY0v+vkf/siT+5+RBaNr7x6DbYWXxTC96mPD+7N+VqN35tID2emX3oWD3k0fm6/zc5PvV3vq2xveWrfL+8/Hd3d09ZNr/6b/6vZCPj4/2h3TfQl3bKkjaIUD/ujnxQCL0bwrC0PiWqN5Kbt3/79D6zMqGhob/0n/iUWn0aVH+vUPZGjr/tSuwwvZpiOLR0dH+2I97e3v1dWDkcV3fPVnxSCr/qw+Jo/F/m/GLoum3xOj64uZef+H41NoyW9hdfNbJycnxqLT7x5/4no/nb4P/zXDiVm3hS2TlcV3zXEPyUzj+uTTwRCbkbRB+mOaareN5k+FyjuBHbNw4YNn7xr3/47D70LD5t6zumqjngJH/2I+Ojo7lYnf4oWC+mF7utEj2iDTmfSrxRyrunw/kZwLL1vWWrfS/zPORqfP98fKjt/GGofHv7+/+7erK0umHnuKDm+Lf39/t5d7829bm2dT949Dzt8HqwLFse6r4raDsjJz3lIPopHzGpnT4q3DZVmXSNkvnj0LnjD7mgjbjWzHiSzHwRyrunQXv8/3+9POquOWot+Tu4s3u3b3rzrfUxrHu166boa6coK76uKzTvJvoppv6vpDuyYfdboDmkXH1fGffR2H3oV/kbVnnjkHtrTbOIDTiUiLhUCD/rhTuoROM0RHwAAAABXRSTlMg77MAvxFwlo8AAA5CSURBVHja1JtJaBNRGMfj8scXk8MwSYZxoiFhaC9FEQoRA7YHE6K9iMZ4KIoWcTkpqK3oRVH05HISwX1Db66guF48eHHFBfcFQUUQ933BL6PxZZyZRE2+of4aMhPyWvKb73vzf3nQQL9A/z743+lLGoE+Qlea0JLShVA0mFHFwP9Hn0CgL7QwdERgKnGlmY7QBYgNGwY2lleHBjLSNwAIpdlAFPRIQERBsIjcvDGQEUEimqLq0FFysUR4WLKiG5yQiKGiJGIqIJYCKQEGDhV6wAmJCD3VBDWqCxCmrjeBg+7tK06DkQD8Yd3C2ML1YMM/kY2FWM9GsOGfyLLuWOEg2PBPZMW22JotYMM3kYs9sdjbw2DDN5Eta2KxbQWw4ZtIYRmJrAAbfomsXxgjFq4DF36JXCmQB2eQ+CWybHuMKPAFiU8i61bESqy5Ai58EtnYY4l08wWJTyKHuy2R7d3gwieRnmWkwRok/ogsoc6y4FzI84tYsU4wBwm/iBXrP+i5CC74RcqxTnAu5C0RMypgppQ4lCaIlN6CxnKwUBZZcwhsWN/ZE4CeUA3TiCIiEImjoVCs/6T7MNggkXAk1YIo4lGoCaEDiQQIRRGN4fynaWVu3BRskIjRBJ0sbPtaDRR5fm/1jtWrV+/YsXrH+3uCDRJpDpNI5Me+VlwnMYFG8qYz9IvdZ8AFicQjioKmlC4A0JFeNJSuvBRZuxdcBEAIlGcGkUBDWdIVkuy6DCb4c+TqzpmhED0sipvBBL9IMU8WZToXgAd+kSknQhUs7wQP/CKXd1WK5E88BAv8IvnllSKruO6/7CJzFq0KVbJ2ClhgF9nbFbLRdQ4ssIss6LSLFJ+BBXaRYinWZ9LPT3YyBQmviIx19iCpS2QOanK1GCJ4g6R+kQVLUIvO5SE7+SLqgkWkmEcNptwP/c4i1AeDyIGu3VNqDSn+DyL5zp3VZ66M9Zm9WeTcfVpwVJvvtliXInNQBwwiFHU1UkHGOvcaRYqYKoSiAc0mEs2KwB8wZRFF3apdVa8vuRIzfRRZaiAiVMPUDOiq+KN9rQO7an/jo1h3cIJTxCgJIB5FWMR1wBSoTdGax/ku75LIWJ/pU0WEoRr4y32tS1/GjB4zevSYu9eEJ6/vjh5DY8pPNJ6ePl8SPARIIBqZr0ZKFQHiEUATtUXGHh87dtSosaPu7DkrvDh+ZxQNGVt6EHQsne15IngIgKCKGIaigaCDjppMXxz8SW6G55j2oAu58eDBEkGCZExYmCZqc6Qt+JOOHDzYRGOcZPeDgX/OkczEYLJ8iafCnWQ6SNA4G21T8Y9wiGzKyk/WkYE77bN/OPRmkcyx2s2yn2R7u8h4muqybTpa4casScEKkr1SZFbaPn/Hw4VMa9CNzAz8Exwi09sn2q71MbeSXKCquTGpF4lMbfutXxa7lORFeZAcm+xtInTvtZN2KUlrR4VE76zI1Jw9G4h2R0lkrCftKpNmwZ2qic8i0pp2XOtJrQ7brDSoHJ5MH/H6s+NQBQYRucySzHaUJFlhaxPyFMnMgycsIu9ss9irYXJyHtmF0h6tNa69FZ5wiIyjj+hWkun2hs86FX5WJAlXxufavXuLQ2Rqm3s8XIdExrq9raqsAzAvk/XuLQ6RTIcVCcnfS5IdB4kj1muLJI+lPXuLQ4SWWbUTm2I9+bci6dbZXr3FIkLLLHdm58ZV9Elb0FMk7T71KHfq6i1LZG6LQEJrAehovZA4cm62vffleZssiYx15xJlYsZjrlsLhDoo/9dbSjU0s8mAoqlV9rWut5UFHCoTqSQy1r1wisgaUm/VJ6LGof/Y12pW4zoQ9hJxLLNsRtlfJdmU/WsRus3V2VsBEIZm29fyZFMu6E0HlUTG+l+KpDvKi896RDQDKO9r6YDqua91+/iQKuwpb9btue096NYp4capD9Z7Z+va1zJ1VY0rYSoLoWtzI/AQefRgsIOj8vTjSWHx9KRzkDx7IFx4/ONXvr2sT0TTNBEPt8AirCXgwYSVA4hBA0rIM8mIkSixdfKAKgyDC/u+Wu8NHQ4XGp4j32kvYxWFgSiKVsOkVlnyVrARbJYJM1XID6QJWKQT/Qm7VAFBLLbKH1hZ2/iBq6s4WW/MDle8oCHeIeT47ry85MlW96pKyvy8zlntWcNA9rtfb5nMwXoDSOZ0W4Ik211hs7zoxhS5guQKZatbTRdgvQHEVfp/mSZp8yJ60QXSLC82kS0GZF30Acj9u7K9pF0gdaE1kS0SRIyWW0AQA+Mm8OtzkGOqNZEtDgSiT6qoFWhz63OQrXeAlM5v2HBh1dK1AtmDENkiQdwWk0SoE6SxmsgWB5KlQAAnLMg80ZrIFgdiDlw9EOSI/TD1NpmtOg8EqQstDAaudhlcfLHytnDZsqdAkNIE3St6ISBy8Gu5bM2TDEBCxiwuZfIMxNiz+VK21imAhIxZLBOAwF5n+9Z+FQiysp0DCfMkWW169jqbLVN9B4FkTeBtC5yBjyAL11oqTLbyRIeBWMO2KRSClDv9YrYy1wKZDM6f6HKcdIxZ0Hx7qiD35Pn/ua8iprpafLZK40GieBaf39PH0fBjOIUxC6YsXrtSPShZepfrW27rQT4HaqhmIxVHavoF67oKIFz/NY8gdeNn/tBs4evM6Q4SqdHlOAzovcKOKQACl6eytXB/QeLLMR7/kGoFK27DQDQXixzKTgVljxswKo6NwVBsCDgH5wMM/QbfNt18QU495Zz8RdkfyLEsLXT30OveSy+99Bs6ltWVlfHKMnoklhTNyPM0T5ITEtx8uNh7tYbt841vJyL0h4ep2qLfAj7/J/Lm7Y1Mxnv869a18bvWz3O4CBFRtJCIojAKQyyxjkVb7fqlTYTG2IVX7A/bZgc0wzq2n6O5icVza412yhvvc/4zn4Izuv+dBQrX7+aqlDw0kS9PV/xK4yO+OOftZ5xjo6tiwVWdKztdtl0vHY98buK+HUXZ8M7h6XEKjx/36PZ79BxZZRWTAEZBesaxFIGB24KOU2dlYMHWbB4SdLkbJXK3kfewRu1D5NNpYNR8H1ggSrO5dCKSVO7Bu1iK5GKivjOKWFg1su43y4y5EFnn03IBY927JCDzSbzq3KKtQ3YwUtpGCKNEkmU/CphIhHrAJZFGSDtpqCv52hJSbAyRHhkinY0mhAJ8clKZRH5lQwOARVvbhmWrPi/hQiSJjbmSF2dIB5KRJiDC0IBxbaUxO/XytSqYA5FtQe7hvXtlZO8kQ5naosfBMe1pZuNCRMSvSgd0050hECLVctiRaEsHbsoTYkLEkhAfgNG4JHISxMGureSBsf4iaXZAiVA5GsGAKvxOeOPUvs3IGFRbdG43697SH8/IqvCJWGvQbBcC8VVd4vw131hY5lYvkn3SOXybWRNCFzjVip0INVkqPMjrbsAEtLbo3NZyD2/656nEzJaQyvvJBEhiRmHVVir3KL1IyqymRKgTjdV/HwbHzwe1VSa7rnOzV0skZ5QI3bAnhEfU502tLsrBvRcky1QdQ0dKxJ4Qd4CfF32Wp09+epH8q+56WqSG4Wj98yCHlQ1JiCGBQhk6KONpQBD04pxH8DZ7moN4WfbgwF5d8Kh4c0XWb+Dfo2cRVARvfgdP+iH8pdlprKGtioudRztN8vq68zYv/ZPD9OKF1Ej+S4dUvbj9j68jsd4zqmhNs7V3uT7C5SshNVSMRgouHTOqBOe05ZKFB6qOR0KiThDbjWylNxpxkFx7VGeZjLAJrFjPaxUMogwPVPGovbeNrcZI8IeR206ylV7XfHdtN67Y62hJKAZZleKw+pP4n8BASrN16UPkbtwK/+1ohCuAjxpG8nCX34u/vW1Mm1IyZqvlNHp5dz1xEXvEiurXwiRCeaxpe/3LlzA/4xcqERqzNjdpJaZiq6Jv99TxZ2B9MWkKCgJRtTBUaLlZ0YF/+Zj9hMcviKlnmF48Di3rb+DHiFRqyykuQHBcWRD2vn8799/x/Ond27dp8bh79+mnJkctX2M9w4AxP7h/vsbOrMkdnT9/NI/1QRvBbCcaWS2a3MGTJ9Ha0I0slrWPJwdo4nDn4LCuDN3IfFln69UCTbxbrt7WlaEbidmarXZ/NXm0ipXBG6mz9epZwv08+gdvpM7W692EO3wfy4M3ss7WzrOUercXy8M3sliFDnmbUnNEDN8IZSs996YYvpGQreUC3dgAIz5bs94O2QAjPluvDtGDDTBC2Zod7aIHm2BksaJzbx82wch8+XoPfdgEI5jN0IuNMLJYoB/BCJNghpcYG+RGFQ6Dwhz9ODZSSIwFJLh1xTTMa93Z+m1o3cWWJyR98KBZ90a0Xs9r5fTxx/j4uYt9cELSq3mznnHFbDJBlx5y8MgAYe29CUXLgMAFhE72yjF4ZPBQcMcvdGSqMHmgcm/ojqAPR5RgDV1OznNrJlTUI6BEA3csMOGFI6kvpVJq1O1SoZRulzI+SqRdp1/B3wC2sN7dwwmsQM6Q534hWK6hRZVEte/8dsowBVglHVN9jK3SS8tKGtRuLUUuVYdUAO1SmSORdhkJ40VURsy+s4IZWzizfqWo1utdlOa0LTTXtI+K0ikXJFWGWTEqtGxKuVDtUlsY1ioV+7xMpL9rRAluBZ+CT41TiEYs84dkXPi/5YzjWkTpFh95KTPHUh6lYJapdinLmWmVagvjEunvGgGXgjNYIZSORuwEBMWclE6CGZgiSicY8Upa3hNcoJgKE6Vjy/dHHVLIVqkoyVkqzU6hFYrW0dgb4XBvxMgKRVtWH3LrnlJUo1VIioNkGPP4bbhvAFNUEUxyYh66WloRrdKy4JN2qeEGqTQ7i3+EPGnoZLvJvItlSMnTWbb57wJH9RLtM9lpbDpOnc3O/ACb/i1NA1ABvwAAAABJRU5ErkJggg==", "description": "Displays changes to time-series data over time visualized with color ranges — for example, temperature or humidity readings.", "descriptor": { "type": "timeseries", @@ -20,9 +20,8 @@ "latestDataKeySettingsDirective": "", "hasBasicMode": true, "basicModeDirective": "tb-range-chart-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"dataZoom\":true,\"rangeColors\":[{\"to\":-20,\"color\":\"#234CC7\"},{\"from\":-20,\"to\":0,\"color\":\"#305AD7\"},{\"from\":0,\"to\":10,\"color\":\"#7191EF\"},{\"from\":10,\"to\":20,\"color\":\"#FFA600\"},{\"from\":20,\"to\":30,\"color\":\"#F36900\"},{\"from\":30,\"to\":40,\"color\":\"#F04022\"},{\"from\":40,\"color\":\"#D81838\"}],\"outOfRangeColor\":\"#ccc\",\"fillArea\":true,\"showLegend\":true,\"legendPosition\":\"top\",\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":\"dd MMM yyyy HH:mm\",\"lastUpdateAgo\":false,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Range chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"°C\",\"decimals\":0,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"dataZoom\":true,\"rangeColors\":[{\"to\":-20,\"color\":\"#234CC7\"},{\"from\":-20,\"to\":0,\"color\":\"#305AD7\"},{\"from\":0,\"to\":10,\"color\":\"#7191EF\"},{\"from\":10,\"to\":20,\"color\":\"#FFA600\"},{\"from\":20,\"to\":30,\"color\":\"#F36900\"},{\"from\":30,\"to\":40,\"color\":\"#F04022\"},{\"from\":40,\"color\":\"#D81838\"}],\"outOfRangeColor\":\"#ccc\",\"fillArea\":true,\"showLegend\":true,\"legendPosition\":\"top\",\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"tooltipDateInterval\":true},\"title\":\"Range chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"°C\",\"decimals\":0,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" }, - "externalId": null, "tags": [ "range", "color range", diff --git a/application/src/main/data/json/system/widget_types/time_series_chart.json b/application/src/main/data/json/system/widget_types/time_series_chart.json index 688cce76fb..1c215e21c8 100644 --- a/application/src/main/data/json/system/widget_types/time_series_chart.json +++ b/application/src/main/data/json/system/widget_types/time_series_chart.json @@ -20,7 +20,7 @@ "latestDataKeySettingsDirective": "", "hasBasicMode": true, "basicModeDirective": "tb-time-series-chart-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":\"dd MMM yyyy HH:mm:ss\",\"lastUpdateAgo\":false,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Time series chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":null,\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0}},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":1000,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"padding\":\"12px\"},\"title\":\"Time series chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" }, "tags": [ "chart", diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html index fe642f3d6c..c98c9274cc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html @@ -195,7 +195,8 @@ {{ 'tooltip.date' | translate }}
- + diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html index 6f888a3de0..5be56204fa 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html @@ -169,7 +169,8 @@ {{ 'tooltip.date' | translate }}
- + diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html index b254b4df68..94866da201 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html @@ -25,10 +25,6 @@ forceSingleDatasource formControlName="datasources"> - - + +
- + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts index 852f4950ce..7ed3e9c1cd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts @@ -37,6 +37,7 @@ import { import { WidgetContext } from '@home/models/widget-component.models'; import { Observable } from 'rxjs'; import { + autoDateFormat, backgroundStyle, ComponentStyle, DateFormatProcessor, @@ -193,7 +194,7 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit } }, tooltipDateInterval: false, - tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss') + tooltipDateFormat: autoDateFormat() }; this.lineChart = new TbTimeSeriesChart(this.ctx, settings, this.chartElement.nativeElement, this.renderer, true); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts index a7981b3acf..fe5953787a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts @@ -30,6 +30,7 @@ import { import { WidgetContext } from '@home/models/widget-component.models'; import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils'; import { + autoDateFormat, backgroundStyle, ColorProcessor, ComponentStyle, @@ -174,7 +175,7 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD } }, tooltipDateInterval: false, - tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss') + tooltipDateFormat: autoDateFormat() }; this.lineChart = new TbTimeSeriesChart(this.ctx, settings, this.chartElement.nativeElement, this.renderer, false); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts index 4784a65c88..da7f4d4e40 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts @@ -57,7 +57,7 @@ import { echartsTooltipFormatter, toNamedData } from '@home/components/widget/lib/chart/echarts-widget.models'; -import { IntervalMath } from '@shared/models/time/time.models'; +import { AggregationType, IntervalMath } from '@shared/models/time/time.models'; type BarChartDataItem = EChartsSeriesItem; @@ -97,6 +97,10 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft legendLabelStyle: ComponentStyle; disabledLegendLabelStyle: ComponentStyle; + private get noAggregation(): boolean { + return this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE; + } + private shapeResize$: ResizeObserver; private decimals = 0; @@ -388,7 +392,8 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft if (this.settings.showTooltip) { const focusedSeriesIndex = this.focusedSeriesIndex(); return echartsTooltipFormatter(this.renderer, this.tooltipDateFormat, - this.settings, params, this.decimals, this.units, focusedSeriesIndex); + this.settings, params, this.decimals, this.units, focusedSeriesIndex, null, + this.noAggregation ? null : this.ctx.timeWindow.interval); } else { return undefined; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts index c446fcbda1..bac5616506 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts @@ -44,9 +44,10 @@ import { } from 'echarts/charts'; import { LabelLayout } from 'echarts/features'; import { CanvasRenderer, SVGRenderer } from 'echarts/renderers'; -import { DataEntry, DataKey, DataSet, LegendDirection } from '@shared/models/widget.models'; +import { DataEntry, DataKey, DataSet } from '@shared/models/widget.models'; import { calculateAggIntervalWithWidgetTimeWindow, + Interval, IntervalMath, WidgetTimewindow } from '@shared/models/time/time.models'; @@ -333,7 +334,8 @@ export const echartsTooltipFormatter = (renderer: Renderer2, decimals: number, units: string, focusedSeriesIndex: number, - series?: EChartsSeriesItem[]): null | HTMLElement => { + series?: EChartsSeriesItem[], + interval?: Interval): null | HTMLElement => { if (!params || Array.isArray(params) && !params[0]) { return null; } @@ -349,8 +351,8 @@ export const echartsTooltipFormatter = (renderer: Renderer2, const startTs = firstParam.value[2]; const endTs = firstParam.value[3]; if (settings.tooltipDateInterval && startTs && endTs && (endTs - 1) > startTs) { - const startDateText = tooltipDateFormat.update(startTs); - const endDateText = tooltipDateFormat.update(endTs - 1); + const startDateText = tooltipDateFormat.update(startTs, interval); + const endDateText = tooltipDateFormat.update(endTs - 1, interval); if (startDateText === endDateText) { dateText = startDateText; } else { @@ -358,7 +360,7 @@ export const echartsTooltipFormatter = (renderer: Renderer2, } } else { const ts = firstParam.value[0]; - dateText = tooltipDateFormat.update(ts); + dateText = tooltipDateFormat.update(ts, interval); } renderer.appendChild(dateElement, renderer.createText(dateText)); renderer.setStyle(dateElement, 'font-family', settings.tooltipDateFont.family); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts index 9909be6532..1705074a1e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts @@ -54,6 +54,7 @@ import { toNamedData } from '@home/components/widget/lib/chart/echarts-widget.models'; import { CallbackDataParams } from 'echarts/types/dist/shared'; +import { AggregationType } from '@shared/models/time/time.models'; interface VisualPiece { lt?: number; @@ -197,6 +198,10 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn disabledLegendLabelStyle: ComponentStyle; visibleRangeItems: RangeItem[]; + private get noAggregation(): boolean { + return this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE; + } + private rangeItems: RangeItem[]; private shapeResize$: ResizeObserver; @@ -324,7 +329,8 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn }, formatter: (params: CallbackDataParams[]) => this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat, - this.settings, params, this.decimals, this.units, 0) : undefined, + this.settings, params, this.decimals, this.units, 0, null, + this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined, padding: [8, 12], backgroundColor: this.settings.tooltipBackgroundColor, borderWidth: 0, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index b5d5968565..3c2343853c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -19,10 +19,16 @@ import { EChartsOption, EChartsSeriesItem, EChartsTooltipTrigger, - EChartsTooltipWidgetSettings, + EChartsTooltipWidgetSettings, getYAxis, measureThresholdLabelOffset } from '@home/components/widget/lib/chart/echarts-widget.models'; -import { ComponentStyle, Font, simpleDateFormat, textStyle } from '@shared/models/widget-settings.models'; +import { + autoDateFormat, AutoDateFormatSettings, + ComponentStyle, + Font, + simpleDateFormat, + textStyle, tsToFormatTimeUnit +} from '@shared/models/widget-settings.models'; import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared'; import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts'; import { @@ -50,6 +56,7 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { TbColorScheme } from '@shared/models/color.models'; import { AbstractControl, ValidationErrors } from '@angular/forms'; import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel'; +import { DatePipe } from '@angular/common'; export enum TimeSeriesChartType { default = 'default', @@ -297,6 +304,20 @@ export interface TimeSeriesChartAxisSettings { splitLinesColor: string; } +export interface TimeSeriesChartXAxisSettings extends TimeSeriesChartAxisSettings { + ticksFormat: AutoDateFormatSettings; +} + +export const defaultXAxisTicksFormat: AutoDateFormatSettings = { + millisecond: 'HH:mm:ss SSS', + second: 'HH:mm:ss', + minute: 'HH:mm', + hour: 'HH:mm', + day: 'MMM dd', + month: 'MMM', + year: 'yyyy' +}; + export type TimeSeriesChartYAxisId = 'default' | string; export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSettings { @@ -304,6 +325,8 @@ export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSetting order?: number; units?: string; decimals?: number; + interval?: number; + splitNumber?: number; min?: number | string; max?: number | string; intervalCalculator?: string; @@ -536,7 +559,7 @@ export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings { dataZoom: boolean; stack: boolean; yAxes: TimeSeriesChartYAxes; - xAxis: TimeSeriesChartAxisSettings; + xAxis: TimeSeriesChartXAxisSettings; animation: TimeSeriesChartAnimationSettings; noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings; } @@ -574,6 +597,7 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { lineHeight: '1' }, tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light, + ticksFormat: {}, showTicks: true, ticksColor: timeSeriesChartColorScheme['axis.ticks'].light, showLine: true, @@ -616,7 +640,7 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { }, tooltipValueColor: 'rgba(0, 0, 0, 0.76)', tooltipShowDate: true, - tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss'), + tooltipDateFormat: autoDateFormat(), tooltipDateFont: { family: 'Roboto', size: 11, @@ -851,12 +875,59 @@ export const createTimeSeriesYAxis = (units: string, return yAxis; }; -export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartAxisSettings, - min: number, max: number, darkMode: boolean): XAXisOption => { +export const updateYAxisIntervals = (chart: ECharts, + yAxis: TimeSeriesChartYAxis, empty: boolean): boolean => { + let changed = false; + let interval: number; + let splitNumber: number; + let minInterval: number; + if (!empty) { + interval = calculateYAxisInterval(chart, yAxis); + if (isUndefinedOrNull(interval)) { + if (isDefinedAndNotNull(yAxis.settings.splitNumber)) { + splitNumber = yAxis.settings.splitNumber; + } else { + minInterval = (1 / Math.pow(10, yAxis.decimals)); + } + } + } + if (yAxis.option.interval !== interval) { + yAxis.option.interval = interval; + changed = true; + } + if (yAxis.option.splitNumber !== splitNumber) { + yAxis.option.splitNumber = splitNumber; + changed = true; + } + if (yAxis.option.minInterval !== minInterval) { + yAxis.option.minInterval = minInterval; + changed = true; + } + return changed; +}; + +const calculateYAxisInterval = (chart: ECharts, yAxis: TimeSeriesChartYAxis): number | undefined => { + let interval = yAxis.settings.interval; + if (yAxis.intervalCalculator) { + const axis = getYAxis(chart, yAxis.id); + if (axis) { + try { + interval = yAxis.intervalCalculator(axis); + } catch (_e) {} + } + } + return interval; +}; + +export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartXAxisSettings, + min: number, max: number, + datePipe: DatePipe, + darkMode: boolean): XAXisOption => { const xAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont, settings.tickLabelColor, darkMode, 'axis.tickLabel'); const xAxisNameStyle = createChartTextStyle(settings.labelFont, settings.labelColor, darkMode, 'axis.label'); + const ticksFormat = mergeDeep({}, defaultXAxisTicksFormat, settings.ticksFormat); return { show: settings.show, type: 'time', @@ -885,15 +956,16 @@ export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartAxisSetting fontFamily: xAxisTickLabelStyle.fontFamily, fontSize: xAxisTickLabelStyle.fontSize, hideOverlap: true, - /* formatter: { - year: '{yyyy}', - month: '{MMM}', - day: '{d}', - hour: '{HH}:{mm}', - minute: '{HH}:{mm}', - second: '{HH}:{mm}:{ss}', - millisecond: '{hh}:{mm}:{ss} {SSS}' - } */ + formatter: (value: number, index: number, extra: {level: number}) => { + const unit = tsToFormatTimeUnit(value); + const format = ticksFormat[unit]; + const formatted = datePipe.transform(value, format); + if (extra.level > 0) { + return `{primary|${formatted}}`; + } else { + return formatted; + } + } }, axisLine: { show: settings.showLine, @@ -970,10 +1042,10 @@ const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[], dataGroupId: item.id, yAxisIndex: item.yAxisIndex, data: [], - tooltip: { - show: false - }, markLine: { + tooltip: { + show: false + }, lineStyle: { width: item.settings.lineWidth, color: prepareChartThemeColor(item.settings.lineColor, darkMode, 'threshold.line'), diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 57e52f5f5d..8e5fc4f305 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -39,7 +39,7 @@ import { TimeSeriesChartYAxis, TimeSeriesChartYAxisId, TimeSeriesChartYAxisSettings, - updateDarkMode + updateDarkMode, updateYAxisIntervals } from '@home/components/widget/lib/chart/time-series-chart.models'; import { ResizeObserver } from '@juggle/resize-observer'; import { @@ -51,12 +51,11 @@ import { echartsTooltipFormatter, EChartsTooltipTrigger, getAxisExtent, - getYAxis, measureXAxisNameHeight, measureYAxisNameWidth, toNamedData } from '@home/components/widget/lib/chart/echarts-widget.models'; -import { DateFormatProcessor } from '@shared/models/widget-settings.models'; +import { autoDateFormat, DateFormatProcessor } from '@shared/models/widget-settings.models'; import { isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils'; import { DataKey, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; import * as echarts from 'echarts/core'; @@ -485,7 +484,8 @@ export class TbTimeSeriesChart { }, formatter: (params: CallbackDataParams[]) => this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat, - this.settings, params, 0, '', -1, this.dataItems) : undefined, + this.settings, params, 0, '', -1, this.dataItems, + this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined, padding: [8, 12], backgroundColor: this.settings.tooltipBackgroundColor, borderWidth: 0, @@ -499,7 +499,7 @@ export class TbTimeSeriesChart { }], xAxis: [ createTimeSeriesXAxisOption(this.settings.xAxis, this.ctx.defaultSubscription.timeWindow.minTime, - this.ctx.defaultSubscription.timeWindow.maxTime, this.darkMode) + this.ctx.defaultSubscription.timeWindow.maxTime, this.ctx.date, this.darkMode) ], yAxis: this.yAxisList.map(axis => axis.option), dataZoom: [ @@ -611,7 +611,7 @@ export class TbTimeSeriesChart { this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis', 'xAxis', 'grid'], lazyUpdate: true}); } - changed = this.calculateYAxisInterval(this.yAxisList); + changed = this.updateYAxesIntervals(this.yAxisList); if (changed) { this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis'], lazyUpdate: true}); @@ -696,26 +696,12 @@ export class TbTimeSeriesChart { return !axisDataItems.length; } - private calculateYAxisInterval(axisList: TimeSeriesChartYAxis[]): boolean { + private updateYAxesIntervals(axisList: TimeSeriesChartYAxis[]): boolean { let changed = false; for (const yAxis of axisList) { - const minInterval = this.yAxisEmpty(yAxis) ? undefined : (1 / Math.pow(10, yAxis.decimals)); - if (yAxis.option.minInterval !== minInterval) { - yAxis.option.minInterval = minInterval; + if (updateYAxisIntervals(this.timeSeriesChart, yAxis, this.yAxisEmpty(yAxis))) { changed = true; } - if (yAxis.intervalCalculator) { - const axis = getYAxis(this.timeSeriesChart, yAxis.id); - if (axis) { - try { - const interval = yAxis.intervalCalculator(axis); - if ((yAxis.option as any).interval !== interval) { - (yAxis.option as any).interval = interval; - changed = true; - } - } catch (_e) {} - } - } } return changed; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html index e40470d75e..b20b941bd1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html @@ -114,7 +114,8 @@ {{ 'tooltip.date' | translate }}
- + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html index 94b64caad4..cf5bbcfdd5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html @@ -104,7 +104,8 @@ {{ 'tooltip.date' | translate }}
- + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html index cd6f1255f8..fbd960be18 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html @@ -123,7 +123,8 @@ {{ 'tooltip.date' | translate }}
- + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.html new file mode 100644 index 0000000000..a88ea77488 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.html @@ -0,0 +1,49 @@ + +
+
date.time-granularity-formats
+
+
+
{{ formatTimeUnitTranslations.get(unit) | translate }}
+
+ + +
+
+ +
{{ previewText[unit] }}
+
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.scss new file mode 100644 index 0000000000..be50f87af8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.scss @@ -0,0 +1,73 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../../../../../scss/constants'; + +.tb-auto-date-format-settings-panel { + width: 620px; + display: flex; + flex-direction: column; + gap: 16px; + @media #{$mat-lt-md} { + width: 90vw; + } + .tb-auto-date-format-settings-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-auto-date-format-settings-panel-content { + display: flex; + flex-direction: column; + gap: 16px; + overflow: auto; + margin: -10px; + padding: 10px; + } + .tb-form-row { + .fixed-title-width { + min-width: 120px; + } + .mat-mdc-form-field.tb-date-format-input { + .mat-mdc-text-field-wrapper.mdc-text-field--outlined { + .mat-mdc-form-field-flex { + .mat-mdc-form-field-icon-suffix { + display: flex; + align-items: center; + line-height: normal; + } + } + } + } + .preview-text { + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + letter-spacing: 0.2px; + color: rgba(0, 0, 0, 0.38); + } + } + .tb-auto-date-format-settings-panel-buttons { + height: 40px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.ts new file mode 100644 index 0000000000..dcfc942778 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.ts @@ -0,0 +1,92 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { + AutoDateFormatSettings, defaultAutoDateFormatSettings, + FormatTimeUnit, + formatTimeUnits, + formatTimeUnitTranslations +} from '@shared/models/widget-settings.models'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DatePipe } from '@angular/common'; + +@Component({ + selector: 'tb-auto-date-format-settings-panel', + templateUrl: './auto-date-format-settings-panel.component.html', + providers: [], + styleUrls: ['./auto-date-format-settings-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class AutoDateFormatSettingsPanelComponent extends PageComponent implements OnInit { + + formatTimeUnits = formatTimeUnits; + + formatTimeUnitTranslations = formatTimeUnitTranslations; + + @Input() + autoDateFormatSettings: AutoDateFormatSettings; + + @Input() + defaultValues = defaultAutoDateFormatSettings; + + @Input() + popover: TbPopoverComponent; + + @Output() + autoDateFormatSettingsApplied = new EventEmitter(); + + autoDateFormatFormGroup: UntypedFormGroup; + + previewText: {[unit in FormatTimeUnit]: string} = {} as any; + + constructor(private date: DatePipe, + private fb: UntypedFormBuilder, + protected store: Store) { + super(store); + } + + ngOnInit(): void { + this.autoDateFormatFormGroup = this.fb.group({}); + for (const unit of formatTimeUnits) { + this.autoDateFormatFormGroup.addControl(unit, + this.fb.control(this.autoDateFormatSettings[unit] || this.defaultValues[unit], [Validators.required])); + this.autoDateFormatFormGroup.get(unit).valueChanges.subscribe((value: string) => { + this.previewText[unit] = this.date.transform(Date.now(), value); + }); + this.previewText[unit] = this.date.transform(Date.now(), this.autoDateFormatSettings[unit] || this.defaultValues[unit]); + } + } + + cancel() { + this.popover?.hide(); + } + + applyAutoDateFormatSettings() { + const autoDateFormatSettings: AutoDateFormatSettings = this.autoDateFormatFormGroup.value; + for (const unit of formatTimeUnits) { + if (autoDateFormatSettings[unit] === this.defaultValues[unit]) { + delete autoDateFormatSettings[unit]; + } + } + this.autoDateFormatSettingsApplied.emit(autoDateFormatSettings); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.html new file mode 100644 index 0000000000..864ad3047c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.html @@ -0,0 +1,25 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.ts new file mode 100644 index 0000000000..cf5b9033fa --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.ts @@ -0,0 +1,99 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { AutoDateFormatSettings, defaultAutoDateFormatSettings } from '@shared/models/widget-settings.models'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { deepClone, mergeDeep } from '@core/utils'; +import { + AutoDateFormatSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/auto-date-format-settings-panel.component'; + +@Component({ + selector: 'tb-auto-date-format-settings', + templateUrl: './auto-date-format-settings.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AutoDateFormatSettingsComponent), + multi: true + } + ] +}) +export class AutoDateFormatSettingsComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + @Input() + defaultValues = defaultAutoDateFormatSettings; + + private modelValue: AutoDateFormatSettings; + + private propagateChange = null; + + constructor(private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef) {} + + ngOnInit(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: AutoDateFormatSettings): void { + this.modelValue = value; + } + + openAutoFormatSettingsPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + autoDateFormatSettings: deepClone(this.modelValue), + defaultValues: this.defaultValues + }; + const autoDateFormatSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, AutoDateFormatSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null, + ctx, + {}, + {}, {}, true); + autoDateFormatSettingsPanelPopover.tbComponentRef.instance.popover = autoDateFormatSettingsPanelPopover; + autoDateFormatSettingsPanelPopover.tbComponentRef.instance.autoDateFormatSettingsApplied.subscribe((autoDateFormatSettings) => { + autoDateFormatSettingsPanelPopover.hide(); + this.modelValue = autoDateFormatSettings; + this.propagateChange(this.modelValue); + }); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html index 9511baf121..1d4bb95f5c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html @@ -74,6 +74,10 @@
widgets.time-series-chart.axis.tick-labels
+ +
+
+
+ {{ 'widgets.time-series-chart.axis.ticks-interval' | translate }} +
+ + + +
+
+
+ {{ 'widgets.time-series-chart.axis.split-number' | translate }} +
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts index a88fc2e123..8dafc7050f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts @@ -23,9 +23,9 @@ import { Validators } from '@angular/forms'; import { - AxisPosition, + AxisPosition, defaultXAxisTicksFormat, timeSeriesAxisPositionTranslations, - TimeSeriesChartAxisSettings, + TimeSeriesChartAxisSettings, TimeSeriesChartXAxisSettings, TimeSeriesChartYAxisSettings } from '@home/components/widget/lib/chart/time-series-chart.models'; import { merge } from 'rxjs'; @@ -58,6 +58,8 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu functionScopeVariables = this.widgetService.getWidgetScopeVariables(); + defaultXAxisTicksFormat = defaultXAxisTicksFormat; + @Input() disabled: boolean; @@ -68,7 +70,7 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu @coerceBoolean() advanced = false; - private modelValue: TimeSeriesChartAxisSettings | TimeSeriesChartYAxisSettings; + private modelValue: TimeSeriesChartXAxisSettings | TimeSeriesChartYAxisSettings; private propagateChange = null; @@ -103,8 +105,12 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu this.axisSettingsFormGroup.addControl('units', this.fb.control(null, [])); this.axisSettingsFormGroup.addControl('decimals', this.fb.control(null, [Validators.min(0)])); this.axisSettingsFormGroup.addControl('ticksFormatter', this.fb.control(null, [])); + this.axisSettingsFormGroup.addControl('interval', this.fb.control(null, [Validators.min(0)])); + this.axisSettingsFormGroup.addControl('splitNumber', this.fb.control(null, [Validators.min(1)])); this.axisSettingsFormGroup.addControl('min', this.fb.control(null, [])); this.axisSettingsFormGroup.addControl('max', this.fb.control(null, [])); + } else if (this.axisType === 'xAxis') { + this.axisSettingsFormGroup.addControl('ticksFormat', this.fb.control(null, [])); } this.axisSettingsFormGroup.valueChanges.subscribe(() => { this.updateModel(); @@ -161,12 +167,18 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu if (this.axisType === 'yAxis') { this.axisSettingsFormGroup.get('ticksFormatter').enable({emitEvent: false}); } + if (this.axisType === 'xAxis') { + this.axisSettingsFormGroup.get('ticksFormat').enable({emitEvent: false}); + } } else { this.axisSettingsFormGroup.get('tickLabelFont').disable({emitEvent: false}); this.axisSettingsFormGroup.get('tickLabelColor').disable({emitEvent: false}); if (this.axisType === 'yAxis') { this.axisSettingsFormGroup.get('ticksFormatter').disable({emitEvent: false}); } + if (this.axisType === 'xAxis') { + this.axisSettingsFormGroup.get('ticksFormat').disable({emitEvent: false}); + } } if (showTicks) { this.axisSettingsFormGroup.get('ticksColor').enable({emitEvent: false}); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.html index 79854d0a65..93d4ec68c6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.html @@ -23,4 +23,9 @@ *ngIf="dateFormatFormControl.value?.custom" matSuffix mat-icon-button (click)="openDateFormatSettingsPopup($event, customFormatButton)"> edit + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts index 3e2393d6ff..144f02fe46 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts @@ -16,16 +16,26 @@ import { Component, forwardRef, Input, OnInit, Renderer2, ViewChild, ViewContainerRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms'; -import { compareDateFormats, dateFormats, DateFormatSettings } from '@shared/models/widget-settings.models'; +import { + AutoDateFormatSettings, + compareDateFormats, + dateFormats, + DateFormatSettings, + dateFormatsWithAuto, + defaultAutoDateFormatSettings +} from '@shared/models/widget-settings.models'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; import { MatButton } from '@angular/material/button'; import { TbPopoverService } from '@shared/components/popover.service'; -import { deepClone } from '@core/utils'; +import { deepClone, mergeDeep } from '@core/utils'; import { DateFormatSettingsPanelComponent } from '@home/components/widget/lib/settings/common/date-format-settings-panel.component'; import { coerceBoolean } from '@shared/decorators/coercion'; +import { + AutoDateFormatSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/auto-date-format-settings-panel.component'; @Component({ selector: 'tb-date-format-select', @@ -51,6 +61,10 @@ export class DateFormatSelectComponent implements OnInit, ControlValueAccessor { @coerceBoolean() excludeLastUpdateAgo = false; + @Input() + @coerceBoolean() + includeAuto = false; + dateFormatList: DateFormatSettings[]; dateFormatsCompare = compareDateFormats; @@ -70,8 +84,9 @@ export class DateFormatSelectComponent implements OnInit, ControlValueAccessor { private viewContainerRef: ViewContainerRef) {} ngOnInit(): void { + const targetDateFormats = this.includeAuto ? dateFormatsWithAuto : dateFormats; this.dateFormatList = this.excludeLastUpdateAgo ? - dateFormats.filter(format => !format.lastUpdateAgo) : dateFormats; + targetDateFormats.filter(format => !format.lastUpdateAgo) : dateFormats; this.dateFormatFormControl = new UntypedFormControl(); this.dateFormatFormControl.valueChanges.subscribe((value: DateFormatSettings) => { this.updateModel(value); @@ -116,6 +131,8 @@ export class DateFormatSelectComponent implements OnInit, ControlValueAccessor { return this.translate.instant('date.custom-date'); } else if (value.lastUpdateAgo) { return this.translate.instant('date.last-update-n-ago'); + } else if (value.auto) { + return this.translate.instant('date.auto'); } else { if (!this.formatCache[value.format]) { this.formatCache[value.format] = this.date.transform(Date.now(), value.format); @@ -148,4 +165,30 @@ export class DateFormatSelectComponent implements OnInit, ControlValueAccessor { }); } } + + openAutoFormatSettingsPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + autoDateFormatSettings: mergeDeep({} as AutoDateFormatSettings, + defaultAutoDateFormatSettings, this.modelValue.autoDateFormatSettings) + }; + const autoDateFormatSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, AutoDateFormatSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null, + ctx, + {}, + {}, {}, true); + autoDateFormatSettingsPanelPopover.tbComponentRef.instance.popover = autoDateFormatSettingsPanelPopover; + autoDateFormatSettingsPanelPopover.tbComponentRef.instance.autoDateFormatSettingsApplied.subscribe((autoDateFormatSettings) => { + autoDateFormatSettingsPanelPopover.hide(); + this.modelValue.autoDateFormatSettings = autoDateFormatSettings; + this.propagateChange(this.modelValue); + }); + } + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index b80b20f402..5ed50a226f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -121,6 +121,12 @@ import { import { TimeSeriesChartAnimationSettingsComponent } from '@home/components/widget/lib/settings/common/chart/time-series-chart-animation-settings.component'; +import { + AutoDateFormatSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/auto-date-format-settings-panel.component'; +import { + AutoDateFormatSettingsComponent +} from '@home/components/widget/lib/settings/common/auto-date-format-settings.component'; @NgModule({ declarations: [ @@ -134,6 +140,8 @@ import { CssSizeInputComponent, DateFormatSelectComponent, DateFormatSettingsPanelComponent, + AutoDateFormatSettingsComponent, + AutoDateFormatSettingsPanelComponent, BackgroundSettingsComponent, BackgroundSettingsPanelComponent, ValueSourceComponent, @@ -185,6 +193,8 @@ import { CssSizeInputComponent, DateFormatSelectComponent, DateFormatSettingsPanelComponent, + AutoDateFormatSettingsComponent, + AutoDateFormatSettingsPanelComponent, BackgroundSettingsComponent, BackgroundSettingsPanelComponent, ValueSourceComponent, diff --git a/ui-ngx/src/app/shared/models/widget-settings.models.ts b/ui-ngx/src/app/shared/models/widget-settings.models.ts index 1e2e0d2415..f3722c54b1 100644 --- a/ui-ngx/src/app/shared/models/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { isDefinedAndNotNull, isNumber, isNumeric, isUndefinedOrNull, parseFunction } from '@core/utils'; +import { isDefinedAndNotNull, isNumber, isNumeric, isUndefinedOrNull, mergeDeep, parseFunction } from '@core/utils'; import { DataEntry, DataKey, @@ -34,6 +34,8 @@ import { Observable, of } from 'rxjs'; import { ImagePipe } from '@shared/pipe/image.pipe'; import { map } from 'rxjs/operators'; import { DomSanitizer } from '@angular/platform-browser'; +import { AVG_MONTH, DAY, HOUR, Interval, IntervalMath, MINUTE, SECOND, YEAR } from '@shared/models/time/time.models'; +import moment from 'moment'; export type ComponentStyle = {[klass: string]: any}; @@ -323,35 +325,85 @@ class FunctionColorProcessor extends ColorProcessor { } } +export type FormatTimeUnit = 'millisecond' | 'second' | 'minute' | 'hour' + | 'day' | 'month' | 'year'; + + +export const formatTimeUnits: FormatTimeUnit[] = [ + 'year', 'month', 'day', 'hour', 'minute', 'second', 'millisecond' +]; + +export const formatTimeUnitTranslations = new Map( + [ + ['year', 'date.unit-year'], + ['month', 'date.unit-month'], + ['day', 'date.unit-day'], + ['hour', 'date.unit-hour'], + ['minute', 'date.unit-minute'], + ['second', 'date.unit-second'], + ['millisecond', 'date.unit-millisecond'] + ] +); + +export type AutoDateFormatSettings = { + [unit in FormatTimeUnit]?: string; +}; + export interface DateFormatSettings { format?: string; lastUpdateAgo?: boolean; custom?: boolean; + auto?: boolean; hideLastUpdatePrefix?: boolean; + autoDateFormatSettings?: AutoDateFormatSettings; } export const simpleDateFormat = (format: string): DateFormatSettings => ({ format, lastUpdateAgo: false, - custom: false + custom: false, + auto: false }); export const lastUpdateAgoDateFormat = (): DateFormatSettings => ({ format: null, lastUpdateAgo: true, - custom: false + custom: false, + auto: false }); export const customDateFormat = (format: string): DateFormatSettings => ({ format, lastUpdateAgo: false, - custom: true + custom: true, + auto: false +}); + +export const defaultAutoDateFormatSettings: AutoDateFormatSettings = { + millisecond: 'MMM dd yyyy HH:mm:ss.SSS', + second: 'MMM dd yyyy HH:mm:ss', + minute: 'MMM dd yyyy HH:mm', + hour: 'MMM dd yyyy HH:mm', + day: 'MMM dd yyyy', + month: 'MMM yyyy', + year: 'yyyy' +}; + +export const autoDateFormat = (): DateFormatSettings => ({ + format: null, + lastUpdateAgo: false, + custom: false, + auto: true, + autoDateFormatSettings: {} }); -export const dateFormats = ['MMM dd yyyy HH:mm', 'dd MMM yyyy HH:mm', 'dd MMM yyyy HH:mm:ss', 'yyyy MMM dd HH:mm', - 'MM/dd/yyyy HH:mm', 'dd/MM/yyyy HH:mm', 'yyyy/MM/dd HH:mm:ss', 'yyyy-MM-dd HH:mm:ss', 'yyyy-MM-dd HH:mm:ss.SSS'] +export const dateFormats = ['MMM yyyy', 'MMM dd yyyy', 'MMM dd yyyy HH:mm', 'dd MMM yyyy HH:mm', 'dd MMM yyyy HH:mm:ss', + 'yyyy MMM dd HH:mm', 'MM/dd/yyyy HH:mm', 'dd/MM/yyyy HH:mm', 'MMM dd yyyy HH:mm:ss', 'yyyy/MM/dd HH:mm:ss', 'yyyy-MM-dd HH:mm:ss', + 'MMM dd yyyy HH:mm:ss.SSS', 'yyyy-MM-dd HH:mm:ss.SSS'] .map(f => simpleDateFormat(f)).concat([lastUpdateAgoDateFormat(), customDateFormat('EEE, MMMM dd, yyyy')]); +export const dateFormatsWithAuto = [autoDateFormat()].concat(dateFormats); + export const compareDateFormats = (df1: DateFormatSettings, df2: DateFormatSettings): boolean => { if (df1 === df2) { return true; @@ -360,7 +412,9 @@ export const compareDateFormats = (df1: DateFormatSettings, df2: DateFormatSetti return true; } else if (df1.custom && df2.custom) { return true; - } else if (!df1.lastUpdateAgo && !df2.lastUpdateAgo && !df1.custom && !df2.custom) { + } else if (df1.auto && df2.auto && !df1.custom && !df2.custom) { + return true; + } else if (!df1.lastUpdateAgo && !df2.lastUpdateAgo && !df1.custom && !df2.custom && !df1.auto && !df2.auto) { return df1.format === df2.format; } } @@ -372,6 +426,8 @@ export abstract class DateFormatProcessor { static fromSettings($injector: Injector, settings: DateFormatSettings): DateFormatProcessor { if (settings.lastUpdateAgo) { return new LastUpdateAgoDateFormatProcessor($injector, settings); + } else if (settings.auto && !settings.custom) { + return new AutoDateFormatProcessor($injector, settings); } else { return new SimpleDateFormatProcessor($injector, settings); } @@ -383,7 +439,7 @@ export abstract class DateFormatProcessor { protected settings: DateFormatSettings) { } - abstract update(ts: string | number | Date): string; + abstract update(ts: string | number | Date, interval?: Interval): string; } @@ -437,6 +493,91 @@ export class LastUpdateAgoDateFormatProcessor extends DateFormatProcessor { } +export class AutoDateFormatProcessor extends DateFormatProcessor { + + private datePipe: DatePipe; + private readonly autoDateFormatSettings: AutoDateFormatSettings; + + constructor(protected $injector: Injector, + protected settings: DateFormatSettings) { + super($injector, settings); + this.datePipe = $injector.get(DatePipe); + this.autoDateFormatSettings = mergeDeep({} as AutoDateFormatSettings, + defaultAutoDateFormatSettings, this.settings.autoDateFormatSettings); + } + + update(ts: string| number | Date, interval?: Interval): string { + if (ts) { + const unit = interval ? intervalToFormatTimeUnit(interval) : tsToFormatTimeUnit(ts); + const format = this.autoDateFormatSettings[unit]; + if (format) { + this.formatted = this.datePipe.transform(ts, format); + } else { + this.formatted = ' '; + } + } else { + this.formatted = ' '; + } + return this.formatted; + } +} + +const intervalToFormatTimeUnit = (interval: Interval): FormatTimeUnit => { + const intervalValue = IntervalMath.numberValue(interval); + if (intervalValue < SECOND) { + return 'millisecond'; + } else if (intervalValue < MINUTE) { + return 'second'; + } else if (intervalValue < HOUR) { + return 'minute'; + } else if (intervalValue < DAY) { + return 'hour'; + } else if (intervalValue < AVG_MONTH) { + return 'day'; + } else if (intervalValue < YEAR) { + return 'month'; + } else { + return 'year'; + } +}; + +export const tsToFormatTimeUnit = (ts: string | number | Date): FormatTimeUnit => { + const date = moment(ts); + const M = date.month() + 1; + const d = date.date(); + const h = date.hours(); + const m = date.minutes(); + const s = date.seconds(); + const S = date.milliseconds(); + const isSecond = S === 0; + const isMinute = isSecond && s === 0; + const isHour = isMinute && m === 0; + const isDay = isHour && h === 0; + const isMonth = isDay && d === 1; + const isYear = isMonth && M === 1; + if (isYear) { + return 'year'; + } + else if (isMonth) { + return 'month'; + } + else if (isDay) { + return 'day'; + } + else if (isHour) { + return 'hour'; + } + else if (isMinute) { + return 'minute'; + } + else if (isSecond) { + return 'second'; + } + else { + return 'millisecond'; + } +}; + export enum BackgroundType { image = 'image', color = 'color' diff --git a/ui-ngx/src/assets/dashboard/sys_admin_home_page.json b/ui-ngx/src/assets/dashboard/sys_admin_home_page.json index 29c692ad87..a47103059e 100644 --- a/ui-ngx/src/assets/dashboard/sys_admin_home_page.json +++ b/ui-ngx/src/assets/dashboard/sys_admin_home_page.json @@ -2023,6 +2023,7 @@ "lineHeight": "1" }, "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormat": {}, "showTicks": false, "ticksColor": "rgba(0, 0, 0, 0.54)", "showLine": false, @@ -2076,9 +2077,11 @@ "tooltipValueColor": "rgba(0, 0, 0, 0.76)", "tooltipShowDate": true, "tooltipDateFormat": { - "format": "MMMM dd, yyyy", + "format": null, "lastUpdateAgo": false, - "custom": true + "custom": false, + "auto": true, + "autoDateFormatSettings": {} }, "tooltipDateFont": { "family": "Roboto", @@ -2494,6 +2497,7 @@ "lineHeight": "1" }, "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormat": {}, "showTicks": false, "ticksColor": "rgba(0, 0, 0, 0.54)", "showLine": false, @@ -2547,9 +2551,11 @@ "tooltipValueColor": "rgba(0, 0, 0, 0.76)", "tooltipShowDate": true, "tooltipDateFormat": { - "format": "dd MMM yyyy HH:mm:ss", + "format": null, "lastUpdateAgo": false, - "custom": false + "custom": false, + "auto": true, + "autoDateFormatSettings": {} }, "tooltipDateFont": { "family": "Roboto", diff --git a/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json index 063cb0def2..85669b8bf4 100644 --- a/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json +++ b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json @@ -671,6 +671,7 @@ "lineHeight": "1" }, "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormat": {}, "showTicks": false, "ticksColor": "rgba(0, 0, 0, 0.54)", "showLine": false, @@ -724,9 +725,11 @@ "tooltipValueColor": "rgba(0, 0, 0, 0.76)", "tooltipShowDate": true, "tooltipDateFormat": { - "format": "MMMM dd, yyyy", + "format": null, "lastUpdateAgo": false, - "custom": true + "custom": false, + "auto": true, + "autoDateFormatSettings": {} }, "tooltipDateFont": { "family": "Roboto", @@ -997,6 +1000,7 @@ "lineHeight": "1" }, "tickLabelColor": "rgba(0, 0, 0, 0.54)", + "ticksFormat": {}, "showTicks": false, "ticksColor": "rgba(0, 0, 0, 0.54)", "showLine": false, @@ -1050,9 +1054,11 @@ "tooltipValueColor": "rgba(0, 0, 0, 0.76)", "tooltipShowDate": true, "tooltipDateFormat": { - "format": "MMMM dd, yyyy", + "format": null, "lastUpdateAgo": false, - "custom": true + "custom": false, + "auto": true, + "autoDateFormatSettings": {} }, "tooltipDateFont": { "family": "Roboto", diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 8f02129a80..b836c7fad9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1025,7 +1025,16 @@ "last-update-n-ago-text": "Last update {{ agoText }}", "custom-date": "Custom date", "format": "Format", - "preview": "Preview" + "preview": "Preview", + "auto": "Auto", + "time-granularity-formats": "Time granularity formats", + "unit-year": "Years", + "unit-month": "Months", + "unit-day": "Days", + "unit-hour": "Hours", + "unit-minute": "Minutes", + "unit-second": "Seconds", + "unit-millisecond": "Milliseconds" }, "datetime": { "date-from": "Date from", @@ -6691,6 +6700,10 @@ "show-split-lines": "Show split lines", "show-split-lines-x-axis-hint": "If enabled, the vertical lines on the chart will be shown.", "show-split-lines-y-axis-hint": "If enabled, the horizontal lines on the chart will be shown.", + "ticks-interval": "Ticks interval", + "ticks-interval-hint": "Compulsively set segmentation interval for axis.", + "split-number": "Split number", + "split-number-hint": "Number of segments that the axis is split into.", "scale": "Scale", "scale-min": "min", "scale-max": "max", From 15a88a90a31c7f03efe56d60ac1d43e4350818b8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 13 Mar 2024 20:32:24 +0200 Subject: [PATCH 191/209] UI: time series chart - fix axis intervals calculation. --- .../widget/lib/chart/time-series-chart.ts | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 8e5fc4f305..521a3ce06e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -55,7 +55,7 @@ import { measureYAxisNameWidth, toNamedData } from '@home/components/widget/lib/chart/echarts-widget.models'; -import { autoDateFormat, DateFormatProcessor } from '@shared/models/widget-settings.models'; +import { DateFormatProcessor } from '@shared/models/widget-settings.models'; import { isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils'; import { DataKey, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; import * as echarts from 'echarts/core'; @@ -73,7 +73,7 @@ import { BarRenderSharedContext } from '@home/components/widget/lib/chart/time-s export class TbTimeSeriesChart { public static dataKeySettings(type = TimeSeriesChartType.default): DataKeySettingsFunction { - return (key, isLatestDataKey) => { + return (_key, isLatestDataKey) => { if (!isLatestDataKey) { const settings = mergeDeep({} as TimeSeriesChartKeySettings, timeSeriesChartKeyDefaultSettings); @@ -629,18 +629,6 @@ export class TbTimeSeriesChart { } } - private updateYAxisScale(axisList: TimeSeriesChartYAxis[]): boolean { - let changed = false; - for (const yAxis of axisList) { - const scaleYAxis = this.scaleYAxis(yAxis); - if (yAxis.option.scale !== scaleYAxis) { - yAxis.option.scale = scaleYAxis; - changed = true; - } - } - return changed; - } - private updateYAxisOffset(axisList: TimeSeriesChartYAxis[]): {offset: number; changed: boolean} { const result = {offset: 0, changed: false}; let width = 0; @@ -684,6 +672,21 @@ export class TbTimeSeriesChart { return result; } + private updateYAxisScale(axisList: TimeSeriesChartYAxis[]): boolean { + let changed = false; + for (const yAxis of axisList) { + const scaleYAxis = this.scaleYAxis(yAxis); + if (yAxis.option.scale !== scaleYAxis) { + yAxis.option.scale = scaleYAxis; + changed = true; + } + if (updateYAxisIntervals(this.timeSeriesChart, yAxis, this.yAxisEmpty(yAxis))) { + changed = true; + } + } + return changed; + } + private scaleYAxis(yAxis: TimeSeriesChartYAxis): boolean { const axisBarDataItems = this.dataItems.filter(d => d.yAxisId === yAxis.id && d.enabled && d.data.length && d.dataKey.settings.type === TimeSeriesChartSeriesType.bar); From c4bc1ba6bb8398f7e0c36d5fb9d3f2855b6de2d6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 14 Mar 2024 12:32:51 +0200 Subject: [PATCH 192/209] UI: Improve time series charts ticks generation. --- ui-ngx/patches/echarts+5.5.0.patch | 141 ++++++++++++++++++ .../aggregated-value-card-widget.component.ts | 7 +- .../lib/chart/time-series-chart.models.ts | 112 +++++++------- .../widget/lib/chart/time-series-chart.ts | 29 +--- 4 files changed, 196 insertions(+), 93 deletions(-) diff --git a/ui-ngx/patches/echarts+5.5.0.patch b/ui-ngx/patches/echarts+5.5.0.patch index 39551ac137..f74beb7ea8 100644 --- a/ui-ngx/patches/echarts+5.5.0.patch +++ b/ui-ngx/patches/echarts+5.5.0.patch @@ -207,3 +207,144 @@ index b8a9b95..8e4cb2f 100644 return; } var axisValueLabel = axisPointerViewHelper.getValueLabel(axisValue, axisModel.axis, ecModel, axisItem.seriesDataIndices, axisItem.valueLabelOpt); +diff --git a/node_modules/echarts/lib/coord/axisHelper.js b/node_modules/echarts/lib/coord/axisHelper.js +index a76c66b..be22cb0 100644 +--- a/node_modules/echarts/lib/coord/axisHelper.js ++++ b/node_modules/echarts/lib/coord/axisHelper.js +@@ -187,7 +187,9 @@ export function createScaleByModel(model, axisType) { + }); + default: + // case 'value'/'interval', 'log', or others. +- return new (Scale.getClass(axisType) || IntervalScale)(); ++ return new (Scale.getClass(axisType) || IntervalScale)({ ++ ticksGenerator: model.get('ticksGenerator') ++ }); + } + } + } +diff --git a/node_modules/echarts/lib/coord/cartesian/Grid.js b/node_modules/echarts/lib/coord/cartesian/Grid.js +index 5b18f02..4960e67 100644 +--- a/node_modules/echarts/lib/coord/cartesian/Grid.js ++++ b/node_modules/echarts/lib/coord/cartesian/Grid.js +@@ -91,11 +91,11 @@ var Grid = /** @class */function () { + var scale = axis.scale; + if ( + // Only value and log axis without interval support alignTicks. +- isIntervalOrLogScale(scale) && model.get('alignTicks') && model.get('interval') == null) { ++ isIntervalOrLogScale(scale) && model.get('alignTicks') && model.get('interval') == null && model.get('ticksGenerator') == null) { + axisNeedsAlign.push(axis); + } else { + niceScaleExtent(scale, model); +- if (isIntervalOrLogScale(scale)) { ++ if (isIntervalOrLogScale(scale) && !scale.isBlank()) { + // Can only align to interval or log axis. + alignTo = axis; + } +@@ -105,10 +105,15 @@ var Grid = /** @class */function () { + // All axes has set alignTicks. Pick the first one. + // PENDING. Should we find the axis that both set interval, min, max and align to this one? + if (axisNeedsAlign.length) { +- if (!alignTo) { +- alignTo = axisNeedsAlign.pop(); +- niceScaleExtent(alignTo.scale, alignTo.model); ++ while (!alignTo && axisNeedsAlign.length) { ++ var axis = axisNeedsAlign.pop(); ++ niceScaleExtent(axis.scale, axis.model); ++ if (!axis.scale.isBlank()) { ++ alignTo = axis; ++ } + } ++ } ++ if (axisNeedsAlign.length && alignTo) { + each(axisNeedsAlign, function (axis) { + alignScaleTicks(axis.scale, axis.model, alignTo.scale); + }); +diff --git a/node_modules/echarts/lib/scale/Interval.js b/node_modules/echarts/lib/scale/Interval.js +index 1094662..8f4e07a 100644 +--- a/node_modules/echarts/lib/scale/Interval.js ++++ b/node_modules/echarts/lib/scale/Interval.js +@@ -46,12 +46,17 @@ import * as numberUtil from '../util/number.js'; + import * as formatUtil from '../util/format.js'; + import Scale from './Scale.js'; + import * as helper from './helper.js'; ++import { isFunction } from 'zrender/lib/core/util.js'; + var roundNumber = numberUtil.round; + var IntervalScale = /** @class */function (_super) { + __extends(IntervalScale, _super); +- function IntervalScale() { +- var _this = _super !== null && _super.apply(this, arguments) || this; ++ function IntervalScale(setting) { ++ var _this = _super.call(this, setting) || this; + _this.type = 'interval'; ++ var ticksGenerator = _this.getSetting('ticksGenerator'); ++ if (isFunction(ticksGenerator)) { ++ _this._ticksGenerator = ticksGenerator; ++ } + // Step is calculated in adjustExtent. + _this._interval = 0; + _this._intervalPrecision = 2; +@@ -104,7 +109,17 @@ var IntervalScale = /** @class */function (_super) { + var extent = this._extent; + var niceTickExtent = this._niceExtent; + var intervalPrecision = this._intervalPrecision; +- var ticks = []; ++ var ticksGenerator = this._ticksGenerator; ++ var ticks; ++ if (ticksGenerator) { ++ try { ++ ticks = ticksGenerator(extent, interval, niceTickExtent, intervalPrecision); ++ if (ticks) { ++ return ticks; ++ } ++ } catch (_e) {} ++ } ++ ticks = []; + // If interval is 0, return []; + if (!interval) { + return ticks; +diff --git a/node_modules/echarts/types/dist/shared.d.ts b/node_modules/echarts/types/dist/shared.d.ts +index ca74097..98f8b18 100644 +--- a/node_modules/echarts/types/dist/shared.d.ts ++++ b/node_modules/echarts/types/dist/shared.d.ts +@@ -2422,6 +2422,9 @@ interface AxisBaseOptionCommon extends ComponentOption, AnimationOptionMixin { + max: number; + }) => ScaleDataValue); + } ++ ++declare type NumericAxisTicksGenerator = (extent?: number[], interval?: number, niceTickExtent?: number[], intervalPrecision?: number) => {value: number}[]; ++ + interface NumericAxisBaseOptionCommon extends AxisBaseOptionCommon { + boundaryGap?: [number | string, number | string]; + /** +@@ -2447,6 +2450,8 @@ interface NumericAxisBaseOptionCommon extends AxisBaseOptionCommon { + * Will be ignored if interval is set. + */ + alignTicks?: boolean; ++ ++ ticksGenerator?: NumericAxisTicksGenerator; + } + interface CategoryAxisBaseOption extends AxisBaseOptionCommon { + type?: 'category'; +diff --git a/node_modules/echarts/types/src/coord/axisCommonTypes.d.ts b/node_modules/echarts/types/src/coord/axisCommonTypes.d.ts +index c5c2792..d524b70 100644 +--- a/node_modules/echarts/types/src/coord/axisCommonTypes.d.ts ++++ b/node_modules/echarts/types/src/coord/axisCommonTypes.d.ts +@@ -56,6 +56,9 @@ export interface AxisBaseOptionCommon extends ComponentOption, AnimationOptionMi + max: number; + }) => ScaleDataValue); + } ++ ++export declare type NumericAxisTicksGenerator = (extent?: number[], interval?: number, niceTickExtent?: number[], intervalPrecision?: number) => {value: number}[]; ++ + export interface NumericAxisBaseOptionCommon extends AxisBaseOptionCommon { + boundaryGap?: [number | string, number | string]; + /** +@@ -81,6 +84,8 @@ export interface NumericAxisBaseOptionCommon extends AxisBaseOptionCommon { + * Will be ignored if interval is set. + */ + alignTicks?: boolean; ++ ++ ticksGenerator?: NumericAxisTicksGenerator; + } + export interface CategoryAxisBaseOption extends AxisBaseOptionCommon { + type?: 'category'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts index 7ed3e9c1cd..908ed263e4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts @@ -44,7 +44,6 @@ import { getDataKey, getLatestSingleTsValue, overlayStyle, - simpleDateFormat, textStyle } from '@shared/models/widget-settings.models'; import { DataKey } from '@shared/models/widget.models'; @@ -55,10 +54,9 @@ import { ImagePipe } from '@shared/pipe/image.pipe'; import { DomSanitizer } from '@angular/platform-browser'; import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart'; import { - TimeSeriesChartAxisSettings, TimeSeriesChartKeySettings, TimeSeriesChartSeriesType, - TimeSeriesChartSettings, TimeSeriesChartYAxisSettings + TimeSeriesChartSettings } from '@home/components/widget/lib/chart/time-series-chart.models'; import { DeepPartial } from '@shared/models/common'; @@ -189,8 +187,7 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit showSplitLines: true, min: 'dataMin', max: 'dataMax', - intervalCalculator: - 'var scale = axis.scale; return !scale.isBlank() ? ((scale.getExtent()[1] - scale.getExtent()[0]) / 2) : undefined;' + ticksGenerator: (extent?: number[]) => (extent ? [{ value: (extent[0] + extent[1]) / 2}] : []) } }, tooltipDateInterval: false, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index 3c2343853c..fba1a9f8e2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -19,21 +19,23 @@ import { EChartsOption, EChartsSeriesItem, EChartsTooltipTrigger, - EChartsTooltipWidgetSettings, getYAxis, + EChartsTooltipWidgetSettings, measureThresholdLabelOffset } from '@home/components/widget/lib/chart/echarts-widget.models'; import { - autoDateFormat, AutoDateFormatSettings, + autoDateFormat, + AutoDateFormatSettings, ComponentStyle, Font, - simpleDateFormat, - textStyle, tsToFormatTimeUnit + textStyle, + tsToFormatTimeUnit } from '@shared/models/widget-settings.models'; import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared'; import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts'; import { formatValue, isDefinedAndNotNull, + isFunction, isNumeric, isUndefined, isUndefinedOrNull, @@ -42,7 +44,6 @@ import { } from '@core/utils'; import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; import tinycolor from 'tinycolor2'; -import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D'; import { ValueAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes'; import { SeriesLabelOption } from 'echarts/types/src/util/types'; import { @@ -320,6 +321,12 @@ export const defaultXAxisTicksFormat: AutoDateFormatSettings = { export type TimeSeriesChartYAxisId = 'default' | string; +export type TimeSeriesChartTicksGenerator = + (extent?: number[], interval?: number, niceTickExtent?: number[], intervalPrecision?: number) => {value: number}[]; + +export type TimeSeriesChartTicksFormatter = + (value: any) => string; + export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSettings { id?: TimeSeriesChartYAxisId; order?: number; @@ -329,8 +336,8 @@ export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSetting splitNumber?: number; min?: number | string; max?: number | string; - intervalCalculator?: string; - ticksFormatter?: string; + ticksGenerator?: TimeSeriesChartTicksGenerator | string; + ticksFormatter?: TimeSeriesChartTicksFormatter | string; } export const timeSeriesChartYAxisValid = (axis: TimeSeriesChartYAxisSettings): boolean => @@ -788,8 +795,6 @@ export interface TimeSeriesChartYAxis { decimals: number; settings: TimeSeriesChartYAxisSettings; option: YAXisOption & ValueAxisBaseOption; - intervalCalculator?: (axis: Axis2D) => number; - ticksFormatter?: (value: any) => string; } export const createTimeSeriesYAxis = (units: string, @@ -800,11 +805,37 @@ export const createTimeSeriesYAxis = (units: string, settings.tickLabelColor, darkMode, 'axis.tickLabel'); const yAxisNameStyle = createChartTextStyle(settings.labelFont, settings.labelColor, darkMode, 'axis.label'); - let ticksFormatter: (value: any) => string; - if (settings.ticksFormatter && settings.ticksFormatter.length) { - ticksFormatter = parseFunction(settings.ticksFormatter, ['value']); + + let ticksFormatter: TimeSeriesChartTicksFormatter; + if (settings.ticksFormatter) { + if (isFunction(settings.ticksFormatter)) { + ticksFormatter = settings.ticksFormatter as TimeSeriesChartTicksFormatter; + } else if (settings.ticksFormatter.length) { + ticksFormatter = parseFunction(settings.ticksFormatter, ['value']); + } + } + let ticksGenerator: TimeSeriesChartTicksGenerator; + let minInterval: number; + let interval: number; + let splitNumber: number; + if (settings.ticksGenerator) { + if (isFunction(settings.ticksGenerator)) { + ticksGenerator = settings.ticksGenerator as TimeSeriesChartTicksGenerator; + } else if (settings.ticksGenerator.length) { + ticksGenerator = parseFunction(settings.ticksGenerator, ['extent', 'interval', 'niceTickExtent', 'intervalPrecision']); + } } - const yAxis: TimeSeriesChartYAxis = { + if (!ticksGenerator) { + interval = settings.interval; + if (isUndefinedOrNull(interval)) { + if (isDefinedAndNotNull(settings.splitNumber)) { + splitNumber = settings.splitNumber; + } else { + minInterval = (1 / Math.pow(10, decimals)); + } + } + } + return { id: settings.id, decimals, settings, @@ -818,6 +849,10 @@ export const createTimeSeriesYAxis = (units: string, scale: true, min: settings.min, max: settings.max, + minInterval, + splitNumber, + interval, + ticksGenerator, name: settings.label, nameLocation: 'middle', nameRotate: settings.position === AxisPosition.left ? 90 : -90, @@ -853,7 +888,8 @@ export const createTimeSeriesYAxis = (units: string, if (ticksFormatter) { try { result = ticksFormatter(value); - } catch (_e) {} + } catch (_e) { + } } if (isUndefined(result)) { result = formatValue(value, decimals, units, false); @@ -869,54 +905,6 @@ export const createTimeSeriesYAxis = (units: string, } } }; - if (settings.intervalCalculator && settings.intervalCalculator.length) { - yAxis.intervalCalculator = parseFunction(settings.intervalCalculator, ['axis']); - } - return yAxis; -}; - -export const updateYAxisIntervals = (chart: ECharts, - yAxis: TimeSeriesChartYAxis, empty: boolean): boolean => { - let changed = false; - let interval: number; - let splitNumber: number; - let minInterval: number; - if (!empty) { - interval = calculateYAxisInterval(chart, yAxis); - if (isUndefinedOrNull(interval)) { - if (isDefinedAndNotNull(yAxis.settings.splitNumber)) { - splitNumber = yAxis.settings.splitNumber; - } else { - minInterval = (1 / Math.pow(10, yAxis.decimals)); - } - } - } - if (yAxis.option.interval !== interval) { - yAxis.option.interval = interval; - changed = true; - } - if (yAxis.option.splitNumber !== splitNumber) { - yAxis.option.splitNumber = splitNumber; - changed = true; - } - if (yAxis.option.minInterval !== minInterval) { - yAxis.option.minInterval = minInterval; - changed = true; - } - return changed; -}; - -const calculateYAxisInterval = (chart: ECharts, yAxis: TimeSeriesChartYAxis): number | undefined => { - let interval = yAxis.settings.interval; - if (yAxis.intervalCalculator) { - const axis = getYAxis(chart, yAxis.id); - if (axis) { - try { - interval = yAxis.intervalCalculator(axis); - } catch (_e) {} - } - } - return interval; }; export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartXAxisSettings, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 521a3ce06e..cbb6c2e3e0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -27,7 +27,8 @@ import { TimeSeriesChartDataItem, timeSeriesChartDefaultSettings, timeSeriesChartKeyDefaultSettings, - TimeSeriesChartKeySettings, TimeSeriesChartNoAggregationBarWidthStrategy, + TimeSeriesChartKeySettings, + TimeSeriesChartNoAggregationBarWidthStrategy, TimeSeriesChartSeriesType, TimeSeriesChartSettings, TimeSeriesChartShape, @@ -39,7 +40,7 @@ import { TimeSeriesChartYAxis, TimeSeriesChartYAxisId, TimeSeriesChartYAxisSettings, - updateDarkMode, updateYAxisIntervals + updateDarkMode } from '@home/components/widget/lib/chart/time-series-chart.models'; import { ResizeObserver } from '@juggle/resize-observer'; import { @@ -611,11 +612,6 @@ export class TbTimeSeriesChart { this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis', 'xAxis', 'grid'], lazyUpdate: true}); } - changed = this.updateYAxesIntervals(this.yAxisList); - if (changed) { - this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); - this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis'], lazyUpdate: true}); - } if (this.yAxisList.length) { const extent = getAxisExtent(this.timeSeriesChart, this.yAxisList[0].id); const min = extent[0]; @@ -680,9 +676,6 @@ export class TbTimeSeriesChart { yAxis.option.scale = scaleYAxis; changed = true; } - if (updateYAxisIntervals(this.timeSeriesChart, yAxis, this.yAxisEmpty(yAxis))) { - changed = true; - } } return changed; } @@ -693,22 +686,6 @@ export class TbTimeSeriesChart { return !axisBarDataItems.length; } - private yAxisEmpty(yAxis: TimeSeriesChartYAxis): boolean { - const axisDataItems = this.dataItems.filter(d => d.yAxisId === yAxis.id && d.enabled && - d.data.length); - return !axisDataItems.length; - } - - private updateYAxesIntervals(axisList: TimeSeriesChartYAxis[]): boolean { - let changed = false; - for (const yAxis of axisList) { - if (updateYAxisIntervals(this.timeSeriesChart, yAxis, this.yAxisEmpty(yAxis))) { - changed = true; - } - } - return changed; - } - private minTopOffset(): number { const showTickLabels = !!this.yAxisList.find(yAxis => yAxis.settings.show && yAxis.settings.showTickLabels); From 43de32fe9dec01c3ca9e372990bfe34debb81603 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 14 Mar 2024 12:38:43 +0200 Subject: [PATCH 193/209] UI: Automatically close default dialogs (Alert/Confirm/Error) on route change --- .../src/app/core/services/dialog.service.ts | 37 ++++++++++--------- .../dialog/alert-dialog.component.ts | 14 +++++-- .../dialog/confirm-dialog.component.ts | 14 +++++-- .../dialog/error-alert-dialog.component.ts | 11 +++++- 4 files changed, 51 insertions(+), 25 deletions(-) diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts index 011a21987f..8a2de4a694 100644 --- a/ui-ngx/src/app/core/services/dialog.service.ts +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -21,22 +21,25 @@ import { TranslateService } from '@ngx-translate/core'; import { AuthService } from '@core/auth/auth.service'; import { ColorPickerDialogComponent, - ColorPickerDialogData, ColorPickerDialogResult + ColorPickerDialogData, + ColorPickerDialogResult } from '@shared/components/dialog/color-picker-dialog.component'; import { MaterialIconsDialogComponent, - MaterialIconsDialogData, MaterialIconsDialogResult + MaterialIconsDialogData, + MaterialIconsDialogResult } from '@shared/components/dialog/material-icons-dialog.component'; -import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component'; -import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; -import { ErrorAlertDialogComponent } from '@shared/components/dialog/error-alert-dialog.component'; +import { ConfirmDialogComponent, ConfirmDialogData } from '@shared/components/dialog/confirm-dialog.component'; +import { AlertDialogComponent, AlertDialogData } from '@shared/components/dialog/alert-dialog.component'; +import { + ErrorAlertDialogComponent, + ErrorAlertDialogData +} from '@shared/components/dialog/error-alert-dialog.component'; import { TodoDialogComponent } from '@shared/components/dialog/todo-dialog.component'; -@Injectable( - { - providedIn: 'root' - } -) +@Injectable({ + providedIn: 'root' +}) export class DialogService { constructor( @@ -47,7 +50,7 @@ export class DialogService { } confirm(title: string, message: string, cancel: string = null, ok: string = null, fullscreen: boolean = false): Observable { - const dialogConfig: MatDialogConfig = { + const dialogConfig: MatDialogConfig = { disableClose: true, data: { title, @@ -59,12 +62,12 @@ export class DialogService { if (fullscreen) { dialogConfig.panelClass = ['tb-fullscreen-dialog']; } - const dialogRef = this.dialog.open(ConfirmDialogComponent, dialogConfig); + const dialogRef = this.dialog.open(ConfirmDialogComponent, dialogConfig); return dialogRef.afterClosed(); } alert(title: string, message: string, ok: string = null, fullscreen: boolean = false): Observable { - const dialogConfig: MatDialogConfig = { + const dialogConfig: MatDialogConfig = { disableClose: true, data: { title, @@ -75,12 +78,12 @@ export class DialogService { if (fullscreen) { dialogConfig.panelClass = ['tb-fullscreen-dialog']; } - const dialogRef = this.dialog.open(AlertDialogComponent, dialogConfig); + const dialogRef = this.dialog.open(AlertDialogComponent, dialogConfig); return dialogRef.afterClosed(); } - errorAlert(title: string, message: string, error: any, ok: string = null, fullscreen: boolean = false): Observable { - const dialogConfig: MatDialogConfig = { + errorAlert(title: string, message: string, error: any, ok: string = null, fullscreen: boolean = false): Observable { + const dialogConfig: MatDialogConfig = { disableClose: true, data: { title, @@ -92,7 +95,7 @@ export class DialogService { if (fullscreen) { dialogConfig.panelClass = ['tb-fullscreen-dialog']; } - const dialogRef = this.dialog.open(ErrorAlertDialogComponent, dialogConfig); + const dialogRef = this.dialog.open(ErrorAlertDialogComponent, dialogConfig); return dialogRef.afterClosed(); } diff --git a/ui-ngx/src/app/shared/components/dialog/alert-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/alert-dialog.component.ts index da539bd328..c0e78cb968 100644 --- a/ui-ngx/src/app/shared/components/dialog/alert-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/alert-dialog.component.ts @@ -16,6 +16,10 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; export interface AlertDialogData { title: string; @@ -29,7 +33,11 @@ export interface AlertDialogData { templateUrl: './alert-dialog.component.html', styleUrls: ['./alert-dialog.component.scss'] }) -export class AlertDialogComponent { - constructor(public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: AlertDialogData) {} +export class AlertDialogComponent extends DialogComponent{ + constructor(protected store: Store, + protected router: Router, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: AlertDialogData) { + super(store, router, dialogRef); + } } diff --git a/ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.ts index d315dad6a9..e2cf5ec7d7 100644 --- a/ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.ts @@ -16,6 +16,10 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; export interface ConfirmDialogData { title: string; @@ -30,7 +34,11 @@ export interface ConfirmDialogData { templateUrl: './confirm-dialog.component.html', styleUrls: ['./confirm-dialog.component.scss'] }) -export class ConfirmDialogComponent { - constructor(public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: ConfirmDialogData) {} +export class ConfirmDialogComponent extends DialogComponent{ + constructor(protected store: Store, + protected router: Router, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: ConfirmDialogData) { + super(store, router, dialogRef); + } } diff --git a/ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.ts index f8a3859b44..e1418f26ea 100644 --- a/ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/error-alert-dialog.component.ts @@ -16,6 +16,10 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { DialogComponent } from '@shared/components/dialog.component'; export interface ErrorAlertDialogData { title: string; @@ -29,15 +33,18 @@ export interface ErrorAlertDialogData { templateUrl: './error-alert-dialog.component.html', styleUrls: ['./error-alert-dialog.component.scss'] }) -export class ErrorAlertDialogComponent { +export class ErrorAlertDialogComponent extends DialogComponent{ title: string; message: string; errorMessage: string; errorDetails?: string; - constructor(public dialogRef: MatDialogRef, + constructor(protected store: Store, + protected router: Router, + public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: ErrorAlertDialogData) { + super(store, router, dialogRef); this.title = this.data.title; this.message = this.data.message; this.errorMessage = this.data.error.message ? this.data.error.message : JSON.stringify(this.data.error); From f49e7c5c0bf2548368bbf401b93a5121b1b6f09d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 14 Mar 2024 15:37:44 +0200 Subject: [PATCH 194/209] UI: Improved notification settings behavior --- ...otification-template-configuration.component.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html index 8297985a16..21b660010d 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html @@ -30,7 +30,7 @@
- + {{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.WEB).icon }} @@ -83,7 +83,7 @@
- + {{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MOBILE_APP).icon }} @@ -148,7 +148,7 @@
- + {{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SMS).icon }} @@ -180,7 +180,7 @@
- + {{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.EMAIL).icon }} @@ -210,7 +210,7 @@
- + {{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SLACK).icon }} @@ -236,7 +236,7 @@
- + {{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MICROSOFT_TEAMS).icon }} From 0fcb5aeaba60d7eafb5eb87ae0597f689b09bb00 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 14 Mar 2024 15:38:36 +0200 Subject: [PATCH 195/209] Suppress JWT token expired error in websocket handler. --- .../server/controller/plugin/TbWebSocketHandler.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 64536ddb54..90dea7cc31 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -46,6 +46,7 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider; +import org.thingsboard.server.service.security.exception.JwtExpiredTokenException; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.subscription.SubscriptionErrorCode; @@ -232,6 +233,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } catch (InvalidParameterException e) { log.warn("[{}] Failed to start session", session.getId(), e); session.close(CloseStatus.BAD_DATA.withReason(e.getMessage())); + } catch (JwtExpiredTokenException e) { + log.trace("[{}] Failed to start session", session.getId(), e); + session.close(CloseStatus.SERVER_ERROR.withReason(e.getMessage())); } catch (Exception e) { log.warn("[{}] Failed to start session", session.getId(), e); session.close(CloseStatus.SERVER_ERROR.withReason(e.getMessage())); From e58906dd7ab16025c41f2b229c78e6c79f4430c0 Mon Sep 17 00:00:00 2001 From: rusikv Date: Thu, 14 Mar 2024 17:00:13 +0200 Subject: [PATCH 196/209] UI: added sorting to 'Originator' column for alarm talbe --- .../home/models/entity/entities-table-config.models.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts index e1a7700d67..9dd434101d 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts @@ -120,8 +120,9 @@ export class EntityLinkTableColumn> extends BaseEntity public title: string, public width: string = '0px', public cellContentFunction: CellContentFunction = (entity, property) => entity[property] ? entity[property] : '', - public entityURL: (entity) => string) { - super('link', key, title, width, false); + public entityURL: (entity) => string, + public sortable: boolean = true) { + super('link', key, title, width, sortable); } } From 8ef012bfda08843d4b9701de42b559e6f5d113f6 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 15 Mar 2024 10:51:32 +0200 Subject: [PATCH 197/209] UI: Fixed expand notification delivery template when editing --- ...ation-template-configuration.component.html | 14 +++++++------- ...ication-template-configuration.component.ts | 18 +++++++++++++++++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html index 21b660010d..88d31996ca 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html @@ -30,7 +30,7 @@
- + {{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.WEB).icon }} @@ -83,7 +83,7 @@
- + {{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MOBILE_APP).icon }} @@ -138,7 +138,7 @@
@@ -148,7 +148,7 @@
- + {{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SMS).icon }} @@ -180,7 +180,7 @@
- + {{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.EMAIL).icon }} @@ -210,7 +210,7 @@
- + {{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.SLACK).icon }} @@ -236,7 +236,7 @@
- + {{ NotificationDeliveryMethodInfoMap.get(NotificationDeliveryMethod.MICROSOFT_TEAMS).icon }} diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.ts b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.ts index 0adb1a557f..eac2dc5a7e 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.ts @@ -66,6 +66,7 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co if (isDefinedAndNotNull(value)) { this.templateConfigurationForm.patchValue(value, {emitEvent: false}); this.updateDisabledForms(); + this.updateExpandedForm(); this.templateConfigurationForm.updateValueAndValidity(); } } @@ -96,6 +97,7 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co private propagateChange = (v: any) => { }; private readonly destroy$ = new Subject(); + private expendedBlocks: NotificationDeliveryMethod[]; constructor(private fb: FormBuilder, private translate: TranslateService) { @@ -122,6 +124,7 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co } } this.templateConfigurationForm.patchValue(settings, {emitEvent: false}); + this.updateExpandedForm(); } registerOnChange(fn: any): void { @@ -147,7 +150,7 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co }; } - get hotificationTapActionHint(): string { + get notificationTapActionHint(): string { switch (this.notificationType) { case NotificationType.ALARM: case NotificationType.ALARM_ASSIGNMENT: @@ -157,6 +160,19 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co return ''; } + expandedForm(name: NotificationDeliveryMethod): boolean { + return this.expendedBlocks.includes(name); + } + + private updateExpandedForm() { + this.expendedBlocks = []; + Object.keys(this.templateConfigurationForm.controls).forEach((name: NotificationDeliveryMethod) => { + if (this.templateConfigurationForm.get(name).invalid) { + this.expendedBlocks.push(name); + } + }); + } + private updateDisabledForms(){ Object.values(NotificationDeliveryMethod).forEach((method) => { const form = this.templateConfigurationForm.get(method); From 883eebe43025fa5ceaa5e8d7369b6a7995a4d67b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 15 Mar 2024 15:28:00 +0200 Subject: [PATCH 198/209] Add APNS config for push notification in mobile phone --- .../notification/provider/DefaultFirebaseService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultFirebaseService.java b/application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultFirebaseService.java index a1a02d52f1..b64d0b586d 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultFirebaseService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultFirebaseService.java @@ -22,6 +22,8 @@ import com.google.auth.oauth2.GoogleCredentials; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.messaging.AndroidConfig; +import com.google.firebase.messaging.ApnsConfig; +import com.google.firebase.messaging.Aps; import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.FirebaseMessagingException; import com.google.firebase.messaging.Message; @@ -71,6 +73,11 @@ public class DefaultFirebaseService implements FirebaseService { .setAndroidConfig(AndroidConfig.builder() .setPriority(AndroidConfig.Priority.HIGH) .build()) + .setApnsConfig(ApnsConfig.builder() + .setAps(Aps.builder() + .setContentAvailable(true) + .build()) + .build()) .putAllData(data) .build(); firebaseContext.getMessaging().send(message); From 50f89510727155aaa620492ae90f62abc449ca4a Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 15 Mar 2024 15:49:29 +0200 Subject: [PATCH 199/209] Add callback executor for device state service --- .../service/state/DefaultDeviceStateService.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 30c59b3da8..f1bf10e2a2 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -191,6 +191,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService deviceStates = new ConcurrentHashMap<>(); @@ -199,6 +200,8 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService future; if (persistToTelemetry) { ListenableFuture> tsData = tsService.findLatest(TenantId.SYS_TENANT_ID, device.getId(), PERSISTENT_ATTRIBUTES); - future = Futures.transform(tsData, extractDeviceStateData(device), deviceStateExecutor); + future = Futures.transform(tsData, extractDeviceStateData(device), MoreExecutors.directExecutor()); } else { ListenableFuture> attrData = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), SERVER_SCOPE, PERSISTENT_ATTRIBUTES); - future = Futures.transform(attrData, extractDeviceStateData(device), deviceStateExecutor); + future = Futures.transform(attrData, extractDeviceStateData(device), MoreExecutors.directExecutor()); } return transformInactivityTimeout(future); } @@ -656,8 +662,8 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService Function, DeviceStateData> extractDeviceStateData(Device device) { From 53071ebaecdbc387f2471bb6eeb2e147f5e6cedf Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 15 Mar 2024 17:43:04 +0200 Subject: [PATCH 200/209] UI: Improve time series charts stack handling. Decrease default animation duration parameter to 500ms. --- .../json/system/widget_types/bar_chart.json | 2 +- .../json/system/widget_types/line_chart.json | 2 +- .../json/system/widget_types/point_chart.json | 2 +- .../widget_types/time_series_chart.json | 2 +- ui-ngx/patches/echarts+5.5.0.patch | 96 +++++++++++++++++-- .../widget/lib/chart/echarts-widget.models.ts | 3 + .../lib/chart/time-series-chart.models.ts | 30 +++--- .../widget/lib/chart/time-series-chart.ts | 18 ++-- 8 files changed, 116 insertions(+), 39 deletions(-) diff --git a/application/src/main/data/json/system/widget_types/bar_chart.json b/application/src/main/data/json/system/widget_types/bar_chart.json index b3791f53a1..264c314686 100644 --- a/application/src/main/data/json/system/widget_types/bar_chart.json +++ b/application/src/main/data/json/system/widget_types/bar_chart.json @@ -20,7 +20,7 @@ "latestDataKeySettingsDirective": "", "hasBasicMode": true, "basicModeDirective": "tb-time-series-chart-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":null,\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0}},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":1000,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"padding\":\"12px\"},\"title\":\"Bar chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":null,\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0}},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":500,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"padding\":\"12px\"},\"title\":\"Bar chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" }, "tags": [ "chart", diff --git a/application/src/main/data/json/system/widget_types/line_chart.json b/application/src/main/data/json/system/widget_types/line_chart.json index 273281e17a..f5b123231c 100644 --- a/application/src/main/data/json/system/widget_types/line_chart.json +++ b/application/src/main/data/json/system/widget_types/line_chart.json @@ -20,7 +20,7 @@ "latestDataKeySettingsDirective": "", "hasBasicMode": true, "basicModeDirective": "tb-time-series-chart-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":true,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":false,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"emptyCircle\",\"pointSize\":4,\"fillAreaSettings\":{\"type\":\"opacity\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":true,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":false,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"emptyCircle\",\"pointSize\":4,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.3409583261715494,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"decimals\":null,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":null,\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0}},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":1000,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"padding\":\"12px\"},\"title\":\"Line chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":true,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":false,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"emptyCircle\",\"pointSize\":4,\"fillAreaSettings\":{\"type\":\"opacity\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":true,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":false,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"emptyCircle\",\"pointSize\":4,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.3409583261715494,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"decimals\":null,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":null,\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0}},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":500,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"padding\":\"12px\"},\"title\":\"Line chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" }, "tags": [ "chart", diff --git a/application/src/main/data/json/system/widget_types/point_chart.json b/application/src/main/data/json/system/widget_types/point_chart.json index d692570136..beb99116c9 100644 --- a/application/src/main/data/json/system/widget_types/point_chart.json +++ b/application/src/main/data/json/system/widget_types/point_chart.json @@ -20,7 +20,7 @@ "latestDataKeySettingsDirective": "", "hasBasicMode": true, "basicModeDirective": "tb-time-series-chart-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":false,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":true,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"circle\",\"pointSize\":8,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":false,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":true,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"circle\",\"pointSize\":8,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":null,\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0}},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":1000,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"padding\":\"12px\"},\"title\":\"Point chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":false,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":true,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"circle\",\"pointSize\":8,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":false,\"step\":false,\"stepType\":\"start\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":true,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"pointShape\":\"circle\",\"pointSize\":8,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}}},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":null,\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0}},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":500,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"padding\":\"12px\"},\"title\":\"Point chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" }, "tags": [ "chart", diff --git a/application/src/main/data/json/system/widget_types/time_series_chart.json b/application/src/main/data/json/system/widget_types/time_series_chart.json index 1c215e21c8..9b2069ca6b 100644 --- a/application/src/main/data/json/system/widget_types/time_series_chart.json +++ b/application/src/main/data/json/system/widget_types/time_series_chart.json @@ -20,7 +20,7 @@ "latestDataKeySettingsDirective": "", "hasBasicMode": true, "basicModeDirective": "tb-time-series-chart-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":null,\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0}},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":1000,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"padding\":\"12px\"},\"title\":\"Time series chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#FFC107\",\"settings\":{\"type\":\"bar\"},\"_hash\":0.5534217244004682,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"top\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"yAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":null,\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0}},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":500,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"padding\":\"12px\"},\"title\":\"Time series chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}" }, "tags": [ "chart", diff --git a/ui-ngx/patches/echarts+5.5.0.patch b/ui-ngx/patches/echarts+5.5.0.patch index f74beb7ea8..4f1ebed81a 100644 --- a/ui-ngx/patches/echarts+5.5.0.patch +++ b/ui-ngx/patches/echarts+5.5.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/echarts/lib/component/dataZoom/DataZoomModel.js b/node_modules/echarts/lib/component/dataZoom/DataZoomModel.js -index d6c05f3..d09baed 100644 +index d6c05f3..aafb0b8 100644 --- a/node_modules/echarts/lib/component/dataZoom/DataZoomModel.js +++ b/node_modules/echarts/lib/component/dataZoom/DataZoomModel.js @@ -362,7 +362,10 @@ var DataZoomModel = /** @class */function (_super) { @@ -28,7 +28,7 @@ index d6c05f3..d09baed 100644 } } diff --git a/node_modules/echarts/lib/component/dataZoom/InsideZoomView.js b/node_modules/echarts/lib/component/dataZoom/InsideZoomView.js -index 06469b2..ccfbfa6 100644 +index 06469b2..cf0b2ea 100644 --- a/node_modules/echarts/lib/component/dataZoom/InsideZoomView.js +++ b/node_modules/echarts/lib/component/dataZoom/InsideZoomView.js @@ -96,11 +96,14 @@ var getRangeHandlers = { @@ -52,7 +52,7 @@ index 06469b2..ccfbfa6 100644 }, pan: makeMover(function (range, axisModel, coordSysInfo, coordSysMainType, controller, e) { diff --git a/node_modules/echarts/lib/component/dataZoom/SliderZoomView.js b/node_modules/echarts/lib/component/dataZoom/SliderZoomView.js -index 98912e0..2e809af 100644 +index 98912e0..0cda6be 100644 --- a/node_modules/echarts/lib/component/dataZoom/SliderZoomView.js +++ b/node_modules/echarts/lib/component/dataZoom/SliderZoomView.js @@ -64,7 +64,7 @@ var DEFAULT_MOVE_HANDLE_SIZE = 7; @@ -147,7 +147,7 @@ index 98912e0..2e809af 100644 SliderZoomView.prototype._updateView = function (nonRealtime) { var displaybles = this._displayables; diff --git a/node_modules/echarts/lib/component/dataZoom/dataZoomProcessor.js b/node_modules/echarts/lib/component/dataZoom/dataZoomProcessor.js -index ce98fed..361b138 100644 +index ce98fed..e154118 100644 --- a/node_modules/echarts/lib/component/dataZoom/dataZoomProcessor.js +++ b/node_modules/echarts/lib/component/dataZoom/dataZoomProcessor.js @@ -90,7 +90,10 @@ var dataZoomProcessor = { @@ -175,7 +175,7 @@ index ce98fed..361b138 100644 }); ecModel.eachComponent('dataZoom', function (dataZoomModel) { diff --git a/node_modules/echarts/lib/component/toolbox/feature/DataZoom.js b/node_modules/echarts/lib/component/toolbox/feature/DataZoom.js -index cf8d6bc..9b30ec1 100644 +index cf8d6bc..f9b9f90 100644 --- a/node_modules/echarts/lib/component/toolbox/feature/DataZoom.js +++ b/node_modules/echarts/lib/component/toolbox/feature/DataZoom.js @@ -109,9 +109,12 @@ var DataZoomFeature = /** @class */function (_super) { @@ -195,7 +195,7 @@ index cf8d6bc..9b30ec1 100644 dataZoomModel && (snapshot[dataZoomModel.id] = { dataZoomId: dataZoomModel.id, diff --git a/node_modules/echarts/lib/component/tooltip/TooltipView.js b/node_modules/echarts/lib/component/tooltip/TooltipView.js -index b8a9b95..8e4cb2f 100644 +index b8a9b95..11e49c0 100644 --- a/node_modules/echarts/lib/component/tooltip/TooltipView.js +++ b/node_modules/echarts/lib/component/tooltip/TooltipView.js @@ -360,7 +360,7 @@ var TooltipView = /** @class */function (_super) { @@ -208,7 +208,7 @@ index b8a9b95..8e4cb2f 100644 } var axisValueLabel = axisPointerViewHelper.getValueLabel(axisValue, axisModel.axis, ecModel, axisItem.seriesDataIndices, axisItem.valueLabelOpt); diff --git a/node_modules/echarts/lib/coord/axisHelper.js b/node_modules/echarts/lib/coord/axisHelper.js -index a76c66b..be22cb0 100644 +index a76c66b..e5b7ee5 100644 --- a/node_modules/echarts/lib/coord/axisHelper.js +++ b/node_modules/echarts/lib/coord/axisHelper.js @@ -187,7 +187,9 @@ export function createScaleByModel(model, axisType) { @@ -223,7 +223,7 @@ index a76c66b..be22cb0 100644 } } diff --git a/node_modules/echarts/lib/coord/cartesian/Grid.js b/node_modules/echarts/lib/coord/cartesian/Grid.js -index 5b18f02..4960e67 100644 +index 5b18f02..39a57f8 100644 --- a/node_modules/echarts/lib/coord/cartesian/Grid.js +++ b/node_modules/echarts/lib/coord/cartesian/Grid.js @@ -91,11 +91,11 @@ var Grid = /** @class */function () { @@ -259,8 +259,76 @@ index 5b18f02..4960e67 100644 each(axisNeedsAlign, function (axis) { alignScaleTicks(axis.scale, axis.model, alignTo.scale); }); +diff --git a/node_modules/echarts/lib/data/SeriesData.js b/node_modules/echarts/lib/data/SeriesData.js +index 98d5ce8..1c293a6 100644 +--- a/node_modules/echarts/lib/data/SeriesData.js ++++ b/node_modules/echarts/lib/data/SeriesData.js +@@ -900,13 +900,16 @@ var SeriesData = /** @class */function () { + var dimInfo = data._dimInfos[dim]; + // Currently, only dimensions that has ordinalMeta can create inverted indices. + var ordinalMeta = dimInfo.ordinalMeta; ++ var stack = dimInfo.stack; + var store = data._store; +- if (ordinalMeta) { +- invertedIndices = invertedIndicesMap[dim] = new CtorInt32Array(ordinalMeta.categories.length); +- // The default value of TypedArray is 0. To avoid miss +- // mapping to 0, we should set it as INDEX_NOT_FOUND. +- for (var i = 0; i < invertedIndices.length; i++) { +- invertedIndices[i] = INDEX_NOT_FOUND; ++ if (ordinalMeta || stack) { ++ invertedIndices = invertedIndicesMap[dim] = stack ? new Array(store.count()) : new CtorInt32Array(ordinalMeta.categories.length); ++ if (ordinalMeta) { ++ // The default value of TypedArray is 0. To avoid miss ++ // mapping to 0, we should set it as INDEX_NOT_FOUND. ++ for (var i = 0; i < invertedIndices.length; i++) { ++ invertedIndices[i] = INDEX_NOT_FOUND; ++ } + } + for (var i = 0; i < store.count(); i++) { + // Only support the case that all values are distinct. +diff --git a/node_modules/echarts/lib/data/Source.js b/node_modules/echarts/lib/data/Source.js +index 7dda49b..2dd2b98 100644 +--- a/node_modules/echarts/lib/data/Source.js ++++ b/node_modules/echarts/lib/data/Source.js +@@ -252,7 +252,8 @@ function normalizeDimensionsOption(dimensionsDefine) { + var item = { + name: rawItem.name, + displayName: rawItem.displayName, +- type: rawItem.type ++ type: rawItem.type, ++ stack: rawItem.stack + }; + // User can set null in dimensions. + // We don't auto specify name, otherwise a given name may +diff --git a/node_modules/echarts/lib/data/helper/createDimensions.js b/node_modules/echarts/lib/data/helper/createDimensions.js +index 00d7eb7..dd514b1 100644 +--- a/node_modules/echarts/lib/data/helper/createDimensions.js ++++ b/node_modules/echarts/lib/data/helper/createDimensions.js +@@ -110,6 +110,9 @@ source, opt) { + } + dimDefItem.type != null && (resultItem.type = dimDefItem.type); + dimDefItem.displayName != null && (resultItem.displayName = dimDefItem.displayName); ++ if (dimDefItem.stack) { ++ resultItem.stack = true; ++ } + var newIdx = resultList.length; + indicesMap[dimIdx] = newIdx; + resultItem.storeDimIndex = dimIdx; +diff --git a/node_modules/echarts/lib/data/helper/dataStackHelper.js b/node_modules/echarts/lib/data/helper/dataStackHelper.js +index c25de1b..ea8300d 100644 +--- a/node_modules/echarts/lib/data/helper/dataStackHelper.js ++++ b/node_modules/echarts/lib/data/helper/dataStackHelper.js +@@ -91,7 +91,7 @@ export function enableDataStack(seriesModel, dimensionsInput, opt) { + } + if (mayStack && !dimensionInfo.isExtraCoord) { + // Find the first ordinal dimension as the stackedByDimInfo. +- if (!byIndex && !stackedByDimInfo && dimensionInfo.ordinalMeta) { ++ if (!byIndex && !stackedByDimInfo && (dimensionInfo.ordinalMeta || dimensionInfo.stack)) { + stackedByDimInfo = dimensionInfo; + } + // Find the first stackable dimension as the stackedDimInfo. diff --git a/node_modules/echarts/lib/scale/Interval.js b/node_modules/echarts/lib/scale/Interval.js -index 1094662..8f4e07a 100644 +index 1094662..363c0a5 100644 --- a/node_modules/echarts/lib/scale/Interval.js +++ b/node_modules/echarts/lib/scale/Interval.js @@ -46,12 +46,17 @@ import * as numberUtil from '../util/number.js'; @@ -303,7 +371,7 @@ index 1094662..8f4e07a 100644 if (!interval) { return ticks; diff --git a/node_modules/echarts/types/dist/shared.d.ts b/node_modules/echarts/types/dist/shared.d.ts -index ca74097..98f8b18 100644 +index ca74097..ef41ce2 100644 --- a/node_modules/echarts/types/dist/shared.d.ts +++ b/node_modules/echarts/types/dist/shared.d.ts @@ -2422,6 +2422,9 @@ interface AxisBaseOptionCommon extends ComponentOption, AnimationOptionMixin { @@ -325,6 +393,14 @@ index ca74097..98f8b18 100644 } interface CategoryAxisBaseOption extends AxisBaseOptionCommon { type?: 'category'; +@@ -6412,6 +6417,7 @@ declare type DimensionDefinition = { + type?: DataStoreDimensionType; + name?: DimensionName; + displayName?: string; ++ stack?: boolean; + }; + declare type DimensionDefinitionLoose = DimensionDefinition['name'] | DimensionDefinition; + declare const SOURCE_FORMAT_ORIGINAL: "original"; diff --git a/node_modules/echarts/types/src/coord/axisCommonTypes.d.ts b/node_modules/echarts/types/src/coord/axisCommonTypes.d.ts index c5c2792..d524b70 100644 --- a/node_modules/echarts/types/src/coord/axisCommonTypes.d.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts index bac5616506..7fdd1fbfff 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts @@ -340,6 +340,9 @@ export const echartsTooltipFormatter = (renderer: Renderer2, return null; } const firstParam = Array.isArray(params) ? params[0] : params; + if (!firstParam.value) { + return null; + } const tooltipElement: HTMLElement = renderer.createElement('div'); renderer.setStyle(tooltipElement, 'display', 'flex'); renderer.setStyle(tooltipElement, 'flex-direction', 'column'); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index fba1a9f8e2..6f985466c4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -615,7 +615,7 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { animation: { animation: true, animationThreshold: 2000, - animationDuration: 1000, + animationDuration: 500, animationEasing: TimeSeriesChartAnimationEasing.cubicOut, animationDelay: 0, animationDurationUpdate: 300, @@ -1023,7 +1023,7 @@ const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[], let seriesOption = item.option; if (!item.option) { const thresholdLabelStyle = createChartTextStyle(item.settings.labelFont, - item.settings.labelColor, darkMode, 'threshold.label'); + item.settings.labelColor, false, 'threshold.label'); seriesOption = { type: 'line', id: item.id, @@ -1036,7 +1036,7 @@ const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[], }, lineStyle: { width: item.settings.lineWidth, - color: prepareChartThemeColor(item.settings.lineColor, darkMode, 'threshold.line'), + color: prepareChartThemeColor(item.settings.lineColor, false, 'threshold.line'), type: item.settings.lineType }, label: { @@ -1175,17 +1175,6 @@ export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChart } } } - for (const item of thresholdDataItems) { - if (Array.isArray(options.series)) { - const series = options.series.find(s => s.id === item.id); - if (series) { - series.markLine.lineStyle.color = prepareChartThemeColor(item.settings.lineColor, darkMode, 'threshold.line'); - if (series.markLine?.label?.show) { - series.markLine.label.color = prepareChartThemeColor(item.settings.labelColor, darkMode, 'threshold.label'); - } - } - } - } return options; }; @@ -1209,12 +1198,14 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem, focus: 'series' }, dimensions: [ - {name: 'intervalStart', type: 'number'}, - {name: 'intervalEnd', type: 'number'} + {name: 'x', type: 'time', stack}, + {name: 'y', type: 'float'}, + {name: 'intervalStart', type: 'time'}, + {name: 'intervalEnd', type: 'time'} ], encode: { - intervalStart: 2, - intervalEnd: 3 + x: [0, 2, 3], + y: [1] } }; item.option = seriesOption; @@ -1226,6 +1217,9 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem, lineSettings.pointLabelFont, lineSettings.pointLabelColor, lineSettings.pointLabelPosition, darkMode); lineSeriesOption.step = lineSettings.step ? lineSettings.stepType : false; lineSeriesOption.smooth = lineSettings.smooth; + if (lineSettings.smooth) { + lineSeriesOption.smoothMonotone = 'x'; + } lineSeriesOption.lineStyle = { width: lineSettings.showLine ? lineSettings.lineWidth : 0, type: lineSettings.lineType diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index cbb6c2e3e0..462825087e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -117,7 +117,7 @@ export class TbTimeSeriesChart { private darkMode = false; - private messageChannel = new BroadcastChannel('tbMessageChannel'); + private darkModeObserver: MutationObserver; private topPointLabels = false; @@ -139,7 +139,8 @@ export class TbTimeSeriesChart { this.settings = mergeDeep({} as TimeSeriesChartSettings, timeSeriesChartDefaultSettings, this.inputSettings as TimeSeriesChartSettings); - this.darkMode = this.settings.darkMode; + const dashboardPageElement = this.ctx.$containerParent.parents('.tb-dashboard-page'); + this.darkMode = this.settings.darkMode || dashboardPageElement.hasClass('dark'); this.setupYAxes(); this.setupData(); this.setupThresholds(); @@ -153,12 +154,15 @@ export class TbTimeSeriesChart { }); this.shapeResize$.observe(this.chartElement); } - this.messageChannel.addEventListener('message', (event) => { - if (event?.data?.type === 'tbDarkMode') { - const darkMode = !!event?.data?.darkMode; - this.setDarkMode(darkMode); + this.darkModeObserver = new MutationObserver(mutations => { + for(let mutation of mutations) { + if (mutation.type === 'attributes' && mutation.attributeName === 'class') { + const darkMode = dashboardPageElement.hasClass('dark'); + this.setDarkMode(darkMode); + } } }); + this.darkModeObserver.observe(dashboardPageElement[0], { attributes: true }); } public update(): void { @@ -265,7 +269,7 @@ export class TbTimeSeriesChart { } this.yMinSubject.complete(); this.yMaxSubject.complete(); - this.messageChannel.close(); + this.darkModeObserver.disconnect(); } public resize(): void { From 59a2f771c6252b1e8800e24a4fcd3db21f0959b4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 18 Mar 2024 12:20:42 +0200 Subject: [PATCH 201/209] Update UI help link --- application/src/main/resources/thingsboard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index fd006fe801..8664eaa054 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -193,7 +193,7 @@ ui: # Help parameters help: # Base URL for UI help assets - base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.6.2}" + base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.6.3}" # Database telemetry parameters database: From 5da95a15a27cf90b21a0c65c703a7ae99a9cc84f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 18 Mar 2024 12:29:57 +0200 Subject: [PATCH 202/209] Update rule nodes UI --- .../static/rulenode/rulenode-core-config.js | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 7e161bb6ba..2e50246059 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,25 +1,25 @@ -System.register(["@angular/core","@shared/public-api","@ngrx/store","@angular/forms","@angular/common","@angular/material/checkbox","@angular/material/input","@angular/material/form-field","@angular/flex-layout/flex","@ngx-translate/core","@angular/material/select","@angular/material/core","@angular/material/slide-toggle","@shared/components/hint-tooltip-icon.component","@core/public-api","@shared/components/js-func.component","@angular/material/button","@angular/material/icon","@angular/material/tooltip","@shared/components/script-lang.component","@angular/cdk/keycodes","@angular/material/chips","@shared/pipe/safe.pipe","@shared/components/entity/entity-type-select.component","@shared/components/entity/entity-select.component","@shared/components/toggle-header.component","@shared/components/toggle-select.component","@angular/cdk/coercion","@shared/components/tb-error.component","@angular/flex-layout/extended","@angular/material/list","@angular/cdk/drag-drop","rxjs/operators","@angular/material/autocomplete","@shared/pipe/highlight.pipe","@home/components/public-api","tslib","rxjs","@shared/components/help-popup.component","@shared/components/entity/entity-subtype-list.component","@shared/components/relation/relation-type-autocomplete.component","@home/components/relation/relation-filters.component","@angular/material/expansion","@shared/components/file-input.component","@shared/components/button/toggle-password.component","@shared/components/string-items-list.component","@shared/components/entity/entity-list.component","@shared/components/notification/template-autocomplete.component","@shared/components/tb-checkbox.component","@home/components/sms/sms-provider-configuration.component","@angular/material/radio","@shared/components/slack-conversation-autocomplete.component","@shared/components/entity/entity-autocomplete.component","@shared/components/entity/entity-type-list.component","@angular/cdk/platform"],(function(e){"use strict";var t,n,r,o,a,i,l,s,m,p,d,u,c,f,g,y,x,b,h,v,C,F,k,L,T,I,N,S,q,A,M,E,G,D,w,V,P,R,O,_,B,K,z,U,H,j,$,Q,J,Y,W,Z,X,ee,te,ne,re,oe,ae,ie,le,se,me,pe,de,ue,ce,fe,ge,ye,xe,be,he,ve,Ce,Fe,ke,Le,Te,Ie,Ne,Se,qe,Ae,Me,Ee,Ge,De,we,Ve,Pe,Re,Oe,_e,Be,Ke,ze,Ue,He,je,$e,Qe,Je,Ye,We,Ze,Xe,et,tt,nt,rt,ot,at,it,lt,st,mt,pt;return{setters:[function(e){t=e,n=e.Component,r=e.EventEmitter,o=e.ViewChild,a=e.forwardRef,i=e.Input,l=e.InjectionToken,s=e.Injectable,m=e.Inject,p=e.Optional,d=e.Directive,u=e.Output,c=e.NgModule},function(e){f=e.RuleNodeConfigurationComponent,g=e.AttributeScope,y=e.telemetryTypeTranslations,x=e.ScriptLanguage,b=e.AlarmSeverity,h=e.alarmSeverityTranslations,v=e.EntitySearchDirection,C=e.entitySearchDirectionTranslations,F=e.EntityType,k=e.entityFields,L=e.PageComponent,T=e.messageTypeNames,I=e.MessageType,N=e.coerceBoolean,S=e,q=e.AlarmStatus,A=e.alarmStatusTranslations,M=e.SharedModule,E=e.AggregationType,G=e.aggregationTranslations,D=e.NotificationType,w=e.SlackChanelType,V=e.SlackChanelTypesTranslateMap},function(e){P=e},function(e){R=e,O=e.Validators,_=e.NgControl,B=e.NG_VALUE_ACCESSOR,K=e.NG_VALIDATORS,z=e.FormArray,U=e.FormGroup},function(e){H=e,j=e.DOCUMENT,$=e.CommonModule},function(e){Q=e},function(e){J=e},function(e){Y=e},function(e){W=e},function(e){Z=e},function(e){X=e},function(e){ee=e},function(e){te=e},function(e){ne=e},function(e){re=e.getCurrentAuthState,oe=e,ae=e.isDefinedAndNotNull,ie=e.isEqual,le=e.deepTrim,se=e.isObject,me=e.isNotEmptyStr},function(e){pe=e},function(e){de=e},function(e){ue=e},function(e){ce=e},function(e){fe=e},function(e){ge=e.ENTER,ye=e.COMMA,xe=e.SEMICOLON},function(e){be=e},function(e){he=e},function(e){ve=e},function(e){Ce=e},function(e){Fe=e},function(e){ke=e},function(e){Le=e.coerceBooleanProperty,Te=e.coerceElement,Ie=e.coerceNumberProperty},function(e){Ne=e},function(e){Se=e},function(e){qe=e},function(e){Ae=e},function(e){Me=e.tap,Ee=e.map,Ge=e.startWith,De=e.mergeMap,we=e.share,Ve=e.takeUntil,Pe=e.auditTime},function(e){Re=e},function(e){Oe=e},function(e){_e=e.HomeComponentsModule},function(e){Be=e.__decorate},function(e){Ke=e.Subject,ze=e.takeUntil,Ue=e.of,He=e.EMPTY,je=e.fromEvent},function(e){$e=e},function(e){Qe=e},function(e){Je=e},function(e){Ye=e},function(e){We=e},function(e){Ze=e},function(e){Xe=e},function(e){et=e},function(e){tt=e},function(e){nt=e},function(e){rt=e},function(e){ot=e},function(e){at=e},function(e){it=e},function(e){lt=e},function(e){st=e},function(e){mt=e.normalizePassiveListenerOptions,pt=e}],execute:function(){class dt extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.emptyConfigForm}onConfigurationSet(e){this.emptyConfigForm=this.fb.group({})}}e("EmptyConfigComponent",dt),dt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),dt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:dt,selector:"tb-node-empty-config",usesInheritance:!0,ngImport:t,template:"
",isInline:!0}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dt,decorators:[{type:n,args:[{selector:"tb-node-empty-config",template:"
"}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ut extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.assignCustomerConfigForm}onConfigurationSet(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[O.required,O.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[O.required,O.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("AssignCustomerConfigComponent",ut),ut.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ut,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ut.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ut,selector:"tb-action-node-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ut,decorators:[{type:n,args:[{selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ct extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=g,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y}configForm(){return this.attributesConfigForm}onConfigurationSet(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]],notifyDevice:[!e||e.notifyDevice,[]],sendAttributesUpdatedNotification:[!!e&&e.sendAttributesUpdatedNotification,[]],updateAttributesOnlyOnValueChange:[!!e&&e.updateAttributesOnlyOnValueChange,[]]}),this.attributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==g.SHARED_SCOPE&&this.attributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1}),e===g.CLIENT_SCOPE&&this.attributesConfigForm.get("sendAttributesUpdatedNotification").patchValue(!1,{emitEvent:!1}),this.attributesConfigForm.get("updateAttributesOnlyOnValueChange").patchValue(!1,{emitEvent:!1})}))}}e("AttributesConfigComponent",ct),ct.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ct,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ct.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ct,selector:"tb-action-node-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n
\n \n {{ \'tb.rulenode.update-attributes-only-on-value-change\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.send-attributes-updated-notification\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
\n
\n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ct,decorators:[{type:n,args:[{selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n
\n \n {{ \'tb.rulenode.update-attributes-only-on-value-change\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.send-attributes-updated-notification\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
\n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ft extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-details-function"}configForm(){return this.clearAlarmConfigForm}onConfigurationSet(e){this.clearAlarmConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[]],alarmDetailsBuildTbel:[e?e.alarmDetailsBuildTbel:null,[]],alarmType:[e?e.alarmType:null,[O.required]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.clearAlarmConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.clearAlarmConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.clearAlarmConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(t===x.JS?[O.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(t===x.TBEL?[O.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.clearAlarmConfigForm.get("scriptLang").value,n=t===x.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",r=t===x.JS?"rulenode/clear_alarm_node_script_fn":"rulenode/tbel/clear_alarm_node_script_fn",o=this.clearAlarmConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.clearAlarmConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.clearAlarmConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("ClearAlarmConfigComponent",ft),ft.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ft,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),ft.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ft,selector:"tb-action-node-clear-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ft,decorators:[{type:n,args:[{selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n \n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class gt extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.alarmSeverities=Object.keys(b),this.alarmSeverityTranslationMap=h,this.separatorKeysCodes=[ge,ye,xe],this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-details-function"}configForm(){return this.createAlarmConfigForm}onConfigurationSet(e){this.createAlarmConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[]],alarmDetailsBuildTbel:[e?e.alarmDetailsBuildTbel:null,[]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],overwriteAlarmDetails:[!!e&&e.overwriteAlarmDetails,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]],propagateToOwner:[!!e&&e.propagateToOwner,[]],propagateToTenant:[!!e&&e.propagateToTenant,[]],dynamicSeverity:!1}),this.createAlarmConfigForm.get("dynamicSeverity").valueChanges.subscribe((e=>{e?this.createAlarmConfigForm.get("severity").patchValue("",{emitEvent:!1}):this.createAlarmConfigForm.get("severity").patchValue(this.alarmSeverities[0],{emitEvent:!1})}))}validatorTriggers(){return["useMessageAlarmData","overwriteAlarmDetails","scriptLang"]}updateValidators(e){const t=this.createAlarmConfigForm.get("useMessageAlarmData").value,n=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;t?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([O.required]),this.createAlarmConfigForm.get("severity").setValidators([O.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e});let r=this.createAlarmConfigForm.get("scriptLang").value;r!==x.TBEL||this.tbelEnabled||(r=x.JS,this.createAlarmConfigForm.get("scriptLang").patchValue(r,{emitEvent:!1}),setTimeout((()=>{this.createAlarmConfigForm.updateValueAndValidity({emitEvent:!0})})));const o=!1===t||!0===n;this.createAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(o&&r===x.JS?[O.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(o&&r===x.TBEL?[O.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.createAlarmConfigForm.get("scriptLang").value,n=t===x.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",r=t===x.JS?"rulenode/create_alarm_node_script_fn":"rulenode/tbel/create_alarm_node_script_fn",o=this.createAlarmConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.createAlarmConfigForm.get(n).setValue(e),this.changeScript.emit())}))}removeKey(e,t){const n=this.createAlarmConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.createAlarmConfigForm.get(t).setValue(n,{emitEvent:!0}))}addKey(e,t){const n=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.createAlarmConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.createAlarmConfigForm.get(t).setValue(e,{emitEvent:!0}))}n&&(n.value="")}onValidate(){const e=this.createAlarmConfigForm.get("useMessageAlarmData").value,t=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;if(!e||t){this.createAlarmConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}}e("CreateAlarmConfigComponent",gt),gt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gt,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),gt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:gt,selector:"tb-action-node-create-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.overwrite-alarm-details\' | translate }}\n \n
\n \n \n \n \n \n \n \n
\n \n
\n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-alarm-severity-pattern\' | translate }}\n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n tb.rulenode.alarm-severity-pattern\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
\n \n {{ \'tb.rulenode.propagate-to-owner\' | translate }}\n \n \n {{ \'tb.rulenode.propagate-to-tenant\' | translate }}\n \n
\n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:be.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:be.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:be.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:be.MatChipRow,selector:"mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]",inputs:["color","disabled","disableRipple","tabIndex","editable"],outputs:["edited"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gt,decorators:[{type:n,args:[{selector:"tb-action-node-create-alarm-config",template:'
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.overwrite-alarm-details\' | translate }}\n \n
\n \n \n \n \n \n \n \n
\n \n
\n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-alarm-severity-pattern\' | translate }}\n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n tb.rulenode.alarm-severity-pattern\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
\n \n {{ \'tb.rulenode.propagate-to-owner\' | translate }}\n \n \n {{ \'tb.rulenode.propagate-to-tenant\' | translate }}\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class yt extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(v),this.directionTypeTranslations=C,this.entityType=F}configForm(){return this.createRelationConfigForm}onConfigurationSet(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[O.required]],entityType:[e?e.entityType:null,[O.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[O.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[O.required,O.min(0)]]})}validatorTriggers(){return["entityType"]}updateValidators(e){const t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([O.required,O.pattern(/.*\S.*/)]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==F.DEVICE&&t!==F.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([O.required,O.pattern(/.*\S.*/)]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e.entityTypePattern=e.entityTypePattern?e.entityTypePattern.trim():null,e}}e("CreateRelationConfigComponent",yt),yt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),yt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:yt,selector:"tb-action-node-create-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yt,decorators:[{type:n,args:[{selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class xt extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(v),this.directionTypeTranslations=C,this.entityType=F}configForm(){return this.deleteRelationConfigForm}onConfigurationSet(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[O.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[O.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[O.required,O.min(0)]]})}validatorTriggers(){return["deleteForSingleEntity","entityType"]}updateValidators(e){const t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,n=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([O.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&n?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([O.required,O.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e}}e("DeleteRelationConfigComponent",xt),xt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),xt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:xt,selector:"tb-action-node-delete-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xt,decorators:[{type:n,args:[{selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class bt extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.deviceProfile}onConfigurationSet(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,O.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,O.required]})}}e("DeviceProfileConfigComponent",bt),bt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),bt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:bt,selector:"tb-device-profile-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n',dependencies:[{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bt,decorators:[{type:n,args:[{selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ht extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-generator-function"}configForm(){return this.generatorConfigForm}onConfigurationSet(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[O.required,O.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[O.required,O.min(1)]],originator:[e?e.originator:null,[]],scriptLang:[e?e.scriptLang:x.JS,[O.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.generatorConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.generatorConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.generatorConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.generatorConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.generatorConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.generatorConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.generatorConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS),e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e}prepareOutputConfig(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e}testScript(e){const t=this.generatorConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/generator_node_script_fn":"rulenode/tbel/generator_node_script_fn",o=this.generatorConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.generatorConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.generatorConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}var vt;e("GeneratorConfigComponent",ht),ht.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ht,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),ht.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ht,selector:"tb-action-node-generator-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n\n \n \n \n \n \n \n \n
\n \n
\n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Ce.EntitySelectComponent,selector:"tb-entity-select",inputs:["allowedEntityTypes","useAliasEntityTypes","required","disabled"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ht,decorators:[{type:n,args:[{selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n\n \n \n \n \n \n \n \n
\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}}),function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR",e.ENTITY="ENTITY"}(vt||(vt={}));const Ct=new Map([[vt.CUSTOMER,"tb.rulenode.originator-customer"],[vt.TENANT,"tb.rulenode.originator-tenant"],[vt.RELATED,"tb.rulenode.originator-related"],[vt.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"],[vt.ENTITY,"tb.rulenode.originator-entity"]]),Ft=new Map([[vt.CUSTOMER,"tb.rulenode.originator-customer-desc"],[vt.TENANT,"tb.rulenode.originator-tenant-desc"],[vt.RELATED,"tb.rulenode.originator-related-entity-desc"],[vt.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator-desc"],[vt.ENTITY,"tb.rulenode.originator-entity-by-name-pattern-desc"]]),kt=[k.createdTime,k.name,{value:"type",name:"tb.rulenode.profile-name",keyName:"originatorProfileName"},k.firstName,k.lastName,k.email,k.title,k.country,k.state,k.city,k.address,k.address2,k.zip,k.phone,k.label,{value:"id",name:"tb.rulenode.id",keyName:"id"},{value:"additionalInfo",name:"tb.rulenode.additional-info",keyName:"additionalInfo"}],Lt=new Map([["type","profileName"],["createdTime","createdTime"],["name","name"],["firstName","firstName"],["lastName","lastName"],["email","email"],["title","title"],["country","country"],["state","state"],["city","city"],["address","address"],["address2","address2"],["zip","zip"],["phone","phone"],["label","label"],["id","id"],["additionalInfo","additionalInfo"]]);var Tt;!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(Tt||(Tt={}));const It=new Map([[Tt.CIRCLE,"tb.rulenode.perimeter-circle"],[Tt.POLYGON,"tb.rulenode.perimeter-polygon"]]);var Nt;!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(Nt||(Nt={}));const St=new Map([[Nt.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[Nt.SECONDS,"tb.rulenode.time-unit-seconds"],[Nt.MINUTES,"tb.rulenode.time-unit-minutes"],[Nt.HOURS,"tb.rulenode.time-unit-hours"],[Nt.DAYS,"tb.rulenode.time-unit-days"]]);var qt;!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(qt||(qt={}));const At=new Map([[qt.METER,"tb.rulenode.range-unit-meter"],[qt.KILOMETER,"tb.rulenode.range-unit-kilometer"],[qt.FOOT,"tb.rulenode.range-unit-foot"],[qt.MILE,"tb.rulenode.range-unit-mile"],[qt.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);var Mt;!function(e){e.ID="ID",e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.CITY="CITY",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(Mt||(Mt={}));const Et=new Map([[Mt.ID,"tb.rulenode.entity-details-id"],[Mt.TITLE,"tb.rulenode.entity-details-title"],[Mt.COUNTRY,"tb.rulenode.entity-details-country"],[Mt.STATE,"tb.rulenode.entity-details-state"],[Mt.CITY,"tb.rulenode.entity-details-city"],[Mt.ZIP,"tb.rulenode.entity-details-zip"],[Mt.ADDRESS,"tb.rulenode.entity-details-address"],[Mt.ADDRESS2,"tb.rulenode.entity-details-address2"],[Mt.PHONE,"tb.rulenode.entity-details-phone"],[Mt.EMAIL,"tb.rulenode.entity-details-email"],[Mt.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);var Gt;!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(Gt||(Gt={}));const Dt=new Map([[Gt.FIRST,"tb.rulenode.first"],[Gt.LAST,"tb.rulenode.last"],[Gt.ALL,"tb.rulenode.all"]]),wt=new Map([[Gt.FIRST,"tb.rulenode.first-mode-hint"],[Gt.LAST,"tb.rulenode.last-mode-hint"],[Gt.ALL,"tb.rulenode.all-mode-hint"]]);var Vt,Pt;!function(e){e.ASC="ASC",e.DESC="DESC"}(Vt||(Vt={})),function(e){e.ATTRIBUTES="ATTRIBUTES",e.LATEST_TELEMETRY="LATEST_TELEMETRY",e.FIELDS="FIELDS"}(Pt||(Pt={}));const Rt=new Map([[Pt.ATTRIBUTES,"tb.rulenode.attributes"],[Pt.LATEST_TELEMETRY,"tb.rulenode.latest-telemetry"],[Pt.FIELDS,"tb.rulenode.fields"]]),Ot=new Map([[Pt.ATTRIBUTES,"tb.rulenode.add-mapped-attribute-to"],[Pt.LATEST_TELEMETRY,"tb.rulenode.add-mapped-latest-telemetry-to"],[Pt.FIELDS,"tb.rulenode.add-mapped-fields-to"]]),_t=new Map([[Vt.ASC,"tb.rulenode.ascending"],[Vt.DESC,"tb.rulenode.descending"]]);var Bt;!function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(Bt||(Bt={}));const Kt=new Map([[Bt.STANDARD,"tb.rulenode.sqs-queue-standard"],[Bt.FIFO,"tb.rulenode.sqs-queue-fifo"]]),zt=["anonymous","basic","cert.PEM"],Ut=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),Ht=["sas","cert.PEM"],jt=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);var $t;!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}($t||($t={}));const Qt=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Jt=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]);var Yt;!function(e){e.CUSTOM="CUSTOM",e.ADD="ADD",e.SUB="SUB",e.MULT="MULT",e.DIV="DIV",e.SIN="SIN",e.SINH="SINH",e.COS="COS",e.COSH="COSH",e.TAN="TAN",e.TANH="TANH",e.ACOS="ACOS",e.ASIN="ASIN",e.ATAN="ATAN",e.ATAN2="ATAN2",e.EXP="EXP",e.EXPM1="EXPM1",e.SQRT="SQRT",e.CBRT="CBRT",e.GET_EXP="GET_EXP",e.HYPOT="HYPOT",e.LOG="LOG",e.LOG10="LOG10",e.LOG1P="LOG1P",e.CEIL="CEIL",e.FLOOR="FLOOR",e.FLOOR_DIV="FLOOR_DIV",e.FLOOR_MOD="FLOOR_MOD",e.ABS="ABS",e.MIN="MIN",e.MAX="MAX",e.POW="POW",e.SIGNUM="SIGNUM",e.RAD="RAD",e.DEG="DEG"}(Yt||(Yt={}));const Wt=new Map([[Yt.CUSTOM,{value:Yt.CUSTOM,name:"Custom Function",description:"Use this function to specify complex mathematical expression.",minArgs:1,maxArgs:16}],[Yt.ADD,{value:Yt.ADD,name:"Addition",description:"x + y",minArgs:2,maxArgs:2}],[Yt.SUB,{value:Yt.SUB,name:"Subtraction",description:"x - y",minArgs:2,maxArgs:2}],[Yt.MULT,{value:Yt.MULT,name:"Multiplication",description:"x * y",minArgs:2,maxArgs:2}],[Yt.DIV,{value:Yt.DIV,name:"Division",description:"x / y",minArgs:2,maxArgs:2}],[Yt.SIN,{value:Yt.SIN,name:"Sine",description:"Returns the trigonometric sine of an angle in radians.",minArgs:1,maxArgs:1}],[Yt.SINH,{value:Yt.SINH,name:"Hyperbolic sine",description:"Returns the hyperbolic sine of an argument.",minArgs:1,maxArgs:1}],[Yt.COS,{value:Yt.COS,name:"Cosine",description:"Returns the trigonometric cosine of an angle in radians.",minArgs:1,maxArgs:1}],[Yt.COSH,{value:Yt.COSH,name:"Hyperbolic cosine",description:"Returns the hyperbolic cosine of an argument.",minArgs:1,maxArgs:1}],[Yt.TAN,{value:Yt.TAN,name:"Tangent",description:"Returns the trigonometric tangent of an angle in radians",minArgs:1,maxArgs:1}],[Yt.TANH,{value:Yt.TANH,name:"Hyperbolic tangent",description:"Returns the hyperbolic tangent of an argument",minArgs:1,maxArgs:1}],[Yt.ACOS,{value:Yt.ACOS,name:"Arc cosine",description:"Returns the arc cosine of an argument",minArgs:1,maxArgs:1}],[Yt.ASIN,{value:Yt.ASIN,name:"Arc sine",description:"Returns the arc sine of an argument",minArgs:1,maxArgs:1}],[Yt.ATAN,{value:Yt.ATAN,name:"Arc tangent",description:"Returns the arc tangent of an argument",minArgs:1,maxArgs:1}],[Yt.ATAN2,{value:Yt.ATAN2,name:"2-argument arc tangent",description:"Returns the angle theta from the conversion of rectangular coordinates (x, y) to polar coordinates (r, theta)",minArgs:2,maxArgs:2}],[Yt.EXP,{value:Yt.EXP,name:"Exponential",description:"Returns Euler's number e raised to the power of an argument",minArgs:1,maxArgs:1}],[Yt.EXPM1,{value:Yt.EXPM1,name:"Exponential minus one",description:"Returns Euler's number e raised to the power of an argument minus one",minArgs:1,maxArgs:1}],[Yt.SQRT,{value:Yt.SQRT,name:"Square",description:"Returns the correctly rounded positive square root of an argument",minArgs:1,maxArgs:1}],[Yt.CBRT,{value:Yt.CBRT,name:"Cube root",description:"Returns the cube root of an argument",minArgs:1,maxArgs:1}],[Yt.GET_EXP,{value:Yt.GET_EXP,name:"Get exponent",description:"Returns the unbiased exponent used in the representation of an argument",minArgs:1,maxArgs:1}],[Yt.HYPOT,{value:Yt.HYPOT,name:"Square root",description:"Returns the square root of the squares of the arguments",minArgs:2,maxArgs:2}],[Yt.LOG,{value:Yt.LOG,name:"Logarithm",description:"Returns the natural logarithm of an argument",minArgs:1,maxArgs:1}],[Yt.LOG10,{value:Yt.LOG10,name:"Base 10 logarithm",description:"Returns the base 10 logarithm of an argument",minArgs:1,maxArgs:1}],[Yt.LOG1P,{value:Yt.LOG1P,name:"Logarithm of the sum",description:"Returns the natural logarithm of the sum of an argument",minArgs:1,maxArgs:1}],[Yt.CEIL,{value:Yt.CEIL,name:"Ceiling",description:"Returns the smallest (closest to negative infinity) of an argument",minArgs:1,maxArgs:1}],[Yt.FLOOR,{value:Yt.FLOOR,name:"Floor",description:"Returns the largest (closest to positive infinity) of an argument",minArgs:1,maxArgs:1}],[Yt.FLOOR_DIV,{value:Yt.FLOOR_DIV,name:"Floor division",description:"Returns the largest (closest to positive infinity) of the arguments",minArgs:2,maxArgs:2}],[Yt.FLOOR_MOD,{value:Yt.FLOOR_MOD,name:"Floor modulus",description:"Returns the floor modulus of the arguments",minArgs:2,maxArgs:2}],[Yt.ABS,{value:Yt.ABS,name:"Absolute",description:"Returns the absolute value of an argument",minArgs:1,maxArgs:1}],[Yt.MIN,{value:Yt.MIN,name:"Min",description:"Returns the smaller of the arguments",minArgs:2,maxArgs:2}],[Yt.MAX,{value:Yt.MAX,name:"Max",description:"Returns the greater of the arguments",minArgs:2,maxArgs:2}],[Yt.POW,{value:Yt.POW,name:"Raise to a power",description:"Returns the value of the first argument raised to the power of the second argument",minArgs:2,maxArgs:2}],[Yt.SIGNUM,{value:Yt.SIGNUM,name:"Sign of a real number",description:"Returns the signum function of the argument",minArgs:1,maxArgs:1}],[Yt.RAD,{value:Yt.RAD,name:"Radian",description:"Converts an angle measured in degrees to an approximately equivalent angle measured in radians",minArgs:1,maxArgs:1}],[Yt.DEG,{value:Yt.DEG,name:"Degrees",description:"Converts an angle measured in radians to an approximately equivalent angle measured in degrees.",minArgs:1,maxArgs:1}]]);var Zt,Xt,en;!function(e){e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA",e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES",e.CONSTANT="CONSTANT"}(Zt||(Zt={})),function(e){e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA",e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES"}(Xt||(Xt={})),function(e){e.DATA="DATA",e.METADATA="METADATA"}(en||(en={}));const tn=new Map([[en.DATA,"tb.rulenode.message-to-metadata"],[en.METADATA,"tb.rulenode.metadata-to-message"]]),nn=(new Map([[en.DATA,"tb.rulenode.from-message"],[en.METADATA,"tb.rulenode.from-metadata"]]),new Map([[en.DATA,"tb.rulenode.message"],[en.METADATA,"tb.rulenode.metadata"]])),rn=new Map([[en.DATA,"tb.rulenode.message"],[en.METADATA,"tb.rulenode.message-metadata"]]),on=new Map([[Zt.MESSAGE_BODY,{name:"tb.rulenode.message-body-type",description:"Fetch argument value from incoming message"}],[Zt.MESSAGE_METADATA,{name:"tb.rulenode.message-metadata-type",description:"Fetch argument value from incoming message metadata"}],[Zt.ATTRIBUTE,{name:"tb.rulenode.attribute-type",description:"Fetch attribute value from database"}],[Zt.TIME_SERIES,{name:"tb.rulenode.time-series-type",description:"Fetch latest time-series value from database"}],[Zt.CONSTANT,{name:"tb.rulenode.constant-type",description:"Define constant value"}]]),an=new Map([[Xt.MESSAGE_BODY,{name:"tb.rulenode.message-body-type",description:"Add result to the outgoing message"}],[Xt.MESSAGE_METADATA,{name:"tb.rulenode.message-metadata-type",description:"Add result to the outgoing message metadata"}],[Xt.ATTRIBUTE,{name:"tb.rulenode.attribute-type",description:"Store result as an entity attribute in the database"}],[Xt.TIME_SERIES,{name:"tb.rulenode.time-series-type",description:"Store result as an entity time-series in the database"}]]),ln=["x","y","z","a","b","c","d","k","l","m","n","o","p","r","s","t"];var sn,mn;!function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE",e.CLIENT_SCOPE="CLIENT_SCOPE"}(sn||(sn={})),function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE"}(mn||(mn={}));const pn=new Map([[sn.SHARED_SCOPE,"tb.rulenode.shared-scope"],[sn.SERVER_SCOPE,"tb.rulenode.server-scope"],[sn.CLIENT_SCOPE,"tb.rulenode.client-scope"]]);var dn;!function(e){e.ON_FIRST_MESSAGE="ON_FIRST_MESSAGE",e.ON_EACH_MESSAGE="ON_EACH_MESSAGE"}(dn||(dn={}));const un=new Map([[dn.ON_EACH_MESSAGE,{value:!0,name:"tb.rulenode.presence-monitoring-strategy-on-each-message"}],[dn.ON_FIRST_MESSAGE,{value:!1,name:"tb.rulenode.presence-monitoring-strategy-on-first-message"}]]);class cn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Tt,this.perimeterTypes=Object.keys(Tt),this.perimeterTypeTranslationMap=It,this.rangeUnits=Object.keys(qt),this.rangeUnitTranslationMap=At,this.presenceMonitoringStrategies=un,this.presenceMonitoringStrategyKeys=Array.from(this.presenceMonitoringStrategies.keys()),this.timeUnits=Object.keys(Nt),this.timeUnitsTranslationMap=St,this.defaultPaddingEnable=!0}configForm(){return this.geoActionConfigForm}onConfigurationSet(e){this.geoActionConfigForm=this.fb.group({reportPresenceStatusOnEachMessage:[!e||e.reportPresenceStatusOnEachMessage,[O.required]],latitudeKeyName:[e?e.latitudeKeyName:null,[O.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[O.required]],perimeterType:[e?e.perimeterType:null,[O.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e?e.perimeterKeyName:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[O.required,O.min(1),O.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[O.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[O.required,O.min(1),O.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[O.required]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterKeyName").setValidators([O.required]):this.geoActionConfigForm.get("perimeterKeyName").setValidators([]),t||n!==Tt.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([]),this.defaultPaddingEnable=!0):(this.geoActionConfigForm.get("centerLatitude").setValidators([O.required,O.min(-90),O.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([O.required,O.min(-180),O.max(180)]),this.geoActionConfigForm.get("range").setValidators([O.required,O.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([O.required]),this.defaultPaddingEnable=!1),t||n!==Tt.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([O.required]),this.geoActionConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoActionConfigComponent",cn),cn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),cn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:cn,selector:"tb-action-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
tb.rulenode.coordinate-field-names
\n
\n
\n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
\n
tb.rulenode.coordinate-field-hint
\n
\n
\n
\n
tb.rulenode.geofence-configuration
\n
\n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
\n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n help\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n
\n
\n
{{ \'tb.rulenode.presence-monitoring-strategy\' | translate }}
\n \n \n {{ presenceMonitoringStrategies.get(strategy).name | translate }}\n \n \n
\n
{{ geoActionConfigForm.get(\'reportPresenceStatusOnEachMessage\').value === false ?\n (\'tb.rulenode.presence-monitoring-strategy-on-first-message-hint\' | translate) :\n (\'tb.rulenode.presence-monitoring-strategy-on-each-message-hint\' | translate) }}\n
\n
\n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cn,decorators:[{type:n,args:[{selector:"tb-action-node-gps-geofencing-config",template:'
\n
\n
tb.rulenode.coordinate-field-names
\n
\n
\n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
\n
tb.rulenode.coordinate-field-hint
\n
\n
\n
\n
tb.rulenode.geofence-configuration
\n
\n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
\n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n help\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n
\n
\n
{{ \'tb.rulenode.presence-monitoring-strategy\' | translate }}
\n \n \n {{ presenceMonitoringStrategies.get(strategy).name | translate }}\n \n \n
\n
{{ geoActionConfigForm.get(\'reportPresenceStatusOnEachMessage\').value === false ?\n (\'tb.rulenode.presence-monitoring-strategy-on-first-message-hint\' | translate) :\n (\'tb.rulenode.presence-monitoring-strategy-on-each-message-hint\' | translate) }}\n
\n
\n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class fn extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-to-string-function"}configForm(){return this.logConfigForm}onConfigurationSet(e){this.logConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.logConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.logConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.logConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.logConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.logConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.logConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.logConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.logConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/log_node_script_fn":"rulenode/tbel/log_node_script_fn",o=this.logConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.logConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.logConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("LogConfigComponent",fn),fn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fn,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),fn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:fn,selector:"tb-action-node-log-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n
\n \n
\n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fn,decorators:[{type:n,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n \n \n \n \n
\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class gn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgCountConfigForm}onConfigurationSet(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[O.required,O.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[O.required]]})}}e("MsgCountConfigComponent",gn),gn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),gn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:gn,selector:"tb-action-node-msg-count-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gn,decorators:[{type:n,args:[{selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class yn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgDelayConfigForm}onConfigurationSet(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[O.required,O.min(1),O.max(1e5)]]})}validatorTriggers(){return["useMetadataPeriodInSecondsPatterns"]}updateValidators(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([O.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([O.required,O.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})}}e("MsgDelayConfigComponent",yn),yn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),yn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:yn,selector:"tb-action-node-msg-delay-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yn,decorators:[{type:n,args:[{selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class xn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y}configForm(){return this.pushToCloudConfigForm}onConfigurationSet(e){this.pushToCloudConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]]})}}e("PushToCloudConfigComponent",xn),xn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),xn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:xn,selector:"tb-action-node-push-to-cloud-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xn,decorators:[{type:n,args:[{selector:"tb-action-node-push-to-cloud-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class bn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y}configForm(){return this.pushToEdgeConfigForm}onConfigurationSet(e){this.pushToEdgeConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]]})}}e("PushToEdgeConfigComponent",bn),bn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),bn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:bn,selector:"tb-action-node-push-to-edge-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bn,decorators:[{type:n,args:[{selector:"tb-action-node-push-to-edge-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class hn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcReplyConfigForm}onConfigurationSet(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})}}e("RpcReplyConfigComponent",hn),hn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),hn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:hn,selector:"tb-action-node-rpc-reply-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n',dependencies:[{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hn,decorators:[{type:n,args:[{selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class vn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcRequestConfigForm}onConfigurationSet(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[O.required,O.min(0)]]})}}e("RpcRequestConfigComponent",vn),vn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),vn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:vn,selector:"tb-action-node-rpc-request-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vn,decorators:[{type:n,args:[{selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Cn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.propagateChange=null,this.valueChangeSubscription=null}ngOnInit(){this.ngControl=this.injector.get(_),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();const t=[];if(e)for(const n of Object.keys(e))Object.prototype.hasOwnProperty.call(e,n)&&t.push(this.fb.group({key:[n,[O.required]],value:[e[n],[O.required]]}));this.kvListFormGroup.setControl("keyVals",this.fb.array(t)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))}removeKeyVal(e){this.kvListFormGroup.get("keyVals").removeAt(e)}addKeyVal(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[O.required]],value:["",[O.required]]}))}validate(e){const t=this.kvListFormGroup.get("keyVals").value;if(!t.length&&this.required)return{kvMapRequired:!0};if(!this.kvListFormGroup.valid)return{kvFieldsRequired:!0};if(this.uniqueKeyValuePairValidator)for(const e of t)if(e.key===e.value)return{uniqueKeyValuePair:!0};return null}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("KvMapConfigOldComponent",Cn),Cn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cn,deps:[{token:P.Store},{token:Z.TranslateService},{token:t.Injector},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Cn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Cn,selector:"tb-kv-map-config-old",inputs:{disabled:"disabled",uniqueKeyValuePairValidator:"uniqueKeyValuePairValidator",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",required:"required"},providers:[{provide:B,useExisting:a((()=>Cn)),multi:!0},{provide:K,useExisting:a((()=>Cn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\n \n
\n
\n
\n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n
\n \n
\n \n
\n
\n',styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:#757575;font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .header .tb-required:after{color:#757575;font-size:12px;font-weight:700}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:0;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host .tb-kv-map-config tb-error{display:block;margin-top:-12px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Ne.TbErrorComponent,selector:"tb-error",inputs:["noMargin","error"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:Se.DefaultShowHideDirective,selector:" [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]",inputs:["fxShow","fxShow.print","fxShow.xs","fxShow.sm","fxShow.md","fxShow.lg","fxShow.xl","fxShow.lt-sm","fxShow.lt-md","fxShow.lt-lg","fxShow.lt-xl","fxShow.gt-xs","fxShow.gt-sm","fxShow.gt-md","fxShow.gt-lg","fxHide","fxHide.print","fxHide.xs","fxHide.sm","fxHide.md","fxHide.lg","fxHide.xl","fxHide.lt-sm","fxHide.lt-md","fxHide.lt-lg","fxHide.lt-xl","fxHide.gt-xs","fxHide.gt-sm","fxHide.gt-md","fxHide.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cn,decorators:[{type:n,args:[{selector:"tb-kv-map-config-old",providers:[{provide:B,useExisting:a((()=>Cn)),multi:!0},{provide:K,useExisting:a((()=>Cn)),multi:!0}],template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\n \n
\n
\n
\n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n
\n \n
\n \n
\n
\n',styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:#757575;font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .header .tb-required:after{color:#757575;font-size:12px;font-weight:700}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:0;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host .tb-kv-map-config tb-error{display:block;margin-top:-12px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:t.Injector},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],uniqueKeyValuePairValidator:[{type:i}],requiredText:[{type:i}],keyText:[{type:i}],keyRequiredText:[{type:i}],valText:[{type:i}],valRequiredText:[{type:i}],hintText:[{type:i}],required:[{type:i}]}});class Fn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.saveToCustomTableConfigForm}onConfigurationSet(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[O.required,O.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[O.required]]})}prepareOutputConfig(e){return e.tableName=e.tableName.trim(),e}}e("SaveToCustomTableConfigComponent",Fn),Fn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Fn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Fn,selector:"tb-action-node-custom-table-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fn,decorators:[{type:n,args:[{selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class kn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.timeseriesConfigForm}onConfigurationSet(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[O.required,O.min(0)]],skipLatestPersistence:[!!e&&e.skipLatestPersistence,[]],useServerTs:[!!e&&e.useServerTs,[]]})}}e("TimeseriesConfigComponent",kn),kn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),kn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:kn,selector:"tb-action-node-timeseries-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.skip-latest-persistence\' | translate }}\n \n \n {{ \'tb.rulenode.use-server-ts\' | translate }}\n \n
tb.rulenode.use-server-ts-hint
\n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kn,decorators:[{type:n,args:[{selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.skip-latest-persistence\' | translate }}\n \n \n {{ \'tb.rulenode.use-server-ts\' | translate }}\n \n
tb.rulenode.use-server-ts-hint
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Ln extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.unassignCustomerConfigForm}onConfigurationSet(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[O.required,O.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[O.required,O.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("UnassignCustomerConfigComponent",Ln),Ln.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ln,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ln.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Ln,selector:"tb-action-node-un-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ln,decorators:[{type:n,args:[{selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Tn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=g,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y,this.separatorKeysCodes=[ge,ye,xe]}configForm(){return this.deleteAttributesConfigForm}onConfigurationSet(e){this.deleteAttributesConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]],keys:[e?e.keys:null,[O.required]],sendAttributesDeletedNotification:[!!e&&e.sendAttributesDeletedNotification,[]],notifyDevice:[!!e&&e.notifyDevice,[]]}),this.deleteAttributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==g.SHARED_SCOPE&&this.deleteAttributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1})}))}removeKey(e){const t=this.deleteAttributesConfigForm.get("keys").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.deleteAttributesConfigForm.get("keys").patchValue(t,{emitEvent:!0}))}addKey(e){const t=e.input;let n=e.value;if((n||"").trim()){n=n.trim();let e=this.deleteAttributesConfigForm.get("keys").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.deleteAttributesConfigForm.get("keys").patchValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("DeleteAttributesConfigComponent",Tn),Tn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Tn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Tn,selector:"tb-action-node-delete-attributes-config",viewQueries:[{propertyName:"attributeChipList",first:!0,predicate:["attributeChipList"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'attribute.attributes-scope\' | translate }}\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n \n {{ \'tb.rulenode.attributes-keys\' | translate }}\n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.send-attributes-deleted-notification\' | translate }}\n \n
tb.rulenode.send-attributes-deleted-notification-hint
\n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-on-delete-hint
\n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:be.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:be.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:be.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:be.MatChipRow,selector:"mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]",inputs:["color","disabled","disableRipple","tabIndex","editable"],outputs:["edited"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tn,decorators:[{type:n,args:[{selector:"tb-action-node-delete-attributes-config",template:'
\n \n {{ \'attribute.attributes-scope\' | translate }}\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n \n {{ \'tb.rulenode.attributes-keys\' | translate }}\n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.send-attributes-deleted-notification\' | translate }}\n \n
tb.rulenode.send-attributes-deleted-notification-hint
\n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-on-delete-hint
\n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]},propDecorators:{attributeChipList:[{type:o,args:["attributeChipList"]}]}});class In extends L{get function(){return this.functionValue}set function(e){e&&this.functionValue!==e&&(this.functionValue=e,this.setupArgumentsFormGroup(!0))}constructor(e,t){super(e),this.store=e,this.fb=t,this.maxArgs=16,this.minArgs=1,this.displayArgumentName=!1,this.mathFunctionMap=Wt,this.ArgumentType=Zt,this.attributeScopeMap=pn,this.argumentTypeMap=on,this.arguments=Object.values(Zt),this.attributeScope=Object.values(sn),this.propagateChange=null,this.valueChangeSubscription=[]}ngOnInit(){this.argumentsFormGroup=this.fb.group({arguments:this.fb.array([])}),this.valueChangeSubscription.push(this.argumentsFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))),this.setupArgumentsFormGroup()}onDrop(e){const t=this.argumentsFormArray,n=t.at(e.previousIndex);t.removeAt(e.previousIndex),t.insert(e.currentIndex,n),this.updateArgumentNames()}get argumentsFormArray(){return this.argumentsFormGroup.get("arguments")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.argumentsFormGroup.disable({emitEvent:!1}):(this.argumentsFormGroup.enable({emitEvent:!1}),this.argumentsFormArray.controls.forEach((e=>this.updateArgumentControlValidators(e))))}ngOnDestroy(){this.valueChangeSubscription.length&&this.valueChangeSubscription.forEach((e=>e.unsubscribe()))}writeValue(e){const t=[];e&&e.forEach(((e,n)=>{t.push(this.createArgumentControl(e,n))})),this.argumentsFormGroup.setControl("arguments",this.fb.array(t),{emitEvent:!1}),this.setupArgumentsFormGroup()}removeArgument(e){this.argumentsFormArray.removeAt(e),this.updateArgumentNames()}addArgument(e=!0){const t=this.argumentsFormArray,n=this.createArgumentControl(null,t.length);t.push(n,{emitEvent:e})}validate(e){return this.argumentsFormGroup.valid?null:{argumentsRequired:!0}}setupArgumentsFormGroup(e=!1){if(this.function&&(this.maxArgs=this.mathFunctionMap.get(this.function).maxArgs,this.minArgs=this.mathFunctionMap.get(this.function).minArgs,this.displayArgumentName=this.function===Yt.CUSTOM),this.argumentsFormGroup){for(this.argumentsFormGroup.get("arguments").setValidators([O.minLength(this.minArgs),O.maxLength(this.maxArgs)]);this.argumentsFormArray.length>this.maxArgs;)this.removeArgument(this.maxArgs-1);for(;this.argumentsFormArray.length{this.updateArgumentControlValidators(n),n.get("attributeScope").updateValueAndValidity({emitEvent:!1}),n.get("defaultValue").updateValueAndValidity({emitEvent:!1})}))),n}updateArgumentControlValidators(e){const t=e.get("type").value;t===Zt.ATTRIBUTE?e.get("attributeScope").enable({emitEvent:!1}):e.get("attributeScope").disable({emitEvent:!1}),t&&t!==Zt.CONSTANT?e.get("defaultValue").enable({emitEvent:!1}):e.get("defaultValue").disable({emitEvent:!1})}updateArgumentNames(){this.argumentsFormArray.controls.forEach(((e,t)=>{e.get("name").setValue(ln[t])}))}updateModel(){const e=this.argumentsFormArray.value;e.length&&this.argumentsFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}}e("ArgumentsMapConfigComponent",In),In.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:In,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),In.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:In,selector:"tb-arguments-map-config",inputs:{disabled:"disabled",function:"function"},providers:[{provide:B,useExisting:a((()=>In)),multi:!0},{provide:K,useExisting:a((()=>In)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n\n
\n \n \n
\n \n
\n {{argumentControl.get(\'name\').value}}.\n
\n \n tb.rulenode.argument-source-field-input\n \n \n {{ argumentTypeMap.get(argumentControl.get(\'type\').value)?.name | translate }}\n \n \n {{ argumentTypeMap.get(argument).name | translate }}\n \n {{ argumentTypeMap.get(argument).description }}\n \n \n \n \n tb.rulenode.argument-source-field-input-required\n \n \n
\n \n tb.rulenode.argument-key-field-input\n \n \n help\n \n \n tb.rulenode.argument-key-field-input-required\n \n \n \n tb.rulenode.constant-value-field-input\n \n \n tb.rulenode.constant-value-field-input-required\n \n \n \n tb.rulenode.default-value-field-input\n \n \n
\n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n tb.rulenode.attribute-scope-field-input-required\n \n \n
\n \n
\n
\n
\n
\n
\n
\n tb.rulenode.no-arguments-prompt\n
\n \n
\n',styles:[":host .mat-mdc-list-item.tb-argument{border:solid rgba(0,0,0,.25) 1px;border-radius:4px;padding:10px 0;margin-bottom:16px}:host .arguments-list{padding:0}\n"],dependencies:[{kind:"directive",type:H.NgClass,selector:"[ngClass]",inputs:["class","ngClass"]},{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:qe.MatList,selector:"mat-list",exportAs:["matList"]},{kind:"component",type:qe.MatListItem,selector:"mat-list-item, a[mat-list-item], button[mat-list-item]",inputs:["activated"],exportAs:["matListItem"]},{kind:"directive",type:Ae.CdkDropList,selector:"[cdkDropList], cdk-drop-list",inputs:["cdkDropListConnectedTo","cdkDropListData","cdkDropListOrientation","id","cdkDropListLockAxis","cdkDropListDisabled","cdkDropListSortingDisabled","cdkDropListEnterPredicate","cdkDropListSortPredicate","cdkDropListAutoScrollDisabled","cdkDropListAutoScrollStep"],outputs:["cdkDropListDropped","cdkDropListEntered","cdkDropListExited","cdkDropListSorted"],exportAs:["cdkDropList"]},{kind:"directive",type:Ae.CdkDrag,selector:"[cdkDrag]",inputs:["cdkDragData","cdkDragLockAxis","cdkDragRootElement","cdkDragBoundary","cdkDragStartDelay","cdkDragFreeDragPosition","cdkDragDisabled","cdkDragConstrainPosition","cdkDragPreviewClass","cdkDragPreviewContainer"],outputs:["cdkDragStarted","cdkDragReleased","cdkDragEnded","cdkDragEntered","cdkDragExited","cdkDragDropped","cdkDragMoved"],exportAs:["cdkDrag"]},{kind:"directive",type:Ae.CdkDragHandle,selector:"[cdkDragHandle]",inputs:["cdkDragHandleDisabled"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:Se.DefaultClassDirective,selector:" [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]",inputs:["ngClass","ngClass.xs","ngClass.sm","ngClass.md","ngClass.lg","ngClass.xl","ngClass.lt-sm","ngClass.lt-md","ngClass.lt-lg","ngClass.lt-xl","ngClass.gt-xs","ngClass.gt-sm","ngClass.gt-md","ngClass.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:In,decorators:[{type:n,args:[{selector:"tb-arguments-map-config",providers:[{provide:B,useExisting:a((()=>In)),multi:!0},{provide:K,useExisting:a((()=>In)),multi:!0}],template:'
\n\n
\n \n \n
\n \n
\n {{argumentControl.get(\'name\').value}}.\n
\n \n tb.rulenode.argument-source-field-input\n \n \n {{ argumentTypeMap.get(argumentControl.get(\'type\').value)?.name | translate }}\n \n \n {{ argumentTypeMap.get(argument).name | translate }}\n \n {{ argumentTypeMap.get(argument).description }}\n \n \n \n \n tb.rulenode.argument-source-field-input-required\n \n \n
\n \n tb.rulenode.argument-key-field-input\n \n \n help\n \n \n tb.rulenode.argument-key-field-input-required\n \n \n \n tb.rulenode.constant-value-field-input\n \n \n tb.rulenode.constant-value-field-input-required\n \n \n \n tb.rulenode.default-value-field-input\n \n \n
\n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n tb.rulenode.attribute-scope-field-input-required\n \n \n
\n \n
\n
\n
\n
\n
\n
\n tb.rulenode.no-arguments-prompt\n
\n \n
\n',styles:[":host .mat-mdc-list-item.tb-argument{border:solid rgba(0,0,0,.25) 1px;border-radius:4px;padding:10px 0;margin-bottom:16px}:host .arguments-list{padding:0}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],function:[{type:i}]}});class Nn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.searchText="",this.dirty=!1,this.mathOperation=[...Wt.values()],this.propagateChange=null}ngOnInit(){this.mathFunctionForm=this.fb.group({operation:[""]}),this.filteredOptions=this.mathFunctionForm.get("operation").valueChanges.pipe(Me((e=>{let t;t="string"==typeof e&&Yt[e]?Yt[e]:null,this.updateView(t)})),Ee((e=>(this.searchText=e||"",e?this._filter(e):this.mathOperation.slice()))))}_filter(e){const t=e.toLowerCase();return this.mathOperation.filter((e=>e.name.toLowerCase().includes(t)||e.value.toLowerCase().includes(t)))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.mathFunctionForm.disable({emitEvent:!1}):this.mathFunctionForm.enable({emitEvent:!1})}mathFunctionDisplayFn(e){if(e){const t=Wt.get(e);return t.value+" | "+t.name}return""}writeValue(e){this.modelValue=e,this.mathFunctionForm.get("operation").setValue(e,{emitEvent:!1}),this.dirty=!0}updateView(e){this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}onFocus(){this.dirty&&(this.mathFunctionForm.get("operation").updateValueAndValidity({onlySelf:!0}),this.dirty=!1)}clear(){this.mathFunctionForm.get("operation").patchValue(""),setTimeout((()=>{this.operationInput.nativeElement.blur(),this.operationInput.nativeElement.focus()}),0)}}e("MathFunctionAutocompleteComponent",Nn),Nn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nn,deps:[{token:P.Store},{token:Z.TranslateService},{token:t.Injector},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Nn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Nn,selector:"tb-math-function-autocomplete",inputs:{required:"required",disabled:"disabled"},providers:[{provide:B,useExisting:a((()=>Nn)),multi:!0}],viewQueries:[{propertyName:"operationInput",first:!0,predicate:["operationInput"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'\n tb.rulenode.functions-field-input\n \n \n \n \n \n \n {{ option.description }}\n \n \n \n tb.rulenode.no-option-found\n \n \n\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Re.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Re.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Oe.HighlightPipe,name:"highlight"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nn,decorators:[{type:n,args:[{selector:"tb-math-function-autocomplete",providers:[{provide:B,useExisting:a((()=>Nn)),multi:!0}],template:'\n tb.rulenode.functions-field-input\n \n \n \n \n \n \n {{ option.description }}\n \n \n \n tb.rulenode.no-option-found\n \n \n\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:t.Injector},{type:R.UntypedFormBuilder}]},propDecorators:{required:[{type:i}],disabled:[{type:i}],operationInput:[{type:o,args:["operationInput",{static:!0}]}]}});class Sn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.MathFunction=Yt,this.ArgumentTypeResult=Xt,this.argumentTypeResultMap=an,this.attributeScopeMap=pn,this.argumentsResult=Object.values(Xt),this.attributeScopeResult=Object.values(mn)}configForm(){return this.mathFunctionConfigForm}onConfigurationSet(e){this.mathFunctionConfigForm=this.fb.group({operation:[e?e.operation:null,[O.required]],arguments:[e?e.arguments:null,[O.required]],customFunction:[e?e.customFunction:"",[O.required]],result:this.fb.group({type:[e?e.result.type:null,[O.required]],attributeScope:[e?e.result.attributeScope:null,[O.required]],key:[e?e.result.key:"",[O.required]],resultValuePrecision:[e?e.result.resultValuePrecision:0],addToBody:[!!e&&e.result.addToBody],addToMetadata:[!!e&&e.result.addToMetadata]})})}updateValidators(e){const t=this.mathFunctionConfigForm.get("operation").value,n=this.mathFunctionConfigForm.get("result.type").value;t===Yt.CUSTOM?(this.mathFunctionConfigForm.get("customFunction").enable({emitEvent:!1}),null===this.mathFunctionConfigForm.get("customFunction").value&&this.mathFunctionConfigForm.get("customFunction").patchValue("(x - 32) / 1.8",{emitEvent:!1})):this.mathFunctionConfigForm.get("customFunction").disable({emitEvent:!1}),n===Xt.ATTRIBUTE?this.mathFunctionConfigForm.get("result.attributeScope").enable({emitEvent:!1}):this.mathFunctionConfigForm.get("result.attributeScope").disable({emitEvent:!1}),this.mathFunctionConfigForm.get("customFunction").updateValueAndValidity({emitEvent:e}),this.mathFunctionConfigForm.get("result.attributeScope").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["operation","result.type"]}}e("MathFunctionConfigComponent",Sn),Sn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Sn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Sn,selector:"tb-action-node-math-function-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n tb.rulenode.argument-tile\n \n \n
\n
\n {{\'tb.rulenode.custom-expression-field-input\' | translate }} *\n \n \n \n tb.rulenode.custom-expression-field-input-required\n \n tb.rulenode.custom-expression-field-input-hint\n \n
\n
\n tb.rulenode.result-title\n
\n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(mathFunctionConfigForm.get(\'result.type\').value)?.name | translate }}\n \n \n {{ argumentTypeResultMap.get(argument).name | translate }}\n \n {{ argumentTypeResultMap.get(argument).description }}\n \n \n \n \n tb.rulenode.type-field-input-required\n \n \n
\n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n \n tb.rulenode.key-field-input\n \n help\n \n tb.rulenode.key-field-input-required\n \n \n
\n
\n \n tb.rulenode.number-floating-point-field-input\n \n \n \n
\n
\n \n {{\'tb.rulenode.add-to-message-field-input\' | translate }}\n \n \n {{\'tb.rulenode.add-to-metadata-field-input\' | translate}}\n \n
\n
\n
\n
\n',styles:[":host ::ng-deep .fields-group{padding:0 16px 8px;margin:10px 0;border:1px groove rgba(0,0,0,.25);border-radius:4px}:host ::ng-deep .fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}:host ::ng-deep .fields-group legend{color:#000000b3;width:-moz-fit-content;width:fit-content}:host ::ng-deep .fields-group legend+*{display:block}:host ::ng-deep .fields-group legend+*.no-margin-top{margin-top:0}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:R.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:In,selector:"tb-arguments-map-config",inputs:["disabled","function"]},{kind:"component",type:Nn,selector:"tb-math-function-autocomplete",inputs:["required","disabled"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sn,decorators:[{type:n,args:[{selector:"tb-action-node-math-function-config",template:'
\n \n \n
\n tb.rulenode.argument-tile\n \n \n
\n
\n {{\'tb.rulenode.custom-expression-field-input\' | translate }} *\n \n \n \n tb.rulenode.custom-expression-field-input-required\n \n tb.rulenode.custom-expression-field-input-hint\n \n
\n
\n tb.rulenode.result-title\n
\n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(mathFunctionConfigForm.get(\'result.type\').value)?.name | translate }}\n \n \n {{ argumentTypeResultMap.get(argument).name | translate }}\n \n {{ argumentTypeResultMap.get(argument).description }}\n \n \n \n \n tb.rulenode.type-field-input-required\n \n \n
\n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n \n tb.rulenode.key-field-input\n \n help\n \n tb.rulenode.key-field-input-required\n \n \n
\n
\n \n tb.rulenode.number-floating-point-field-input\n \n \n \n
\n
\n \n {{\'tb.rulenode.add-to-message-field-input\' | translate }}\n \n \n {{\'tb.rulenode.add-to-metadata-field-input\' | translate}}\n \n
\n
\n
\n
\n',styles:[":host ::ng-deep .fields-group{padding:0 16px 8px;margin:10px 0;border:1px groove rgba(0,0,0,.25);border-radius:4px}:host ::ng-deep .fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}:host ::ng-deep .fields-group legend{color:#000000b3;width:-moz-fit-content;width:fit-content}:host ::ng-deep .fields-group legend+*{display:block}:host ::ng-deep .fields-group legend+*.no-margin-top{margin-top:0}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class qn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.messageTypeNames=T,this.eventOptions=[I.CONNECT_EVENT,I.ACTIVITY_EVENT,I.DISCONNECT_EVENT,I.INACTIVITY_EVENT]}configForm(){return this.deviceState}prepareInputConfig(e){return{event:ae(e?.event)?e.event:I.ACTIVITY_EVENT}}onConfigurationSet(e){this.deviceState=this.fb.group({event:[e.event,[O.required]]})}}e("DeviceStateConfigComponent",qn),qn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:qn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),qn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:qn,selector:"tb-action-node-device-state-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.select-device-connectivity-event\' | translate }}\n \n \n {{ messageTypeNames.get(eventOption) }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:qn,decorators:[{type:n,args:[{selector:"tb-action-node-device-state-config",template:'
\n \n {{ \'tb.rulenode.select-device-connectivity-event\' | translate }}\n \n \n {{ messageTypeNames.get(eventOption) }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class An{constructor(){this.textAlign="left"}}e("ExampleHintComponent",An),An.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:An,deps:[],target:t.ɵɵFactoryTarget.Component}),An.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:An,selector:"tb-example-hint",inputs:{hintText:"hintText",popupHelpLink:"popupHelpLink",textAlign:"textAlign"},ngImport:t,template:'
\n
\n
\n
\n
\n',styles:[":host .space-between{display:flex;justify-content:space-between;gap:20px}:host .space-between .see-example{display:flex;flex-shrink:0}:host .hint-text{width:100%}\n"],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:$e.HelpPopupComponent,selector:"[tb-help-popup], [tb-help-popup-content]",inputs:["tb-help-popup","tb-help-popup-content","trigger-text","trigger-style","tb-help-popup-placement","tb-help-popup-style","hintMode"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:An,decorators:[{type:n,args:[{selector:"tb-example-hint",template:'
\n
\n
\n
\n
\n',styles:[":host .space-between{display:flex;justify-content:space-between;gap:20px}:host .space-between .see-example{display:flex;flex-shrink:0}:host .hint-text{width:100%}\n"]}]}],propDecorators:{hintText:[{type:i}],popupHelpLink:[{type:i}],textAlign:[{type:i}]}});class Mn{constructor(e,t){this.injector=e,this.fb=t,this.propagateChange=()=>{},this.destroy$=new Ke,this.disabled=!1,this.uniqueKeyValuePairValidator=!1,this.required=!1,this.duplicateValuesValidator=e=>e.controls.key.value===e.controls.value.value&&e.controls.key.value&&e.controls.value.value?{uniqueKeyValuePair:!0}:null,this.oneMapRequiredValidator=e=>e.get("keyVals").value.length,this.propagateNestedErrors=e=>{if(this.kvListFormGroup&&this.kvListFormGroup.get("keyVals")&&"VALID"===this.kvListFormGroup.get("keyVals")?.status)return null;const t={};if(this.kvListFormGroup&&this.kvListFormGroup.setErrors(null),e instanceof z||e instanceof U){if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;for(const n of Object.keys(e.controls)){const r=this.propagateNestedErrors(e.controls[n]);if(r&&Object.keys(r).length)for(const e of Object.keys(r))t[e]=!0}return t}if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;return ie(t,{})?null:t}}ngOnInit(){this.ngControl=this.injector.get(_),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({keyVals:this.fb.array([])},{validators:[this.propagateNestedErrors,this.oneMapRequiredValidator]}),this.kvListFormGroup.valueChanges.pipe(ze(this.destroy$)).subscribe((()=>{this.updateModel()}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){const t=Object.keys(e).map((t=>({key:t,value:e[t]})));if(this.keyValsFormArray().length===t.length)this.keyValsFormArray().patchValue(t,{emitEvent:!1});else{const e=[];t.forEach((t=>{e.push(this.fb.group({key:[t.key,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:[t.value,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]},{validators:this.uniqueKeyValuePairValidator?[this.duplicateValuesValidator]:[]}))})),this.kvListFormGroup.setControl("keyVals",this.fb.array(e,this.propagateNestedErrors),{emitEvent:!1})}}removeKeyVal(e){this.keyValsFormArray().removeAt(e)}addKeyVal(){this.keyValsFormArray().push(this.fb.group({key:["",[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:["",[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]},{validators:this.uniqueKeyValuePairValidator?[this.duplicateValuesValidator]:[]}))}validate(){const e=this.kvListFormGroup.get("keyVals").value;if(!e.length&&this.required)return{kvMapRequired:!0};if(!this.kvListFormGroup.valid)return{kvFieldsRequired:!0};if(this.uniqueKeyValuePairValidator)for(const t of e)if(t.key===t.value)return{uniqueKeyValuePair:!0};return null}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("KvMapConfigComponent",Mn),Mn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Mn,deps:[{token:t.Injector},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Mn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Mn,selector:"tb-kv-map-config",inputs:{disabled:"disabled",uniqueKeyValuePairValidator:"uniqueKeyValuePairValidator",labelText:"labelText",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",popupHelpLink:"popupHelpLink",required:"required"},providers:[{provide:B,useExisting:a((()=>Mn)),multi:!0},{provide:K,useExisting:a((()=>Mn)),multi:!0}],ngImport:t,template:'
\n
\n
{{ labelText }}
\n
\n {{ requiredText }}\n
\n
\n tb.rulenode.map-fields-required\n
\n
\n {{ \'tb.key-val.unique-key-value-pair-error\' | translate:\n {\n valText: valText,\n keyText: keyText\n } }}\n
\n
\n
\n
\n
\n
{{ keyText }}
\n
{{ valText }}
\n
\n
\n
\n
\n \n \n \n \n \n \n
\n \n
\n
\n
\n
\n
\n
\n \n
\n \n
\n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),Be([N()],Mn.prototype,"disabled",void 0),Be([N()],Mn.prototype,"uniqueKeyValuePairValidator",void 0),Be([N()],Mn.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Mn,decorators:[{type:n,args:[{selector:"tb-kv-map-config",providers:[{provide:B,useExisting:a((()=>Mn)),multi:!0},{provide:K,useExisting:a((()=>Mn)),multi:!0}],template:'
\n
\n
{{ labelText }}
\n
\n {{ requiredText }}\n
\n
\n tb.rulenode.map-fields-required\n
\n
\n {{ \'tb.key-val.unique-key-value-pair-error\' | translate:\n {\n valText: valText,\n keyText: keyText\n } }}\n
\n
\n
\n
\n
\n
{{ keyText }}
\n
{{ valText }}
\n
\n
\n
\n
\n \n \n \n \n \n \n
\n \n
\n
\n
\n
\n
\n
\n \n
\n \n
\n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:t.Injector},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],uniqueKeyValuePairValidator:[{type:i}],labelText:[{type:i}],requiredText:[{type:i}],keyText:[{type:i}],keyRequiredText:[{type:i}],valText:[{type:i}],valRequiredText:[{type:i}],hintText:[{type:i}],popupHelpLink:[{type:i}],required:[{type:i}]}});class En extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.values(v),this.directionTypeTranslations=C,this.entityType=F,this.propagateChange=null}ngOnInit(){this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[O.required]],maxLevel:[null,[O.min(1)]],relationType:[null],deviceTypes:[null,[O.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((e=>{this.deviceRelationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})}}e("DeviceRelationsQueryConfigComponent",En),En.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:En,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),En.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:En,selector:"tb-device-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>En)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n \n \n help\n \n
\n',styles:[":host .last-level-slide-toggle{margin:8px 0 24px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Qe.EntitySubTypeListComponent,selector:"tb-entity-subtype-list",inputs:["required","floatLabel","label","disabled","entityType","emptyInputPlaceholder","filledInputPlaceholder","appearance","subscriptSizing","additionalClasses"]},{kind:"component",type:Je.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["showLabel","additionalClasses","appearance","required","disabled","subscriptSizing"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:En,decorators:[{type:n,args:[{selector:"tb-device-relations-query-config",providers:[{provide:B,useExisting:a((()=>En)),multi:!0}],template:'
\n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n \n \n help\n \n
\n',styles:[":host .last-level-slide-toggle{margin:8px 0 24px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class Gn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.values(v),this.directionTypeTranslations=C,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[O.required]],maxLevel:[null,[O.min(1)]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}}e("RelationsQueryConfigComponent",Gn),Gn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Gn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Gn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Gn,selector:"tb-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>Gn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
tb.rulenode.relations-query
\n
\n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n
\n
\n
relation.relation-filters
\n \n \n
\n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Ye.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Gn,decorators:[{type:n,args:[{selector:"tb-relations-query-config",providers:[{provide:B,useExisting:a((()=>Gn)),multi:!0}],template:'
\n
tb.rulenode.relations-query
\n
\n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n
\n
\n
relation.relation-filters
\n \n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class Dn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.truncate=n,this.fb=r,this.placeholder="tb.rulenode.add-message-type",this.separatorKeysCodes=[ge,ye,xe],this.messageTypes=[],this.messageTypesList=[],this.searchText="",this.propagateChange=e=>{},this.messageTypeConfigForm=this.fb.group({messageType:[null]});for(const e of Object.keys(I))this.messageTypesList.push({name:T.get(I[e]),value:e})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnInit(){this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(Ge(""),Ee((e=>e||"")),De((e=>this.fetchMessageTypes(e))),we())}setDisabledState(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})}writeValue(e){this.searchText="",this.messageTypes.length=0,e&&e.forEach((e=>{const t=this.messageTypesList.find((t=>t.value===e));t?this.messageTypes.push({name:t.name,value:t.value}):this.messageTypes.push({name:e,value:e})}))}displayMessageTypeFn(e){return e?e.name:void 0}textIsNotEmpty(e){return e&&e.length>0}createMessageType(e,t){e.preventDefault(),this.transformMessageType(t)}add(e){this.transformMessageType(e.value)}fetchMessageTypes(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ue(this.messageTypesList.filter((t=>t.name.toUpperCase().includes(e))))}return Ue(this.messageTypesList)}transformMessageType(e){if((e||"").trim()){let t;const n=e.trim(),r=this.messageTypesList.find((e=>e.name===n));t=r?{name:r.name,value:r.value}:{name:n,value:n},t&&this.addMessageType(t)}this.clear("")}remove(e){const t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())}selected(e){this.addMessageType(e.option.value),this.clear("")}addMessageType(e){-1===this.messageTypes.findIndex((t=>t.value===e.value))&&(this.messageTypes.push(e),this.updateModel())}onFocus(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.messageTypeInput.nativeElement.blur(),this.messageTypeInput.nativeElement.focus()}),0)}updateModel(){const e=this.messageTypes.map((e=>e.value));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))}}e("MessageTypesConfigComponent",Dn),Dn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Dn,deps:[{token:P.Store},{token:Z.TranslateService},{token:S.TruncatePipe},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Dn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Dn,selector:"tb-message-types-config",inputs:{required:"required",label:"label",placeholder:"placeholder",disabled:"disabled"},providers:[{provide:B,useExisting:a((()=>Dn)),multi:!0}],viewQueries:[{propertyName:"chipList",first:!0,predicate:["chipList"],descendants:!0},{propertyName:"matAutocomplete",first:!0,predicate:["messageTypeAutocomplete"],descendants:!0},{propertyName:"messageTypeInput",first:!0,predicate:["messageTypeInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ \'tb.rulenode.no-message-type-matching\' | translate :\n {messageType: truncate.transform(searchText, true, 6, '...')}\n }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n help\n \n {{ \'tb.rulenode.select-message-types-required\' | translate }}\n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:Re.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Re.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:Re.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{kind:"component",type:be.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:be.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:be.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:be.MatChipRow,selector:"mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]",inputs:["color","disabled","disableRipple","tabIndex","editable"],outputs:["edited"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Oe.HighlightPipe,name:"highlight"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Dn,decorators:[{type:n,args:[{selector:"tb-message-types-config",providers:[{provide:B,useExisting:a((()=>Dn)),multi:!0}],template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ \'tb.rulenode.no-message-type-matching\' | translate :\n {messageType: truncate.transform(searchText, true, 6, '...')}\n }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n help\n \n {{ \'tb.rulenode.select-message-types-required\' | translate }}\n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:S.TruncatePipe},{type:R.FormBuilder}]},propDecorators:{required:[{type:i}],label:[{type:i}],placeholder:[{type:i}],disabled:[{type:i}],chipList:[{type:o,args:["chipList",{static:!1}]}],matAutocomplete:[{type:o,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:o,args:["messageTypeInput",{static:!1}]}]}});class wn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[],this.disableCertPemCredentials=!1,this.passwordFieldRequired=!0,this.allCredentialsTypes=zt,this.credentialsTypeTranslationsMap=Ut,this.propagateChange=e=>{}}ngOnInit(){this.credentialsConfigFormGroup=this.fb.group({type:[null,[O.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.subscribe((()=>{this.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((()=>{this.credentialsTypeChanged()})))}ngOnChanges(e){for(const t of Object.keys(e)){const n=e[t];if(!n.firstChange&&n.currentValue!==n.previousValue&&n.currentValue&&"disableCertPemCredentials"===t){"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((()=>{this.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}writeValue(e){ae(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators())}setDisabledState(e){e?this.credentialsConfigFormGroup.disable({emitEvent:!1}):(this.credentialsConfigFormGroup.enable({emitEvent:!1}),this.updateValidators())}updateView(){let e=this.credentialsConfigFormGroup.value;const t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}validate(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}}credentialsTypeChanged(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()}updateValidators(e=!1){const t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([O.required]),this.credentialsConfigFormGroup.get("password").setValidators(this.passwordFieldRequired?[O.required]:[]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(O.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})}requiredFilesSelected(e,t=null){return n=>{t||(t=[Object.keys(n.controls)]);return n?.controls&&t.some((t=>t.every((t=>!e(n.controls[t])))))?null:{notAllRequiredFilesSelected:!0}}}}e("CredentialsConfigComponent",wn),wn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:wn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),wn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:wn,selector:"tb-credentials-config",inputs:{required:"required",disableCertPemCredentials:"disableCertPemCredentials",passwordFieldRequired:"passwordFieldRequired"},providers:[{provide:B,useExisting:a((()=>wn)),multi:!0},{provide:K,useExisting:a((()=>wn)),multi:!0}],usesInheritance:!0,usesOnChanges:!0,ngImport:t,template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:H.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{kind:"directive",type:H.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:We.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:We.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:We.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:We.MatExpansionPanelDescription,selector:"mat-panel-description"},{kind:"directive",type:We.MatExpansionPanelContent,selector:"ng-template[matExpansionPanelContent]"},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Ze.FileInputComponent,selector:"tb-file-input",inputs:["label","hint","accept","noFileText","inputId","allowedExtensions","dropLabel","maxSizeByte","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:wn,decorators:[{type:n,args:[{selector:"tb-credentials-config",providers:[{provide:B,useExisting:a((()=>wn)),multi:!0},{provide:K,useExisting:a((()=>wn)),multi:!0}],template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{required:[{type:i}],disableCertPemCredentials:[{type:i}],passwordFieldRequired:[{type:i}]}});const Vn=new l("WindowToken","undefined"!=typeof window&&window.document?{providedIn:"root",factory:()=>window}:{providedIn:"root",factory:()=>{}});class Pn{constructor(e,t,n){this.ngZone=e,this.document=t,this.window=n,this.copySubject=new Ke,this.copyResponse$=this.copySubject.asObservable(),this.config={}}configure(e){this.config=e}copy(e){if(!this.isSupported||!e)return this.pushCopyResponse({isSuccess:!1,content:e});const t=this.copyFromContent(e);return t?this.pushCopyResponse({content:e,isSuccess:t}):this.pushCopyResponse({isSuccess:!1,content:e})}get isSupported(){return!!this.document.queryCommandSupported&&!!this.document.queryCommandSupported("copy")&&!!this.window}isTargetValid(e){if(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement){if(e.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');return!0}throw new Error("Target should be input or textarea")}copyFromInputElement(e,t=!0){try{this.selectTarget(e);const n=this.copyText();return this.clearSelection(t?e:void 0,this.window),n&&this.isCopySuccessInIE11()}catch(e){return!1}}isCopySuccessInIE11(){const e=this.window.clipboardData;return!(e&&e.getData&&!e.getData("Text"))}copyFromContent(e,t=this.document.body){if(this.tempTextArea&&!t.contains(this.tempTextArea)&&this.destroy(this.tempTextArea.parentElement||void 0),!this.tempTextArea){this.tempTextArea=this.createTempTextArea(this.document,this.window);try{t.appendChild(this.tempTextArea)}catch(e){throw new Error("Container should be a Dom element")}}this.tempTextArea.value=e;const n=this.copyFromInputElement(this.tempTextArea,!1);return this.config.cleanUpAfterCopy&&this.destroy(this.tempTextArea.parentElement||void 0),n}destroy(e=this.document.body){this.tempTextArea&&(e.removeChild(this.tempTextArea),this.tempTextArea=void 0)}selectTarget(e){return e.select(),e.setSelectionRange(0,e.value.length),e.value.length}copyText(){return this.document.execCommand("copy")}clearSelection(e,t){e&&e.focus(),t.getSelection()?.removeAllRanges()}createTempTextArea(e,t){const n="rtl"===e.documentElement.getAttribute("dir");let r;r=e.createElement("textarea"),r.style.fontSize="12pt",r.style.border="0",r.style.padding="0",r.style.margin="0",r.style.position="absolute",r.style[n?"right":"left"]="-9999px";const o=t.pageYOffset||e.documentElement.scrollTop;return r.style.top=o+"px",r.setAttribute("readonly",""),r}pushCopyResponse(e){this.copySubject.observers.length>0&&this.ngZone.run((()=>{this.copySubject.next(e)}))}pushCopyReponse(e){this.pushCopyResponse(e)}}Pn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Pn,deps:[{token:t.NgZone},{token:j},{token:Vn,optional:!0}],target:t.ɵɵFactoryTarget.Injectable}),Pn.ɵprov=t.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Pn,providedIn:"root"}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Pn,decorators:[{type:s,args:[{providedIn:"root"}]}],ctorParameters:function(){return[{type:t.NgZone},{type:void 0,decorators:[{type:m,args:[j]}]},{type:void 0,decorators:[{type:p},{type:m,args:[Vn]}]}]}});class Rn{constructor(e,t,n,o){this.ngZone=e,this.host=t,this.renderer=n,this.clipboardSrv=o,this.cbOnSuccess=new r,this.cbOnError=new r,this.onClick=e=>{this.clipboardSrv.isSupported?this.targetElm&&this.clipboardSrv.isTargetValid(this.targetElm)?this.handleResult(this.clipboardSrv.copyFromInputElement(this.targetElm),this.targetElm.value,e):this.cbContent&&this.handleResult(this.clipboardSrv.copyFromContent(this.cbContent,this.container),this.cbContent,e):this.handleResult(!1,void 0,e)}}ngOnInit(){this.ngZone.runOutsideAngular((()=>{this.clickListener=this.renderer.listen(this.host.nativeElement,"click",this.onClick)}))}ngOnDestroy(){this.clickListener&&this.clickListener(),this.clipboardSrv.destroy(this.container)}handleResult(e,t,n){let r={isSuccess:e,content:t,successMessage:this.cbSuccessMsg,event:n};e?this.cbOnSuccess.observed&&this.ngZone.run((()=>{this.cbOnSuccess.emit(r)})):this.cbOnError.observed&&this.ngZone.run((()=>{this.cbOnError.emit(r)})),this.clipboardSrv.pushCopyResponse(r)}}Rn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Rn,deps:[{token:t.NgZone},{token:t.ElementRef},{token:t.Renderer2},{token:Pn}],target:t.ɵɵFactoryTarget.Directive}),Rn.ɵdir=t.ɵɵngDeclareDirective({minVersion:"12.0.0",version:"13.0.1",type:Rn,selector:"[ngxClipboard]",inputs:{targetElm:["ngxClipboard","targetElm"],container:"container",cbContent:"cbContent",cbSuccessMsg:"cbSuccessMsg"},outputs:{cbOnSuccess:"cbOnSuccess",cbOnError:"cbOnError"},ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Rn,decorators:[{type:d,args:[{selector:"[ngxClipboard]"}]}],ctorParameters:function(){return[{type:t.NgZone},{type:t.ElementRef},{type:t.Renderer2},{type:Pn}]},propDecorators:{targetElm:[{type:i,args:["ngxClipboard"]}],container:[{type:i}],cbContent:[{type:i}],cbSuccessMsg:[{type:i}],cbOnSuccess:[{type:u}],cbOnError:[{type:u}]}});class On{constructor(e,t,n){this._clipboardService=e,this._viewContainerRef=t,this._templateRef=n}ngOnInit(){this._clipboardService.isSupported&&this._viewContainerRef.createEmbeddedView(this._templateRef)}}On.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:On,deps:[{token:Pn},{token:t.ViewContainerRef},{token:t.TemplateRef}],target:t.ɵɵFactoryTarget.Directive}),On.ɵdir=t.ɵɵngDeclareDirective({minVersion:"12.0.0",version:"13.0.1",type:On,selector:"[ngxClipboardIfSupported]",ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:On,decorators:[{type:d,args:[{selector:"[ngxClipboardIfSupported]"}]}],ctorParameters:function(){return[{type:Pn},{type:t.ViewContainerRef},{type:t.TemplateRef}]}});class _n{}_n.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:_n,deps:[],target:t.ɵɵFactoryTarget.NgModule}),_n.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:_n,declarations:[Rn,On],imports:[$],exports:[Rn,On]}),_n.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:_n,imports:[[$]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:_n,decorators:[{type:c,args:[{imports:[$],declarations:[Rn,On],exports:[Rn,On]}]}]});class Bn{set required(e){this.requiredValue!==e&&(this.requiredValue=e,this.updateValidators())}get required(){return this.requiredValue}constructor(e){this.fb=e,this.subscriptSizing="fixed",this.messageTypes=[{name:"Post attributes",value:"POST_ATTRIBUTES_REQUEST"},{name:"Post telemetry",value:"POST_TELEMETRY_REQUEST"},{name:"Custom",value:""}],this.propagateChange=()=>{},this.destroy$=new Ke,this.messageTypeFormGroup=this.fb.group({messageTypeAlias:[null,[O.required]],messageType:[{value:null,disabled:!0},[O.maxLength(255)]]}),this.messageTypeFormGroup.get("messageTypeAlias").valueChanges.pipe(ze(this.destroy$)).subscribe((e=>this.updateMessageTypeValue(e))),this.messageTypeFormGroup.valueChanges.pipe(ze(this.destroy$)).subscribe((()=>this.updateView()))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}registerOnTouched(e){}registerOnChange(e){this.propagateChange=e}writeValue(e){this.modelValue=e;let t=this.messageTypes.find((t=>t.value===e));t||(t=this.messageTypes.find((e=>""===e.value))),this.messageTypeFormGroup.get("messageTypeAlias").patchValue(t,{emitEvent:!1}),this.messageTypeFormGroup.get("messageType").patchValue(e,{emitEvent:!1})}validate(){return this.messageTypeFormGroup.valid?null:{messageTypeInvalid:!0}}setDisabledState(e){this.disabled=e,e?this.messageTypeFormGroup.disable({emitEvent:!1}):(this.messageTypeFormGroup.enable({emitEvent:!1}),"Custom"!==this.messageTypeFormGroup.get("messageTypeAlias").value?.name&&this.messageTypeFormGroup.get("messageType").disable({emitEvent:!1}))}updateView(){const e=this.messageTypeFormGroup.getRawValue().messageType;this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}updateValidators(){this.messageTypeFormGroup.get("messageType").setValidators(this.required?[O.required,O.maxLength(255)]:[O.maxLength(255)]),this.messageTypeFormGroup.get("messageType").updateValueAndValidity({emitEvent:!1})}updateMessageTypeValue(e){"Custom"!==e?.name?this.messageTypeFormGroup.get("messageType").disable({emitEvent:!1}):this.messageTypeFormGroup.get("messageType").enable({emitEvent:!1}),this.messageTypeFormGroup.get("messageType").patchValue(e.value??null)}}e("OutputMessageTypeAutocompleteComponent",Bn),Bn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Bn,deps:[{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Bn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Bn,selector:"tb-output-message-type-autocomplete",inputs:{subscriptSizing:"subscriptSizing",disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>Bn)),multi:!0},{provide:K,useExisting:a((()=>Bn)),multi:!0}],ngImport:t,template:'
\n \n {{\'tb.rulenode.output-message-type\' | translate}}\n \n \n {{msgType.name}}\n \n \n \n \n {{\'tb.rulenode.message-type-value\' | translate}}\n \n \n \n {{ \'tb.rulenode.message-type-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.message-type-value-max-length\' | translate }}\n \n \n
\n\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:Rn,selector:"[ngxClipboard]",inputs:["ngxClipboard","container","cbContent","cbSuccessMsg"],outputs:["cbOnSuccess","cbOnError"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),Be([N()],Bn.prototype,"disabled",void 0),Be([N()],Bn.prototype,"required",null),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Bn,decorators:[{type:n,args:[{selector:"tb-output-message-type-autocomplete",providers:[{provide:B,useExisting:a((()=>Bn)),multi:!0},{provide:K,useExisting:a((()=>Bn)),multi:!0}],template:'
\n \n {{\'tb.rulenode.output-message-type\' | translate}}\n \n \n {{msgType.name}}\n \n \n \n \n {{\'tb.rulenode.message-type-value\' | translate}}\n \n \n \n {{ \'tb.rulenode.message-type-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.message-type-value-max-length\' | translate }}\n \n \n
\n\n'}]}],ctorParameters:function(){return[{type:R.FormBuilder}]},propDecorators:{subscriptSizing:[{type:i}],disabled:[{type:i}],required:[{type:i}]}});class Kn{constructor(e,t){this.fb=e,this.translate=t,this.translation=nn,this.propagateChange=()=>{},this.destroy$=new Ke,this.selectOptions=[]}ngOnInit(){this.initOptions(),this.chipControlGroup=this.fb.group({chipControl:[null,[]]}),this.chipControlGroup.get("chipControl").valueChanges.pipe(Ve(this.destroy$)).subscribe((e=>{e&&this.propagateChange(e)}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}initOptions(){for(const e of this.translation.keys())this.selectOptions.push({value:e,name:this.translate.instant(this.translation.get(e))})}writeValue(e){this.chipControlGroup.get("chipControl").patchValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){e?this.chipControlGroup.disable({emitEvent:!1}):this.chipControlGroup.enable({emitEvent:!1})}}e("MsgMetadataChipComponent",Kn),Kn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kn,deps:[{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Kn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Kn,selector:"tb-msg-metadata-chip",inputs:{labelText:"labelText",translation:"translation"},providers:[{provide:B,useExisting:a((()=>Kn)),multi:!0}],ngImport:t,template:'
\n
{{ labelText }}
\n \n {{ option.name }}\n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:be.MatChipListbox,selector:"mat-chip-listbox",inputs:["tabIndex","multiple","aria-orientation","selectable","compareWith","required","hideSingleSelectionIndicator","value"],outputs:["change"]},{kind:"component",type:be.MatChipOption,selector:"mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]",inputs:["color","disabled","disableRipple","tabIndex","selectable","selected"],outputs:["selectionChange"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kn,decorators:[{type:n,args:[{selector:"tb-msg-metadata-chip",providers:[{provide:B,useExisting:a((()=>Kn)),multi:!0}],template:'
\n
{{ labelText }}
\n \n {{ option.name }}\n \n
\n'}]}],ctorParameters:function(){return[{type:R.FormBuilder},{type:Z.TranslateService}]},propDecorators:{labelText:[{type:i}],translation:[{type:i}]}});class zn extends L{constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.destroy$=new Ke,this.sourceFieldSubcritption=[],this.propagateChange=null,this.disabled=!1,this.required=!1,this.oneMapRequiredValidator=e=>e.get("keyVals").value.length,this.propagateNestedErrors=e=>{if(this.svListFormGroup&&this.svListFormGroup.get("keyVals")&&"VALID"===this.svListFormGroup.get("keyVals")?.status)return null;const t={};if(this.svListFormGroup&&this.svListFormGroup.setErrors(null),e instanceof z||e instanceof U){if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;for(const n of Object.keys(e.controls)){const r=this.propagateNestedErrors(e.controls[n]);if(r&&Object.keys(r).length)for(const e of Object.keys(r))t[e]=!0}return t}if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;return ie(t,{})?null:t}}ngOnInit(){this.ngControl=this.injector.get(_),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.svListFormGroup=this.fb.group({keyVals:this.fb.array([])},{validators:[this.propagateNestedErrors,this.oneMapRequiredValidator]}),this.svListFormGroup.valueChanges.pipe(Ve(this.destroy$)).subscribe((()=>{this.updateModel()}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}keyValsFormArray(){return this.svListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.svListFormGroup.disable({emitEvent:!1}):this.svListFormGroup.enable({emitEvent:!1})}writeValue(e){const t=Object.keys(e).map((t=>({key:t,value:e[t]})));if(this.keyValsFormArray().length===t.length)this.keyValsFormArray().patchValue(t,{emitEvent:!1});else{const e=[];t.forEach((t=>{e.push(this.fb.group({key:[t.key,[O.required]],value:[t.value,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]}))})),this.svListFormGroup.setControl("keyVals",this.fb.array(e,this.propagateNestedErrors),{emitEvent:!1});for(const e of this.keyValsFormArray().controls)this.keyChangeSubscribe(e)}}filterSelectOptions(e){const t=[];for(const e of this.svListFormGroup.get("keyVals").value){const n=this.selectOptions.find((t=>t.value===e.key));n&&t.push(n)}const n=[];for(const r of this.selectOptions)ae(t.find((e=>e.value===r.value)))&&r.value!==e?.get("key").value||n.push(r);return n}removeKeyVal(e){this.keyValsFormArray().removeAt(e),this.sourceFieldSubcritption[e].unsubscribe(),this.sourceFieldSubcritption.splice(e,1)}addKeyVal(){this.keyValsFormArray().push(this.fb.group({key:["",[O.required]],value:["",[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]})),this.keyChangeSubscribe(this.keyValsFormArray().at(this.keyValsFormArray().length-1))}keyChangeSubscribe(e){this.sourceFieldSubcritption.push(e.get("key").valueChanges.pipe(Ve(this.destroy$)).subscribe((t=>{const n=Lt.get(t);e.get("value").patchValue(this.targetKeyPrefix+n[0].toUpperCase()+n.slice(1))})))}validate(e){return!this.svListFormGroup.get("keyVals").value.length&&this.required?{svMapRequired:!0}:this.svListFormGroup.valid?null:{svFieldsRequired:!0}}updateModel(){const e=this.svListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.svListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("SvMapConfigComponent",zn),zn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zn,deps:[{token:P.Store},{token:Z.TranslateService},{token:t.Injector},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),zn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:zn,selector:"tb-sv-map-config",inputs:{selectOptions:"selectOptions",disabled:"disabled",labelText:"labelText",requiredText:"requiredText",targetKeyPrefix:"targetKeyPrefix",selectText:"selectText",selectRequiredText:"selectRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",popupHelpLink:"popupHelpLink",required:"required"},providers:[{provide:B,useExisting:a((()=>zn)),multi:!0},{provide:K,useExisting:a((()=>zn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n
{{ labelText }}
\n
\n tb.rulenode.map-fields-required\n
\n
\n {{ requiredText }}\n
\n
\n
\n
\n
\n
{{ selectText }}
\n
{{ valText }}
\n
\n
\n
\n
\n \n \n \n {{option.name}}\n \n \n \n \n \n \n
\n \n
\n
\n
\n
\n
\n
\n \n
\n \n
\n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgClass,selector:"[ngClass]",inputs:["class","ngClass"]},{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:Se.DefaultClassDirective,selector:" [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]",inputs:["ngClass","ngClass.xs","ngClass.sm","ngClass.md","ngClass.lg","ngClass.xl","ngClass.lt-sm","ngClass.lt-md","ngClass.lt-lg","ngClass.lt-xl","ngClass.gt-xs","ngClass.gt-sm","ngClass.gt-md","ngClass.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),Be([N()],zn.prototype,"disabled",void 0),Be([N()],zn.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zn,decorators:[{type:n,args:[{selector:"tb-sv-map-config",providers:[{provide:B,useExisting:a((()=>zn)),multi:!0},{provide:K,useExisting:a((()=>zn)),multi:!0}],template:'
\n
\n
{{ labelText }}
\n
\n tb.rulenode.map-fields-required\n
\n
\n {{ requiredText }}\n
\n
\n
\n
\n
\n
{{ selectText }}
\n
{{ valText }}
\n
\n
\n
\n
\n \n \n \n {{option.name}}\n \n \n \n \n \n \n
\n \n
\n
\n
\n
\n
\n
\n \n
\n \n
\n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:t.Injector},{type:R.FormBuilder}]},propDecorators:{selectOptions:[{type:i}],disabled:[{type:i}],labelText:[{type:i}],requiredText:[{type:i}],targetKeyPrefix:[{type:i}],selectText:[{type:i}],selectRequiredText:[{type:i}],valText:[{type:i}],valRequiredText:[{type:i}],hintText:[{type:i}],popupHelpLink:[{type:i}],required:[{type:i}]}});class Un extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(v),this.directionTypeTranslations=C,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[O.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}}e("RelationsQueryConfigOldComponent",Un),Un.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Un,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Un.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Un,selector:"tb-relations-query-config-old",inputs:{disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>Un)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Ye.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Un,decorators:[{type:n,args:[{selector:"tb-relations-query-config-old",providers:[{provide:B,useExisting:a((()=>Un)),multi:!0}],template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class Hn{constructor(e,t){this.translate=e,this.fb=t,this.propagateChange=e=>{},this.destroy$=new Ke,this.separatorKeysCodes=[ge,ye,xe],this.onTouched=()=>{}}ngOnInit(){this.attributeControlGroup=this.fb.group({clientAttributeNames:[[],[]],sharedAttributeNames:[[],[]],serverAttributeNames:[[],[]],latestTsKeyNames:[[],[]],getLatestValueWithTs:[!1,[]]},{validators:this.atLeastOne(O.required,["clientAttributeNames","sharedAttributeNames","serverAttributeNames","latestTsKeyNames"])}),this.attributeControlGroup.valueChanges.pipe(Ve(this.destroy$)).subscribe((e=>{this.propagateChange(this.preparePropagateValue(e))}))}preparePropagateValue(e){const t={};for(const n in e)t[n]="getLatestValueWithTs"===n||ae(e[n])?e[n]:[];return t}validate(){return this.attributeControlGroup.valid?null:{atLeastOneRequired:!0}}atLeastOne(e,t=null){return n=>{t||(t=Object.keys(n.controls));return n?.controls&&t.some((t=>!e(n.controls[t])))?null:{atLeastOne:!0}}}writeValue(e){this.attributeControlGroup.setValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){this.onTouched=e}setDisabledState(e){e?this.attributeControlGroup.disable({emitEvent:!1}):this.attributeControlGroup.enable({emitEvent:!1})}ngOnDestroy(){this.destroy$.next(null),this.destroy$.complete()}}e("SelectAttributesComponent",Hn),Hn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hn,deps:[{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Hn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Hn,selector:"tb-select-attributes",inputs:{popupHelpLink:"popupHelpLink"},providers:[{provide:B,useExisting:a((()=>Hn)),multi:!0},{provide:K,useExisting:Hn,multi:!0}],ngImport:t,template:'
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n {{ \'tb.rulenode.fetch-latest-telemetry-with-timestamp\' | translate }}\n \n
\n
\n\n\n help\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:H.NgTemplateOutlet,selector:"[ngTemplateOutlet]",inputs:["ngTemplateOutletContext","ngTemplateOutlet","ngTemplateOutletInjector"]},{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hn,decorators:[{type:n,args:[{selector:"tb-select-attributes",providers:[{provide:B,useExisting:a((()=>Hn)),multi:!0},{provide:K,useExisting:Hn,multi:!0}],template:'
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n {{ \'tb.rulenode.fetch-latest-telemetry-with-timestamp\' | translate }}\n \n
\n
\n\n\n help\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:Z.TranslateService},{type:R.FormBuilder}]},propDecorators:{popupHelpLink:[{type:i}]}});class jn extends L{constructor(e,t){super(e),this.store=e,this.fb=t,this.propagateChange=null,this.destroy$=new Ke,this.alarmStatus=q,this.alarmStatusTranslations=A}ngOnInit(){this.alarmStatusGroup=this.fb.group({alarmStatus:[null,[]]}),this.alarmStatusGroup.get("alarmStatus").valueChanges.pipe(Ve(this.destroy$)).subscribe((e=>{this.propagateChange(e)}))}setDisabledState(e){e?this.alarmStatusGroup.disable({emitEvent:!1}):this.alarmStatusGroup.enable({emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}writeValue(e){this.alarmStatusGroup.get("alarmStatus").patchValue(e,{emitEvent:!1})}}e("AlarmStatusSelectComponent",jn),jn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:jn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),jn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:jn,selector:"tb-alarm-status-select",providers:[{provide:B,useExisting:a((()=>jn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n
\n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_ACK) | translate }}\n \n
\n
\n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_ACK) | translate }}\n \n
\n
\n
\n',styles:[":host .chip-listbox{max-width:460px;width:100%}:host .chip-listbox .toggle-column{display:flex;flex:1 1 100%;gap:8px}:host .chip-listbox .option{margin:0}@media screen and (max-width: 959px){:host .chip-listbox{max-width:360px}:host .chip-listbox .toggle-column{flex-direction:column}}:host ::ng-deep .chip-listbox .mdc-evolution-chip-set__chips{gap:8px}:host ::ng-deep .chip-listbox .option button{flex-basis:100%;justify-content:start}:host ::ng-deep .chip-listbox .option .mdc-evolution-chip__graphic{flex-grow:0}\n"],dependencies:[{kind:"component",type:be.MatChipListbox,selector:"mat-chip-listbox",inputs:["tabIndex","multiple","aria-orientation","selectable","compareWith","required","hideSingleSelectionIndicator","value"],outputs:["change"]},{kind:"component",type:be.MatChipOption,selector:"mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]",inputs:["color","disabled","disableRipple","tabIndex","selectable","selected"],outputs:["selectionChange"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:jn,decorators:[{type:n,args:[{selector:"tb-alarm-status-select",providers:[{provide:B,useExisting:a((()=>jn)),multi:!0}],template:'
\n \n
\n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_ACK) | translate }}\n \n
\n
\n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_ACK) | translate }}\n \n
\n
\n
\n',styles:[":host .chip-listbox{max-width:460px;width:100%}:host .chip-listbox .toggle-column{display:flex;flex:1 1 100%;gap:8px}:host .chip-listbox .option{margin:0}@media screen and (max-width: 959px){:host .chip-listbox{max-width:360px}:host .chip-listbox .toggle-column{flex-direction:column}}:host ::ng-deep .chip-listbox .mdc-evolution-chip-set__chips{gap:8px}:host ::ng-deep .chip-listbox .option button{flex-basis:100%;justify-content:start}:host ::ng-deep .chip-listbox .option .mdc-evolution-chip__graphic{flex-grow:0}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class $n{}e("RulenodeCoreConfigCommonModule",$n),$n.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:$n,deps:[],target:t.ɵɵFactoryTarget.NgModule}),$n.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:$n,declarations:[Mn,En,Gn,Dn,wn,In,Nn,Bn,Cn,Kn,zn,Un,Hn,jn,An],imports:[$,M,_e],exports:[Mn,En,Gn,Dn,wn,In,Nn,Bn,Cn,Kn,zn,Un,Hn,jn,An]}),$n.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:$n,imports:[$,M,_e]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:$n,decorators:[{type:c,args:[{declarations:[Mn,En,Gn,Dn,wn,In,Nn,Bn,Cn,Kn,zn,Un,Hn,jn,An],imports:[$,M,_e],exports:[Mn,En,Gn,Dn,wn,In,Nn,Bn,Cn,Kn,zn,Un,Hn,jn,An]}]}]});class Qn{}e("RuleNodeCoreConfigActionModule",Qn),Qn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Qn,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Qn.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Qn,declarations:[Tn,ct,kn,vn,fn,ut,ft,gt,yt,yn,xt,ht,cn,gn,hn,Fn,Ln,bt,bn,xn,Sn,qn],imports:[$,M,_e,$n],exports:[Tn,ct,kn,vn,fn,ut,ft,gt,yt,yn,xt,ht,cn,gn,hn,Fn,Ln,bt,bn,xn,Sn,qn]}),Qn.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Qn,imports:[$,M,_e,$n]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Qn,decorators:[{type:c,args:[{declarations:[Tn,ct,kn,vn,fn,ut,ft,gt,yt,yn,xt,ht,cn,gn,hn,Fn,Ln,bt,bn,xn,Sn,qn],imports:[$,M,_e,$n],exports:[Tn,ct,kn,vn,fn,ut,ft,gt,yt,yn,xt,ht,cn,gn,hn,Fn,Ln,bt,bn,xn,Sn,qn]}]}]});class Jn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[ge,ye,xe]}configForm(){return this.calculateDeltaConfigForm}onConfigurationSet(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e.inputValueKey,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],outputValueKey:[e.outputValueKey,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],useCache:[e.useCache,[]],addPeriodBetweenMsgs:[e.addPeriodBetweenMsgs,[]],periodValueKey:[e.periodValueKey,[]],round:[e.round,[O.min(0),O.max(15)]],tellFailureIfDeltaIsNegative:[e.tellFailureIfDeltaIsNegative,[]]})}prepareInputConfig(e){return{inputValueKey:ae(e?.inputValueKey)?e.inputValueKey:null,outputValueKey:ae(e?.outputValueKey)?e.outputValueKey:null,useCache:!ae(e?.useCache)||e.useCache,addPeriodBetweenMsgs:!!ae(e?.addPeriodBetweenMsgs)&&e.addPeriodBetweenMsgs,periodValueKey:ae(e?.periodValueKey)?e.periodValueKey:null,round:ae(e?.round)?e.round:null,tellFailureIfDeltaIsNegative:!ae(e?.tellFailureIfDeltaIsNegative)||e.tellFailureIfDeltaIsNegative}}prepareOutputConfig(e){return le(e)}updateValidators(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([O.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["addPeriodBetweenMsgs"]}}e("CalculateDeltaConfigComponent",Jn),Jn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Jn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Jn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Jn,selector:"tb-enrichment-node-calculate-delta-config",usesInheritance:!0,ngImport:t,template:"
\n
\n \n {{ 'tb.rulenode.input-value-key' | translate }}\n \n \n {{ 'tb.rulenode.input-value-key-required' | translate }}\n \n \n \n {{ 'tb.rulenode.output-value-key' | translate }}\n \n \n {{ 'tb.rulenode.output-value-key-required' | translate }}\n \n \n
\n \n {{ 'tb.rulenode.number-of-digits-after-floating-point' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n
\n
\n \n {{ 'tb.rulenode.failure-if-delta-negative' | translate }}\n \n
\n
\n \n {{ 'tb.rulenode.use-caching' | translate }}\n \n
\n
\n
\n \n {{ 'tb.rulenode.add-time-difference-between-readings' | translate:\n { inputValueKey: calculateDeltaConfigForm.get('inputValueKey').valid ?\n calculateDeltaConfigForm.get('inputValueKey').value : 'tb.rulenode.input-value-key' | translate } }}\n \n
\n \n {{ 'tb.rulenode.period-value-key' | translate }}\n \n \n {{ 'tb.rulenode.period-value-key-required' | translate }}\n \n \n
\n
\n
\n",dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Jn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-calculate-delta-config",template:"
\n
\n \n {{ 'tb.rulenode.input-value-key' | translate }}\n \n \n {{ 'tb.rulenode.input-value-key-required' | translate }}\n \n \n \n {{ 'tb.rulenode.output-value-key' | translate }}\n \n \n {{ 'tb.rulenode.output-value-key-required' | translate }}\n \n \n
\n \n {{ 'tb.rulenode.number-of-digits-after-floating-point' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n
\n
\n \n {{ 'tb.rulenode.failure-if-delta-negative' | translate }}\n \n
\n
\n \n {{ 'tb.rulenode.use-caching' | translate }}\n \n
\n
\n
\n \n {{ 'tb.rulenode.add-time-difference-between-readings' | translate:\n { inputValueKey: calculateDeltaConfigForm.get('inputValueKey').valid ?\n calculateDeltaConfigForm.get('inputValueKey').value : 'tb.rulenode.input-value-key' | translate } }}\n \n
\n \n {{ 'tb.rulenode.period-value-key' | translate }}\n \n \n {{ 'tb.rulenode.period-value-key-required' | translate }}\n \n \n
\n
\n
\n"}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Yn extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=Pt;for(const e of Rt.keys())e!==Pt.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(Rt.get(e))})}configForm(){return this.customerAttributesConfigForm}prepareOutputConfig(e){const t={};for(const n of Object.keys(e.dataMapping))t[n.trim()]=e.dataMapping[n];return e.dataMapping=t,le(e)}prepareInputConfig(e){let t,n;return t=ae(e?.telemetry)?e.telemetry?Pt.LATEST_TELEMETRY:Pt.ATTRIBUTES:ae(e?.dataToFetch)?e.dataToFetch:Pt.ATTRIBUTES,n=ae(e?.attrMapping)?e.attrMapping:ae(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA}}selectTranslation(e,t){return this.customerAttributesConfigForm.get("dataToFetch").value===Pt.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.customerAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[O.required]],fetchTo:[e.fetchTo]})}}e("CustomerAttributesConfigComponent",Yn),Yn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Yn,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Yn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Yn,selector:"tb-enrichment-node-customer-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n
tb.rulenode.mapping-of-customers
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Mn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Yn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
\n
tb.rulenode.mapping-of-customers
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Wn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n}configForm(){return this.deviceAttributesConfigForm}onConfigurationSet(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e.deviceRelationsQuery,[O.required]],tellFailureIfAbsent:[e.tellFailureIfAbsent,[]],fetchTo:[e.fetchTo,[]],attributesControl:[e.attributesControl,[]]})}prepareInputConfig(e){return se(e)&&(e.attributesControl={clientAttributeNames:ae(e?.clientAttributeNames)?e.clientAttributeNames:[],latestTsKeyNames:ae(e?.latestTsKeyNames)?e.latestTsKeyNames:[],serverAttributeNames:ae(e?.serverAttributeNames)?e.serverAttributeNames:[],sharedAttributeNames:ae(e?.sharedAttributeNames)?e.sharedAttributeNames:[],getLatestValueWithTs:!!ae(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{deviceRelationsQuery:ae(e?.deviceRelationsQuery)?e.deviceRelationsQuery:null,tellFailureIfAbsent:!ae(e?.tellFailureIfAbsent)||e.tellFailureIfAbsent,fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA,attributesControl:e?e.attributesControl:null}}prepareOutputConfig(e){for(const t of Object.keys(e.attributesControl))e[t]=e.attributesControl[t];return delete e.attributesControl,e}}e("DeviceAttributesConfigComponent",Wn),Wn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Wn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Wn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Wn,selector:"tb-enrichment-node-device-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
tb.rulenode.device-relations-query
\n \n \n
\n
\n
\n
tb.rulenode.related-device-attributes
\n
\n tb.rulenode.at-least-one-field-required\n
\n
\n \n \n
\n
\n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:En,selector:"tb-device-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:Hn,selector:"tb-select-attributes",inputs:["popupHelpLink"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Wn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
\n
\n
tb.rulenode.device-relations-query
\n \n \n
\n
\n
\n
tb.rulenode.related-device-attributes
\n
\n tb.rulenode.at-least-one-field-required\n
\n
\n \n \n
\n
\n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Zn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.predefinedValues=[];for(const e of Object.keys(Mt))this.predefinedValues.push({value:Mt[e],name:this.translate.instant(Et.get(Mt[e]))})}ngOnInit(){super.ngOnInit()}configForm(){return this.entityDetailsConfigForm}prepareInputConfig(e){let t;return t=ae(e?.addToMetadata)?e.addToMetadata?en.METADATA:en.DATA:e?.fetchTo?e.fetchTo:en.DATA,{detailsList:ae(e?.detailsList)?e.detailsList:null,fetchTo:t}}onConfigurationSet(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e.detailsList,[O.required]],fetchTo:[e.fetchTo,[]]})}}e("EntityDetailsConfigComponent",Zn),Zn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Zn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Zn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Zn,selector:"tb-enrichment-node-entity-details-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n help\n \n \n \n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Zn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
\n \n \n help\n \n \n \n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Xn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[ge,ye,xe],this.aggregationTypes=E,this.aggregations=Object.values(E),this.aggregationTypesTranslations=G,this.fetchMode=Gt,this.samplingOrders=Object.values(Vt),this.samplingOrdersTranslate=_t,this.timeUnits=Object.values(Nt),this.timeUnitsTranslationMap=St,this.deduplicationStrategiesHintTranslations=wt,this.headerOptions=[],this.timeUnitMap={[Nt.MILLISECONDS]:1,[Nt.SECONDS]:1e3,[Nt.MINUTES]:6e4,[Nt.HOURS]:36e5,[Nt.DAYS]:864e5},this.intervalValidator=()=>e=>e.get("startInterval").value*this.timeUnitMap[e.get("startIntervalTimeUnit").value]<=e.get("endInterval").value*this.timeUnitMap[e.get("endIntervalTimeUnit").value]?{intervalError:!0}:null;for(const e of Dt.keys())this.headerOptions.push({value:e,name:this.translate.instant(Dt.get(e))})}configForm(){return this.getTelemetryFromDatabaseConfigForm}onConfigurationSet(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e.latestTsKeyNames,[O.required]],aggregation:[e.aggregation,[O.required]],fetchMode:[e.fetchMode,[O.required]],orderBy:[e.orderBy,[]],limit:[e.limit,[]],useMetadataIntervalPatterns:[e.useMetadataIntervalPatterns,[]],interval:this.fb.group({startInterval:[e.interval.startInterval,[]],startIntervalTimeUnit:[e.interval.startIntervalTimeUnit,[]],endInterval:[e.interval.endInterval,[]],endIntervalTimeUnit:[e.interval.endIntervalTimeUnit,[]]}),startIntervalPattern:[e.startIntervalPattern,[]],endIntervalPattern:[e.endIntervalPattern,[]]})}validatorTriggers(){return["fetchMode","useMetadataIntervalPatterns"]}toggleChange(e){this.getTelemetryFromDatabaseConfigForm.get("fetchMode").patchValue(e,{emitEvent:!0})}prepareOutputConfig(e){return e.startInterval=e.interval.startInterval,e.startIntervalTimeUnit=e.interval.startIntervalTimeUnit,e.endInterval=e.interval.endInterval,e.endIntervalTimeUnit=e.interval.endIntervalTimeUnit,delete e.interval,le(e)}prepareInputConfig(e){return se(e)&&(e.interval={startInterval:e.startInterval,startIntervalTimeUnit:e.startIntervalTimeUnit,endInterval:e.endInterval,endIntervalTimeUnit:e.endIntervalTimeUnit}),{latestTsKeyNames:ae(e?.latestTsKeyNames)?e.latestTsKeyNames:null,aggregation:ae(e?.aggregation)?e.aggregation:E.NONE,fetchMode:ae(e?.fetchMode)?e.fetchMode:Gt.FIRST,orderBy:ae(e?.orderBy)?e.orderBy:Vt.ASC,limit:ae(e?.limit)?e.limit:1e3,useMetadataIntervalPatterns:!!ae(e?.useMetadataIntervalPatterns)&&e.useMetadataIntervalPatterns,interval:{startInterval:ae(e?.interval?.startInterval)?e.interval.startInterval:2,startIntervalTimeUnit:ae(e?.interval?.startIntervalTimeUnit)?e.interval.startIntervalTimeUnit:Nt.MINUTES,endInterval:ae(e?.interval?.endInterval)?e.interval.endInterval:1,endIntervalTimeUnit:ae(e?.interval?.endIntervalTimeUnit)?e.interval.endIntervalTimeUnit:Nt.MINUTES},startIntervalPattern:ae(e?.startIntervalPattern)?e.startIntervalPattern:null,endIntervalPattern:ae(e?.endIntervalPattern)?e.endIntervalPattern:null}}updateValidators(e){const t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,n=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===Gt.ALL?(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([O.required,O.min(2),O.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),n?(this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)])):(this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").setValidators([O.required,O.min(1),O.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").setValidators([O.required,O.min(1),O.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("interval").setValidators([this.intervalValidator()]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("aggregation").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})}removeKey(e,t){const n=this.getTelemetryFromDatabaseConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(n,{emitEvent:!0}))}clearChipGrid(){this.getTelemetryFromDatabaseConfigForm.get("latestTsKeyNames").patchValue([],{emitEvent:!0})}addKey(e,t){const n=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.getTelemetryFromDatabaseConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(e,{emitEvent:!0}))}n&&(n.value="")}defaultPaddingEnable(){return this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value===Gt.ALL&&this.getTelemetryFromDatabaseConfigForm.get("aggregation").value===E.NONE}}e("GetTelemetryFromDatabaseConfigComponent",Xn),Xn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Xn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Xn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Xn,selector:"tb-enrichment-node-get-telemetry-from-database",usesInheritance:!0,ngImport:t,template:'
\n \n
\n help\n \n
\n
\n
tb.rulenode.fetch-interval
\n
\n \n {{ \'tb.rulenode.use-metadata-dynamic-interval\' | translate }}\n \n
\n
\n
\n \n {{ \'tb.rulenode.interval-start\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n {{ \'tb.rulenode.interval-end\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n error_outline\n
\n \n {{ \'tb.rulenode.fetch-timeseries-from-to\' | translate:\n {\n startInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.startInterval\').value,\n endInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.endInterval\').value,\n startIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.startIntervalTimeUnit\').value.toLowerCase(),\n endIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.endIntervalTimeUnit\').value.toLowerCase()\n } }}\n \n \n {{ "tb.rulenode.fetch-timeseries-from-to-invalid" | translate }}\n \n
\n
\n
\n \n
\n \n {{ \'tb.rulenode.start-interval\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.end-interval\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-required\' | translate }}\n \n \n \n \n
\n
\n
\n
\n
tb.rulenode.fetch-strategy
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get(\'fetchMode\').value) | translate }}\n
\n
\n
\n \n {{ \'aggregation.function\' | translate }}\n \n \n {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}\n \n \n \n
\n \n {{ "tb.rulenode.order-by-timestamp" | translate }} \n \n \n {{ samplingOrdersTranslate.get(order) | translate }}\n \n \n \n \n {{ "tb.rulenode.limit" | translate }}\n \n {{ "tb.rulenode.limit-hint" | translate }}\n \n {{ \'tb.rulenode.limit-required\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n
\n
\n
\n
\n',styles:[":host .see-example{display:inline-block}:host .description-block{display:flex;align-items:center;border-radius:6px;border:1px solid #EAEAEA}:host .description-block .description-icon{font-size:24px;height:24px;min-height:24px;width:24px;min-width:24px;line-height:24px;color:#d9d9d9;margin:4px}:host .description-block .description-text{font-size:12px;line-height:16px;letter-spacing:.25px;margin:6px}:host .description-block.error{color:var(--mdc-theme-error, #f44336)}:host .description-block.error .description-icon{color:var(--mdc-theme-error, #f44336)}:host .item-center{align-items:center}:host .item-center .fetch-mod-toggle{width:100%}:host .hint-container{width:100%}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:$e.HelpPopupComponent,selector:"[tb-help-popup], [tb-help-popup-content]",inputs:["tb-help-popup","tb-help-popup-content","trigger-text","trigger-style","tb-help-popup-placement","tb-help-popup-style","hintMode"]},{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:R.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Xn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n
\n help\n \n
\n
\n
tb.rulenode.fetch-interval
\n
\n \n {{ \'tb.rulenode.use-metadata-dynamic-interval\' | translate }}\n \n
\n
\n
\n \n {{ \'tb.rulenode.interval-start\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n {{ \'tb.rulenode.interval-end\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n error_outline\n
\n \n {{ \'tb.rulenode.fetch-timeseries-from-to\' | translate:\n {\n startInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.startInterval\').value,\n endInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.endInterval\').value,\n startIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.startIntervalTimeUnit\').value.toLowerCase(),\n endIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.endIntervalTimeUnit\').value.toLowerCase()\n } }}\n \n \n {{ "tb.rulenode.fetch-timeseries-from-to-invalid" | translate }}\n \n
\n
\n
\n \n
\n \n {{ \'tb.rulenode.start-interval\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.end-interval\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-required\' | translate }}\n \n \n \n \n
\n
\n
\n
\n
tb.rulenode.fetch-strategy
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get(\'fetchMode\').value) | translate }}\n
\n
\n
\n \n {{ \'aggregation.function\' | translate }}\n \n \n {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}\n \n \n \n
\n \n {{ "tb.rulenode.order-by-timestamp" | translate }} \n \n \n {{ samplingOrdersTranslate.get(order) | translate }}\n \n \n \n \n {{ "tb.rulenode.limit" | translate }}\n \n {{ "tb.rulenode.limit-hint" | translate }}\n \n {{ \'tb.rulenode.limit-required\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n
\n
\n
\n
\n',styles:[":host .see-example{display:inline-block}:host .description-block{display:flex;align-items:center;border-radius:6px;border:1px solid #EAEAEA}:host .description-block .description-icon{font-size:24px;height:24px;min-height:24px;width:24px;min-width:24px;line-height:24px;color:#d9d9d9;margin:4px}:host .description-block .description-text{font-size:12px;line-height:16px;letter-spacing:.25px;margin:6px}:host .description-block.error{color:var(--mdc-theme-error, #f44336)}:host .description-block.error .description-icon{color:var(--mdc-theme-error, #f44336)}:host .item-center{align-items:center}:host .item-center .fetch-mod-toggle{width:100%}:host .hint-container{width:100%}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class er extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n}configForm(){return this.originatorAttributesConfigForm}onConfigurationSet(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[e.tellFailureIfAbsent,[]],fetchTo:[e.fetchTo,[]],attributesControl:[e.attributesControl,[]]})}prepareInputConfig(e){return se(e)&&(e.attributesControl={clientAttributeNames:ae(e?.clientAttributeNames)?e.clientAttributeNames:[],latestTsKeyNames:ae(e?.latestTsKeyNames)?e.latestTsKeyNames:[],serverAttributeNames:ae(e?.serverAttributeNames)?e.serverAttributeNames:[],sharedAttributeNames:ae(e?.sharedAttributeNames)?e.sharedAttributeNames:[],getLatestValueWithTs:!!ae(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA,tellFailureIfAbsent:!!ae(e?.tellFailureIfAbsent)&&e.tellFailureIfAbsent,attributesControl:ae(e?.attributesControl)?e.attributesControl:null}}prepareOutputConfig(e){for(const t of Object.keys(e.attributesControl))e[t]=e.attributesControl[t];return delete e.attributesControl,e}}e("OriginatorAttributesConfigComponent",er),er.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:er,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),er.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:er,selector:"tb-enrichment-node-originator-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
\n
tb.rulenode.originator-attributes
\n
\n tb.rulenode.at-least-one-field-required\n
\n
\n \n \n \n \n
\n
\n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:Hn,selector:"tb-select-attributes",inputs:["popupHelpLink"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:er,decorators:[{type:n,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
\n
\n
\n
tb.rulenode.originator-attributes
\n
\n tb.rulenode.at-least-one-field-required\n
\n
\n \n \n \n \n
\n
\n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class tr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.originatorFields=[];for(const e of kt)this.originatorFields.push({value:e.value,name:this.translate.instant(e.name)})}configForm(){return this.originatorFieldsConfigForm}prepareOutputConfig(e){return le(e)}prepareInputConfig(e){return{dataMapping:ae(e?.dataMapping)?e.dataMapping:null,ignoreNullStrings:ae(e?.ignoreNullStrings)?e.ignoreNullStrings:null,fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA}}onConfigurationSet(e){this.originatorFieldsConfigForm=this.fb.group({dataMapping:[e.dataMapping,[O.required]],ignoreNullStrings:[e.ignoreNullStrings,[]],fetchTo:[e.fetchTo,[]]})}}e("OriginatorFieldsConfigComponent",tr),tr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:tr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),tr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:tr,selector:"tb-enrichment-node-originator-fields-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n
\n \n {{ \'tb.rulenode.skip-empty-fields\' | translate }}\n \n
\n
\n',dependencies:[{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:zn,selector:"tb-sv-map-config",inputs:["selectOptions","disabled","labelText","requiredText","targetKeyPrefix","selectText","selectRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:tr,decorators:[{type:n,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n \n
\n \n {{ \'tb.rulenode.skip-empty-fields\' | translate }}\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class nr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.DataToFetch=Pt,this.msgMetadataLabelTranslations=Ot,this.originatorFields=[],this.fetchToData=[];for(const e of Object.keys(kt))this.originatorFields.push({value:kt[e].value,name:this.translate.instant(kt[e].name)});for(const e of Rt.keys())this.fetchToData.push({value:e,name:this.translate.instant(Rt.get(e))})}configForm(){return this.relatedAttributesConfigForm}prepareOutputConfig(e){e.dataToFetch===Pt.FIELDS?(e.dataMapping=e.svMap,delete e.svMap):(e.dataMapping=e.kvMap,delete e.kvMap);const t={};if(e&&e.dataMapping)for(const n of Object.keys(e.dataMapping))t[n.trim()]=e.dataMapping[n];return e.dataMapping=t,delete e.svMap,delete e.kvMap,le(e)}prepareInputConfig(e){let t,n,r={[k.name.value]:`relatedEntity${this.translate.instant(k.name.name)}`},o={serialNumber:"sn"};return t=ae(e?.telemetry)?e.telemetry?Pt.LATEST_TELEMETRY:Pt.ATTRIBUTES:ae(e?.dataToFetch)?e.dataToFetch:Pt.ATTRIBUTES,n=ae(e?.attrMapping)?e.attrMapping:ae(e?.dataMapping)?e.dataMapping:null,t===Pt.FIELDS?r=n:o=n,{relationsQuery:ae(e?.relationsQuery)?e.relationsQuery:null,dataToFetch:t,svMap:r,kvMap:o,fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA}}selectTranslation(e,t){return this.relatedAttributesConfigForm.get("dataToFetch").value===Pt.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e.relationsQuery,[O.required]],dataToFetch:[e.dataToFetch,[]],kvMap:[e.kvMap,[O.required]],svMap:[e.svMap,[O.required]],fetchTo:[e.fetchTo,[]]})}validatorTriggers(){return["dataToFetch"]}updateValidators(e){this.relatedAttributesConfigForm.get("dataToFetch").value===Pt.FIELDS?(this.relatedAttributesConfigForm.get("svMap").enable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").disable({emitEvent:!1}),this.relatedAttributesConfigForm.get("svMap").updateValueAndValidity()):(this.relatedAttributesConfigForm.get("svMap").disable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").enable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").updateValueAndValidity())}}e("RelatedAttributesConfigComponent",nr),nr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:nr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),nr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:nr,selector:"tb-enrichment-node-related-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n
tb.rulenode.data-to-fetch
\n \n \n {{ data.name }}\n \n \n \n \n \n \n \n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Mn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:Gn,selector:"tb-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:zn,selector:"tb-sv-map-config",inputs:["selectOptions","disabled","labelText","requiredText","targetKeyPrefix","selectText","selectRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:nr,decorators:[{type:n,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n
\n
tb.rulenode.data-to-fetch
\n \n \n {{ data.name }}\n \n \n \n \n \n \n \n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class rr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=Pt;for(const e of Rt.keys())e!==Pt.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(Rt.get(e))})}configForm(){return this.tenantAttributesConfigForm}prepareInputConfig(e){let t,n;return t=ae(e?.telemetry)?e.telemetry?Pt.LATEST_TELEMETRY:Pt.ATTRIBUTES:ae(e?.dataToFetch)?e.dataToFetch:Pt.ATTRIBUTES,n=ae(e?.attrMapping)?e.attrMapping:ae(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA}}selectTranslation(e,t){return this.tenantAttributesConfigForm.get("dataToFetch").value===Pt.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.tenantAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[O.required]],fetchTo:[e.fetchTo,[]]})}}e("TenantAttributesConfigComponent",rr),rr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:rr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),rr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:rr,selector:"tb-enrichment-node-tenant-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n
tb.rulenode.mapping-of-tenant
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Mn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:rr,decorators:[{type:n,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n
tb.rulenode.mapping-of-tenant
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class or extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.fetchDeviceCredentialsConfigForm}prepareInputConfig(e){return{fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA}}onConfigurationSet(e){this.fetchDeviceCredentialsConfigForm=this.fb.group({fetchTo:[e.fetchTo,[]]})}}e("FetchDeviceCredentialsConfigComponent",or),or.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:or,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),or.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:or,selector:"./tb-enrichment-node-fetch-device-credentials-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',dependencies:[{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:or,decorators:[{type:n,args:[{selector:"./tb-enrichment-node-fetch-device-credentials-config",template:'
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class ar{}e("RulenodeCoreConfigEnrichmentModule",ar),ar.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ar,deps:[],target:t.ɵɵFactoryTarget.NgModule}),ar.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:ar,declarations:[Yn,Zn,Wn,er,tr,Xn,nr,rr,Jn,or],imports:[$,M,$n],exports:[Yn,Zn,Wn,er,tr,Xn,nr,rr,Jn,or]}),ar.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ar,imports:[$,M,$n]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ar,decorators:[{type:c,args:[{declarations:[Yn,Zn,Wn,er,tr,Xn,nr,rr,Jn,or],imports:[$,M,$n],exports:[Yn,Zn,Wn,er,tr,Xn,nr,rr,Jn,or]}]}]});class ir extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.allAzureIotHubCredentialsTypes=Ht,this.azureIotHubCredentialsTypeTranslationsMap=jt}configForm(){return this.azureIotHubConfigForm}onConfigurationSet(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[O.required]],host:[e?e.host:null,[O.required]],port:[e?e.port:null,[O.required,O.min(1),O.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[O.required,O.min(1),O.max(200)]],clientId:[e?e.clientId:null,[O.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[O.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})}prepareOutputConfig(e){const t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e}validatorTriggers(){return["credentials.type"]}updateValidators(e){const t=this.azureIotHubConfigForm.get("credentials"),n=t.get("type").value;switch(e&&t.reset({type:n},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),n){case"sas":t.get("sasKey").setValidators([O.required]);break;case"cert.PEM":t.get("privateKey").setValidators([O.required]),t.get("privateKeyFileName").setValidators([O.required]),t.get("cert").setValidators([O.required]),t.get("certFileName").setValidators([O.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})}}e("AzureIotHubConfigComponent",ir),ir.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ir,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ir.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ir,selector:"tb-external-node-azure-iot-hub-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:H.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{kind:"directive",type:H.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:We.MatAccordion,selector:"mat-accordion",inputs:["multi","hideToggle","displayMode","togglePosition"],exportAs:["matAccordion"]},{kind:"component",type:We.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:We.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:We.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:We.MatExpansionPanelDescription,selector:"mat-panel-description"},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:R.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"component",type:Ze.FileInputComponent,selector:"tb-file-input",inputs:["label","hint","accept","noFileText","inputId","allowedExtensions","dropLabel","maxSizeByte","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ir,decorators:[{type:n,args:[{selector:"tb-external-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class lr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.ackValues=["all","-1","0","1"],this.ToByteStandartCharsetTypesValues=Qt,this.ToByteStandartCharsetTypeTranslationMap=Jt}configForm(){return this.kafkaConfigForm}onConfigurationSet(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[O.required]],keyPattern:[e?e.keyPattern:null],bootstrapServers:[e?e.bootstrapServers:null,[O.required]],retries:[e?e.retries:null,[O.min(0)]],batchSize:[e?e.batchSize:null,[O.min(0)]],linger:[e?e.linger:null,[O.min(0)]],bufferMemory:[e?e.bufferMemory:null,[O.min(0)]],acks:[e?e.acks:null,[O.required]],keySerializer:[e?e.keySerializer:null,[O.required]],valueSerializer:[e?e.valueSerializer:null,[O.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})}validatorTriggers(){return["addMetadataKeyValuesAsKafkaHeaders"]}updateValidators(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([O.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})}}e("KafkaConfigComponent",lr),lr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:lr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),lr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:lr,selector:"tb-external-node-kafka-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.key-pattern\n \n tb.rulenode.general-pattern-hint\n \n
tb.rulenode.key-pattern-hint
\n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:lr,decorators:[{type:n,args:[{selector:"tb-external-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.key-pattern\n \n tb.rulenode.general-pattern-hint\n \n
tb.rulenode.key-pattern-hint
\n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class sr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.mqttConfigForm}onConfigurationSet(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[O.required]],host:[e?e.host:null,[O.required]],port:[e?e.port:null,[O.required,O.min(1),O.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[O.required,O.min(1),O.max(200)]],clientId:[e?e.clientId:null,[]],appendClientIdSuffix:[{value:!!e&&e.appendClientIdSuffix,disabled:!(e&&me(e.clientId))},[]],cleanSession:[!!e&&e.cleanSession,[]],retainedMessage:[!!e&&e.retainedMessage,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]})}updateValidators(e){me(this.mqttConfigForm.get("clientId").value)?this.mqttConfigForm.get("appendClientIdSuffix").enable({emitEvent:!1}):this.mqttConfigForm.get("appendClientIdSuffix").disable({emitEvent:!1}),this.mqttConfigForm.get("appendClientIdSuffix").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["clientId"]}}e("MqttConfigComponent",sr),sr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:sr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),sr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:sr,selector:"tb-external-node-mqtt-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n {{\'tb.rulenode.client-id-hint\' | translate}}\n \n \n {{ \'tb.rulenode.append-client-id-suffix\' | translate }}\n \n
{{ "tb.rulenode.client-id-suffix-hint" | translate }}
\n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ "tb.rulenode.retained-message" | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n"],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:wn,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRequired"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:sr,decorators:[{type:n,args:[{selector:"tb-external-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n {{\'tb.rulenode.client-id-hint\' | translate}}\n \n \n {{ \'tb.rulenode.append-client-id-suffix\' | translate }}\n \n
{{ "tb.rulenode.client-id-suffix-hint" | translate }}
\n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ "tb.rulenode.retained-message" | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class mr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.notificationType=D,this.entityType=F}configForm(){return this.notificationConfigForm}onConfigurationSet(e){this.notificationConfigForm=this.fb.group({templateId:[e?e.templateId:null,[O.required]],targets:[e?e.targets:[],[O.required]]})}}e("NotificationConfigComponent",mr),mr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:mr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),mr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:mr,selector:"tb-external-node-notification-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n
\n',dependencies:[{kind:"component",type:tt.EntityListComponent,selector:"tb-entity-list",inputs:["entityType","subType","labelText","placeholderText","requiredText","required","disabled","subscriptSizing","hint"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:nt.TemplateAutocompleteComponent,selector:"tb-template-autocomplete",inputs:["required","allowCreate","allowEdit","disabled","notificationTypes"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:mr,decorators:[{type:n,args:[{selector:"tb-external-node-notification-config",template:'
\n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class pr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.pubSubConfigForm}onConfigurationSet(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[O.required]],topicName:[e?e.topicName:null,[O.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[O.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[O.required]],messageAttributes:[e?e.messageAttributes:null,[]]})}}e("PubSubConfigComponent",pr),pr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:pr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),pr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:pr,selector:"tb-external-node-pub-sub-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Ze.FileInputComponent,selector:"tb-file-input",inputs:["label","hint","accept","noFileText","inputId","allowedExtensions","dropLabel","maxSizeByte","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:pr,decorators:[{type:n,args:[{selector:"tb-external-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class dr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"]}configForm(){return this.rabbitMqConfigForm}onConfigurationSet(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[O.required]],port:[e?e.port:null,[O.required,O.min(1),O.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[O.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[O.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})}}e("RabbitMqConfigComponent",dr),dr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),dr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:dr,selector:"tb-external-node-rabbit-mq-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dr,decorators:[{type:n,args:[{selector:"tb-external-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ur extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.proxySchemes=["http","https"],this.httpRequestTypes=Object.keys($t)}configForm(){return this.restApiCallConfigForm}onConfigurationSet(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[O.required]],requestMethod:[e?e.requestMethod:null,[O.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],parseToPlainText:[!!e&&e.parseToPlainText,[]],ignoreRequestBody:[!!e&&e.ignoreRequestBody,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[O.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]],credentials:[e?e.credentials:null,[]]})}validatorTriggers(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]}updateValidators(e){const t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,n=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,r=this.restApiCallConfigForm.get("enableProxy").value,o=this.restApiCallConfigForm.get("useSystemProxyProperties").value;r&&!o?(this.restApiCallConfigForm.get("proxyHost").setValidators(r?[O.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(r?[O.required,O.min(1),O.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([O.min(0)])),n?this.restApiCallConfigForm.get("maxQueueSize").setValidators([O.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})}}e("RestApiCallConfigComponent",ur),ur.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ur,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ur.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ur,selector:"tb-external-node-rest-api-call-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n \n {{ \'tb.rulenode.parse-to-plain-text\' | translate }}\n \n
tb.rulenode.parse-to-plain-text-hint
\n \n {{ \'tb.rulenode.ignore-request-body\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:wn,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRequired"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ur,decorators:[{type:n,args:[{selector:"tb-external-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n \n {{ \'tb.rulenode.parse-to-plain-text\' | translate }}\n \n
tb.rulenode.parse-to-plain-text-hint
\n \n {{ \'tb.rulenode.ignore-request-body\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class cr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.smtpProtocols=["smtp","smtps"],this.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"]}configForm(){return this.sendEmailConfigForm}onConfigurationSet(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})}validatorTriggers(){return["useSystemSmtpSettings","enableProxy"]}updateValidators(e){const t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,n=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([O.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([O.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([O.required,O.min(1),O.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([O.required,O.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(n?[O.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(n?[O.required,O.min(1),O.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})}}e("SendEmailConfigComponent",cr),cr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),cr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:cr,selector:"tb-external-node-send-email-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n
\n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:rt.TbCheckboxComponent,selector:"tb-checkbox",inputs:["disabled","trueValue","falseValue"],outputs:["valueChange"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cr,decorators:[{type:n,args:[{selector:"tb-external-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class fr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.sendSmsConfigForm}onConfigurationSet(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[O.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[O.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})}validatorTriggers(){return["useSystemSmsSettings"]}updateValidators(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([O.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})}}e("SendSmsConfigComponent",fr),fr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),fr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:fr,selector:"tb-external-node-send-sms-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ot.SmsProviderConfigurationComponent,selector:"tb-sms-provider-configuration",inputs:["required","disabled"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fr,decorators:[{type:n,args:[{selector:"tb-external-node-send-sms-config",template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class gr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.slackChanelTypes=Object.keys(w),this.slackChanelTypesTranslateMap=V}configForm(){return this.slackConfigForm}onConfigurationSet(e){this.slackConfigForm=this.fb.group({botToken:[e?e.botToken:null],useSystemSettings:[!!e&&e.useSystemSettings],messageTemplate:[e?e.messageTemplate:null,[O.required]],conversationType:[e?e.conversationType:null,[O.required]],conversation:[e?e.conversation:null,[O.required]]})}validatorTriggers(){return["useSystemSettings"]}updateValidators(e){this.slackConfigForm.get("useSystemSettings").value?this.slackConfigForm.get("botToken").clearValidators():this.slackConfigForm.get("botToken").setValidators([O.required]),this.slackConfigForm.get("botToken").updateValueAndValidity({emitEvent:e})}}e("SlackConfigComponent",gr),gr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),gr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:gr,selector:"tb-external-node-slack-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.message-template\n \n \n {{ \'tb.rulenode.message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-slack-settings\' | translate }}\n \n \n tb.rulenode.slack-api-token\n \n \n {{ \'tb.rulenode.slack-api-token-required\' | translate }}\n \n \n \n \n \n {{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }}\n \n \n \n \n
\n',styles:[":host .tb-title{display:block;padding-bottom:6px}:host ::ng-deep .mat-mdc-radio-group{display:flex;flex-direction:row;margin-bottom:22px;gap:12px}:host ::ng-deep .mat-mdc-radio-group .mat-mdc-radio-button{flex:1 1 100%;padding:4px;border:1px solid rgba(0,0,0,.12);border-radius:6px}@media screen and (max-width: 599px){:host ::ng-deep .mat-mdc-radio-group{flex-direction:column}}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:at.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{kind:"component",type:at.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:it.SlackConversationAutocompleteComponent,selector:"tb-slack-conversation-autocomplete",inputs:["labelText","requiredText","required","disabled","slackChanelType","token"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gr,decorators:[{type:n,args:[{selector:"tb-external-node-slack-config",template:'
\n \n tb.rulenode.message-template\n \n \n {{ \'tb.rulenode.message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-slack-settings\' | translate }}\n \n \n tb.rulenode.slack-api-token\n \n \n {{ \'tb.rulenode.slack-api-token-required\' | translate }}\n \n \n \n \n \n {{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }}\n \n \n \n \n
\n',styles:[":host .tb-title{display:block;padding-bottom:6px}:host ::ng-deep .mat-mdc-radio-group{display:flex;flex-direction:row;margin-bottom:22px;gap:12px}:host ::ng-deep .mat-mdc-radio-group .mat-mdc-radio-button{flex:1 1 100%;padding:4px;border:1px solid rgba(0,0,0,.12);border-radius:6px}@media screen and (max-width: 599px){:host ::ng-deep .mat-mdc-radio-group{flex-direction:column}}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class yr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.snsConfigForm}onConfigurationSet(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[O.required]],accessKeyId:[e?e.accessKeyId:null,[O.required]],secretAccessKey:[e?e.secretAccessKey:null,[O.required]],region:[e?e.region:null,[O.required]]})}}e("SnsConfigComponent",yr),yr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),yr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:yr,selector:"tb-external-node-sns-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yr,decorators:[{type:n,args:[{selector:"tb-external-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class xr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.sqsQueueType=Bt,this.sqsQueueTypes=Object.keys(Bt),this.sqsQueueTypeTranslationsMap=Kt}configForm(){return this.sqsConfigForm}onConfigurationSet(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[O.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[O.required]],delaySeconds:[e?e.delaySeconds:null,[O.min(0),O.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[O.required]],secretAccessKey:[e?e.secretAccessKey:null,[O.required]],region:[e?e.region:null,[O.required]]})}}e("SqsConfigComponent",xr),xr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),xr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:xr,selector:"tb-external-node-sqs-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xr,decorators:[{type:n,args:[{selector:"tb-external-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class br{}e("RulenodeCoreConfigExternalModule",br),br.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:br,deps:[],target:t.ɵɵFactoryTarget.NgModule}),br.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:br,declarations:[yr,xr,pr,lr,sr,mr,dr,ur,cr,ir,fr,gr],imports:[$,M,_e,$n],exports:[yr,xr,pr,lr,sr,mr,dr,ur,cr,ir,fr,gr]}),br.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:br,imports:[$,M,_e,$n]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:br,decorators:[{type:c,args:[{declarations:[yr,xr,pr,lr,sr,mr,dr,ur,cr,ir,fr,gr],imports:[$,M,_e,$n],exports:[yr,xr,pr,lr,sr,mr,dr,ur,cr,ir,fr,gr]}]}]});class hr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.searchText=""}configForm(){return this.alarmStatusConfigForm}prepareInputConfig(e){return{alarmStatusList:ae(e?.alarmStatusList)?e.alarmStatusList:null}}onConfigurationSet(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e.alarmStatusList,[O.required]]})}}e("CheckAlarmStatusComponent",hr),hr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),hr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:hr,selector:"tb-filter-node-check-alarm-status-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
tb.rulenode.alarm-status
\n
\n tb.rulenode.alarm-required\n
\n
\n \n
\n\n\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:jn,selector:"tb-alarm-status-select"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hr,decorators:[{type:n,args:[{selector:"tb-filter-node-check-alarm-status-config",template:'
\n
\n
tb.rulenode.alarm-status
\n
\n tb.rulenode.alarm-required\n
\n
\n \n
\n\n\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class vr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.checkMessageConfigForm}prepareInputConfig(e){return{messageNames:ae(e?.messageNames)?e.messageNames:[],metadataNames:ae(e?.metadataNames)?e.metadataNames:[],checkAllKeys:!!ae(e?.checkAllKeys)&&e.checkAllKeys}}prepareOutputConfig(e){return{messageNames:ae(e?.messageNames)?e.messageNames:[],metadataNames:ae(e?.metadataNames)?e.metadataNames:[],checkAllKeys:e.checkAllKeys}}atLeastOne(e,t=null){return n=>{t||(t=Object.keys(n.controls));return n?.controls&&t.some((t=>!e(n.controls[t])))?null:{atLeastOne:!0}}}onConfigurationSet(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e.messageNames,[]],metadataNames:[e.metadataNames,[]],checkAllKeys:[e.checkAllKeys,[]]},{validators:this.atLeastOne(O.required,["messageNames","metadataNames"])})}get touchedValidationControl(){return["messageNames","metadataNames"].some((e=>this.checkMessageConfigForm.get(e).touched))}}e("CheckMessageConfigComponent",vr),vr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),vr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:vr,selector:"tb-filter-node-check-message-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
tb.rulenode.fields-to-check
\n
\n tb.rulenode.at-least-one-field-required\n
\n
\n \n help\n \n \n help\n \n
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vr,decorators:[{type:n,args:[{selector:"tb-filter-node-check-message-config",template:'
\n
\n
tb.rulenode.fields-to-check
\n
\n tb.rulenode.at-least-one-field-required\n
\n
\n \n help\n \n \n help\n \n
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Cr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.entitySearchDirection=Object.values(v),this.entitySearchDirectionTranslationsMap=C}configForm(){return this.checkRelationConfigForm}prepareInputConfig(e){return{checkForSingleEntity:!!ae(e?.checkForSingleEntity)&&e.checkForSingleEntity,direction:ae(e?.direction)?e.direction:null,entityType:ae(e?.entityType)?e.entityType:null,entityId:ae(e?.entityId)?e.entityId:null,relationType:ae(e?.relationType)?e.relationType:null}}onConfigurationSet(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[e.checkForSingleEntity,[]],direction:[e.direction,[]],entityType:[e.entityType,e&&e.checkForSingleEntity?[O.required]:[]],entityId:[e.entityId,e&&e.checkForSingleEntity?[O.required]:[]],relationType:[e.relationType,[O.required]]})}validatorTriggers(){return["checkForSingleEntity"]}updateValidators(e){const t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[O.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[O.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})}}e("CheckRelationConfigComponent",Cr),Cr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Cr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Cr,selector:"tb-filter-node-check-relation-config",usesInheritance:!0,ngImport:t,template:'
\n
tb.rulenode.relation-search-parameters
\n
\n \n {{ \'relation.direction\' | translate }}\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n \n
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
\n
\n \n \n \n \n
\n
\n
\n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:lt.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","useFullEntityId","appearance","required","disabled"],outputs:["entityChanged"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:Je.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["showLabel","additionalClasses","appearance","required","disabled","subscriptSizing"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cr,decorators:[{type:n,args:[{selector:"tb-filter-node-check-relation-config",template:'
\n
tb.rulenode.relation-search-parameters
\n
\n \n {{ \'relation.direction\' | translate }}\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n \n
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
\n
\n \n \n \n \n
\n
\n
\n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Fr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Tt,this.perimeterTypes=Object.values(Tt),this.perimeterTypeTranslationMap=It,this.rangeUnits=Object.values(qt),this.rangeUnitTranslationMap=At,this.defaultPaddingEnable=!0}configForm(){return this.geoFilterConfigForm}prepareInputConfig(e){return{latitudeKeyName:ae(e?.latitudeKeyName)?e.latitudeKeyName:null,longitudeKeyName:ae(e?.longitudeKeyName)?e.longitudeKeyName:null,perimeterType:ae(e?.perimeterType)?e.perimeterType:null,fetchPerimeterInfoFromMessageMetadata:!!ae(e?.fetchPerimeterInfoFromMessageMetadata)&&e.fetchPerimeterInfoFromMessageMetadata,perimeterKeyName:ae(e?.perimeterKeyName)?e.perimeterKeyName:null,centerLatitude:ae(e?.centerLatitude)?e.centerLatitude:null,centerLongitude:ae(e?.centerLongitude)?e.centerLongitude:null,range:ae(e?.range)?e.range:null,rangeUnit:ae(e?.rangeUnit)?e.rangeUnit:null,polygonsDefinition:ae(e?.polygonsDefinition)?e.polygonsDefinition:null}}onConfigurationSet(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e.latitudeKeyName,[O.required]],longitudeKeyName:[e.longitudeKeyName,[O.required]],perimeterType:[e.perimeterType,[O.required]],fetchPerimeterInfoFromMessageMetadata:[e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e.perimeterKeyName,[]],centerLatitude:[e.centerLatitude,[]],centerLongitude:[e.centerLongitude,[]],range:[e.range,[]],rangeUnit:[e.rangeUnit,[]],polygonsDefinition:[e.polygonsDefinition,[]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterKeyName").setValidators([O.required]):this.geoFilterConfigForm.get("perimeterKeyName").setValidators([]),t||n!==Tt.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([]),this.defaultPaddingEnable=!0):(this.geoFilterConfigForm.get("centerLatitude").setValidators([O.required,O.min(-90),O.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([O.required,O.min(-180),O.max(180)]),this.geoFilterConfigForm.get("range").setValidators([O.required,O.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([O.required]),this.defaultPaddingEnable=!1),t||n!==Tt.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([O.required]),this.geoFilterConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoFilterConfigComponent",Fr),Fr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Fr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Fr,selector:"tb-filter-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
tb.rulenode.coordinate-field-names
\n
\n
\n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
\n
tb.rulenode.coordinate-field-hint
\n
\n
\n
\n
tb.rulenode.geofence-configuration
\n
\n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
\n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
\n
\n \n {{ \'tb.rulenode.polygon-definition\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-hint\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fr,decorators:[{type:n,args:[{selector:"tb-filter-node-gps-geofencing-config",template:'
\n
\n
tb.rulenode.coordinate-field-names
\n
\n
\n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
\n
tb.rulenode.coordinate-field-hint
\n
\n
\n
\n
tb.rulenode.geofence-configuration
\n
\n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
\n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
\n
\n \n {{ \'tb.rulenode.polygon-definition\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-hint\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class kr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.messageTypeConfigForm}prepareInputConfig(e){return{messageTypes:ae(e?.messageTypes)?e.messageTypes:null}}onConfigurationSet(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e.messageTypes,[O.required]]})}}e("MessageTypeConfigComponent",kr),kr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),kr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:kr,selector:"tb-filter-node-message-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',dependencies:[{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Dn,selector:"tb-message-types-config",inputs:["required","label","placeholder","disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kr,decorators:[{type:n,args:[{selector:"tb-filter-node-message-type-config",template:'
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Lr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.allowedEntityTypes=[F.DEVICE,F.ASSET,F.ENTITY_VIEW,F.TENANT,F.CUSTOMER,F.USER,F.DASHBOARD,F.RULE_CHAIN,F.RULE_NODE,F.EDGE]}configForm(){return this.originatorTypeConfigForm}prepareInputConfig(e){return{originatorTypes:ae(e?.originatorTypes)?e.originatorTypes:null}}onConfigurationSet(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e.originatorTypes,[O.required]]})}}e("OriginatorTypeConfigComponent",Lr),Lr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Lr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Lr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Lr,selector:"tb-filter-node-originator-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n help\n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:st.EntityTypeListComponent,selector:"tb-entity-type-list",inputs:["required","additionalClasses","appearance","label","floatLabel","disabled","subscriptSizing","allowedEntityTypes","emptyInputPlaceholder","filledInputPlaceholder","ignoreAuthorityFilter"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Lr,decorators:[{type:n,args:[{selector:"tb-filter-node-originator-type-config",template:'
\n \n help\n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Tr extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-filter-function"}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e.scriptLang,[O.required]],jsScript:[e.jsScript,[]],tbelScript:[e.tbelScript,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),{scriptLang:ae(e?.scriptLang)?e.scriptLang:x.JS,jsScript:ae(e?.jsScript)?e.jsScript:null,tbelScript:ae(e?.tbelScript)?e.tbelScript:null}}testScript(e){const t=this.scriptConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/filter_node_script_fn":"rulenode/tbel/filter_node_script_fn",o=this.scriptConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.scriptConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("ScriptConfigComponent",Tr),Tr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tr,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Tr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Tr,selector:"tb-filter-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tr,decorators:[{type:n,args:[{selector:"tb-filter-node-script-config",template:'
\n \n \n \n \n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class Ir extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-switch-function"}configForm(){return this.switchConfigForm}onConfigurationSet(e){this.switchConfigForm=this.fb.group({scriptLang:[e.scriptLang,[O.required]],jsScript:[e.jsScript,[]],tbelScript:[e.tbelScript,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.switchConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.switchConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.switchConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.switchConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.switchConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.switchConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.switchConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),{scriptLang:ae(e?.scriptLang)?e.scriptLang:x.JS,jsScript:ae(e?.jsScript)?e.jsScript:null,tbelScript:ae(e?.tbelScript)?e.tbelScript:null}}testScript(e){const t=this.switchConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/switch_node_script_fn":"rulenode/tbel/switch_node_script_fn",o=this.switchConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.switchConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.switchConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("SwitchConfigComponent",Ir),Ir.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ir,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Ir.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Ir,selector:"tb-filter-node-switch-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ir,decorators:[{type:n,args:[{selector:"tb-filter-node-switch-config",template:'
\n \n \n \n \n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class Nr{}e("RuleNodeCoreConfigFilterModule",Nr),Nr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Nr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Nr,declarations:[vr,Cr,Fr,kr,Lr,Tr,Ir,hr],imports:[$,M,$n],exports:[vr,Cr,Fr,kr,Lr,Tr,Ir,hr]}),Nr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nr,imports:[$,M,$n]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nr,decorators:[{type:c,args:[{declarations:[vr,Cr,Fr,kr,Lr,Tr,Ir,hr],imports:[$,M,$n],exports:[vr,Cr,Fr,kr,Lr,Tr,Ir,hr]}]}]});class Sr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.originatorSource=vt,this.originatorSources=Object.keys(vt),this.originatorSourceTranslationMap=Ct,this.originatorSourceDescTranslationMap=Ft,this.allowedEntityTypes=[F.DEVICE,F.ASSET,F.ENTITY_VIEW,F.USER,F.EDGE]}configForm(){return this.changeOriginatorConfigForm}onConfigurationSet(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[O.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationsQuery:[e?e.relationsQuery:null,[]]})}validatorTriggers(){return["originatorSource"]}updateValidators(e){const t=this.changeOriginatorConfigForm.get("originatorSource").value;t===vt.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([O.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),t===vt.ENTITY?(this.changeOriginatorConfigForm.get("entityType").setValidators([O.required]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([O.required,O.pattern(/.*\S.*/)])):(this.changeOriginatorConfigForm.get("entityType").patchValue(null,{emitEvent:e}),this.changeOriginatorConfigForm.get("entityNamePattern").patchValue(null,{emitEvent:e}),this.changeOriginatorConfigForm.get("entityType").setValidators([]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([])),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e}),this.changeOriginatorConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.changeOriginatorConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}}e("ChangeOriginatorConfigComponent",Sr),Sr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Sr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Sr,selector:"tb-transformation-node-change-originator-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.new-originator\n \n \n \n {{ originatorSourceTranslationMap.get(changeOriginatorConfigForm.get(\'originatorSource\').value) | translate }}\n \n \n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n
\n \n {{ originatorSourceDescTranslationMap.get(source) | translate }}\n \n
\n
\n
\n
\n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n
\n
\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:qe.MatListItemTitle,selector:"[matListItemTitle]"},{kind:"directive",type:qe.MatListItemMeta,selector:"[matListItemMeta]"},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Gn,selector:"tb-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sr,decorators:[{type:n,args:[{selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.new-originator\n \n \n \n {{ originatorSourceTranslationMap.get(changeOriginatorConfigForm.get(\'originatorSource\').value) | translate }}\n \n \n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n
\n \n {{ originatorSourceDescTranslationMap.get(source) | translate }}\n \n
\n
\n
\n
\n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n
\n
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class qr extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-transformer-function"}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],jsScript:[e?e.jsScript:null,[O.required]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.scriptConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/transformation_node_script_fn":"rulenode/tbel/transformation_node_script_fn",o=this.scriptConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.scriptConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("TransformScriptConfigComponent",qr),qr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:qr,deps:[{token:P.Store},{token:R.FormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),qr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:qr,selector:"tb-transformation-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n
\n \n
\n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:qr,decorators:[{type:n,args:[{selector:"tb-transformation-node-script-config",template:'
\n \n \n \n \n \n \n \n
\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}}); - /** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - const Ar=mt({passive:!0});class Mr{constructor(e,t){this._platform=e,this._ngZone=t,this._monitoredElements=new Map}monitor(e){if(!this._platform.isBrowser)return He;const t=Te(e),n=this._monitoredElements.get(t);if(n)return n.subject;const r=new Ke,o="cdk-text-field-autofilled",a=e=>{"cdk-text-field-autofill-start"!==e.animationName||t.classList.contains(o)?"cdk-text-field-autofill-end"===e.animationName&&t.classList.contains(o)&&(t.classList.remove(o),this._ngZone.run((()=>r.next({target:e.target,isAutofilled:!1})))):(t.classList.add(o),this._ngZone.run((()=>r.next({target:e.target,isAutofilled:!0}))))};return this._ngZone.runOutsideAngular((()=>{t.addEventListener("animationstart",a,Ar),t.classList.add("cdk-text-field-autofill-monitored")})),this._monitoredElements.set(t,{subject:r,unlisten:()=>{t.removeEventListener("animationstart",a,Ar)}}),r}stopMonitoring(e){const t=Te(e),n=this._monitoredElements.get(t);n&&(n.unlisten(),n.subject.complete(),t.classList.remove("cdk-text-field-autofill-monitored"),t.classList.remove("cdk-text-field-autofilled"),this._monitoredElements.delete(t))}ngOnDestroy(){this._monitoredElements.forEach(((e,t)=>this.stopMonitoring(t)))}}Mr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Mr,deps:[{token:pt.Platform},{token:t.NgZone}],target:t.ɵɵFactoryTarget.Injectable}),Mr.ɵprov=t.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Mr,providedIn:"root"}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Mr,decorators:[{type:s,args:[{providedIn:"root"}]}],ctorParameters:function(){return[{type:pt.Platform},{type:t.NgZone}]}});class Er{constructor(e,t){this._elementRef=e,this._autofillMonitor=t,this.cdkAutofill=new r}ngOnInit(){this._autofillMonitor.monitor(this._elementRef).subscribe((e=>this.cdkAutofill.emit(e)))}ngOnDestroy(){this._autofillMonitor.stopMonitoring(this._elementRef)}}Er.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Er,deps:[{token:t.ElementRef},{token:Mr}],target:t.ɵɵFactoryTarget.Directive}),Er.ɵdir=t.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"15.2.0-rc.0",type:Er,selector:"[cdkAutofill]",outputs:{cdkAutofill:"cdkAutofill"},ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Er,decorators:[{type:d,args:[{selector:"[cdkAutofill]"}]}],ctorParameters:function(){return[{type:t.ElementRef},{type:Mr}]},propDecorators:{cdkAutofill:[{type:u}]}}); - /** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - class Gr{get minRows(){return this._minRows}set minRows(e){this._minRows=Ie(e),this._setMinHeight()}get maxRows(){return this._maxRows}set maxRows(e){this._maxRows=Ie(e),this._setMaxHeight()}get enabled(){return this._enabled}set enabled(e){e=Le(e),this._enabled!==e&&((this._enabled=e)?this.resizeToFitContent(!0):this.reset())}get placeholder(){return this._textareaElement.placeholder}set placeholder(e){this._cachedPlaceholderHeight=void 0,e?this._textareaElement.setAttribute("placeholder",e):this._textareaElement.removeAttribute("placeholder"),this._cacheTextareaPlaceholderHeight()}constructor(e,t,n,r){this._elementRef=e,this._platform=t,this._ngZone=n,this._destroyed=new Ke,this._enabled=!0,this._previousMinRows=-1,this._isViewInited=!1,this._handleFocusEvent=e=>{this._hasFocus="focus"===e.type},this._document=r,this._textareaElement=this._elementRef.nativeElement}_setMinHeight(){const e=this.minRows&&this._cachedLineHeight?this.minRows*this._cachedLineHeight+"px":null;e&&(this._textareaElement.style.minHeight=e)}_setMaxHeight(){const e=this.maxRows&&this._cachedLineHeight?this.maxRows*this._cachedLineHeight+"px":null;e&&(this._textareaElement.style.maxHeight=e)}ngAfterViewInit(){this._platform.isBrowser&&(this._initialHeight=this._textareaElement.style.height,this.resizeToFitContent(),this._ngZone.runOutsideAngular((()=>{const e=this._getWindow();je(e,"resize").pipe(Pe(16),Ve(this._destroyed)).subscribe((()=>this.resizeToFitContent(!0))),this._textareaElement.addEventListener("focus",this._handleFocusEvent),this._textareaElement.addEventListener("blur",this._handleFocusEvent)})),this._isViewInited=!0,this.resizeToFitContent(!0))}ngOnDestroy(){this._textareaElement.removeEventListener("focus",this._handleFocusEvent),this._textareaElement.removeEventListener("blur",this._handleFocusEvent),this._destroyed.next(),this._destroyed.complete()}_cacheTextareaLineHeight(){if(this._cachedLineHeight)return;let e=this._textareaElement.cloneNode(!1);e.rows=1,e.style.position="absolute",e.style.visibility="hidden",e.style.border="none",e.style.padding="0",e.style.height="",e.style.minHeight="",e.style.maxHeight="",e.style.overflow="hidden",this._textareaElement.parentNode.appendChild(e),this._cachedLineHeight=e.clientHeight,e.remove(),this._setMinHeight(),this._setMaxHeight()}_measureScrollHeight(){const e=this._textareaElement,t=e.style.marginBottom||"",n=this._platform.FIREFOX,r=n&&this._hasFocus,o=n?"cdk-textarea-autosize-measuring-firefox":"cdk-textarea-autosize-measuring";r&&(e.style.marginBottom=`${e.clientHeight}px`),e.classList.add(o);const a=e.scrollHeight-4;return e.classList.remove(o),r&&(e.style.marginBottom=t),a}_cacheTextareaPlaceholderHeight(){if(!this._isViewInited||null!=this._cachedPlaceholderHeight)return;if(!this.placeholder)return void(this._cachedPlaceholderHeight=0);const e=this._textareaElement.value;this._textareaElement.value=this._textareaElement.placeholder,this._cachedPlaceholderHeight=this._measureScrollHeight(),this._textareaElement.value=e}ngDoCheck(){this._platform.isBrowser&&this.resizeToFitContent()}resizeToFitContent(e=!1){if(!this._enabled)return;if(this._cacheTextareaLineHeight(),this._cacheTextareaPlaceholderHeight(),!this._cachedLineHeight)return;const t=this._elementRef.nativeElement,n=t.value;if(!e&&this._minRows===this._previousMinRows&&n===this._previousValue)return;const r=this._measureScrollHeight(),o=Math.max(r,this._cachedPlaceholderHeight||0);t.style.height=`${o}px`,this._ngZone.runOutsideAngular((()=>{"undefined"!=typeof requestAnimationFrame?requestAnimationFrame((()=>this._scrollToCaretPosition(t))):setTimeout((()=>this._scrollToCaretPosition(t)))})),this._previousValue=n,this._previousMinRows=this._minRows}reset(){void 0!==this._initialHeight&&(this._textareaElement.style.height=this._initialHeight)}_noopInputHandler(){}_getDocument(){return this._document||document}_getWindow(){return this._getDocument().defaultView||window}_scrollToCaretPosition(e){const{selectionStart:t,selectionEnd:n}=e;!this._destroyed.isStopped&&this._hasFocus&&e.setSelectionRange(t,n)}}Gr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Gr,deps:[{token:t.ElementRef},{token:pt.Platform},{token:t.NgZone},{token:j,optional:!0}],target:t.ɵɵFactoryTarget.Directive}),Gr.ɵdir=t.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"15.2.0-rc.0",type:Gr,selector:"textarea[cdkTextareaAutosize]",inputs:{minRows:["cdkAutosizeMinRows","minRows"],maxRows:["cdkAutosizeMaxRows","maxRows"],enabled:["cdkTextareaAutosize","enabled"],placeholder:"placeholder"},host:{attributes:{rows:"1"},listeners:{input:"_noopInputHandler()"},classAttribute:"cdk-textarea-autosize"},exportAs:["cdkTextareaAutosize"],ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Gr,decorators:[{type:d,args:[{selector:"textarea[cdkTextareaAutosize]",exportAs:"cdkTextareaAutosize",host:{class:"cdk-textarea-autosize",rows:"1","(input)":"_noopInputHandler()"}}]}],ctorParameters:function(){return[{type:t.ElementRef},{type:pt.Platform},{type:t.NgZone},{type:void 0,decorators:[{type:p},{type:m,args:[j]}]}]},propDecorators:{minRows:[{type:i,args:["cdkAutosizeMinRows"]}],maxRows:[{type:i,args:["cdkAutosizeMaxRows"]}],enabled:[{type:i,args:["cdkTextareaAutosize"]}],placeholder:[{type:i}]}}); - /** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - class Dr{}Dr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Dr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Dr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.0-rc.0",ngImport:t,type:Dr,declarations:[Er,Gr],exports:[Er,Gr]}),Dr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Dr}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Dr,decorators:[{type:c,args:[{declarations:[Er,Gr],exports:[Er,Gr]}]}]});class wr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.mailBodyTypes=[{name:"tb.mail-body-type.plain-text",description:"tb.mail-body-type.plain-text-description",value:"false"},{name:"tb.mail-body-type.html",description:"tb.mail-body-type.html-text-description",value:"true"},{name:"tb.mail-body-type.use-body-type-template",description:"tb.mail-body-type.dynamic-text-description",value:"dynamic"}]}configForm(){return this.toEmailConfigForm}onConfigurationSet(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[O.required]],toTemplate:[e?e.toTemplate:null,[O.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[O.required]],mailBodyType:[e?e.mailBodyType:null],isHtmlTemplate:[e?e.isHtmlTemplate:null,[O.required]],bodyTemplate:[e?e.bodyTemplate:null,[O.required]]})}prepareInputConfig(e){return{fromTemplate:ae(e?.fromTemplate)?e.fromTemplate:null,toTemplate:ae(e?.toTemplate)?e.toTemplate:null,ccTemplate:ae(e?.ccTemplate)?e.ccTemplate:null,bccTemplate:ae(e?.bccTemplate)?e.bccTemplate:null,subjectTemplate:ae(e?.subjectTemplate)?e.subjectTemplate:null,mailBodyType:ae(e?.mailBodyType)?e.mailBodyType:null,isHtmlTemplate:ae(e?.isHtmlTemplate)?e.isHtmlTemplate:null,bodyTemplate:ae(e?.bodyTemplate)?e.bodyTemplate:null}}updateValidators(e){"dynamic"===this.toEmailConfigForm.get("mailBodyType").value?this.toEmailConfigForm.get("isHtmlTemplate").enable({emitEvent:!1}):this.toEmailConfigForm.get("isHtmlTemplate").disable({emitEvent:!1}),this.toEmailConfigForm.get("isHtmlTemplate").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["mailBodyType"]}getBodyTypeName(){return this.mailBodyTypes.find((e=>e.value===this.toEmailConfigForm.get("mailBodyType").value)).name}}e("ToEmailConfigComponent",wr),wr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:wr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),wr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:wr,selector:"tb-transformation-node-to-email-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
tb.rulenode.email-sender
\n
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.email-from-template-hint\' | translate }}\n \n \n
\n
\n
\n
\n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n
\n
\n
\n
\n
\n
tb.rulenode.recipients
\n \n \n
\n
\n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n tb.rulenode.cc-template\n \n \n \n tb.rulenode.bcc-template\n \n \n
\n
\n
\n
tb.rulenode.message-subject-and-content
\n \n \n
\n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n tb.rulenode.mail-body-type\n \n \n \n {{ getBodyTypeName() | translate }}\n \n \n \n \n {{ type.name | translate }}\n \n
\n \n {{ type.description | translate }}\n \n
\n
\n
\n \n tb.rulenode.body-type-template\n \n tb.mail-body-type.after-template-evaluation-hint\n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n
\n
\n
\n',styles:[":host .input-bottom-double-hint{display:inline-flex}:host .input-bottom-double-hint .see-example{flex-shrink:0;padding-right:16px}:host textarea.tb-enable-vertical-resize{resize:vertical}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:$e.HelpPopupComponent,selector:"[tb-help-popup], [tb-help-popup-content]",inputs:["tb-help-popup","tb-help-popup-content","trigger-text","trigger-style","tb-help-popup-placement","tb-help-popup-style","hintMode"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Gr,selector:"textarea[cdkTextareaAutosize]",inputs:["cdkAutosizeMinRows","cdkAutosizeMaxRows","cdkTextareaAutosize","placeholder"],exportAs:["cdkTextareaAutosize"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:qe.MatListItemTitle,selector:"[matListItemTitle]"},{kind:"directive",type:qe.MatListItemMeta,selector:"[matListItemMeta]"},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:wr,decorators:[{type:n,args:[{selector:"tb-transformation-node-to-email-config",template:'
\n
\n
tb.rulenode.email-sender
\n
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.email-from-template-hint\' | translate }}\n \n \n
\n
\n
\n
\n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n
\n
\n
\n
\n
\n
tb.rulenode.recipients
\n \n \n
\n
\n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n tb.rulenode.cc-template\n \n \n \n tb.rulenode.bcc-template\n \n \n
\n
\n
\n
tb.rulenode.message-subject-and-content
\n \n \n
\n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n tb.rulenode.mail-body-type\n \n \n \n {{ getBodyTypeName() | translate }}\n \n \n \n \n {{ type.name | translate }}\n \n
\n \n {{ type.description | translate }}\n \n
\n
\n
\n \n tb.rulenode.body-type-template\n \n tb.mail-body-type.after-template-evaluation-hint\n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n
\n
\n
\n',styles:[":host .input-bottom-double-hint{display:inline-flex}:host .input-bottom-double-hint .see-example{flex-shrink:0;padding-right:16px}:host textarea.tb-enable-vertical-resize{resize:vertical}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Vr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.copyFrom=[],this.translation=tn;for(const e of this.translation.keys())this.copyFrom.push({value:e,name:this.translate.instant(this.translation.get(e))})}onConfigurationSet(e){this.copyKeysConfigForm=this.fb.group({copyFrom:[e.copyFrom,[O.required]],keys:[e?e.keys:null,[O.required]]})}configForm(){return this.copyKeysConfigForm}prepareInputConfig(e){let t;return t=ae(e?.fromMetadata)?e.copyFrom?en.METADATA:en.DATA:ae(e?.copyFrom)?e.copyFrom:en.DATA,{keys:ae(e?.keys)?e.keys:null,copyFrom:t}}}e("CopyKeysConfigComponent",Vr),Vr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Vr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Vr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Vr,selector:"tb-transformation-node-copy-keys-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n help\n \n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Vr,decorators:[{type:n,args:[{selector:"tb-transformation-node-copy-keys-config",template:'
\n \n \n \n \n help\n \n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Pr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.renameIn=[],this.translation=rn;for(const e of this.translation.keys())this.renameIn.push({value:e,name:this.translate.instant(this.translation.get(e))})}configForm(){return this.renameKeysConfigForm}onConfigurationSet(e){this.renameKeysConfigForm=this.fb.group({renameIn:[e?e.renameIn:null,[O.required]],renameKeysMapping:[e?e.renameKeysMapping:null,[O.required]]})}prepareInputConfig(e){let t;return t=ae(e?.fromMetadata)?e.fromMetadata?en.METADATA:en.DATA:ae(e?.renameIn)?e?.renameIn:en.DATA,{renameKeysMapping:ae(e?.renameKeysMapping)?e.renameKeysMapping:null,renameIn:t}}}e("RenameKeysConfigComponent",Pr),Pr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Pr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Pr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Pr,selector:"tb-transformation-node-rename-keys-config",usesInheritance:!0,ngImport:t,template:'
\n
tb.rulenode.rename-keys-in
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n \n \n
\n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}:host .fx-centered{display:flex;width:100%;justify-content:space-around}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Mn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Pr,decorators:[{type:n,args:[{selector:"tb-transformation-node-rename-keys-config",template:'
\n
tb.rulenode.rename-keys-in
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n \n \n
\n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}:host .fx-centered{display:flex;width:100%;justify-content:space-around}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Rr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.jsonPathConfigForm}onConfigurationSet(e){this.jsonPathConfigForm=this.fb.group({jsonPath:[e?e.jsonPath:null,[O.required]]})}}e("NodeJsonPathConfigComponent",Rr),Rr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Rr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Rr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Rr,selector:"tb-transformation-node-json-path-config",usesInheritance:!0,ngImport:t,template:"
\n \n {{ 'tb.rulenode.json-path-expression' | translate }}\n \n {{ 'tb.rulenode.json-path-expression-hint' | translate }}\n {{ 'tb.rulenode.json-path-expression-required' | translate }}\n \n
\n",dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Rr,decorators:[{type:n,args:[{selector:"tb-transformation-node-json-path-config",template:"
\n \n {{ 'tb.rulenode.json-path-expression' | translate }}\n \n {{ 'tb.rulenode.json-path-expression-hint' | translate }}\n {{ 'tb.rulenode.json-path-expression-required' | translate }}\n \n
\n"}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Or extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.deleteFrom=[],this.translation=nn;for(const e of this.translation.keys())this.deleteFrom.push({value:e,name:this.translate.instant(this.translation.get(e))})}onConfigurationSet(e){this.deleteKeysConfigForm=this.fb.group({deleteFrom:[e.deleteFrom,[O.required]],keys:[e?e.keys:null,[O.required]]})}prepareInputConfig(e){let t;return t=ae(e?.fromMetadata)?e.fromMetadata?en.METADATA:en.DATA:ae(e?.deleteFrom)?e?.deleteFrom:en.DATA,{keys:ae(e?.keys)?e.keys:null,deleteFrom:t}}configForm(){return this.deleteKeysConfigForm}}e("DeleteKeysConfigComponent",Or),Or.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Or,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Or.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Or,selector:"tb-transformation-node-delete-keys-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n help\n \n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Or,decorators:[{type:n,args:[{selector:"tb-transformation-node-delete-keys-config",template:'
\n \n \n \n \n help\n \n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class _r extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.deduplicationStrategie=Gt,this.deduplicationStrategies=Object.keys(this.deduplicationStrategie),this.deduplicationStrategiesTranslations=Dt}configForm(){return this.deduplicationConfigForm}onConfigurationSet(e){this.deduplicationConfigForm=this.fb.group({interval:[ae(e?.interval)?e.interval:null,[O.required,O.min(1)]],strategy:[ae(e?.strategy)?e.strategy:null,[O.required]],outMsgType:[ae(e?.outMsgType)?e.outMsgType:null,[O.required]],maxPendingMsgs:[ae(e?.maxPendingMsgs)?e.maxPendingMsgs:null,[O.required,O.min(1),O.max(1e3)]],maxRetries:[ae(e?.maxRetries)?e.maxRetries:null,[O.required,O.min(0),O.max(100)]]})}prepareInputConfig(e){return e||(e={}),e.outMsgType||(e.outMsgType="POST_TELEMETRY_REQUEST"),super.prepareInputConfig(e)}updateValidators(e){this.deduplicationConfigForm.get("strategy").value===this.deduplicationStrategie.ALL?this.deduplicationConfigForm.get("outMsgType").enable({emitEvent:!1}):this.deduplicationConfigForm.get("outMsgType").disable({emitEvent:!1}),this.deduplicationConfigForm.get("outMsgType").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["strategy"]}}e("DeduplicationConfigComponent",_r),_r.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_r,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),_r.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:_r,selector:"tb-action-node-msg-deduplication-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{\'tb.rulenode.interval\' | translate}}\n \n \n {{\'tb.rulenode.interval-required\' | translate}}\n \n \n {{\'tb.rulenode.interval-min-error\' | translate}}\n \n help\n \n
\n
\n
tb.rulenode.strategy
\n \n \n {{ deduplicationStrategiesTranslations.get(strategy) | translate }}\n \n \n \n \n \n \n \n \n
\n \n \n
\n
\n
\n \n \n tb.rulenode.advanced-settings\n \n
\n \n {{\'tb.rulenode.max-pending-msgs\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-required\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-min-error\' | translate}}\n \n help\n \n \n {{\'tb.rulenode.max-retries\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-required\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-min-error\' | translate}}\n \n help\n \n
\n
\n
\n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:We.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:We.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:We.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Bn,selector:"tb-output-message-type-autocomplete",inputs:["subscriptSizing","disabled","required"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_r,decorators:[{type:n,args:[{selector:"tb-action-node-msg-deduplication-config",template:'
\n \n {{\'tb.rulenode.interval\' | translate}}\n \n \n {{\'tb.rulenode.interval-required\' | translate}}\n \n \n {{\'tb.rulenode.interval-min-error\' | translate}}\n \n help\n \n
\n
\n
tb.rulenode.strategy
\n \n \n {{ deduplicationStrategiesTranslations.get(strategy) | translate }}\n \n \n \n \n \n \n \n \n
\n \n \n
\n
\n
\n \n \n tb.rulenode.advanced-settings\n \n
\n \n {{\'tb.rulenode.max-pending-msgs\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-required\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-min-error\' | translate}}\n \n help\n \n \n {{\'tb.rulenode.max-retries\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-required\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-min-error\' | translate}}\n \n help\n \n
\n
\n
\n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Br{}e("RulenodeCoreConfigTransformModule",Br),Br.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Br,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Br.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Br,declarations:[Sr,qr,wr,Vr,Pr,Rr,Or,_r],imports:[$,M,$n],exports:[Sr,qr,wr,Vr,Pr,Rr,Or,_r]}),Br.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Br,imports:[$,M,$n]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Br,decorators:[{type:c,args:[{declarations:[Sr,qr,wr,Vr,Pr,Rr,Or,_r],imports:[$,M,$n],exports:[Sr,qr,wr,Vr,Pr,Rr,Or,_r]}]}]});class Kr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.entityType=F}configForm(){return this.ruleChainInputConfigForm}onConfigurationSet(e){this.ruleChainInputConfigForm=this.fb.group({ruleChainId:[e?e.ruleChainId:null,[O.required]]})}}e("RuleChainInputComponent",Kr),Kr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Kr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Kr,selector:"tb-flow-node-rule-chain-input-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',dependencies:[{kind:"component",type:lt.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","useFullEntityId","appearance","required","disabled"],outputs:["entityChanged"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kr,decorators:[{type:n,args:[{selector:"tb-flow-node-rule-chain-input-config",template:'
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class zr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.ruleChainOutputConfigForm}onConfigurationSet(e){this.ruleChainOutputConfigForm=this.fb.group({})}}e("RuleChainOutputComponent",zr),zr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),zr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:zr,selector:"tb-flow-node-rule-chain-output-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
\n',dependencies:[{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zr,decorators:[{type:n,args:[{selector:"tb-flow-node-rule-chain-output-config",template:'
\n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Ur{}e("RuleNodeCoreConfigFlowModule",Ur),Ur.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ur,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Ur.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Ur,declarations:[Kr,zr],imports:[$,M,$n],exports:[Kr,zr]}),Ur.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ur,imports:[$,M,$n]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ur,decorators:[{type:c,args:[{declarations:[Kr,zr],imports:[$,M,$n],exports:[Kr,zr]}]}]});class Hr{constructor(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{id:"Id","additional-info":"Additional Info","advanced-settings":"Advanced settings","create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","select-device-connectivity-event":"Select device connectivity event","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","copy-message-type":"Copy message type","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","message-type-value":"Message type value","message-type-value-required":"Message type value is required","message-type-value-max-length":"Message type value should be less than 256","output-message-type":"Output message type","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","interval-start":"Interval start","interval-end":"Interval end","time-unit":"Time unit","fetch-mode":"Fetch mode","order-by-timestamp":"Order by timestamp",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. If you want to fetch a single entry, select fetch mode 'First' or 'Last'.","limit-required":"Limit is required.","limit-range":"Limit should be in a range from 2 to 1000.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Allowing range from 1 to 2147483647.","start-interval-value-required":"Interval start is required.","end-interval-value-required":"Interval end is required.",filter:"Filter",switch:"Switch","math-templatization-tooltip":"This field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","add-message-type":"Add message type","select-message-types-required":"At least one message type should be selected.","select-message-types":"Select message types","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one.","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","attributes-keys":"Attributes keys","attributes-keys-required":"Attributes keys are required","notify-device":"Force notification to the device","send-attributes-updated-notification":"Send attributes updated notification","send-attributes-updated-notification-hint":"Send notification about updated attributes as a separate message to the rule engine queue.","send-attributes-deleted-notification":"Send attributes deleted notification","send-attributes-deleted-notification-hint":"Send notification about deleted attributes as a separate message to the rule engine queue.","update-attributes-only-on-value-change":"Save attributes only if the value changes","update-attributes-only-on-value-change-hint":"Updates the attributes on every incoming message disregarding if their value has changed. Increases API usage and reduces performance.","update-attributes-only-on-value-change-hint-enabled":"Updates the attributes only if their value has changed. If the value is not changed, no update to the attribute timestamp nor attribute change notification will be sent.","fetch-credentials-to-metadata":"Fetch credentials to metadata","notify-device-on-update-hint":"If enabled, force notification to the device about shared attributes update. If disabled, the notification behavior is controlled by the 'notifyDevice' parameter from the incoming message metadata. To turn off the notification, the message metadata must contain the 'notifyDevice' parameter set to 'false'. Any other case will trigger the notification to the device.","notify-device-on-delete-hint":"If enabled, force notification to the device about shared attributes removal. If disabled, the notification behavior is controlled by the 'notifyDevice' parameter from the incoming message metadata. To turn on the notification, the message metadata must contain the 'notifyDevice' parameter set to 'true'. In any other case, the notification will not be triggered to the device.","latest-timeseries":"Latest time-series data keys","timeseries-keys":"Timeseries keys","timeseries-keys-required":"At least one timeseries key should be selected.","add-timeseries-key":"Add timeseries key","add-message-field":"Add message field","relation-search-parameters":"Relation search parameters","add-metadata-field":"Add metadata field","data-keys":"Message field names","copy-from":"Copy from","data-to-metadata":"Data to metadata","metadata-to-data":"Metadata to data","use-regular-expression-hint":"Use regular expression to copy keys by pattern.\n\nTips & tricks:\nPress 'Enter' to complete field name input.\nPress 'Backspace' to delete field name. Multiple field names supported.",interval:"Interval","interval-required":"Interval is required","interval-hint":"Deduplication interval in seconds.","interval-min-error":"Min allowed value is 1","max-pending-msgs":"Max pending messages","max-pending-msgs-hint":"Maximum number of messages that are stored in memory for each unique deduplication id.","max-pending-msgs-required":"Max pending messages is required","max-pending-msgs-max-error":"Max allowed value is 1000","max-pending-msgs-min-error":"Min allowed value is 1","max-retries":"Max retries","max-retries-required":"Max retries is required","max-retries-hint":"Maximum number of retries to push the deduplicated messages into the queue. 10 seconds delay is used between retries","max-retries-max-error":"Max allowed value is 100","max-retries-min-error":"Min allowed value is 0",strategy:"Strategy","strategy-required":"Strategy is required","strategy-all-hint":"Return all messages that arrived during deduplication period as a single JSON array message. Where each element represents object with msg and metadata inner properties.","strategy-first-hint":"Return first message that arrived during deduplication period.","strategy-last-hint":"Return last message that arrived during deduplication period.",first:"First",last:"Last",all:"All","output-msg-type-hint":"The message type of the deduplication result.","queue-name-hint":"The queue name where the deduplication result will be published.",keys:"Keys","keys-required":"Keys are required","rename-keys-in":"Rename keys in",data:"Data",message:"Message",metadata:"Metadata","current-key-name":"Current key name","key-name-required":"Key name is required","new-key-name":"New key name","new-key-name-required":"New key name is required","metadata-keys":"Metadata field names","json-path-expression":"JSON path expression","json-path-expression-required":"JSON path expression is required","json-path-expression-hint":"JSONPath specifies a path to an element or a set of elements in a JSON structure. '$' represents the root object or array.","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","max-relation-level-error":"Value should be greater than 0 or unspecified.","relation-type":"Relation type","relation-type-pattern":"Relation type pattern","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","add-telemetry-key":"Add telemetry key","delete-from":"Delete from","use-regular-expression-delete-hint":"Use regular expression to delete keys by pattern.\n\nTips & tricks:\nPress 'Enter' to complete field name input.\nPress 'Backspace' to delete field name.\nMultiple field names supported.","fetch-into":"Fetch into","attr-mapping":"Attributes mapping:","source-attribute":"Source attribute key","source-attribute-required":"Source attribute key is required.","source-telemetry":"Source telemetry key","source-telemetry-required":"Source telemetry key is required.","target-key":"Target key","target-key-required":"Target key is required.","attr-mapping-required":"At least one mapping entry should be specified.","fields-mapping":"Fields mapping","relations-query-config-direction-suffix":"originator","profile-name":"Profile name","fetch-circle-parameter-info-from-metadata-hint":'Metadata field \'{{perimeterKeyName}}\' should be defined in next format: {"latitude":48.196, "longitude":24.6532, "radius":100.0, "radiusUnit":"METER"}',"fetch-poligon-parameter-info-from-metadata-hint":"Metadata field '{{perimeterKeyName}}' should be defined in next format: [[48.19736,24.65235],[48.19800,24.65060],...,[48.19849,24.65420]]","short-templatization-tooltip":"Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","fields-mapping-required":"At least one field mapping should be specified.","at-least-one-field-required":"At least one input field must have a value(s) provided.","originator-fields-sv-map-hint":"Target key fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","sv-map-hint":"Only target key fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","new-originator":"New originator","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related entity","originator-alarm-originator":"Alarm Originator","originator-entity":"Entity by name pattern","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","select-entity-types":"Select entity types","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-severity-pattern":"Alarm severity pattern","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate alarm to related entities","propagate-to-owner":"Propagate alarm to entity owner (Customer or Tenant)","propagate-to-tenant":"Propagate alarm to Tenant",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From","from-template-required":"From is required","message-to-metadata":"Message to metadata","metadata-to-message":"Metadata to message","from-message":"From message","from-metadata":"From metadata","to-template":"To","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc","bcc-template":"Bcc","subject-template":"Subject","subject-template-required":"Subject Template is required","body-template":"Body","body-template-required":"Body Template is required","dynamic-mail-body-type":"Dynamic mail body type","mail-body-type":"Mail body type","body-type-template":"Body type template","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","ignore-request-body":"Without request body","parse-to-plain-text":"Parse to plain text","parse-to-plain-text-hint":'If selected, request body message payload will be transformed from JSON string to plain text, e.g. msg = "Hello,\\t\\"world\\"" will be parsed to Hello, "world"',"read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","key-pattern":"Key pattern","key-pattern-hint":"Optional. If a valid partition number is specified, it will be used when sending the record. If no partition is specified, the key will be used instead. If neither is specified, a partition will be assigned in a round-robin fashion.","topic-pattern-required":"Topic pattern is required",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","client-id-hint":'Optional. Leave empty for auto-generated Client ID. Be careful when specifying the Client ID. Majority of the MQTT brokers will not allow multiple connections with the same Client ID. To connect to such brokers, your mqtt Client ID must be unique. When platform is running in a micro-services mode, the copy of rule node is launched in each micro-service. This will automatically lead to multiple mqtt clients with the same ID and may cause failures of the rule node. To avoid such failures enable "Add Service ID as suffix to Client ID" option below.',"append-client-id-suffix":"Add Service ID as suffix to Client ID","client-id-suffix-hint":'Optional. Applied when "Client ID" specified explicitly. If selected then Service ID will be added to Client ID as a suffix. Helps to avoid failures when platform is running in a micro-services mode.',"device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"Server CA certificate file","private-key":"Client private key file",cert:"Client certificate file","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-dynamic-interval":"Use dynamic interval","metadata-dynamic-interval-hint":"Interval start and end input fields support templatization. Note that the substituted template value should be set in milliseconds. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","overwrite-alarm-details":"Overwrite alarm details","use-alarm-severity-pattern":"Use alarm severity pattern","check-all-keys":"Check that all specified fields are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-to-specific-entity-tooltip":"If enabled, checks the presence of relation with a specific entity otherwise, checks the presence of relation with any entity. In both cases, relation lookup is based on configured direction and type.","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval":"Interval start","end-interval":"Interval end","start-interval-required":"Interval start is required.","end-interval-required":"Interval end is required.","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":'Comma separated Phone Numbers, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'Press "Enter" to complete field input.',"select-details":"Select details","entity-details-id":"Id","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-city":"City","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","email-sender":"Email sender","fields-to-check":"Fields to check","add-detail":"Add detail","check-all-keys-tooltip":"If enabled, checks the presence of all fields listed in the message and metadata field names within the incoming message and its metadata.","fields-to-check-hint":'Press "Enter" to complete field name input. Multiple field names supported.',"entity-details-list-empty":"At least one detail should be selected.","alarm-status":"Alarm status","alarm-required":"At least one alarm status should be selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"Enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-field-name":"Latitude field name","longitude-field-name":"Longitude field name","latitude-field-name-required":"Latitude field name is required.","longitude-field-name-required":"Longitude field name is required.","fetch-perimeter-info-from-metadata":"Fetch perimeter information from metadata","fetch-perimeter-info-from-metadata-tooltip":"If perimeter type is set to 'Polygon' the value of metadata field '{{perimeterKeyName}}' will be set as perimeter definition without additional parsing of the value. Otherwise, if perimeter type is set to 'Circle' the value of '{{perimeterKeyName}}' metadata field will be parsed to extract 'latitude', 'longitude', 'radius', 'radiusUnit' fields that uses for circle perimeter definition.","perimeter-key-name":"Perimeter key name","perimeter-key-name-hint":"Metadata field name that includes perimeter information.","perimeter-key-name-required":"Perimeter key name is required.","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units","range-units-required":"Range units is required.",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch timestamp for the latest telemetry values","get-latest-value-with-ts-hint":'If selected, the latest telemetry values will also include timestamp, e.g: "temp": "{"ts":1574329385897, "value":42}"',"use-redis-queue":"Use redis queue for message persistence","ignore-null-strings":"Ignore null strings","ignore-null-strings-hint":"If selected rule node will ignore entity fields with empty value.","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.","number-of-digits-after-floating-point":"Number of digits after floating point","number-of-digits-after-floating-point-range":"Number of digits after floating point should be in a range from 0 to 15.","failure-if-delta-negative":"Tell Failure if delta is negative","failure-if-delta-negative-tooltip":"Rule node forces failure of message processing if delta value is negative.","use-caching":"Use caching","use-caching-tooltip":'Rule node will cache the value of "{{inputValueKey}}" that arrives from the incoming message to improve performance. Note that the cache will not be updated if you modify the "{{inputValueKey}}" value elsewhere.',"add-time-difference-between-readings":'Add the time difference between "{{inputValueKey}}" readings',"add-time-difference-between-readings-tooltip":'If enabled, the rule node will add the "{{periodValueKey}}" to the outbound message.',"period-value-key":"Period value key","period-value-key-required":"Period value key is required.","general-pattern-hint":"Use ${metadataKey} for value from metadata, $[messageKey] for value from message body.","alarm-severity-pattern-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)',"output-node-name-hint":"The rule node name corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain.","skip-latest-persistence":"Skip latest persistence","use-server-ts":"Use server ts","use-server-ts-hint":"Enable this setting to use the timestamp of the message processing instead of the timestamp from the message. Useful for all sorts of sequential processing if you merge messages from multiple sources (devices, assets, etc).","kv-map-pattern-hint":"All input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","shared-scope":"Shared scope","server-scope":"Server scope","client-scope":"Client scope","attribute-type":"Attribute","constant-type":"Constant","time-series-type":"Time-series","message-body-type":"Message","message-metadata-type":"Metadata","argument-tile":"Arguments","no-arguments-prompt":"No arguments configured","result-title":"Result","functions-field-input":"Functions","no-option-found":"No option found","argument-source-field-input":"Source","argument-source-field-input-required":"Argument source is required.","argument-key-field-input":"Key","argument-key-field-input-required":"Argument key is required.","constant-value-field-input":"Constant value","constant-value-field-input-required":"Constant value is required.","attribute-scope-field-input":"Attribute scope","attribute-scope-field-input-required":"Attribute scope os required.","default-value-field-input":"Default value","type-field-input":"Type","type-field-input-required":"Type is required.","key-field-input":"Key","add-entity-type":"Add entity type","add-device-profile":"Add device profile","key-field-input-required":"Key is required.","number-floating-point-field-input":"Number of digits after floating point","number-floating-point-field-input-hint":"Use 0 to convert result to integer","add-to-message-field-input":"Add to message","add-to-metadata-field-input":"Add to metadata","custom-expression-field-input":"Mathematical Expression","custom-expression-field-input-required":"Mathematical expression is required","custom-expression-field-input-hint":"Specify a mathematical expression to evaluate. Default expression demonstrates how to transform Fahrenheit to Celsius","retained-message":"Retained","attributes-mapping":"Attributes mapping","latest-telemetry-mapping":"Latest telemetry mapping","add-mapped-attribute-to":"Add mapped attributes to","add-mapped-latest-telemetry-to":"Add mapped latest telemetry to","add-mapped-fields-to":"Add mapped fields to","add-selected-details-to":"Add selected details to","clear-selected-types":"Clear selected types","clear-selected-details":"Clear selected details","clear-selected-fields":"Clear selected fields","clear-selected-keys":"Clear selected keys","geofence-configuration":"Geofence configuration","coordinate-field-names":"Coordinate field names","coordinate-field-hint":"Rule node tries to retrieve the specified fields from the message. If they are not present, it will look them up in the metadata.","presence-monitoring-strategy":"Presence monitoring strategy","presence-monitoring-strategy-on-first-message":"On first message","presence-monitoring-strategy-on-each-message":"On each message","presence-monitoring-strategy-on-first-message-hint":"Reports presence status 'Inside' or 'Outside' on the first message after the configured minimal duration has passed since previous presence status 'Entered' or 'Left' update.","presence-monitoring-strategy-on-each-message-hint":"Reports presence status 'Inside' or 'Outside' on each message after presence status 'Entered' or 'Left' update.","fetch-credentials-to":"Fetch credentials to","add-originator-attributes-to":"Add originator attributes to","originator-attributes":"Originator attributes","fetch-latest-telemetry-with-timestamp":"Fetch latest telemetry with timestamp","fetch-latest-telemetry-with-timestamp-tooltip":'If selected, latest telemetry values will be added to the outbound metadata with timestamp, e.g: "{{latestTsKeyName}}": "{"ts":1574329385897, "value":42}"',"tell-failure":"Tell failure if any of the attributes are missing","tell-failure-tooltip":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"created-time":"Created time","chip-help":"Press 'Enter' to complete {{inputName}} input. \nPress 'Backspace' to delete {{inputName}}. \nMultiple values supported.",detail:"detail","field-name":"field name","device-profile":"device profile","entity-type":"entity type","message-type":"message type","timeseries-key":"timeseries key",type:"Type","first-name":"First name","last-name":"Last name",label:"Label","originator-fields-mapping":"Originator fields mapping","add-mapped-originator-fields-to":"Add mapped originator fields to",fields:"Fields","skip-empty-fields":"Skip empty fields","skip-empty-fields-tooltip":"Fields with empty values will not be added to the output message/output metadata.","fetch-interval":"Fetch interval","fetch-strategy":"Fetch strategy","fetch-timeseries-from-to":"Fetch timeseries from {{startInterval}} {{startIntervalTimeUnit}} ago to {{endInterval}} {{endIntervalTimeUnit}} ago.","fetch-timeseries-from-to-invalid":'Fetch timeseries invalid ("Interval start" should be less than "Interval end").',"use-metadata-dynamic-interval-tooltip":"If selected, the rule node will use dynamic interval start and end based on the message and metadata patterns.","all-mode-hint":'If selected fetch mode "All" rule node will retrieve telemetry from the fetch interval with configurable query parameters.',"first-mode-hint":'If selected fetch mode "First" rule node will retrieve the closest telemetry to the fetch interval\'s start.',"last-mode-hint":'If selected fetch mode "Last" rule node will retrieve the closest telemetry to the fetch interval\'s end.',ascending:"Ascending",descending:"Descending",min:"Min",max:"Max",average:"Average",sum:"Sum",count:"Count",none:"None","last-level-relation-tooltip":"If selected, the rule node will search related entities only on the level set in the max relation level.","last-level-device-relation-tooltip":"If selected, the rule node will search related devices only on the level set in the max relation level.","data-to-fetch":"Data to fetch","mapping-of-customers":"Mapping of customer's","map-fields-required":"All mapping fields are required.",attributes:"Attributes","related-device-attributes":"Related device attributes","add-selected-attributes-to":"Add selected attributes to","device-profiles":"Device profiles","mapping-of-tenant":"Mapping of tenant's","add-attribute-key":"Add attribute key","message-template":"Message template","message-template-required":"Message template is required","use-system-slack-settings":"Use system slack settings","slack-api-token":"Slack API token","slack-api-token-required":"Slack API token is required","keys-mapping":"keys mapping","add-key":"Add key",recipients:"Recipients","message-subject-and-content":"Message subject and content","template-rules-hint":"Both input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","originator-customer-desc":"Use customer of incoming message originator as new originator.","originator-tenant-desc":"Use current tenant as new originator.","originator-related-entity-desc":"Use related entity as new originator. Lookup based on configured relation type and direction.","originator-alarm-originator-desc":"Use alarm originator as new originator. Only if incoming message originator is alarm entity.","originator-entity-by-name-pattern-desc":"Use entity fetched from DB as new originator. Lookup based on entity type and specified name pattern.","email-from-template-hint":"Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","recipients-block-main-hint":"Comma-separated address list. All input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata."},"key-val":{key:"Key",value:"Value","see-examples":"See examples.","remove-entry":"Remove entry","remove-mapping-entry":"Remove mapping entry","add-mapping-entry":"Add mapping","add-entry":"Add entry","copy-key-values-from":"Copy key-values from","delete-key-values":"Delete key-values","delete-key-values-from":"Delete key-values from","at-least-one-key-error":"At least one key should be selected.","unique-key-value-pair-error":"'{{keyText}}' must be different from the '{{valText}}'!"},"mail-body-type":{"plain-text":"Plain text",html:"HTML",dynamic:"Dynamic","use-body-type-template":"Use body type template","plain-text-description":"Simple, unformatted text with no special styling or formating.","html-text-description":"Allows you to use HTML tags for formatting, links and images in your mai body.","dynamic-text-description":"Allows to use Plain Text or HTML body type dynamically based on templatization feature.","after-template-evaluation-hint":"After template evaluation value should be true for HTML, and false for Plain text."}}},!0)}(e)}}e("RuleNodeCoreConfigModule",Hr),Hr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hr,deps:[{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.NgModule}),Hr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Hr,declarations:[dt],imports:[$,M],exports:[Qn,Nr,ar,br,Br,Ur,dt]}),Hr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hr,imports:[$,M,Qn,Nr,ar,br,Br,Ur]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hr,decorators:[{type:c,args:[{declarations:[dt],imports:[$,M],exports:[Qn,Nr,ar,br,Br,Ur,dt]}]}],ctorParameters:function(){return[{type:Z.TranslateService}]}})}}}));//# sourceMappingURL=rulenode-core-config.js.map +System.register(["@angular/core","@shared/public-api","@ngrx/store","@angular/forms","@angular/common","@angular/material/checkbox","@angular/material/input","@angular/material/form-field","@angular/flex-layout/flex","@ngx-translate/core","@angular/material/select","@angular/material/core","@angular/material/slide-toggle","@shared/components/hint-tooltip-icon.component","@core/public-api","@shared/components/js-func.component","@angular/material/button","@angular/material/icon","@angular/material/tooltip","@shared/components/script-lang.component","@angular/cdk/keycodes","@angular/material/chips","@shared/pipe/safe.pipe","@shared/components/entity/entity-type-select.component","@shared/components/entity/entity-select.component","@shared/components/toggle-header.component","@shared/components/toggle-select.component","@angular/cdk/coercion","@shared/components/tb-error.component","@angular/flex-layout/extended","@angular/material/list","@angular/cdk/drag-drop","rxjs/operators","@angular/material/autocomplete","@shared/pipe/highlight.pipe","@home/components/public-api","tslib","rxjs","@shared/components/help-popup.component","@shared/components/entity/entity-subtype-list.component","@shared/components/relation/relation-type-autocomplete.component","@home/components/relation/relation-filters.component","@angular/material/expansion","@shared/components/file-input.component","@shared/components/button/toggle-password.component","@shared/components/string-items-list.component","@shared/components/entity/entity-list.component","@shared/components/notification/template-autocomplete.component","@shared/components/tb-checkbox.component","@home/components/sms/sms-provider-configuration.component","@angular/material/radio","@shared/components/slack-conversation-autocomplete.component","@shared/components/entity/entity-autocomplete.component","@shared/components/entity/entity-type-list.component","@angular/cdk/platform"],(function(e){"use strict";var t,n,r,o,a,i,l,s,m,p,d,u,c,f,g,y,x,b,h,v,C,F,k,L,T,I,N,S,q,A,M,E,G,D,w,V,P,R,O,_,B,K,z,U,H,j,$,Q,J,Y,W,Z,X,ee,te,ne,re,oe,ae,ie,le,se,me,pe,de,ue,ce,fe,ge,ye,xe,be,he,ve,Ce,Fe,ke,Le,Te,Ie,Ne,Se,qe,Ae,Me,Ee,Ge,De,we,Ve,Pe,Re,Oe,_e,Be,Ke,ze,Ue,He,je,$e,Qe,Je,Ye,We,Ze,Xe,et,tt,nt,rt,ot,at,it,lt,st,mt,pt;return{setters:[function(e){t=e,n=e.Component,r=e.EventEmitter,o=e.ViewChild,a=e.forwardRef,i=e.Input,l=e.InjectionToken,s=e.Injectable,m=e.Inject,p=e.Optional,d=e.Directive,u=e.Output,c=e.NgModule},function(e){f=e.RuleNodeConfigurationComponent,g=e.AttributeScope,y=e.telemetryTypeTranslations,x=e.ScriptLanguage,b=e.AlarmSeverity,h=e.alarmSeverityTranslations,v=e.EntitySearchDirection,C=e.entitySearchDirectionTranslations,F=e.EntityType,k=e.entityFields,L=e.PageComponent,T=e.messageTypeNames,I=e.MessageType,N=e.coerceBoolean,S=e,q=e.AlarmStatus,A=e.alarmStatusTranslations,M=e.SharedModule,E=e.AggregationType,G=e.aggregationTranslations,D=e.NotificationType,w=e.SlackChanelType,V=e.SlackChanelTypesTranslateMap},function(e){P=e},function(e){R=e,O=e.Validators,_=e.NgControl,B=e.NG_VALUE_ACCESSOR,K=e.NG_VALIDATORS,z=e.FormArray,U=e.FormGroup},function(e){H=e,j=e.DOCUMENT,$=e.CommonModule},function(e){Q=e},function(e){J=e},function(e){Y=e},function(e){W=e},function(e){Z=e},function(e){X=e},function(e){ee=e},function(e){te=e},function(e){ne=e},function(e){re=e.getCurrentAuthState,oe=e,ae=e.isDefinedAndNotNull,ie=e.isEqual,le=e.deepTrim,se=e.isObject,me=e.isNotEmptyStr},function(e){pe=e},function(e){de=e},function(e){ue=e},function(e){ce=e},function(e){fe=e},function(e){ge=e.ENTER,ye=e.COMMA,xe=e.SEMICOLON},function(e){be=e},function(e){he=e},function(e){ve=e},function(e){Ce=e},function(e){Fe=e},function(e){ke=e},function(e){Le=e.coerceBooleanProperty,Te=e.coerceElement,Ie=e.coerceNumberProperty},function(e){Ne=e},function(e){Se=e},function(e){qe=e},function(e){Ae=e},function(e){Me=e.tap,Ee=e.map,Ge=e.startWith,De=e.mergeMap,we=e.share,Ve=e.takeUntil,Pe=e.auditTime},function(e){Re=e},function(e){Oe=e},function(e){_e=e.HomeComponentsModule},function(e){Be=e.__decorate},function(e){Ke=e.Subject,ze=e.takeUntil,Ue=e.of,He=e.EMPTY,je=e.fromEvent},function(e){$e=e},function(e){Qe=e},function(e){Je=e},function(e){Ye=e},function(e){We=e},function(e){Ze=e},function(e){Xe=e},function(e){et=e},function(e){tt=e},function(e){nt=e},function(e){rt=e},function(e){ot=e},function(e){at=e},function(e){it=e},function(e){lt=e},function(e){st=e},function(e){mt=e.normalizePassiveListenerOptions,pt=e}],execute:function(){class dt extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.emptyConfigForm}onConfigurationSet(e){this.emptyConfigForm=this.fb.group({})}}e("EmptyConfigComponent",dt),dt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),dt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:dt,selector:"tb-node-empty-config",usesInheritance:!0,ngImport:t,template:"
",isInline:!0}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dt,decorators:[{type:n,args:[{selector:"tb-node-empty-config",template:"
"}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ut extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.assignCustomerConfigForm}onConfigurationSet(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[O.required,O.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[O.required,O.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("AssignCustomerConfigComponent",ut),ut.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ut,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ut.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ut,selector:"tb-action-node-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ut,decorators:[{type:n,args:[{selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ct extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=g,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y}configForm(){return this.attributesConfigForm}onConfigurationSet(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]],notifyDevice:[!e||e.notifyDevice,[]],sendAttributesUpdatedNotification:[!!e&&e.sendAttributesUpdatedNotification,[]],updateAttributesOnlyOnValueChange:[!!e&&e.updateAttributesOnlyOnValueChange,[]]}),this.attributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==g.SHARED_SCOPE&&this.attributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1}),e===g.CLIENT_SCOPE&&this.attributesConfigForm.get("sendAttributesUpdatedNotification").patchValue(!1,{emitEvent:!1}),this.attributesConfigForm.get("updateAttributesOnlyOnValueChange").patchValue(!1,{emitEvent:!1})}))}}e("AttributesConfigComponent",ct),ct.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ct,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ct.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ct,selector:"tb-action-node-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n
\n \n {{ \'tb.rulenode.update-attributes-only-on-value-change\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.send-attributes-updated-notification\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
\n
\n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ct,decorators:[{type:n,args:[{selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n
\n \n {{ \'tb.rulenode.update-attributes-only-on-value-change\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.send-attributes-updated-notification\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
\n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ft extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-details-function"}configForm(){return this.clearAlarmConfigForm}onConfigurationSet(e){this.clearAlarmConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[]],alarmDetailsBuildTbel:[e?e.alarmDetailsBuildTbel:null,[]],alarmType:[e?e.alarmType:null,[O.required]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.clearAlarmConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.clearAlarmConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.clearAlarmConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(t===x.JS?[O.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(t===x.TBEL?[O.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.clearAlarmConfigForm.get("scriptLang").value,n=t===x.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",r=t===x.JS?"rulenode/clear_alarm_node_script_fn":"rulenode/tbel/clear_alarm_node_script_fn",o=this.clearAlarmConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.clearAlarmConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.clearAlarmConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("ClearAlarmConfigComponent",ft),ft.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ft,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),ft.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ft,selector:"tb-action-node-clear-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","hideBrackets","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ft,decorators:[{type:n,args:[{selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n \n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class gt extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.alarmSeverities=Object.keys(b),this.alarmSeverityTranslationMap=h,this.separatorKeysCodes=[ge,ye,xe],this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-details-function"}configForm(){return this.createAlarmConfigForm}onConfigurationSet(e){this.createAlarmConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[]],alarmDetailsBuildTbel:[e?e.alarmDetailsBuildTbel:null,[]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],overwriteAlarmDetails:[!!e&&e.overwriteAlarmDetails,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]],propagateToOwner:[!!e&&e.propagateToOwner,[]],propagateToTenant:[!!e&&e.propagateToTenant,[]],dynamicSeverity:!1}),this.createAlarmConfigForm.get("dynamicSeverity").valueChanges.subscribe((e=>{e?this.createAlarmConfigForm.get("severity").patchValue("",{emitEvent:!1}):this.createAlarmConfigForm.get("severity").patchValue(this.alarmSeverities[0],{emitEvent:!1})}))}validatorTriggers(){return["useMessageAlarmData","overwriteAlarmDetails","scriptLang"]}updateValidators(e){const t=this.createAlarmConfigForm.get("useMessageAlarmData").value,n=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;t?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([O.required]),this.createAlarmConfigForm.get("severity").setValidators([O.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e});let r=this.createAlarmConfigForm.get("scriptLang").value;r!==x.TBEL||this.tbelEnabled||(r=x.JS,this.createAlarmConfigForm.get("scriptLang").patchValue(r,{emitEvent:!1}),setTimeout((()=>{this.createAlarmConfigForm.updateValueAndValidity({emitEvent:!0})})));const o=!1===t||!0===n;this.createAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(o&&r===x.JS?[O.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(o&&r===x.TBEL?[O.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.createAlarmConfigForm.get("scriptLang").value,n=t===x.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",r=t===x.JS?"rulenode/create_alarm_node_script_fn":"rulenode/tbel/create_alarm_node_script_fn",o=this.createAlarmConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.createAlarmConfigForm.get(n).setValue(e),this.changeScript.emit())}))}removeKey(e,t){const n=this.createAlarmConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.createAlarmConfigForm.get(t).setValue(n,{emitEvent:!0}))}addKey(e,t){const n=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.createAlarmConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.createAlarmConfigForm.get(t).setValue(e,{emitEvent:!0}))}n&&(n.value="")}onValidate(){const e=this.createAlarmConfigForm.get("useMessageAlarmData").value,t=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;if(!e||t){this.createAlarmConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}}e("CreateAlarmConfigComponent",gt),gt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gt,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),gt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:gt,selector:"tb-action-node-create-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.overwrite-alarm-details\' | translate }}\n \n
\n \n \n \n \n \n \n \n
\n \n
\n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-alarm-severity-pattern\' | translate }}\n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n tb.rulenode.alarm-severity-pattern\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
\n \n {{ \'tb.rulenode.propagate-to-owner\' | translate }}\n \n \n {{ \'tb.rulenode.propagate-to-tenant\' | translate }}\n \n
\n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","hideBrackets","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:be.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:be.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:be.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:be.MatChipRow,selector:"mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]",inputs:["color","disabled","disableRipple","tabIndex","editable"],outputs:["edited"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gt,decorators:[{type:n,args:[{selector:"tb-action-node-create-alarm-config",template:'
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.overwrite-alarm-details\' | translate }}\n \n
\n \n \n \n \n \n \n \n
\n \n
\n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-alarm-severity-pattern\' | translate }}\n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n tb.rulenode.alarm-severity-pattern\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
\n \n {{ \'tb.rulenode.propagate-to-owner\' | translate }}\n \n \n {{ \'tb.rulenode.propagate-to-tenant\' | translate }}\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class yt extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(v),this.directionTypeTranslations=C,this.entityType=F}configForm(){return this.createRelationConfigForm}onConfigurationSet(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[O.required]],entityType:[e?e.entityType:null,[O.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[O.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[O.required,O.min(0)]]})}validatorTriggers(){return["entityType"]}updateValidators(e){const t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([O.required,O.pattern(/.*\S.*/)]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==F.DEVICE&&t!==F.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([O.required,O.pattern(/.*\S.*/)]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e.entityTypePattern=e.entityTypePattern?e.entityTypePattern.trim():null,e}}e("CreateRelationConfigComponent",yt),yt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),yt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:yt,selector:"tb-action-node-create-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yt,decorators:[{type:n,args:[{selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class xt extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(v),this.directionTypeTranslations=C,this.entityType=F}configForm(){return this.deleteRelationConfigForm}onConfigurationSet(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[O.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[O.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[O.required,O.min(0)]]})}validatorTriggers(){return["deleteForSingleEntity","entityType"]}updateValidators(e){const t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,n=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([O.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&n?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([O.required,O.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e}}e("DeleteRelationConfigComponent",xt),xt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),xt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:xt,selector:"tb-action-node-delete-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xt,decorators:[{type:n,args:[{selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class bt extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.deviceProfile}onConfigurationSet(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,O.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,O.required]})}}e("DeviceProfileConfigComponent",bt),bt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),bt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:bt,selector:"tb-device-profile-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n',dependencies:[{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bt,decorators:[{type:n,args:[{selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ht extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-generator-function"}configForm(){return this.generatorConfigForm}onConfigurationSet(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[O.required,O.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[O.required,O.min(1)]],originator:[e?e.originator:null,[]],scriptLang:[e?e.scriptLang:x.JS,[O.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.generatorConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.generatorConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.generatorConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.generatorConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.generatorConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.generatorConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.generatorConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS),e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e}prepareOutputConfig(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e}testScript(e){const t=this.generatorConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/generator_node_script_fn":"rulenode/tbel/generator_node_script_fn",o=this.generatorConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.generatorConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.generatorConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}var vt;e("GeneratorConfigComponent",ht),ht.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ht,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),ht.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ht,selector:"tb-action-node-generator-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n\n \n \n \n \n \n \n \n
\n \n
\n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Ce.EntitySelectComponent,selector:"tb-entity-select",inputs:["allowedEntityTypes","useAliasEntityTypes","required","disabled"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","hideBrackets","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ht,decorators:[{type:n,args:[{selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n\n \n \n \n \n \n \n \n
\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}}),function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR",e.ENTITY="ENTITY"}(vt||(vt={}));const Ct=new Map([[vt.CUSTOMER,"tb.rulenode.originator-customer"],[vt.TENANT,"tb.rulenode.originator-tenant"],[vt.RELATED,"tb.rulenode.originator-related"],[vt.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"],[vt.ENTITY,"tb.rulenode.originator-entity"]]),Ft=new Map([[vt.CUSTOMER,"tb.rulenode.originator-customer-desc"],[vt.TENANT,"tb.rulenode.originator-tenant-desc"],[vt.RELATED,"tb.rulenode.originator-related-entity-desc"],[vt.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator-desc"],[vt.ENTITY,"tb.rulenode.originator-entity-by-name-pattern-desc"]]),kt=[k.createdTime,k.name,{value:"type",name:"tb.rulenode.profile-name",keyName:"originatorProfileName"},k.firstName,k.lastName,k.email,k.title,k.country,k.state,k.city,k.address,k.address2,k.zip,k.phone,k.label,{value:"id",name:"tb.rulenode.id",keyName:"id"},{value:"additionalInfo",name:"tb.rulenode.additional-info",keyName:"additionalInfo"}],Lt=new Map([["type","profileName"],["createdTime","createdTime"],["name","name"],["firstName","firstName"],["lastName","lastName"],["email","email"],["title","title"],["country","country"],["state","state"],["city","city"],["address","address"],["address2","address2"],["zip","zip"],["phone","phone"],["label","label"],["id","id"],["additionalInfo","additionalInfo"]]);var Tt;!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(Tt||(Tt={}));const It=new Map([[Tt.CIRCLE,"tb.rulenode.perimeter-circle"],[Tt.POLYGON,"tb.rulenode.perimeter-polygon"]]);var Nt;!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(Nt||(Nt={}));const St=new Map([[Nt.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[Nt.SECONDS,"tb.rulenode.time-unit-seconds"],[Nt.MINUTES,"tb.rulenode.time-unit-minutes"],[Nt.HOURS,"tb.rulenode.time-unit-hours"],[Nt.DAYS,"tb.rulenode.time-unit-days"]]);var qt;!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(qt||(qt={}));const At=new Map([[qt.METER,"tb.rulenode.range-unit-meter"],[qt.KILOMETER,"tb.rulenode.range-unit-kilometer"],[qt.FOOT,"tb.rulenode.range-unit-foot"],[qt.MILE,"tb.rulenode.range-unit-mile"],[qt.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);var Mt;!function(e){e.ID="ID",e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.CITY="CITY",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(Mt||(Mt={}));const Et=new Map([[Mt.ID,"tb.rulenode.entity-details-id"],[Mt.TITLE,"tb.rulenode.entity-details-title"],[Mt.COUNTRY,"tb.rulenode.entity-details-country"],[Mt.STATE,"tb.rulenode.entity-details-state"],[Mt.CITY,"tb.rulenode.entity-details-city"],[Mt.ZIP,"tb.rulenode.entity-details-zip"],[Mt.ADDRESS,"tb.rulenode.entity-details-address"],[Mt.ADDRESS2,"tb.rulenode.entity-details-address2"],[Mt.PHONE,"tb.rulenode.entity-details-phone"],[Mt.EMAIL,"tb.rulenode.entity-details-email"],[Mt.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);var Gt;!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(Gt||(Gt={}));const Dt=new Map([[Gt.FIRST,"tb.rulenode.first"],[Gt.LAST,"tb.rulenode.last"],[Gt.ALL,"tb.rulenode.all"]]),wt=new Map([[Gt.FIRST,"tb.rulenode.first-mode-hint"],[Gt.LAST,"tb.rulenode.last-mode-hint"],[Gt.ALL,"tb.rulenode.all-mode-hint"]]);var Vt,Pt;!function(e){e.ASC="ASC",e.DESC="DESC"}(Vt||(Vt={})),function(e){e.ATTRIBUTES="ATTRIBUTES",e.LATEST_TELEMETRY="LATEST_TELEMETRY",e.FIELDS="FIELDS"}(Pt||(Pt={}));const Rt=new Map([[Pt.ATTRIBUTES,"tb.rulenode.attributes"],[Pt.LATEST_TELEMETRY,"tb.rulenode.latest-telemetry"],[Pt.FIELDS,"tb.rulenode.fields"]]),Ot=new Map([[Pt.ATTRIBUTES,"tb.rulenode.add-mapped-attribute-to"],[Pt.LATEST_TELEMETRY,"tb.rulenode.add-mapped-latest-telemetry-to"],[Pt.FIELDS,"tb.rulenode.add-mapped-fields-to"]]),_t=new Map([[Vt.ASC,"tb.rulenode.ascending"],[Vt.DESC,"tb.rulenode.descending"]]);var Bt;!function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(Bt||(Bt={}));const Kt=new Map([[Bt.STANDARD,"tb.rulenode.sqs-queue-standard"],[Bt.FIFO,"tb.rulenode.sqs-queue-fifo"]]),zt=["anonymous","basic","cert.PEM"],Ut=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),Ht=["sas","cert.PEM"],jt=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);var $t;!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}($t||($t={}));const Qt=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Jt=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]);var Yt;!function(e){e.CUSTOM="CUSTOM",e.ADD="ADD",e.SUB="SUB",e.MULT="MULT",e.DIV="DIV",e.SIN="SIN",e.SINH="SINH",e.COS="COS",e.COSH="COSH",e.TAN="TAN",e.TANH="TANH",e.ACOS="ACOS",e.ASIN="ASIN",e.ATAN="ATAN",e.ATAN2="ATAN2",e.EXP="EXP",e.EXPM1="EXPM1",e.SQRT="SQRT",e.CBRT="CBRT",e.GET_EXP="GET_EXP",e.HYPOT="HYPOT",e.LOG="LOG",e.LOG10="LOG10",e.LOG1P="LOG1P",e.CEIL="CEIL",e.FLOOR="FLOOR",e.FLOOR_DIV="FLOOR_DIV",e.FLOOR_MOD="FLOOR_MOD",e.ABS="ABS",e.MIN="MIN",e.MAX="MAX",e.POW="POW",e.SIGNUM="SIGNUM",e.RAD="RAD",e.DEG="DEG"}(Yt||(Yt={}));const Wt=new Map([[Yt.CUSTOM,{value:Yt.CUSTOM,name:"Custom Function",description:"Use this function to specify complex mathematical expression.",minArgs:1,maxArgs:16}],[Yt.ADD,{value:Yt.ADD,name:"Addition",description:"x + y",minArgs:2,maxArgs:2}],[Yt.SUB,{value:Yt.SUB,name:"Subtraction",description:"x - y",minArgs:2,maxArgs:2}],[Yt.MULT,{value:Yt.MULT,name:"Multiplication",description:"x * y",minArgs:2,maxArgs:2}],[Yt.DIV,{value:Yt.DIV,name:"Division",description:"x / y",minArgs:2,maxArgs:2}],[Yt.SIN,{value:Yt.SIN,name:"Sine",description:"Returns the trigonometric sine of an angle in radians.",minArgs:1,maxArgs:1}],[Yt.SINH,{value:Yt.SINH,name:"Hyperbolic sine",description:"Returns the hyperbolic sine of an argument.",minArgs:1,maxArgs:1}],[Yt.COS,{value:Yt.COS,name:"Cosine",description:"Returns the trigonometric cosine of an angle in radians.",minArgs:1,maxArgs:1}],[Yt.COSH,{value:Yt.COSH,name:"Hyperbolic cosine",description:"Returns the hyperbolic cosine of an argument.",minArgs:1,maxArgs:1}],[Yt.TAN,{value:Yt.TAN,name:"Tangent",description:"Returns the trigonometric tangent of an angle in radians",minArgs:1,maxArgs:1}],[Yt.TANH,{value:Yt.TANH,name:"Hyperbolic tangent",description:"Returns the hyperbolic tangent of an argument",minArgs:1,maxArgs:1}],[Yt.ACOS,{value:Yt.ACOS,name:"Arc cosine",description:"Returns the arc cosine of an argument",minArgs:1,maxArgs:1}],[Yt.ASIN,{value:Yt.ASIN,name:"Arc sine",description:"Returns the arc sine of an argument",minArgs:1,maxArgs:1}],[Yt.ATAN,{value:Yt.ATAN,name:"Arc tangent",description:"Returns the arc tangent of an argument",minArgs:1,maxArgs:1}],[Yt.ATAN2,{value:Yt.ATAN2,name:"2-argument arc tangent",description:"Returns the angle theta from the conversion of rectangular coordinates (x, y) to polar coordinates (r, theta)",minArgs:2,maxArgs:2}],[Yt.EXP,{value:Yt.EXP,name:"Exponential",description:"Returns Euler's number e raised to the power of an argument",minArgs:1,maxArgs:1}],[Yt.EXPM1,{value:Yt.EXPM1,name:"Exponential minus one",description:"Returns Euler's number e raised to the power of an argument minus one",minArgs:1,maxArgs:1}],[Yt.SQRT,{value:Yt.SQRT,name:"Square",description:"Returns the correctly rounded positive square root of an argument",minArgs:1,maxArgs:1}],[Yt.CBRT,{value:Yt.CBRT,name:"Cube root",description:"Returns the cube root of an argument",minArgs:1,maxArgs:1}],[Yt.GET_EXP,{value:Yt.GET_EXP,name:"Get exponent",description:"Returns the unbiased exponent used in the representation of an argument",minArgs:1,maxArgs:1}],[Yt.HYPOT,{value:Yt.HYPOT,name:"Square root",description:"Returns the square root of the squares of the arguments",minArgs:2,maxArgs:2}],[Yt.LOG,{value:Yt.LOG,name:"Logarithm",description:"Returns the natural logarithm of an argument",minArgs:1,maxArgs:1}],[Yt.LOG10,{value:Yt.LOG10,name:"Base 10 logarithm",description:"Returns the base 10 logarithm of an argument",minArgs:1,maxArgs:1}],[Yt.LOG1P,{value:Yt.LOG1P,name:"Logarithm of the sum",description:"Returns the natural logarithm of the sum of an argument",minArgs:1,maxArgs:1}],[Yt.CEIL,{value:Yt.CEIL,name:"Ceiling",description:"Returns the smallest (closest to negative infinity) of an argument",minArgs:1,maxArgs:1}],[Yt.FLOOR,{value:Yt.FLOOR,name:"Floor",description:"Returns the largest (closest to positive infinity) of an argument",minArgs:1,maxArgs:1}],[Yt.FLOOR_DIV,{value:Yt.FLOOR_DIV,name:"Floor division",description:"Returns the largest (closest to positive infinity) of the arguments",minArgs:2,maxArgs:2}],[Yt.FLOOR_MOD,{value:Yt.FLOOR_MOD,name:"Floor modulus",description:"Returns the floor modulus of the arguments",minArgs:2,maxArgs:2}],[Yt.ABS,{value:Yt.ABS,name:"Absolute",description:"Returns the absolute value of an argument",minArgs:1,maxArgs:1}],[Yt.MIN,{value:Yt.MIN,name:"Min",description:"Returns the smaller of the arguments",minArgs:2,maxArgs:2}],[Yt.MAX,{value:Yt.MAX,name:"Max",description:"Returns the greater of the arguments",minArgs:2,maxArgs:2}],[Yt.POW,{value:Yt.POW,name:"Raise to a power",description:"Returns the value of the first argument raised to the power of the second argument",minArgs:2,maxArgs:2}],[Yt.SIGNUM,{value:Yt.SIGNUM,name:"Sign of a real number",description:"Returns the signum function of the argument",minArgs:1,maxArgs:1}],[Yt.RAD,{value:Yt.RAD,name:"Radian",description:"Converts an angle measured in degrees to an approximately equivalent angle measured in radians",minArgs:1,maxArgs:1}],[Yt.DEG,{value:Yt.DEG,name:"Degrees",description:"Converts an angle measured in radians to an approximately equivalent angle measured in degrees.",minArgs:1,maxArgs:1}]]);var Zt,Xt,en;!function(e){e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA",e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES",e.CONSTANT="CONSTANT"}(Zt||(Zt={})),function(e){e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA",e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES"}(Xt||(Xt={})),function(e){e.DATA="DATA",e.METADATA="METADATA"}(en||(en={}));const tn=new Map([[en.DATA,"tb.rulenode.message-to-metadata"],[en.METADATA,"tb.rulenode.metadata-to-message"]]),nn=(new Map([[en.DATA,"tb.rulenode.from-message"],[en.METADATA,"tb.rulenode.from-metadata"]]),new Map([[en.DATA,"tb.rulenode.message"],[en.METADATA,"tb.rulenode.metadata"]])),rn=new Map([[en.DATA,"tb.rulenode.message"],[en.METADATA,"tb.rulenode.message-metadata"]]),on=new Map([[Zt.MESSAGE_BODY,{name:"tb.rulenode.message-body-type",description:"Fetch argument value from incoming message"}],[Zt.MESSAGE_METADATA,{name:"tb.rulenode.message-metadata-type",description:"Fetch argument value from incoming message metadata"}],[Zt.ATTRIBUTE,{name:"tb.rulenode.attribute-type",description:"Fetch attribute value from database"}],[Zt.TIME_SERIES,{name:"tb.rulenode.time-series-type",description:"Fetch latest time-series value from database"}],[Zt.CONSTANT,{name:"tb.rulenode.constant-type",description:"Define constant value"}]]),an=new Map([[Xt.MESSAGE_BODY,{name:"tb.rulenode.message-body-type",description:"Add result to the outgoing message"}],[Xt.MESSAGE_METADATA,{name:"tb.rulenode.message-metadata-type",description:"Add result to the outgoing message metadata"}],[Xt.ATTRIBUTE,{name:"tb.rulenode.attribute-type",description:"Store result as an entity attribute in the database"}],[Xt.TIME_SERIES,{name:"tb.rulenode.time-series-type",description:"Store result as an entity time-series in the database"}]]),ln=["x","y","z","a","b","c","d","k","l","m","n","o","p","r","s","t"];var sn,mn;!function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE",e.CLIENT_SCOPE="CLIENT_SCOPE"}(sn||(sn={})),function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE"}(mn||(mn={}));const pn=new Map([[sn.SHARED_SCOPE,"tb.rulenode.shared-scope"],[sn.SERVER_SCOPE,"tb.rulenode.server-scope"],[sn.CLIENT_SCOPE,"tb.rulenode.client-scope"]]);var dn;!function(e){e.ON_FIRST_MESSAGE="ON_FIRST_MESSAGE",e.ON_EACH_MESSAGE="ON_EACH_MESSAGE"}(dn||(dn={}));const un=new Map([[dn.ON_EACH_MESSAGE,{value:!0,name:"tb.rulenode.presence-monitoring-strategy-on-each-message"}],[dn.ON_FIRST_MESSAGE,{value:!1,name:"tb.rulenode.presence-monitoring-strategy-on-first-message"}]]);class cn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Tt,this.perimeterTypes=Object.keys(Tt),this.perimeterTypeTranslationMap=It,this.rangeUnits=Object.keys(qt),this.rangeUnitTranslationMap=At,this.presenceMonitoringStrategies=un,this.presenceMonitoringStrategyKeys=Array.from(this.presenceMonitoringStrategies.keys()),this.timeUnits=Object.keys(Nt),this.timeUnitsTranslationMap=St,this.defaultPaddingEnable=!0}configForm(){return this.geoActionConfigForm}onConfigurationSet(e){this.geoActionConfigForm=this.fb.group({reportPresenceStatusOnEachMessage:[!e||e.reportPresenceStatusOnEachMessage,[O.required]],latitudeKeyName:[e?e.latitudeKeyName:null,[O.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[O.required]],perimeterType:[e?e.perimeterType:null,[O.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e?e.perimeterKeyName:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[O.required,O.min(1),O.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[O.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[O.required,O.min(1),O.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[O.required]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterKeyName").setValidators([O.required]):this.geoActionConfigForm.get("perimeterKeyName").setValidators([]),t||n!==Tt.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([]),this.defaultPaddingEnable=!0):(this.geoActionConfigForm.get("centerLatitude").setValidators([O.required,O.min(-90),O.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([O.required,O.min(-180),O.max(180)]),this.geoActionConfigForm.get("range").setValidators([O.required,O.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([O.required]),this.defaultPaddingEnable=!1),t||n!==Tt.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([O.required]),this.geoActionConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoActionConfigComponent",cn),cn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),cn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:cn,selector:"tb-action-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
tb.rulenode.coordinate-field-names
\n
\n
\n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
\n
tb.rulenode.coordinate-field-hint
\n
\n
\n
\n
tb.rulenode.geofence-configuration
\n
\n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
\n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n help\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n
\n
\n
{{ \'tb.rulenode.presence-monitoring-strategy\' | translate }}
\n \n \n {{ presenceMonitoringStrategies.get(strategy).name | translate }}\n \n \n
\n
{{ geoActionConfigForm.get(\'reportPresenceStatusOnEachMessage\').value === false ?\n (\'tb.rulenode.presence-monitoring-strategy-on-first-message-hint\' | translate) :\n (\'tb.rulenode.presence-monitoring-strategy-on-each-message-hint\' | translate) }}\n
\n
\n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cn,decorators:[{type:n,args:[{selector:"tb-action-node-gps-geofencing-config",template:'
\n
\n
tb.rulenode.coordinate-field-names
\n
\n
\n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
\n
tb.rulenode.coordinate-field-hint
\n
\n
\n
\n
tb.rulenode.geofence-configuration
\n
\n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
\n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n help\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n
\n
\n
{{ \'tb.rulenode.presence-monitoring-strategy\' | translate }}
\n \n \n {{ presenceMonitoringStrategies.get(strategy).name | translate }}\n \n \n
\n
{{ geoActionConfigForm.get(\'reportPresenceStatusOnEachMessage\').value === false ?\n (\'tb.rulenode.presence-monitoring-strategy-on-first-message-hint\' | translate) :\n (\'tb.rulenode.presence-monitoring-strategy-on-each-message-hint\' | translate) }}\n
\n
\n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class fn extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-to-string-function"}configForm(){return this.logConfigForm}onConfigurationSet(e){this.logConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.logConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.logConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.logConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.logConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.logConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.logConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.logConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.logConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/log_node_script_fn":"rulenode/tbel/log_node_script_fn",o=this.logConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.logConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.logConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("LogConfigComponent",fn),fn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fn,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),fn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:fn,selector:"tb-action-node-log-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n
\n \n
\n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","hideBrackets","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fn,decorators:[{type:n,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n \n \n \n \n
\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class gn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgCountConfigForm}onConfigurationSet(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[O.required,O.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[O.required]]})}}e("MsgCountConfigComponent",gn),gn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),gn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:gn,selector:"tb-action-node-msg-count-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gn,decorators:[{type:n,args:[{selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class yn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgDelayConfigForm}onConfigurationSet(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[O.required,O.min(1),O.max(1e5)]]})}validatorTriggers(){return["useMetadataPeriodInSecondsPatterns"]}updateValidators(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([O.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([O.required,O.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})}}e("MsgDelayConfigComponent",yn),yn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),yn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:yn,selector:"tb-action-node-msg-delay-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yn,decorators:[{type:n,args:[{selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class xn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y}configForm(){return this.pushToCloudConfigForm}onConfigurationSet(e){this.pushToCloudConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]]})}}e("PushToCloudConfigComponent",xn),xn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),xn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:xn,selector:"tb-action-node-push-to-cloud-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xn,decorators:[{type:n,args:[{selector:"tb-action-node-push-to-cloud-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class bn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y}configForm(){return this.pushToEdgeConfigForm}onConfigurationSet(e){this.pushToEdgeConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]]})}}e("PushToEdgeConfigComponent",bn),bn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),bn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:bn,selector:"tb-action-node-push-to-edge-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bn,decorators:[{type:n,args:[{selector:"tb-action-node-push-to-edge-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class hn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcReplyConfigForm}onConfigurationSet(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})}}e("RpcReplyConfigComponent",hn),hn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),hn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:hn,selector:"tb-action-node-rpc-reply-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n',dependencies:[{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hn,decorators:[{type:n,args:[{selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class vn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcRequestConfigForm}onConfigurationSet(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[O.required,O.min(0)]]})}}e("RpcRequestConfigComponent",vn),vn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),vn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:vn,selector:"tb-action-node-rpc-request-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vn,decorators:[{type:n,args:[{selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Cn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.propagateChange=null,this.valueChangeSubscription=null}ngOnInit(){this.ngControl=this.injector.get(_),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();const t=[];if(e)for(const n of Object.keys(e))Object.prototype.hasOwnProperty.call(e,n)&&t.push(this.fb.group({key:[n,[O.required]],value:[e[n],[O.required]]}));this.kvListFormGroup.setControl("keyVals",this.fb.array(t)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))}removeKeyVal(e){this.kvListFormGroup.get("keyVals").removeAt(e)}addKeyVal(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[O.required]],value:["",[O.required]]}))}validate(e){const t=this.kvListFormGroup.get("keyVals").value;if(!t.length&&this.required)return{kvMapRequired:!0};if(!this.kvListFormGroup.valid)return{kvFieldsRequired:!0};if(this.uniqueKeyValuePairValidator)for(const e of t)if(e.key===e.value)return{uniqueKeyValuePair:!0};return null}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("KvMapConfigOldComponent",Cn),Cn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cn,deps:[{token:P.Store},{token:Z.TranslateService},{token:t.Injector},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Cn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Cn,selector:"tb-kv-map-config-old",inputs:{disabled:"disabled",uniqueKeyValuePairValidator:"uniqueKeyValuePairValidator",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",required:"required"},providers:[{provide:B,useExisting:a((()=>Cn)),multi:!0},{provide:K,useExisting:a((()=>Cn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\n \n
\n
\n
\n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n
\n \n
\n \n
\n
\n',styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:#757575;font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .header .tb-required:after{color:#757575;font-size:12px;font-weight:700}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:0;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host .tb-kv-map-config tb-error{display:block;margin-top:-12px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Ne.TbErrorComponent,selector:"tb-error",inputs:["noMargin","error"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:Se.DefaultShowHideDirective,selector:" [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]",inputs:["fxShow","fxShow.print","fxShow.xs","fxShow.sm","fxShow.md","fxShow.lg","fxShow.xl","fxShow.lt-sm","fxShow.lt-md","fxShow.lt-lg","fxShow.lt-xl","fxShow.gt-xs","fxShow.gt-sm","fxShow.gt-md","fxShow.gt-lg","fxHide","fxHide.print","fxHide.xs","fxHide.sm","fxHide.md","fxHide.lg","fxHide.xl","fxHide.lt-sm","fxHide.lt-md","fxHide.lt-lg","fxHide.lt-xl","fxHide.gt-xs","fxHide.gt-sm","fxHide.gt-md","fxHide.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cn,decorators:[{type:n,args:[{selector:"tb-kv-map-config-old",providers:[{provide:B,useExisting:a((()=>Cn)),multi:!0},{provide:K,useExisting:a((()=>Cn)),multi:!0}],template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\n \n
\n
\n
\n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n
\n \n
\n \n
\n
\n',styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:#757575;font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .header .tb-required:after{color:#757575;font-size:12px;font-weight:700}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:0;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host .tb-kv-map-config tb-error{display:block;margin-top:-12px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:t.Injector},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],uniqueKeyValuePairValidator:[{type:i}],requiredText:[{type:i}],keyText:[{type:i}],keyRequiredText:[{type:i}],valText:[{type:i}],valRequiredText:[{type:i}],hintText:[{type:i}],required:[{type:i}]}});class Fn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.saveToCustomTableConfigForm}onConfigurationSet(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[O.required,O.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[O.required]]})}prepareOutputConfig(e){return e.tableName=e.tableName.trim(),e}}e("SaveToCustomTableConfigComponent",Fn),Fn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Fn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Fn,selector:"tb-action-node-custom-table-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fn,decorators:[{type:n,args:[{selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class kn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.timeseriesConfigForm}onConfigurationSet(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[O.required,O.min(0)]],skipLatestPersistence:[!!e&&e.skipLatestPersistence,[]],useServerTs:[!!e&&e.useServerTs,[]]})}}e("TimeseriesConfigComponent",kn),kn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),kn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:kn,selector:"tb-action-node-timeseries-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.skip-latest-persistence\' | translate }}\n \n \n {{ \'tb.rulenode.use-server-ts\' | translate }}\n \n
tb.rulenode.use-server-ts-hint
\n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kn,decorators:[{type:n,args:[{selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.skip-latest-persistence\' | translate }}\n \n \n {{ \'tb.rulenode.use-server-ts\' | translate }}\n \n
tb.rulenode.use-server-ts-hint
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Ln extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.unassignCustomerConfigForm}onConfigurationSet(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[O.required,O.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[O.required,O.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("UnassignCustomerConfigComponent",Ln),Ln.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ln,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ln.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Ln,selector:"tb-action-node-un-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ln,decorators:[{type:n,args:[{selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Tn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=g,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y,this.separatorKeysCodes=[ge,ye,xe]}configForm(){return this.deleteAttributesConfigForm}onConfigurationSet(e){this.deleteAttributesConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]],keys:[e?e.keys:null,[O.required]],sendAttributesDeletedNotification:[!!e&&e.sendAttributesDeletedNotification,[]],notifyDevice:[!!e&&e.notifyDevice,[]]}),this.deleteAttributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==g.SHARED_SCOPE&&this.deleteAttributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1})}))}removeKey(e){const t=this.deleteAttributesConfigForm.get("keys").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.deleteAttributesConfigForm.get("keys").patchValue(t,{emitEvent:!0}))}addKey(e){const t=e.input;let n=e.value;if((n||"").trim()){n=n.trim();let e=this.deleteAttributesConfigForm.get("keys").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.deleteAttributesConfigForm.get("keys").patchValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("DeleteAttributesConfigComponent",Tn),Tn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Tn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Tn,selector:"tb-action-node-delete-attributes-config",viewQueries:[{propertyName:"attributeChipList",first:!0,predicate:["attributeChipList"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'attribute.attributes-scope\' | translate }}\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n \n {{ \'tb.rulenode.attributes-keys\' | translate }}\n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.send-attributes-deleted-notification\' | translate }}\n \n
tb.rulenode.send-attributes-deleted-notification-hint
\n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-on-delete-hint
\n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:be.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:be.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:be.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:be.MatChipRow,selector:"mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]",inputs:["color","disabled","disableRipple","tabIndex","editable"],outputs:["edited"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tn,decorators:[{type:n,args:[{selector:"tb-action-node-delete-attributes-config",template:'
\n \n {{ \'attribute.attributes-scope\' | translate }}\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n \n {{ \'tb.rulenode.attributes-keys\' | translate }}\n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.send-attributes-deleted-notification\' | translate }}\n \n
tb.rulenode.send-attributes-deleted-notification-hint
\n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-on-delete-hint
\n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]},propDecorators:{attributeChipList:[{type:o,args:["attributeChipList"]}]}});class In extends L{get function(){return this.functionValue}set function(e){e&&this.functionValue!==e&&(this.functionValue=e,this.setupArgumentsFormGroup(!0))}constructor(e,t){super(e),this.store=e,this.fb=t,this.maxArgs=16,this.minArgs=1,this.displayArgumentName=!1,this.mathFunctionMap=Wt,this.ArgumentType=Zt,this.attributeScopeMap=pn,this.argumentTypeMap=on,this.arguments=Object.values(Zt),this.attributeScope=Object.values(sn),this.propagateChange=null,this.valueChangeSubscription=[]}ngOnInit(){this.argumentsFormGroup=this.fb.group({arguments:this.fb.array([])}),this.valueChangeSubscription.push(this.argumentsFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))),this.setupArgumentsFormGroup()}onDrop(e){const t=this.argumentsFormArray,n=t.at(e.previousIndex);t.removeAt(e.previousIndex),t.insert(e.currentIndex,n),this.updateArgumentNames()}get argumentsFormArray(){return this.argumentsFormGroup.get("arguments")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.argumentsFormGroup.disable({emitEvent:!1}):(this.argumentsFormGroup.enable({emitEvent:!1}),this.argumentsFormArray.controls.forEach((e=>this.updateArgumentControlValidators(e))))}ngOnDestroy(){this.valueChangeSubscription.length&&this.valueChangeSubscription.forEach((e=>e.unsubscribe()))}writeValue(e){const t=[];e&&e.forEach(((e,n)=>{t.push(this.createArgumentControl(e,n))})),this.argumentsFormGroup.setControl("arguments",this.fb.array(t),{emitEvent:!1}),this.setupArgumentsFormGroup()}removeArgument(e){this.argumentsFormArray.removeAt(e),this.updateArgumentNames()}addArgument(e=!0){const t=this.argumentsFormArray,n=this.createArgumentControl(null,t.length);t.push(n,{emitEvent:e})}validate(e){return this.argumentsFormGroup.valid?null:{argumentsRequired:!0}}setupArgumentsFormGroup(e=!1){if(this.function&&(this.maxArgs=this.mathFunctionMap.get(this.function).maxArgs,this.minArgs=this.mathFunctionMap.get(this.function).minArgs,this.displayArgumentName=this.function===Yt.CUSTOM),this.argumentsFormGroup){for(this.argumentsFormGroup.get("arguments").setValidators([O.minLength(this.minArgs),O.maxLength(this.maxArgs)]);this.argumentsFormArray.length>this.maxArgs;)this.removeArgument(this.maxArgs-1);for(;this.argumentsFormArray.length{this.updateArgumentControlValidators(n),n.get("attributeScope").updateValueAndValidity({emitEvent:!1}),n.get("defaultValue").updateValueAndValidity({emitEvent:!1})}))),n}updateArgumentControlValidators(e){const t=e.get("type").value;t===Zt.ATTRIBUTE?e.get("attributeScope").enable({emitEvent:!1}):e.get("attributeScope").disable({emitEvent:!1}),t&&t!==Zt.CONSTANT?e.get("defaultValue").enable({emitEvent:!1}):e.get("defaultValue").disable({emitEvent:!1})}updateArgumentNames(){this.argumentsFormArray.controls.forEach(((e,t)=>{e.get("name").setValue(ln[t])}))}updateModel(){const e=this.argumentsFormArray.value;e.length&&this.argumentsFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}}e("ArgumentsMapConfigComponent",In),In.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:In,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),In.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:In,selector:"tb-arguments-map-config",inputs:{disabled:"disabled",function:"function"},providers:[{provide:B,useExisting:a((()=>In)),multi:!0},{provide:K,useExisting:a((()=>In)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n\n
\n \n \n
\n \n
\n {{argumentControl.get(\'name\').value}}.\n
\n \n tb.rulenode.argument-source-field-input\n \n \n {{ argumentTypeMap.get(argumentControl.get(\'type\').value)?.name | translate }}\n \n \n {{ argumentTypeMap.get(argument).name | translate }}\n \n {{ argumentTypeMap.get(argument).description }}\n \n \n \n \n tb.rulenode.argument-source-field-input-required\n \n \n
\n \n tb.rulenode.argument-key-field-input\n \n \n help\n \n \n tb.rulenode.argument-key-field-input-required\n \n \n \n tb.rulenode.constant-value-field-input\n \n \n tb.rulenode.constant-value-field-input-required\n \n \n \n tb.rulenode.default-value-field-input\n \n \n
\n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n tb.rulenode.attribute-scope-field-input-required\n \n \n
\n \n
\n
\n
\n
\n
\n
\n tb.rulenode.no-arguments-prompt\n
\n \n
\n',styles:[":host .mat-mdc-list-item.tb-argument{border:solid rgba(0,0,0,.25) 1px;border-radius:4px;padding:10px 0;margin-bottom:16px}:host .arguments-list{padding:0}\n"],dependencies:[{kind:"directive",type:H.NgClass,selector:"[ngClass]",inputs:["class","ngClass"]},{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:qe.MatList,selector:"mat-list",exportAs:["matList"]},{kind:"component",type:qe.MatListItem,selector:"mat-list-item, a[mat-list-item], button[mat-list-item]",inputs:["activated"],exportAs:["matListItem"]},{kind:"directive",type:Ae.CdkDropList,selector:"[cdkDropList], cdk-drop-list",inputs:["cdkDropListConnectedTo","cdkDropListData","cdkDropListOrientation","id","cdkDropListLockAxis","cdkDropListDisabled","cdkDropListSortingDisabled","cdkDropListEnterPredicate","cdkDropListSortPredicate","cdkDropListAutoScrollDisabled","cdkDropListAutoScrollStep"],outputs:["cdkDropListDropped","cdkDropListEntered","cdkDropListExited","cdkDropListSorted"],exportAs:["cdkDropList"]},{kind:"directive",type:Ae.CdkDrag,selector:"[cdkDrag]",inputs:["cdkDragData","cdkDragLockAxis","cdkDragRootElement","cdkDragBoundary","cdkDragStartDelay","cdkDragFreeDragPosition","cdkDragDisabled","cdkDragConstrainPosition","cdkDragPreviewClass","cdkDragPreviewContainer"],outputs:["cdkDragStarted","cdkDragReleased","cdkDragEnded","cdkDragEntered","cdkDragExited","cdkDragDropped","cdkDragMoved"],exportAs:["cdkDrag"]},{kind:"directive",type:Ae.CdkDragHandle,selector:"[cdkDragHandle]",inputs:["cdkDragHandleDisabled"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:Se.DefaultClassDirective,selector:" [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]",inputs:["ngClass","ngClass.xs","ngClass.sm","ngClass.md","ngClass.lg","ngClass.xl","ngClass.lt-sm","ngClass.lt-md","ngClass.lt-lg","ngClass.lt-xl","ngClass.gt-xs","ngClass.gt-sm","ngClass.gt-md","ngClass.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:In,decorators:[{type:n,args:[{selector:"tb-arguments-map-config",providers:[{provide:B,useExisting:a((()=>In)),multi:!0},{provide:K,useExisting:a((()=>In)),multi:!0}],template:'
\n\n
\n \n \n
\n \n
\n {{argumentControl.get(\'name\').value}}.\n
\n \n tb.rulenode.argument-source-field-input\n \n \n {{ argumentTypeMap.get(argumentControl.get(\'type\').value)?.name | translate }}\n \n \n {{ argumentTypeMap.get(argument).name | translate }}\n \n {{ argumentTypeMap.get(argument).description }}\n \n \n \n \n tb.rulenode.argument-source-field-input-required\n \n \n
\n \n tb.rulenode.argument-key-field-input\n \n \n help\n \n \n tb.rulenode.argument-key-field-input-required\n \n \n \n tb.rulenode.constant-value-field-input\n \n \n tb.rulenode.constant-value-field-input-required\n \n \n \n tb.rulenode.default-value-field-input\n \n \n
\n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n tb.rulenode.attribute-scope-field-input-required\n \n \n
\n \n
\n
\n
\n
\n
\n
\n tb.rulenode.no-arguments-prompt\n
\n \n
\n',styles:[":host .mat-mdc-list-item.tb-argument{border:solid rgba(0,0,0,.25) 1px;border-radius:4px;padding:10px 0;margin-bottom:16px}:host .arguments-list{padding:0}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],function:[{type:i}]}});class Nn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.searchText="",this.dirty=!1,this.mathOperation=[...Wt.values()],this.propagateChange=null}ngOnInit(){this.mathFunctionForm=this.fb.group({operation:[""]}),this.filteredOptions=this.mathFunctionForm.get("operation").valueChanges.pipe(Me((e=>{let t;t="string"==typeof e&&Yt[e]?Yt[e]:null,this.updateView(t)})),Ee((e=>(this.searchText=e||"",e?this._filter(e):this.mathOperation.slice()))))}_filter(e){const t=e.toLowerCase();return this.mathOperation.filter((e=>e.name.toLowerCase().includes(t)||e.value.toLowerCase().includes(t)))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.mathFunctionForm.disable({emitEvent:!1}):this.mathFunctionForm.enable({emitEvent:!1})}mathFunctionDisplayFn(e){if(e){const t=Wt.get(e);return t.value+" | "+t.name}return""}writeValue(e){this.modelValue=e,this.mathFunctionForm.get("operation").setValue(e,{emitEvent:!1}),this.dirty=!0}updateView(e){this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}onFocus(){this.dirty&&(this.mathFunctionForm.get("operation").updateValueAndValidity({onlySelf:!0}),this.dirty=!1)}clear(){this.mathFunctionForm.get("operation").patchValue(""),setTimeout((()=>{this.operationInput.nativeElement.blur(),this.operationInput.nativeElement.focus()}),0)}}e("MathFunctionAutocompleteComponent",Nn),Nn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nn,deps:[{token:P.Store},{token:Z.TranslateService},{token:t.Injector},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Nn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Nn,selector:"tb-math-function-autocomplete",inputs:{required:"required",disabled:"disabled"},providers:[{provide:B,useExisting:a((()=>Nn)),multi:!0}],viewQueries:[{propertyName:"operationInput",first:!0,predicate:["operationInput"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'\n tb.rulenode.functions-field-input\n \n \n \n \n \n \n {{ option.description }}\n \n \n \n tb.rulenode.no-option-found\n \n \n\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Re.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Re.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Oe.HighlightPipe,name:"highlight"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nn,decorators:[{type:n,args:[{selector:"tb-math-function-autocomplete",providers:[{provide:B,useExisting:a((()=>Nn)),multi:!0}],template:'\n tb.rulenode.functions-field-input\n \n \n \n \n \n \n {{ option.description }}\n \n \n \n tb.rulenode.no-option-found\n \n \n\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:t.Injector},{type:R.UntypedFormBuilder}]},propDecorators:{required:[{type:i}],disabled:[{type:i}],operationInput:[{type:o,args:["operationInput",{static:!0}]}]}});class Sn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.MathFunction=Yt,this.ArgumentTypeResult=Xt,this.argumentTypeResultMap=an,this.attributeScopeMap=pn,this.argumentsResult=Object.values(Xt),this.attributeScopeResult=Object.values(mn)}configForm(){return this.mathFunctionConfigForm}onConfigurationSet(e){this.mathFunctionConfigForm=this.fb.group({operation:[e?e.operation:null,[O.required]],arguments:[e?e.arguments:null,[O.required]],customFunction:[e?e.customFunction:"",[O.required]],result:this.fb.group({type:[e?e.result.type:null,[O.required]],attributeScope:[e?e.result.attributeScope:null,[O.required]],key:[e?e.result.key:"",[O.required]],resultValuePrecision:[e?e.result.resultValuePrecision:0],addToBody:[!!e&&e.result.addToBody],addToMetadata:[!!e&&e.result.addToMetadata]})})}updateValidators(e){const t=this.mathFunctionConfigForm.get("operation").value,n=this.mathFunctionConfigForm.get("result.type").value;t===Yt.CUSTOM?(this.mathFunctionConfigForm.get("customFunction").enable({emitEvent:!1}),null===this.mathFunctionConfigForm.get("customFunction").value&&this.mathFunctionConfigForm.get("customFunction").patchValue("(x - 32) / 1.8",{emitEvent:!1})):this.mathFunctionConfigForm.get("customFunction").disable({emitEvent:!1}),n===Xt.ATTRIBUTE?this.mathFunctionConfigForm.get("result.attributeScope").enable({emitEvent:!1}):this.mathFunctionConfigForm.get("result.attributeScope").disable({emitEvent:!1}),this.mathFunctionConfigForm.get("customFunction").updateValueAndValidity({emitEvent:e}),this.mathFunctionConfigForm.get("result.attributeScope").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["operation","result.type"]}}e("MathFunctionConfigComponent",Sn),Sn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Sn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Sn,selector:"tb-action-node-math-function-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n tb.rulenode.argument-tile\n \n \n
\n
\n {{\'tb.rulenode.custom-expression-field-input\' | translate }} *\n \n \n \n tb.rulenode.custom-expression-field-input-required\n \n tb.rulenode.custom-expression-field-input-hint\n \n
\n
\n tb.rulenode.result-title\n
\n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(mathFunctionConfigForm.get(\'result.type\').value)?.name | translate }}\n \n \n {{ argumentTypeResultMap.get(argument).name | translate }}\n \n {{ argumentTypeResultMap.get(argument).description }}\n \n \n \n \n tb.rulenode.type-field-input-required\n \n \n
\n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n \n tb.rulenode.key-field-input\n \n help\n \n tb.rulenode.key-field-input-required\n \n \n
\n
\n \n tb.rulenode.number-floating-point-field-input\n \n \n \n
\n
\n \n {{\'tb.rulenode.add-to-message-field-input\' | translate }}\n \n \n {{\'tb.rulenode.add-to-metadata-field-input\' | translate}}\n \n
\n
\n
\n
\n',styles:[":host ::ng-deep .fields-group{padding:0 16px 8px;margin:10px 0;border:1px groove rgba(0,0,0,.25);border-radius:4px}:host ::ng-deep .fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}:host ::ng-deep .fields-group legend{color:#000000b3;width:-moz-fit-content;width:fit-content}:host ::ng-deep .fields-group legend+*{display:block}:host ::ng-deep .fields-group legend+*.no-margin-top{margin-top:0}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:R.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:In,selector:"tb-arguments-map-config",inputs:["disabled","function"]},{kind:"component",type:Nn,selector:"tb-math-function-autocomplete",inputs:["required","disabled"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sn,decorators:[{type:n,args:[{selector:"tb-action-node-math-function-config",template:'
\n \n \n
\n tb.rulenode.argument-tile\n \n \n
\n
\n {{\'tb.rulenode.custom-expression-field-input\' | translate }} *\n \n \n \n tb.rulenode.custom-expression-field-input-required\n \n tb.rulenode.custom-expression-field-input-hint\n \n
\n
\n tb.rulenode.result-title\n
\n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(mathFunctionConfigForm.get(\'result.type\').value)?.name | translate }}\n \n \n {{ argumentTypeResultMap.get(argument).name | translate }}\n \n {{ argumentTypeResultMap.get(argument).description }}\n \n \n \n \n tb.rulenode.type-field-input-required\n \n \n
\n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n \n tb.rulenode.key-field-input\n \n help\n \n tb.rulenode.key-field-input-required\n \n \n
\n
\n \n tb.rulenode.number-floating-point-field-input\n \n \n \n
\n
\n \n {{\'tb.rulenode.add-to-message-field-input\' | translate }}\n \n \n {{\'tb.rulenode.add-to-metadata-field-input\' | translate}}\n \n
\n
\n
\n
\n',styles:[":host ::ng-deep .fields-group{padding:0 16px 8px;margin:10px 0;border:1px groove rgba(0,0,0,.25);border-radius:4px}:host ::ng-deep .fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}:host ::ng-deep .fields-group legend{color:#000000b3;width:-moz-fit-content;width:fit-content}:host ::ng-deep .fields-group legend+*{display:block}:host ::ng-deep .fields-group legend+*.no-margin-top{margin-top:0}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class qn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.messageTypeNames=T,this.eventOptions=[I.CONNECT_EVENT,I.ACTIVITY_EVENT,I.DISCONNECT_EVENT,I.INACTIVITY_EVENT]}configForm(){return this.deviceState}prepareInputConfig(e){return{event:ae(e?.event)?e.event:I.ACTIVITY_EVENT}}onConfigurationSet(e){this.deviceState=this.fb.group({event:[e.event,[O.required]]})}}e("DeviceStateConfigComponent",qn),qn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:qn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),qn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:qn,selector:"tb-action-node-device-state-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.select-device-connectivity-event\' | translate }}\n \n \n {{ messageTypeNames.get(eventOption) }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:qn,decorators:[{type:n,args:[{selector:"tb-action-node-device-state-config",template:'
\n \n {{ \'tb.rulenode.select-device-connectivity-event\' | translate }}\n \n \n {{ messageTypeNames.get(eventOption) }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class An{constructor(){this.textAlign="left"}}e("ExampleHintComponent",An),An.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:An,deps:[],target:t.ɵɵFactoryTarget.Component}),An.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:An,selector:"tb-example-hint",inputs:{hintText:"hintText",popupHelpLink:"popupHelpLink",textAlign:"textAlign"},ngImport:t,template:'
\n
\n
\n
\n
\n',styles:[":host .space-between{display:flex;justify-content:space-between;gap:20px}:host .space-between .see-example{display:flex;flex-shrink:0}:host .hint-text{width:100%}\n"],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:$e.HelpPopupComponent,selector:"[tb-help-popup], [tb-help-popup-content]",inputs:["tb-help-popup","tb-help-popup-content","trigger-text","trigger-style","tb-help-popup-placement","tb-help-popup-style","hintMode"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:An,decorators:[{type:n,args:[{selector:"tb-example-hint",template:'
\n
\n
\n
\n
\n',styles:[":host .space-between{display:flex;justify-content:space-between;gap:20px}:host .space-between .see-example{display:flex;flex-shrink:0}:host .hint-text{width:100%}\n"]}]}],propDecorators:{hintText:[{type:i}],popupHelpLink:[{type:i}],textAlign:[{type:i}]}});class Mn{constructor(e,t){this.injector=e,this.fb=t,this.propagateChange=()=>{},this.destroy$=new Ke,this.disabled=!1,this.uniqueKeyValuePairValidator=!1,this.required=!1,this.duplicateValuesValidator=e=>e.controls.key.value===e.controls.value.value&&e.controls.key.value&&e.controls.value.value?{uniqueKeyValuePair:!0}:null,this.oneMapRequiredValidator=e=>e.get("keyVals").value.length,this.propagateNestedErrors=e=>{if(this.kvListFormGroup&&this.kvListFormGroup.get("keyVals")&&"VALID"===this.kvListFormGroup.get("keyVals")?.status)return null;const t={};if(this.kvListFormGroup&&this.kvListFormGroup.setErrors(null),e instanceof z||e instanceof U){if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;for(const n of Object.keys(e.controls)){const r=this.propagateNestedErrors(e.controls[n]);if(r&&Object.keys(r).length)for(const e of Object.keys(r))t[e]=!0}return t}if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;return ie(t,{})?null:t}}ngOnInit(){this.ngControl=this.injector.get(_),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({keyVals:this.fb.array([])},{validators:[this.propagateNestedErrors,this.oneMapRequiredValidator]}),this.kvListFormGroup.valueChanges.pipe(ze(this.destroy$)).subscribe((()=>{this.updateModel()}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){const t=Object.keys(e).map((t=>({key:t,value:e[t]})));if(this.keyValsFormArray().length===t.length)this.keyValsFormArray().patchValue(t,{emitEvent:!1});else{const e=[];t.forEach((t=>{e.push(this.fb.group({key:[t.key,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:[t.value,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]},{validators:this.uniqueKeyValuePairValidator?[this.duplicateValuesValidator]:[]}))})),this.kvListFormGroup.setControl("keyVals",this.fb.array(e,this.propagateNestedErrors),{emitEvent:!1})}}removeKeyVal(e){this.keyValsFormArray().removeAt(e)}addKeyVal(){this.keyValsFormArray().push(this.fb.group({key:["",[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:["",[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]},{validators:this.uniqueKeyValuePairValidator?[this.duplicateValuesValidator]:[]}))}validate(){const e=this.kvListFormGroup.get("keyVals").value;if(!e.length&&this.required)return{kvMapRequired:!0};if(!this.kvListFormGroup.valid)return{kvFieldsRequired:!0};if(this.uniqueKeyValuePairValidator)for(const t of e)if(t.key===t.value)return{uniqueKeyValuePair:!0};return null}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("KvMapConfigComponent",Mn),Mn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Mn,deps:[{token:t.Injector},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Mn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Mn,selector:"tb-kv-map-config",inputs:{disabled:"disabled",uniqueKeyValuePairValidator:"uniqueKeyValuePairValidator",labelText:"labelText",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",popupHelpLink:"popupHelpLink",required:"required"},providers:[{provide:B,useExisting:a((()=>Mn)),multi:!0},{provide:K,useExisting:a((()=>Mn)),multi:!0}],ngImport:t,template:'
\n
\n
{{ labelText }}
\n
\n {{ requiredText }}\n
\n
\n tb.rulenode.map-fields-required\n
\n
\n {{ \'tb.key-val.unique-key-value-pair-error\' | translate:\n {\n valText: valText,\n keyText: keyText\n } }}\n
\n
\n
\n
\n
\n
{{ keyText }}
\n
{{ valText }}
\n
\n
\n
\n
\n \n \n \n \n \n \n
\n \n
\n
\n
\n
\n
\n
\n \n
\n \n
\n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),Be([N()],Mn.prototype,"disabled",void 0),Be([N()],Mn.prototype,"uniqueKeyValuePairValidator",void 0),Be([N()],Mn.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Mn,decorators:[{type:n,args:[{selector:"tb-kv-map-config",providers:[{provide:B,useExisting:a((()=>Mn)),multi:!0},{provide:K,useExisting:a((()=>Mn)),multi:!0}],template:'
\n
\n
{{ labelText }}
\n
\n {{ requiredText }}\n
\n
\n tb.rulenode.map-fields-required\n
\n
\n {{ \'tb.key-val.unique-key-value-pair-error\' | translate:\n {\n valText: valText,\n keyText: keyText\n } }}\n
\n
\n
\n
\n
\n
{{ keyText }}
\n
{{ valText }}
\n
\n
\n
\n
\n \n \n \n \n \n \n
\n \n
\n
\n
\n
\n
\n
\n \n
\n \n
\n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:t.Injector},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],uniqueKeyValuePairValidator:[{type:i}],labelText:[{type:i}],requiredText:[{type:i}],keyText:[{type:i}],keyRequiredText:[{type:i}],valText:[{type:i}],valRequiredText:[{type:i}],hintText:[{type:i}],popupHelpLink:[{type:i}],required:[{type:i}]}});class En extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.values(v),this.directionTypeTranslations=C,this.entityType=F,this.propagateChange=null}ngOnInit(){this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[O.required]],maxLevel:[null,[O.min(1)]],relationType:[null],deviceTypes:[null,[O.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((e=>{this.deviceRelationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})}}e("DeviceRelationsQueryConfigComponent",En),En.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:En,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),En.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:En,selector:"tb-device-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>En)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n \n \n help\n \n
\n',styles:[":host .last-level-slide-toggle{margin:8px 0 24px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Qe.EntitySubTypeListComponent,selector:"tb-entity-subtype-list",inputs:["required","floatLabel","label","disabled","entityType","emptyInputPlaceholder","filledInputPlaceholder","appearance","subscriptSizing","additionalClasses"]},{kind:"component",type:Je.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["showLabel","additionalClasses","appearance","required","disabled","subscriptSizing"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:En,decorators:[{type:n,args:[{selector:"tb-device-relations-query-config",providers:[{provide:B,useExisting:a((()=>En)),multi:!0}],template:'
\n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n \n \n help\n \n
\n',styles:[":host .last-level-slide-toggle{margin:8px 0 24px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class Gn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.values(v),this.directionTypeTranslations=C,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[O.required]],maxLevel:[null,[O.min(1)]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}}e("RelationsQueryConfigComponent",Gn),Gn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Gn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Gn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Gn,selector:"tb-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>Gn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
tb.rulenode.relations-query
\n
\n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n
\n
\n
relation.relation-filters
\n \n \n
\n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Ye.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Gn,decorators:[{type:n,args:[{selector:"tb-relations-query-config",providers:[{provide:B,useExisting:a((()=>Gn)),multi:!0}],template:'
\n
tb.rulenode.relations-query
\n
\n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
\n
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n
\n
\n
relation.relation-filters
\n \n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class Dn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.truncate=n,this.fb=r,this.placeholder="tb.rulenode.add-message-type",this.separatorKeysCodes=[ge,ye,xe],this.messageTypes=[],this.messageTypesList=[],this.searchText="",this.propagateChange=e=>{},this.messageTypeConfigForm=this.fb.group({messageType:[null]});for(const e of Object.keys(I))this.messageTypesList.push({name:T.get(I[e]),value:e})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnInit(){this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(Ge(""),Ee((e=>e||"")),De((e=>this.fetchMessageTypes(e))),we())}setDisabledState(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})}writeValue(e){this.searchText="",this.messageTypes.length=0,e&&e.forEach((e=>{const t=this.messageTypesList.find((t=>t.value===e));t?this.messageTypes.push({name:t.name,value:t.value}):this.messageTypes.push({name:e,value:e})}))}displayMessageTypeFn(e){return e?e.name:void 0}textIsNotEmpty(e){return e&&e.length>0}createMessageType(e,t){e.preventDefault(),this.transformMessageType(t)}add(e){this.transformMessageType(e.value)}fetchMessageTypes(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ue(this.messageTypesList.filter((t=>t.name.toUpperCase().includes(e))))}return Ue(this.messageTypesList)}transformMessageType(e){if((e||"").trim()){let t;const n=e.trim(),r=this.messageTypesList.find((e=>e.name===n));t=r?{name:r.name,value:r.value}:{name:n,value:n},t&&this.addMessageType(t)}this.clear("")}remove(e){const t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())}selected(e){this.addMessageType(e.option.value),this.clear("")}addMessageType(e){-1===this.messageTypes.findIndex((t=>t.value===e.value))&&(this.messageTypes.push(e),this.updateModel())}onFocus(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.messageTypeInput.nativeElement.blur(),this.messageTypeInput.nativeElement.focus()}),0)}updateModel(){const e=this.messageTypes.map((e=>e.value));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))}}e("MessageTypesConfigComponent",Dn),Dn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Dn,deps:[{token:P.Store},{token:Z.TranslateService},{token:S.TruncatePipe},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Dn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Dn,selector:"tb-message-types-config",inputs:{required:"required",label:"label",placeholder:"placeholder",disabled:"disabled"},providers:[{provide:B,useExisting:a((()=>Dn)),multi:!0}],viewQueries:[{propertyName:"chipList",first:!0,predicate:["chipList"],descendants:!0},{propertyName:"matAutocomplete",first:!0,predicate:["messageTypeAutocomplete"],descendants:!0},{propertyName:"messageTypeInput",first:!0,predicate:["messageTypeInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ \'tb.rulenode.no-message-type-matching\' | translate :\n {messageType: truncate.transform(searchText, true, 6, '...')}\n }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n help\n \n {{ \'tb.rulenode.select-message-types-required\' | translate }}\n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:Re.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Re.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:Re.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{kind:"component",type:be.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:be.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:be.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:be.MatChipRow,selector:"mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]",inputs:["color","disabled","disableRipple","tabIndex","editable"],outputs:["edited"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Oe.HighlightPipe,name:"highlight"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Dn,decorators:[{type:n,args:[{selector:"tb-message-types-config",providers:[{provide:B,useExisting:a((()=>Dn)),multi:!0}],template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ \'tb.rulenode.no-message-type-matching\' | translate :\n {messageType: truncate.transform(searchText, true, 6, '...')}\n }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n help\n \n {{ \'tb.rulenode.select-message-types-required\' | translate }}\n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:S.TruncatePipe},{type:R.FormBuilder}]},propDecorators:{required:[{type:i}],label:[{type:i}],placeholder:[{type:i}],disabled:[{type:i}],chipList:[{type:o,args:["chipList",{static:!1}]}],matAutocomplete:[{type:o,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:o,args:["messageTypeInput",{static:!1}]}]}});class wn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[],this.disableCertPemCredentials=!1,this.passwordFieldRequired=!0,this.allCredentialsTypes=zt,this.credentialsTypeTranslationsMap=Ut,this.propagateChange=e=>{}}ngOnInit(){this.credentialsConfigFormGroup=this.fb.group({type:[null,[O.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.subscribe((()=>{this.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((()=>{this.credentialsTypeChanged()})))}ngOnChanges(e){for(const t of Object.keys(e)){const n=e[t];if(!n.firstChange&&n.currentValue!==n.previousValue&&n.currentValue&&"disableCertPemCredentials"===t){"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((()=>{this.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}writeValue(e){ae(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators())}setDisabledState(e){e?this.credentialsConfigFormGroup.disable({emitEvent:!1}):(this.credentialsConfigFormGroup.enable({emitEvent:!1}),this.updateValidators())}updateView(){let e=this.credentialsConfigFormGroup.value;const t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}validate(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}}credentialsTypeChanged(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()}updateValidators(e=!1){const t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([O.required]),this.credentialsConfigFormGroup.get("password").setValidators(this.passwordFieldRequired?[O.required]:[]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(O.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})}requiredFilesSelected(e,t=null){return n=>{t||(t=[Object.keys(n.controls)]);return n?.controls&&t.some((t=>t.every((t=>!e(n.controls[t])))))?null:{notAllRequiredFilesSelected:!0}}}}e("CredentialsConfigComponent",wn),wn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:wn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),wn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:wn,selector:"tb-credentials-config",inputs:{required:"required",disableCertPemCredentials:"disableCertPemCredentials",passwordFieldRequired:"passwordFieldRequired"},providers:[{provide:B,useExisting:a((()=>wn)),multi:!0},{provide:K,useExisting:a((()=>wn)),multi:!0}],usesInheritance:!0,usesOnChanges:!0,ngImport:t,template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:H.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{kind:"directive",type:H.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:We.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:We.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:We.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:We.MatExpansionPanelDescription,selector:"mat-panel-description"},{kind:"directive",type:We.MatExpansionPanelContent,selector:"ng-template[matExpansionPanelContent]"},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Ze.FileInputComponent,selector:"tb-file-input",inputs:["label","hint","accept","noFileText","inputId","allowedExtensions","dropLabel","maxSizeByte","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:wn,decorators:[{type:n,args:[{selector:"tb-credentials-config",providers:[{provide:B,useExisting:a((()=>wn)),multi:!0},{provide:K,useExisting:a((()=>wn)),multi:!0}],template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{required:[{type:i}],disableCertPemCredentials:[{type:i}],passwordFieldRequired:[{type:i}]}});const Vn=new l("WindowToken","undefined"!=typeof window&&window.document?{providedIn:"root",factory:()=>window}:{providedIn:"root",factory:()=>{}});class Pn{constructor(e,t,n){this.ngZone=e,this.document=t,this.window=n,this.copySubject=new Ke,this.copyResponse$=this.copySubject.asObservable(),this.config={}}configure(e){this.config=e}copy(e){if(!this.isSupported||!e)return this.pushCopyResponse({isSuccess:!1,content:e});const t=this.copyFromContent(e);return t?this.pushCopyResponse({content:e,isSuccess:t}):this.pushCopyResponse({isSuccess:!1,content:e})}get isSupported(){return!!this.document.queryCommandSupported&&!!this.document.queryCommandSupported("copy")&&!!this.window}isTargetValid(e){if(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement){if(e.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');return!0}throw new Error("Target should be input or textarea")}copyFromInputElement(e,t=!0){try{this.selectTarget(e);const n=this.copyText();return this.clearSelection(t?e:void 0,this.window),n&&this.isCopySuccessInIE11()}catch(e){return!1}}isCopySuccessInIE11(){const e=this.window.clipboardData;return!(e&&e.getData&&!e.getData("Text"))}copyFromContent(e,t=this.document.body){if(this.tempTextArea&&!t.contains(this.tempTextArea)&&this.destroy(this.tempTextArea.parentElement||void 0),!this.tempTextArea){this.tempTextArea=this.createTempTextArea(this.document,this.window);try{t.appendChild(this.tempTextArea)}catch(e){throw new Error("Container should be a Dom element")}}this.tempTextArea.value=e;const n=this.copyFromInputElement(this.tempTextArea,!1);return this.config.cleanUpAfterCopy&&this.destroy(this.tempTextArea.parentElement||void 0),n}destroy(e=this.document.body){this.tempTextArea&&(e.removeChild(this.tempTextArea),this.tempTextArea=void 0)}selectTarget(e){return e.select(),e.setSelectionRange(0,e.value.length),e.value.length}copyText(){return this.document.execCommand("copy")}clearSelection(e,t){e&&e.focus(),t.getSelection()?.removeAllRanges()}createTempTextArea(e,t){const n="rtl"===e.documentElement.getAttribute("dir");let r;r=e.createElement("textarea"),r.style.fontSize="12pt",r.style.border="0",r.style.padding="0",r.style.margin="0",r.style.position="absolute",r.style[n?"right":"left"]="-9999px";const o=t.pageYOffset||e.documentElement.scrollTop;return r.style.top=o+"px",r.setAttribute("readonly",""),r}pushCopyResponse(e){this.copySubject.observers.length>0&&this.ngZone.run((()=>{this.copySubject.next(e)}))}pushCopyReponse(e){this.pushCopyResponse(e)}}Pn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Pn,deps:[{token:t.NgZone},{token:j},{token:Vn,optional:!0}],target:t.ɵɵFactoryTarget.Injectable}),Pn.ɵprov=t.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Pn,providedIn:"root"}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Pn,decorators:[{type:s,args:[{providedIn:"root"}]}],ctorParameters:function(){return[{type:t.NgZone},{type:void 0,decorators:[{type:m,args:[j]}]},{type:void 0,decorators:[{type:p},{type:m,args:[Vn]}]}]}});class Rn{constructor(e,t,n,o){this.ngZone=e,this.host=t,this.renderer=n,this.clipboardSrv=o,this.cbOnSuccess=new r,this.cbOnError=new r,this.onClick=e=>{this.clipboardSrv.isSupported?this.targetElm&&this.clipboardSrv.isTargetValid(this.targetElm)?this.handleResult(this.clipboardSrv.copyFromInputElement(this.targetElm),this.targetElm.value,e):this.cbContent&&this.handleResult(this.clipboardSrv.copyFromContent(this.cbContent,this.container),this.cbContent,e):this.handleResult(!1,void 0,e)}}ngOnInit(){this.ngZone.runOutsideAngular((()=>{this.clickListener=this.renderer.listen(this.host.nativeElement,"click",this.onClick)}))}ngOnDestroy(){this.clickListener&&this.clickListener(),this.clipboardSrv.destroy(this.container)}handleResult(e,t,n){let r={isSuccess:e,content:t,successMessage:this.cbSuccessMsg,event:n};e?this.cbOnSuccess.observed&&this.ngZone.run((()=>{this.cbOnSuccess.emit(r)})):this.cbOnError.observed&&this.ngZone.run((()=>{this.cbOnError.emit(r)})),this.clipboardSrv.pushCopyResponse(r)}}Rn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Rn,deps:[{token:t.NgZone},{token:t.ElementRef},{token:t.Renderer2},{token:Pn}],target:t.ɵɵFactoryTarget.Directive}),Rn.ɵdir=t.ɵɵngDeclareDirective({minVersion:"12.0.0",version:"13.0.1",type:Rn,selector:"[ngxClipboard]",inputs:{targetElm:["ngxClipboard","targetElm"],container:"container",cbContent:"cbContent",cbSuccessMsg:"cbSuccessMsg"},outputs:{cbOnSuccess:"cbOnSuccess",cbOnError:"cbOnError"},ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Rn,decorators:[{type:d,args:[{selector:"[ngxClipboard]"}]}],ctorParameters:function(){return[{type:t.NgZone},{type:t.ElementRef},{type:t.Renderer2},{type:Pn}]},propDecorators:{targetElm:[{type:i,args:["ngxClipboard"]}],container:[{type:i}],cbContent:[{type:i}],cbSuccessMsg:[{type:i}],cbOnSuccess:[{type:u}],cbOnError:[{type:u}]}});class On{constructor(e,t,n){this._clipboardService=e,this._viewContainerRef=t,this._templateRef=n}ngOnInit(){this._clipboardService.isSupported&&this._viewContainerRef.createEmbeddedView(this._templateRef)}}On.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:On,deps:[{token:Pn},{token:t.ViewContainerRef},{token:t.TemplateRef}],target:t.ɵɵFactoryTarget.Directive}),On.ɵdir=t.ɵɵngDeclareDirective({minVersion:"12.0.0",version:"13.0.1",type:On,selector:"[ngxClipboardIfSupported]",ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:On,decorators:[{type:d,args:[{selector:"[ngxClipboardIfSupported]"}]}],ctorParameters:function(){return[{type:Pn},{type:t.ViewContainerRef},{type:t.TemplateRef}]}});class _n{}_n.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:_n,deps:[],target:t.ɵɵFactoryTarget.NgModule}),_n.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:_n,declarations:[Rn,On],imports:[$],exports:[Rn,On]}),_n.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:_n,imports:[[$]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:_n,decorators:[{type:c,args:[{imports:[$],declarations:[Rn,On],exports:[Rn,On]}]}]});class Bn{set required(e){this.requiredValue!==e&&(this.requiredValue=e,this.updateValidators())}get required(){return this.requiredValue}constructor(e){this.fb=e,this.subscriptSizing="fixed",this.messageTypes=[{name:"Post attributes",value:"POST_ATTRIBUTES_REQUEST"},{name:"Post telemetry",value:"POST_TELEMETRY_REQUEST"},{name:"Custom",value:""}],this.propagateChange=()=>{},this.destroy$=new Ke,this.messageTypeFormGroup=this.fb.group({messageTypeAlias:[null,[O.required]],messageType:[{value:null,disabled:!0},[O.maxLength(255)]]}),this.messageTypeFormGroup.get("messageTypeAlias").valueChanges.pipe(ze(this.destroy$)).subscribe((e=>this.updateMessageTypeValue(e))),this.messageTypeFormGroup.valueChanges.pipe(ze(this.destroy$)).subscribe((()=>this.updateView()))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}registerOnTouched(e){}registerOnChange(e){this.propagateChange=e}writeValue(e){this.modelValue=e;let t=this.messageTypes.find((t=>t.value===e));t||(t=this.messageTypes.find((e=>""===e.value))),this.messageTypeFormGroup.get("messageTypeAlias").patchValue(t,{emitEvent:!1}),this.messageTypeFormGroup.get("messageType").patchValue(e,{emitEvent:!1})}validate(){return this.messageTypeFormGroup.valid?null:{messageTypeInvalid:!0}}setDisabledState(e){this.disabled=e,e?this.messageTypeFormGroup.disable({emitEvent:!1}):(this.messageTypeFormGroup.enable({emitEvent:!1}),"Custom"!==this.messageTypeFormGroup.get("messageTypeAlias").value?.name&&this.messageTypeFormGroup.get("messageType").disable({emitEvent:!1}))}updateView(){const e=this.messageTypeFormGroup.getRawValue().messageType;this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}updateValidators(){this.messageTypeFormGroup.get("messageType").setValidators(this.required?[O.required,O.maxLength(255)]:[O.maxLength(255)]),this.messageTypeFormGroup.get("messageType").updateValueAndValidity({emitEvent:!1})}updateMessageTypeValue(e){"Custom"!==e?.name?this.messageTypeFormGroup.get("messageType").disable({emitEvent:!1}):this.messageTypeFormGroup.get("messageType").enable({emitEvent:!1}),this.messageTypeFormGroup.get("messageType").patchValue(e.value??null)}}e("OutputMessageTypeAutocompleteComponent",Bn),Bn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Bn,deps:[{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Bn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Bn,selector:"tb-output-message-type-autocomplete",inputs:{subscriptSizing:"subscriptSizing",disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>Bn)),multi:!0},{provide:K,useExisting:a((()=>Bn)),multi:!0}],ngImport:t,template:'
\n \n {{\'tb.rulenode.output-message-type\' | translate}}\n \n \n {{msgType.name}}\n \n \n \n \n {{\'tb.rulenode.message-type-value\' | translate}}\n \n \n \n {{ \'tb.rulenode.message-type-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.message-type-value-max-length\' | translate }}\n \n \n
\n\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:Rn,selector:"[ngxClipboard]",inputs:["ngxClipboard","container","cbContent","cbSuccessMsg"],outputs:["cbOnSuccess","cbOnError"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),Be([N()],Bn.prototype,"disabled",void 0),Be([N()],Bn.prototype,"required",null),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Bn,decorators:[{type:n,args:[{selector:"tb-output-message-type-autocomplete",providers:[{provide:B,useExisting:a((()=>Bn)),multi:!0},{provide:K,useExisting:a((()=>Bn)),multi:!0}],template:'
\n \n {{\'tb.rulenode.output-message-type\' | translate}}\n \n \n {{msgType.name}}\n \n \n \n \n {{\'tb.rulenode.message-type-value\' | translate}}\n \n \n \n {{ \'tb.rulenode.message-type-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.message-type-value-max-length\' | translate }}\n \n \n
\n\n'}]}],ctorParameters:function(){return[{type:R.FormBuilder}]},propDecorators:{subscriptSizing:[{type:i}],disabled:[{type:i}],required:[{type:i}]}});class Kn{constructor(e,t){this.fb=e,this.translate=t,this.translation=nn,this.propagateChange=()=>{},this.destroy$=new Ke,this.selectOptions=[]}ngOnInit(){this.initOptions(),this.chipControlGroup=this.fb.group({chipControl:[null,[]]}),this.chipControlGroup.get("chipControl").valueChanges.pipe(Ve(this.destroy$)).subscribe((e=>{e&&this.propagateChange(e)}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}initOptions(){for(const e of this.translation.keys())this.selectOptions.push({value:e,name:this.translate.instant(this.translation.get(e))})}writeValue(e){this.chipControlGroup.get("chipControl").patchValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){e?this.chipControlGroup.disable({emitEvent:!1}):this.chipControlGroup.enable({emitEvent:!1})}}e("MsgMetadataChipComponent",Kn),Kn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kn,deps:[{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Kn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Kn,selector:"tb-msg-metadata-chip",inputs:{labelText:"labelText",translation:"translation"},providers:[{provide:B,useExisting:a((()=>Kn)),multi:!0}],ngImport:t,template:'
\n
{{ labelText }}
\n \n {{ option.name }}\n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:be.MatChipListbox,selector:"mat-chip-listbox",inputs:["tabIndex","multiple","aria-orientation","selectable","compareWith","required","hideSingleSelectionIndicator","value"],outputs:["change"]},{kind:"component",type:be.MatChipOption,selector:"mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]",inputs:["color","disabled","disableRipple","tabIndex","selectable","selected"],outputs:["selectionChange"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kn,decorators:[{type:n,args:[{selector:"tb-msg-metadata-chip",providers:[{provide:B,useExisting:a((()=>Kn)),multi:!0}],template:'
\n
{{ labelText }}
\n \n {{ option.name }}\n \n
\n'}]}],ctorParameters:function(){return[{type:R.FormBuilder},{type:Z.TranslateService}]},propDecorators:{labelText:[{type:i}],translation:[{type:i}]}});class zn extends L{constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.destroy$=new Ke,this.sourceFieldSubcritption=[],this.propagateChange=null,this.disabled=!1,this.required=!1,this.oneMapRequiredValidator=e=>e.get("keyVals").value.length,this.propagateNestedErrors=e=>{if(this.svListFormGroup&&this.svListFormGroup.get("keyVals")&&"VALID"===this.svListFormGroup.get("keyVals")?.status)return null;const t={};if(this.svListFormGroup&&this.svListFormGroup.setErrors(null),e instanceof z||e instanceof U){if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;for(const n of Object.keys(e.controls)){const r=this.propagateNestedErrors(e.controls[n]);if(r&&Object.keys(r).length)for(const e of Object.keys(r))t[e]=!0}return t}if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;return ie(t,{})?null:t}}ngOnInit(){this.ngControl=this.injector.get(_),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.svListFormGroup=this.fb.group({keyVals:this.fb.array([])},{validators:[this.propagateNestedErrors,this.oneMapRequiredValidator]}),this.svListFormGroup.valueChanges.pipe(Ve(this.destroy$)).subscribe((()=>{this.updateModel()}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}keyValsFormArray(){return this.svListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.svListFormGroup.disable({emitEvent:!1}):this.svListFormGroup.enable({emitEvent:!1})}writeValue(e){const t=Object.keys(e).map((t=>({key:t,value:e[t]})));if(this.keyValsFormArray().length===t.length)this.keyValsFormArray().patchValue(t,{emitEvent:!1});else{const e=[];t.forEach((t=>{e.push(this.fb.group({key:[t.key,[O.required]],value:[t.value,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]}))})),this.svListFormGroup.setControl("keyVals",this.fb.array(e,this.propagateNestedErrors),{emitEvent:!1});for(const e of this.keyValsFormArray().controls)this.keyChangeSubscribe(e)}}filterSelectOptions(e){const t=[];for(const e of this.svListFormGroup.get("keyVals").value){const n=this.selectOptions.find((t=>t.value===e.key));n&&t.push(n)}const n=[];for(const r of this.selectOptions)ae(t.find((e=>e.value===r.value)))&&r.value!==e?.get("key").value||n.push(r);return n}removeKeyVal(e){this.keyValsFormArray().removeAt(e),this.sourceFieldSubcritption[e].unsubscribe(),this.sourceFieldSubcritption.splice(e,1)}addKeyVal(){this.keyValsFormArray().push(this.fb.group({key:["",[O.required]],value:["",[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]})),this.keyChangeSubscribe(this.keyValsFormArray().at(this.keyValsFormArray().length-1))}keyChangeSubscribe(e){this.sourceFieldSubcritption.push(e.get("key").valueChanges.pipe(Ve(this.destroy$)).subscribe((t=>{const n=Lt.get(t);e.get("value").patchValue(this.targetKeyPrefix+n[0].toUpperCase()+n.slice(1))})))}validate(e){return!this.svListFormGroup.get("keyVals").value.length&&this.required?{svMapRequired:!0}:this.svListFormGroup.valid?null:{svFieldsRequired:!0}}updateModel(){const e=this.svListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.svListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("SvMapConfigComponent",zn),zn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zn,deps:[{token:P.Store},{token:Z.TranslateService},{token:t.Injector},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),zn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:zn,selector:"tb-sv-map-config",inputs:{selectOptions:"selectOptions",disabled:"disabled",labelText:"labelText",requiredText:"requiredText",targetKeyPrefix:"targetKeyPrefix",selectText:"selectText",selectRequiredText:"selectRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",popupHelpLink:"popupHelpLink",required:"required"},providers:[{provide:B,useExisting:a((()=>zn)),multi:!0},{provide:K,useExisting:a((()=>zn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n
{{ labelText }}
\n
\n tb.rulenode.map-fields-required\n
\n
\n {{ requiredText }}\n
\n
\n
\n
\n
\n
{{ selectText }}
\n
{{ valText }}
\n
\n
\n
\n
\n \n \n \n {{option.name}}\n \n \n \n \n \n \n
\n \n
\n
\n
\n
\n
\n
\n \n
\n \n
\n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgClass,selector:"[ngClass]",inputs:["class","ngClass"]},{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:Se.DefaultClassDirective,selector:" [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]",inputs:["ngClass","ngClass.xs","ngClass.sm","ngClass.md","ngClass.lg","ngClass.xl","ngClass.lt-sm","ngClass.lt-md","ngClass.lt-lg","ngClass.lt-xl","ngClass.gt-xs","ngClass.gt-sm","ngClass.gt-md","ngClass.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),Be([N()],zn.prototype,"disabled",void 0),Be([N()],zn.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zn,decorators:[{type:n,args:[{selector:"tb-sv-map-config",providers:[{provide:B,useExisting:a((()=>zn)),multi:!0},{provide:K,useExisting:a((()=>zn)),multi:!0}],template:'
\n
\n
{{ labelText }}
\n
\n tb.rulenode.map-fields-required\n
\n
\n {{ requiredText }}\n
\n
\n
\n
\n
\n
{{ selectText }}
\n
{{ valText }}
\n
\n
\n
\n
\n \n \n \n {{option.name}}\n \n \n \n \n \n \n
\n \n
\n
\n
\n
\n
\n
\n \n
\n \n
\n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:t.Injector},{type:R.FormBuilder}]},propDecorators:{selectOptions:[{type:i}],disabled:[{type:i}],labelText:[{type:i}],requiredText:[{type:i}],targetKeyPrefix:[{type:i}],selectText:[{type:i}],selectRequiredText:[{type:i}],valText:[{type:i}],valRequiredText:[{type:i}],hintText:[{type:i}],popupHelpLink:[{type:i}],required:[{type:i}]}});class Un extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(v),this.directionTypeTranslations=C,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[O.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}}e("RelationsQueryConfigOldComponent",Un),Un.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Un,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Un.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Un,selector:"tb-relations-query-config-old",inputs:{disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>Un)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Ye.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Un,decorators:[{type:n,args:[{selector:"tb-relations-query-config-old",providers:[{provide:B,useExisting:a((()=>Un)),multi:!0}],template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class Hn{constructor(e,t){this.translate=e,this.fb=t,this.propagateChange=e=>{},this.destroy$=new Ke,this.separatorKeysCodes=[ge,ye,xe],this.onTouched=()=>{}}ngOnInit(){this.attributeControlGroup=this.fb.group({clientAttributeNames:[[],[]],sharedAttributeNames:[[],[]],serverAttributeNames:[[],[]],latestTsKeyNames:[[],[]],getLatestValueWithTs:[!1,[]]},{validators:this.atLeastOne(O.required,["clientAttributeNames","sharedAttributeNames","serverAttributeNames","latestTsKeyNames"])}),this.attributeControlGroup.valueChanges.pipe(Ve(this.destroy$)).subscribe((e=>{this.propagateChange(this.preparePropagateValue(e))}))}preparePropagateValue(e){const t={};for(const n in e)t[n]="getLatestValueWithTs"===n||ae(e[n])?e[n]:[];return t}validate(){return this.attributeControlGroup.valid?null:{atLeastOneRequired:!0}}atLeastOne(e,t=null){return n=>{t||(t=Object.keys(n.controls));return n?.controls&&t.some((t=>!e(n.controls[t])))?null:{atLeastOne:!0}}}writeValue(e){this.attributeControlGroup.setValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){this.onTouched=e}setDisabledState(e){e?this.attributeControlGroup.disable({emitEvent:!1}):this.attributeControlGroup.enable({emitEvent:!1})}ngOnDestroy(){this.destroy$.next(null),this.destroy$.complete()}}e("SelectAttributesComponent",Hn),Hn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hn,deps:[{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Hn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Hn,selector:"tb-select-attributes",inputs:{popupHelpLink:"popupHelpLink"},providers:[{provide:B,useExisting:a((()=>Hn)),multi:!0},{provide:K,useExisting:Hn,multi:!0}],ngImport:t,template:'
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n {{ \'tb.rulenode.fetch-latest-telemetry-with-timestamp\' | translate }}\n \n
\n
\n\n\n help\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:H.NgTemplateOutlet,selector:"[ngTemplateOutlet]",inputs:["ngTemplateOutletContext","ngTemplateOutlet","ngTemplateOutletInjector"]},{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hn,decorators:[{type:n,args:[{selector:"tb-select-attributes",providers:[{provide:B,useExisting:a((()=>Hn)),multi:!0},{provide:K,useExisting:Hn,multi:!0}],template:'
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n {{ \'tb.rulenode.fetch-latest-telemetry-with-timestamp\' | translate }}\n \n
\n
\n\n\n help\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:Z.TranslateService},{type:R.FormBuilder}]},propDecorators:{popupHelpLink:[{type:i}]}});class jn extends L{constructor(e,t){super(e),this.store=e,this.fb=t,this.propagateChange=null,this.destroy$=new Ke,this.alarmStatus=q,this.alarmStatusTranslations=A}ngOnInit(){this.alarmStatusGroup=this.fb.group({alarmStatus:[null,[]]}),this.alarmStatusGroup.get("alarmStatus").valueChanges.pipe(Ve(this.destroy$)).subscribe((e=>{this.propagateChange(e)}))}setDisabledState(e){e?this.alarmStatusGroup.disable({emitEvent:!1}):this.alarmStatusGroup.enable({emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}writeValue(e){this.alarmStatusGroup.get("alarmStatus").patchValue(e,{emitEvent:!1})}}e("AlarmStatusSelectComponent",jn),jn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:jn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),jn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:jn,selector:"tb-alarm-status-select",providers:[{provide:B,useExisting:a((()=>jn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n
\n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_ACK) | translate }}\n \n
\n
\n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_ACK) | translate }}\n \n
\n
\n
\n',styles:[":host .chip-listbox{max-width:460px;width:100%}:host .chip-listbox .toggle-column{display:flex;flex:1 1 100%;gap:8px}:host .chip-listbox .option{margin:0}@media screen and (max-width: 959px){:host .chip-listbox{max-width:360px}:host .chip-listbox .toggle-column{flex-direction:column}}:host ::ng-deep .chip-listbox .mdc-evolution-chip-set__chips{gap:8px}:host ::ng-deep .chip-listbox .option button{flex-basis:100%;justify-content:start}:host ::ng-deep .chip-listbox .option .mdc-evolution-chip__graphic{flex-grow:0}\n"],dependencies:[{kind:"component",type:be.MatChipListbox,selector:"mat-chip-listbox",inputs:["tabIndex","multiple","aria-orientation","selectable","compareWith","required","hideSingleSelectionIndicator","value"],outputs:["change"]},{kind:"component",type:be.MatChipOption,selector:"mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]",inputs:["color","disabled","disableRipple","tabIndex","selectable","selected"],outputs:["selectionChange"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:jn,decorators:[{type:n,args:[{selector:"tb-alarm-status-select",providers:[{provide:B,useExisting:a((()=>jn)),multi:!0}],template:'
\n \n
\n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_ACK) | translate }}\n \n
\n
\n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_ACK) | translate }}\n \n
\n
\n
\n',styles:[":host .chip-listbox{max-width:460px;width:100%}:host .chip-listbox .toggle-column{display:flex;flex:1 1 100%;gap:8px}:host .chip-listbox .option{margin:0}@media screen and (max-width: 959px){:host .chip-listbox{max-width:360px}:host .chip-listbox .toggle-column{flex-direction:column}}:host ::ng-deep .chip-listbox .mdc-evolution-chip-set__chips{gap:8px}:host ::ng-deep .chip-listbox .option button{flex-basis:100%;justify-content:start}:host ::ng-deep .chip-listbox .option .mdc-evolution-chip__graphic{flex-grow:0}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class $n{}e("RulenodeCoreConfigCommonModule",$n),$n.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:$n,deps:[],target:t.ɵɵFactoryTarget.NgModule}),$n.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:$n,declarations:[Mn,En,Gn,Dn,wn,In,Nn,Bn,Cn,Kn,zn,Un,Hn,jn,An],imports:[$,M,_e],exports:[Mn,En,Gn,Dn,wn,In,Nn,Bn,Cn,Kn,zn,Un,Hn,jn,An]}),$n.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:$n,imports:[$,M,_e]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:$n,decorators:[{type:c,args:[{declarations:[Mn,En,Gn,Dn,wn,In,Nn,Bn,Cn,Kn,zn,Un,Hn,jn,An],imports:[$,M,_e],exports:[Mn,En,Gn,Dn,wn,In,Nn,Bn,Cn,Kn,zn,Un,Hn,jn,An]}]}]});class Qn{}e("RuleNodeCoreConfigActionModule",Qn),Qn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Qn,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Qn.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Qn,declarations:[Tn,ct,kn,vn,fn,ut,ft,gt,yt,yn,xt,ht,cn,gn,hn,Fn,Ln,bt,bn,xn,Sn,qn],imports:[$,M,_e,$n],exports:[Tn,ct,kn,vn,fn,ut,ft,gt,yt,yn,xt,ht,cn,gn,hn,Fn,Ln,bt,bn,xn,Sn,qn]}),Qn.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Qn,imports:[$,M,_e,$n]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Qn,decorators:[{type:c,args:[{declarations:[Tn,ct,kn,vn,fn,ut,ft,gt,yt,yn,xt,ht,cn,gn,hn,Fn,Ln,bt,bn,xn,Sn,qn],imports:[$,M,_e,$n],exports:[Tn,ct,kn,vn,fn,ut,ft,gt,yt,yn,xt,ht,cn,gn,hn,Fn,Ln,bt,bn,xn,Sn,qn]}]}]});class Jn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[ge,ye,xe]}configForm(){return this.calculateDeltaConfigForm}onConfigurationSet(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e.inputValueKey,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],outputValueKey:[e.outputValueKey,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],useCache:[e.useCache,[]],addPeriodBetweenMsgs:[e.addPeriodBetweenMsgs,[]],periodValueKey:[e.periodValueKey,[]],round:[e.round,[O.min(0),O.max(15)]],tellFailureIfDeltaIsNegative:[e.tellFailureIfDeltaIsNegative,[]]})}prepareInputConfig(e){return{inputValueKey:ae(e?.inputValueKey)?e.inputValueKey:null,outputValueKey:ae(e?.outputValueKey)?e.outputValueKey:null,useCache:!ae(e?.useCache)||e.useCache,addPeriodBetweenMsgs:!!ae(e?.addPeriodBetweenMsgs)&&e.addPeriodBetweenMsgs,periodValueKey:ae(e?.periodValueKey)?e.periodValueKey:null,round:ae(e?.round)?e.round:null,tellFailureIfDeltaIsNegative:!ae(e?.tellFailureIfDeltaIsNegative)||e.tellFailureIfDeltaIsNegative}}prepareOutputConfig(e){return le(e)}updateValidators(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([O.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["addPeriodBetweenMsgs"]}}e("CalculateDeltaConfigComponent",Jn),Jn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Jn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Jn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Jn,selector:"tb-enrichment-node-calculate-delta-config",usesInheritance:!0,ngImport:t,template:"
\n
\n \n {{ 'tb.rulenode.input-value-key' | translate }}\n \n \n {{ 'tb.rulenode.input-value-key-required' | translate }}\n \n \n \n {{ 'tb.rulenode.output-value-key' | translate }}\n \n \n {{ 'tb.rulenode.output-value-key-required' | translate }}\n \n \n
\n \n {{ 'tb.rulenode.number-of-digits-after-floating-point' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n
\n
\n \n {{ 'tb.rulenode.failure-if-delta-negative' | translate }}\n \n
\n
\n \n {{ 'tb.rulenode.use-caching' | translate }}\n \n
\n
\n
\n \n {{ 'tb.rulenode.add-time-difference-between-readings' | translate:\n { inputValueKey: calculateDeltaConfigForm.get('inputValueKey').valid ?\n calculateDeltaConfigForm.get('inputValueKey').value : 'tb.rulenode.input-value-key' | translate } }}\n \n
\n \n {{ 'tb.rulenode.period-value-key' | translate }}\n \n \n {{ 'tb.rulenode.period-value-key-required' | translate }}\n \n \n
\n
\n
\n",dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Jn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-calculate-delta-config",template:"
\n
\n \n {{ 'tb.rulenode.input-value-key' | translate }}\n \n \n {{ 'tb.rulenode.input-value-key-required' | translate }}\n \n \n \n {{ 'tb.rulenode.output-value-key' | translate }}\n \n \n {{ 'tb.rulenode.output-value-key-required' | translate }}\n \n \n
\n \n {{ 'tb.rulenode.number-of-digits-after-floating-point' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n
\n
\n \n {{ 'tb.rulenode.failure-if-delta-negative' | translate }}\n \n
\n
\n \n {{ 'tb.rulenode.use-caching' | translate }}\n \n
\n
\n
\n \n {{ 'tb.rulenode.add-time-difference-between-readings' | translate:\n { inputValueKey: calculateDeltaConfigForm.get('inputValueKey').valid ?\n calculateDeltaConfigForm.get('inputValueKey').value : 'tb.rulenode.input-value-key' | translate } }}\n \n
\n \n {{ 'tb.rulenode.period-value-key' | translate }}\n \n \n {{ 'tb.rulenode.period-value-key-required' | translate }}\n \n \n
\n
\n
\n"}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Yn extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=Pt;for(const e of Rt.keys())e!==Pt.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(Rt.get(e))})}configForm(){return this.customerAttributesConfigForm}prepareOutputConfig(e){const t={};for(const n of Object.keys(e.dataMapping))t[n.trim()]=e.dataMapping[n];return e.dataMapping=t,le(e)}prepareInputConfig(e){let t,n;return t=ae(e?.telemetry)?e.telemetry?Pt.LATEST_TELEMETRY:Pt.ATTRIBUTES:ae(e?.dataToFetch)?e.dataToFetch:Pt.ATTRIBUTES,n=ae(e?.attrMapping)?e.attrMapping:ae(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA}}selectTranslation(e,t){return this.customerAttributesConfigForm.get("dataToFetch").value===Pt.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.customerAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[O.required]],fetchTo:[e.fetchTo]})}}e("CustomerAttributesConfigComponent",Yn),Yn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Yn,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Yn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Yn,selector:"tb-enrichment-node-customer-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n
tb.rulenode.mapping-of-customers
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Mn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Yn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
\n
tb.rulenode.mapping-of-customers
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Wn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n}configForm(){return this.deviceAttributesConfigForm}onConfigurationSet(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e.deviceRelationsQuery,[O.required]],tellFailureIfAbsent:[e.tellFailureIfAbsent,[]],fetchTo:[e.fetchTo,[]],attributesControl:[e.attributesControl,[]]})}prepareInputConfig(e){return se(e)&&(e.attributesControl={clientAttributeNames:ae(e?.clientAttributeNames)?e.clientAttributeNames:[],latestTsKeyNames:ae(e?.latestTsKeyNames)?e.latestTsKeyNames:[],serverAttributeNames:ae(e?.serverAttributeNames)?e.serverAttributeNames:[],sharedAttributeNames:ae(e?.sharedAttributeNames)?e.sharedAttributeNames:[],getLatestValueWithTs:!!ae(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{deviceRelationsQuery:ae(e?.deviceRelationsQuery)?e.deviceRelationsQuery:null,tellFailureIfAbsent:!ae(e?.tellFailureIfAbsent)||e.tellFailureIfAbsent,fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA,attributesControl:e?e.attributesControl:null}}prepareOutputConfig(e){for(const t of Object.keys(e.attributesControl))e[t]=e.attributesControl[t];return delete e.attributesControl,e}}e("DeviceAttributesConfigComponent",Wn),Wn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Wn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Wn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Wn,selector:"tb-enrichment-node-device-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
tb.rulenode.device-relations-query
\n \n \n
\n
\n
\n
tb.rulenode.related-device-attributes
\n
\n tb.rulenode.at-least-one-field-required\n
\n
\n \n \n
\n
\n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:En,selector:"tb-device-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:Hn,selector:"tb-select-attributes",inputs:["popupHelpLink"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Wn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
\n
\n
tb.rulenode.device-relations-query
\n \n \n
\n
\n
\n
tb.rulenode.related-device-attributes
\n
\n tb.rulenode.at-least-one-field-required\n
\n
\n \n \n
\n
\n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Zn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.predefinedValues=[];for(const e of Object.keys(Mt))this.predefinedValues.push({value:Mt[e],name:this.translate.instant(Et.get(Mt[e]))})}ngOnInit(){super.ngOnInit()}configForm(){return this.entityDetailsConfigForm}prepareInputConfig(e){let t;return t=ae(e?.addToMetadata)?e.addToMetadata?en.METADATA:en.DATA:e?.fetchTo?e.fetchTo:en.DATA,{detailsList:ae(e?.detailsList)?e.detailsList:null,fetchTo:t}}onConfigurationSet(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e.detailsList,[O.required]],fetchTo:[e.fetchTo,[]]})}}e("EntityDetailsConfigComponent",Zn),Zn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Zn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Zn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Zn,selector:"tb-enrichment-node-entity-details-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n help\n \n \n \n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Zn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
\n \n \n help\n \n \n \n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Xn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[ge,ye,xe],this.aggregationTypes=E,this.aggregations=Object.values(E),this.aggregationTypesTranslations=G,this.fetchMode=Gt,this.samplingOrders=Object.values(Vt),this.samplingOrdersTranslate=_t,this.timeUnits=Object.values(Nt),this.timeUnitsTranslationMap=St,this.deduplicationStrategiesHintTranslations=wt,this.headerOptions=[],this.timeUnitMap={[Nt.MILLISECONDS]:1,[Nt.SECONDS]:1e3,[Nt.MINUTES]:6e4,[Nt.HOURS]:36e5,[Nt.DAYS]:864e5},this.intervalValidator=()=>e=>e.get("startInterval").value*this.timeUnitMap[e.get("startIntervalTimeUnit").value]<=e.get("endInterval").value*this.timeUnitMap[e.get("endIntervalTimeUnit").value]?{intervalError:!0}:null;for(const e of Dt.keys())this.headerOptions.push({value:e,name:this.translate.instant(Dt.get(e))})}configForm(){return this.getTelemetryFromDatabaseConfigForm}onConfigurationSet(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e.latestTsKeyNames,[O.required]],aggregation:[e.aggregation,[O.required]],fetchMode:[e.fetchMode,[O.required]],orderBy:[e.orderBy,[]],limit:[e.limit,[]],useMetadataIntervalPatterns:[e.useMetadataIntervalPatterns,[]],interval:this.fb.group({startInterval:[e.interval.startInterval,[]],startIntervalTimeUnit:[e.interval.startIntervalTimeUnit,[]],endInterval:[e.interval.endInterval,[]],endIntervalTimeUnit:[e.interval.endIntervalTimeUnit,[]]}),startIntervalPattern:[e.startIntervalPattern,[]],endIntervalPattern:[e.endIntervalPattern,[]]})}validatorTriggers(){return["fetchMode","useMetadataIntervalPatterns"]}toggleChange(e){this.getTelemetryFromDatabaseConfigForm.get("fetchMode").patchValue(e,{emitEvent:!0})}prepareOutputConfig(e){return e.startInterval=e.interval.startInterval,e.startIntervalTimeUnit=e.interval.startIntervalTimeUnit,e.endInterval=e.interval.endInterval,e.endIntervalTimeUnit=e.interval.endIntervalTimeUnit,delete e.interval,le(e)}prepareInputConfig(e){return se(e)&&(e.interval={startInterval:e.startInterval,startIntervalTimeUnit:e.startIntervalTimeUnit,endInterval:e.endInterval,endIntervalTimeUnit:e.endIntervalTimeUnit}),{latestTsKeyNames:ae(e?.latestTsKeyNames)?e.latestTsKeyNames:null,aggregation:ae(e?.aggregation)?e.aggregation:E.NONE,fetchMode:ae(e?.fetchMode)?e.fetchMode:Gt.FIRST,orderBy:ae(e?.orderBy)?e.orderBy:Vt.ASC,limit:ae(e?.limit)?e.limit:1e3,useMetadataIntervalPatterns:!!ae(e?.useMetadataIntervalPatterns)&&e.useMetadataIntervalPatterns,interval:{startInterval:ae(e?.interval?.startInterval)?e.interval.startInterval:2,startIntervalTimeUnit:ae(e?.interval?.startIntervalTimeUnit)?e.interval.startIntervalTimeUnit:Nt.MINUTES,endInterval:ae(e?.interval?.endInterval)?e.interval.endInterval:1,endIntervalTimeUnit:ae(e?.interval?.endIntervalTimeUnit)?e.interval.endIntervalTimeUnit:Nt.MINUTES},startIntervalPattern:ae(e?.startIntervalPattern)?e.startIntervalPattern:null,endIntervalPattern:ae(e?.endIntervalPattern)?e.endIntervalPattern:null}}updateValidators(e){const t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,n=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===Gt.ALL?(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([O.required,O.min(2),O.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),n?(this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)])):(this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").setValidators([O.required,O.min(1),O.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").setValidators([O.required,O.min(1),O.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("interval").setValidators([this.intervalValidator()]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("aggregation").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})}removeKey(e,t){const n=this.getTelemetryFromDatabaseConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(n,{emitEvent:!0}))}clearChipGrid(){this.getTelemetryFromDatabaseConfigForm.get("latestTsKeyNames").patchValue([],{emitEvent:!0})}addKey(e,t){const n=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.getTelemetryFromDatabaseConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(e,{emitEvent:!0}))}n&&(n.value="")}defaultPaddingEnable(){return this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value===Gt.ALL&&this.getTelemetryFromDatabaseConfigForm.get("aggregation").value===E.NONE}}e("GetTelemetryFromDatabaseConfigComponent",Xn),Xn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Xn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Xn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Xn,selector:"tb-enrichment-node-get-telemetry-from-database",usesInheritance:!0,ngImport:t,template:'
\n \n
\n help\n \n
\n
\n
tb.rulenode.fetch-interval
\n
\n \n {{ \'tb.rulenode.use-metadata-dynamic-interval\' | translate }}\n \n
\n
\n
\n \n {{ \'tb.rulenode.interval-start\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n {{ \'tb.rulenode.interval-end\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n error_outline\n
\n \n {{ \'tb.rulenode.fetch-timeseries-from-to\' | translate:\n {\n startInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.startInterval\').value,\n endInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.endInterval\').value,\n startIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.startIntervalTimeUnit\').value.toLowerCase(),\n endIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.endIntervalTimeUnit\').value.toLowerCase()\n } }}\n \n \n {{ "tb.rulenode.fetch-timeseries-from-to-invalid" | translate }}\n \n
\n
\n
\n \n
\n \n {{ \'tb.rulenode.start-interval\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.end-interval\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-required\' | translate }}\n \n \n \n \n
\n
\n
\n
\n
tb.rulenode.fetch-strategy
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get(\'fetchMode\').value) | translate }}\n
\n
\n
\n \n {{ \'aggregation.function\' | translate }}\n \n \n {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}\n \n \n \n
\n \n {{ "tb.rulenode.order-by-timestamp" | translate }} \n \n \n {{ samplingOrdersTranslate.get(order) | translate }}\n \n \n \n \n {{ "tb.rulenode.limit" | translate }}\n \n {{ "tb.rulenode.limit-hint" | translate }}\n \n {{ \'tb.rulenode.limit-required\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n
\n
\n
\n
\n',styles:[":host .see-example{display:inline-block}:host .description-block{display:flex;align-items:center;border-radius:6px;border:1px solid #EAEAEA}:host .description-block .description-icon{font-size:24px;height:24px;min-height:24px;width:24px;min-width:24px;line-height:24px;color:#d9d9d9;margin:4px}:host .description-block .description-text{font-size:12px;line-height:16px;letter-spacing:.25px;margin:6px}:host .description-block.error{color:var(--mdc-theme-error, #f44336)}:host .description-block.error .description-icon{color:var(--mdc-theme-error, #f44336)}:host .item-center{align-items:center}:host .item-center .fetch-mod-toggle{width:100%}:host .hint-container{width:100%}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:$e.HelpPopupComponent,selector:"[tb-help-popup], [tb-help-popup-content]",inputs:["tb-help-popup","tb-help-popup-content","trigger-text","trigger-style","tb-help-popup-placement","tb-help-popup-style","hintMode"]},{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:R.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Xn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n
\n help\n \n
\n
\n
tb.rulenode.fetch-interval
\n
\n \n {{ \'tb.rulenode.use-metadata-dynamic-interval\' | translate }}\n \n
\n
\n
\n \n {{ \'tb.rulenode.interval-start\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n {{ \'tb.rulenode.interval-end\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n error_outline\n
\n \n {{ \'tb.rulenode.fetch-timeseries-from-to\' | translate:\n {\n startInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.startInterval\').value,\n endInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.endInterval\').value,\n startIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.startIntervalTimeUnit\').value.toLowerCase(),\n endIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.endIntervalTimeUnit\').value.toLowerCase()\n } }}\n \n \n {{ "tb.rulenode.fetch-timeseries-from-to-invalid" | translate }}\n \n
\n
\n
\n \n
\n \n {{ \'tb.rulenode.start-interval\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.end-interval\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-required\' | translate }}\n \n \n \n \n
\n
\n
\n
\n
tb.rulenode.fetch-strategy
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get(\'fetchMode\').value) | translate }}\n
\n
\n
\n \n {{ \'aggregation.function\' | translate }}\n \n \n {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}\n \n \n \n
\n \n {{ "tb.rulenode.order-by-timestamp" | translate }} \n \n \n {{ samplingOrdersTranslate.get(order) | translate }}\n \n \n \n \n {{ "tb.rulenode.limit" | translate }}\n \n {{ "tb.rulenode.limit-hint" | translate }}\n \n {{ \'tb.rulenode.limit-required\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n
\n
\n
\n
\n',styles:[":host .see-example{display:inline-block}:host .description-block{display:flex;align-items:center;border-radius:6px;border:1px solid #EAEAEA}:host .description-block .description-icon{font-size:24px;height:24px;min-height:24px;width:24px;min-width:24px;line-height:24px;color:#d9d9d9;margin:4px}:host .description-block .description-text{font-size:12px;line-height:16px;letter-spacing:.25px;margin:6px}:host .description-block.error{color:var(--mdc-theme-error, #f44336)}:host .description-block.error .description-icon{color:var(--mdc-theme-error, #f44336)}:host .item-center{align-items:center}:host .item-center .fetch-mod-toggle{width:100%}:host .hint-container{width:100%}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class er extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n}configForm(){return this.originatorAttributesConfigForm}onConfigurationSet(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[e.tellFailureIfAbsent,[]],fetchTo:[e.fetchTo,[]],attributesControl:[e.attributesControl,[]]})}prepareInputConfig(e){return se(e)&&(e.attributesControl={clientAttributeNames:ae(e?.clientAttributeNames)?e.clientAttributeNames:[],latestTsKeyNames:ae(e?.latestTsKeyNames)?e.latestTsKeyNames:[],serverAttributeNames:ae(e?.serverAttributeNames)?e.serverAttributeNames:[],sharedAttributeNames:ae(e?.sharedAttributeNames)?e.sharedAttributeNames:[],getLatestValueWithTs:!!ae(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA,tellFailureIfAbsent:!!ae(e?.tellFailureIfAbsent)&&e.tellFailureIfAbsent,attributesControl:ae(e?.attributesControl)?e.attributesControl:null}}prepareOutputConfig(e){for(const t of Object.keys(e.attributesControl))e[t]=e.attributesControl[t];return delete e.attributesControl,e}}e("OriginatorAttributesConfigComponent",er),er.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:er,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),er.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:er,selector:"tb-enrichment-node-originator-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
\n
tb.rulenode.originator-attributes
\n
\n tb.rulenode.at-least-one-field-required\n
\n
\n \n \n \n \n
\n
\n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:Hn,selector:"tb-select-attributes",inputs:["popupHelpLink"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:er,decorators:[{type:n,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
\n
\n
\n
tb.rulenode.originator-attributes
\n
\n tb.rulenode.at-least-one-field-required\n
\n
\n \n \n \n \n
\n
\n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class tr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.originatorFields=[];for(const e of kt)this.originatorFields.push({value:e.value,name:this.translate.instant(e.name)})}configForm(){return this.originatorFieldsConfigForm}prepareOutputConfig(e){return le(e)}prepareInputConfig(e){return{dataMapping:ae(e?.dataMapping)?e.dataMapping:null,ignoreNullStrings:ae(e?.ignoreNullStrings)?e.ignoreNullStrings:null,fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA}}onConfigurationSet(e){this.originatorFieldsConfigForm=this.fb.group({dataMapping:[e.dataMapping,[O.required]],ignoreNullStrings:[e.ignoreNullStrings,[]],fetchTo:[e.fetchTo,[]]})}}e("OriginatorFieldsConfigComponent",tr),tr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:tr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),tr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:tr,selector:"tb-enrichment-node-originator-fields-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n
\n \n {{ \'tb.rulenode.skip-empty-fields\' | translate }}\n \n
\n
\n',dependencies:[{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:zn,selector:"tb-sv-map-config",inputs:["selectOptions","disabled","labelText","requiredText","targetKeyPrefix","selectText","selectRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:tr,decorators:[{type:n,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n \n
\n \n {{ \'tb.rulenode.skip-empty-fields\' | translate }}\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class nr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.DataToFetch=Pt,this.msgMetadataLabelTranslations=Ot,this.originatorFields=[],this.fetchToData=[];for(const e of Object.keys(kt))this.originatorFields.push({value:kt[e].value,name:this.translate.instant(kt[e].name)});for(const e of Rt.keys())this.fetchToData.push({value:e,name:this.translate.instant(Rt.get(e))})}configForm(){return this.relatedAttributesConfigForm}prepareOutputConfig(e){e.dataToFetch===Pt.FIELDS?(e.dataMapping=e.svMap,delete e.svMap):(e.dataMapping=e.kvMap,delete e.kvMap);const t={};if(e&&e.dataMapping)for(const n of Object.keys(e.dataMapping))t[n.trim()]=e.dataMapping[n];return e.dataMapping=t,delete e.svMap,delete e.kvMap,le(e)}prepareInputConfig(e){let t,n,r={[k.name.value]:`relatedEntity${this.translate.instant(k.name.name)}`},o={serialNumber:"sn"};return t=ae(e?.telemetry)?e.telemetry?Pt.LATEST_TELEMETRY:Pt.ATTRIBUTES:ae(e?.dataToFetch)?e.dataToFetch:Pt.ATTRIBUTES,n=ae(e?.attrMapping)?e.attrMapping:ae(e?.dataMapping)?e.dataMapping:null,t===Pt.FIELDS?r=n:o=n,{relationsQuery:ae(e?.relationsQuery)?e.relationsQuery:null,dataToFetch:t,svMap:r,kvMap:o,fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA}}selectTranslation(e,t){return this.relatedAttributesConfigForm.get("dataToFetch").value===Pt.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e.relationsQuery,[O.required]],dataToFetch:[e.dataToFetch,[]],kvMap:[e.kvMap,[O.required]],svMap:[e.svMap,[O.required]],fetchTo:[e.fetchTo,[]]})}validatorTriggers(){return["dataToFetch"]}updateValidators(e){this.relatedAttributesConfigForm.get("dataToFetch").value===Pt.FIELDS?(this.relatedAttributesConfigForm.get("svMap").enable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").disable({emitEvent:!1}),this.relatedAttributesConfigForm.get("svMap").updateValueAndValidity()):(this.relatedAttributesConfigForm.get("svMap").disable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").enable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").updateValueAndValidity())}}e("RelatedAttributesConfigComponent",nr),nr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:nr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),nr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:nr,selector:"tb-enrichment-node-related-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n
tb.rulenode.data-to-fetch
\n \n \n {{ data.name }}\n \n \n \n \n \n \n \n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Mn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:Gn,selector:"tb-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:zn,selector:"tb-sv-map-config",inputs:["selectOptions","disabled","labelText","requiredText","targetKeyPrefix","selectText","selectRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:nr,decorators:[{type:n,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n
\n
tb.rulenode.data-to-fetch
\n \n \n {{ data.name }}\n \n \n \n \n \n \n \n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class rr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=Pt;for(const e of Rt.keys())e!==Pt.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(Rt.get(e))})}configForm(){return this.tenantAttributesConfigForm}prepareInputConfig(e){let t,n;return t=ae(e?.telemetry)?e.telemetry?Pt.LATEST_TELEMETRY:Pt.ATTRIBUTES:ae(e?.dataToFetch)?e.dataToFetch:Pt.ATTRIBUTES,n=ae(e?.attrMapping)?e.attrMapping:ae(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA}}selectTranslation(e,t){return this.tenantAttributesConfigForm.get("dataToFetch").value===Pt.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.tenantAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[O.required]],fetchTo:[e.fetchTo,[]]})}}e("TenantAttributesConfigComponent",rr),rr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:rr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),rr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:rr,selector:"tb-enrichment-node-tenant-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n
tb.rulenode.mapping-of-tenant
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Mn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:rr,decorators:[{type:n,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n
tb.rulenode.mapping-of-tenant
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n \n \n \n \n
\n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class or extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.fetchDeviceCredentialsConfigForm}prepareInputConfig(e){return{fetchTo:ae(e?.fetchTo)?e.fetchTo:en.METADATA}}onConfigurationSet(e){this.fetchDeviceCredentialsConfigForm=this.fb.group({fetchTo:[e.fetchTo,[]]})}}e("FetchDeviceCredentialsConfigComponent",or),or.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:or,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),or.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:or,selector:"./tb-enrichment-node-fetch-device-credentials-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',dependencies:[{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:or,decorators:[{type:n,args:[{selector:"./tb-enrichment-node-fetch-device-credentials-config",template:'
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class ar{}e("RulenodeCoreConfigEnrichmentModule",ar),ar.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ar,deps:[],target:t.ɵɵFactoryTarget.NgModule}),ar.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:ar,declarations:[Yn,Zn,Wn,er,tr,Xn,nr,rr,Jn,or],imports:[$,M,$n],exports:[Yn,Zn,Wn,er,tr,Xn,nr,rr,Jn,or]}),ar.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ar,imports:[$,M,$n]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ar,decorators:[{type:c,args:[{declarations:[Yn,Zn,Wn,er,tr,Xn,nr,rr,Jn,or],imports:[$,M,$n],exports:[Yn,Zn,Wn,er,tr,Xn,nr,rr,Jn,or]}]}]});class ir extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.allAzureIotHubCredentialsTypes=Ht,this.azureIotHubCredentialsTypeTranslationsMap=jt}configForm(){return this.azureIotHubConfigForm}onConfigurationSet(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[O.required]],host:[e?e.host:null,[O.required]],port:[e?e.port:null,[O.required,O.min(1),O.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[O.required,O.min(1),O.max(200)]],clientId:[e?e.clientId:null,[O.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[O.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})}prepareOutputConfig(e){const t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e}validatorTriggers(){return["credentials.type"]}updateValidators(e){const t=this.azureIotHubConfigForm.get("credentials"),n=t.get("type").value;switch(e&&t.reset({type:n},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),n){case"sas":t.get("sasKey").setValidators([O.required]);break;case"cert.PEM":t.get("privateKey").setValidators([O.required]),t.get("privateKeyFileName").setValidators([O.required]),t.get("cert").setValidators([O.required]),t.get("certFileName").setValidators([O.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})}}e("AzureIotHubConfigComponent",ir),ir.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ir,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ir.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ir,selector:"tb-external-node-azure-iot-hub-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:H.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{kind:"directive",type:H.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:We.MatAccordion,selector:"mat-accordion",inputs:["multi","hideToggle","displayMode","togglePosition"],exportAs:["matAccordion"]},{kind:"component",type:We.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:We.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:We.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:We.MatExpansionPanelDescription,selector:"mat-panel-description"},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:R.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"component",type:Ze.FileInputComponent,selector:"tb-file-input",inputs:["label","hint","accept","noFileText","inputId","allowedExtensions","dropLabel","maxSizeByte","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ir,decorators:[{type:n,args:[{selector:"tb-external-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class lr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.ackValues=["all","-1","0","1"],this.ToByteStandartCharsetTypesValues=Qt,this.ToByteStandartCharsetTypeTranslationMap=Jt}configForm(){return this.kafkaConfigForm}onConfigurationSet(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[O.required]],keyPattern:[e?e.keyPattern:null],bootstrapServers:[e?e.bootstrapServers:null,[O.required]],retries:[e?e.retries:null,[O.min(0)]],batchSize:[e?e.batchSize:null,[O.min(0)]],linger:[e?e.linger:null,[O.min(0)]],bufferMemory:[e?e.bufferMemory:null,[O.min(0)]],acks:[e?e.acks:null,[O.required]],keySerializer:[e?e.keySerializer:null,[O.required]],valueSerializer:[e?e.valueSerializer:null,[O.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})}validatorTriggers(){return["addMetadataKeyValuesAsKafkaHeaders"]}updateValidators(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([O.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})}}e("KafkaConfigComponent",lr),lr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:lr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),lr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:lr,selector:"tb-external-node-kafka-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.key-pattern\n \n tb.rulenode.general-pattern-hint\n \n
tb.rulenode.key-pattern-hint
\n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:lr,decorators:[{type:n,args:[{selector:"tb-external-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.key-pattern\n \n tb.rulenode.general-pattern-hint\n \n
tb.rulenode.key-pattern-hint
\n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class sr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.mqttConfigForm}onConfigurationSet(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[O.required]],host:[e?e.host:null,[O.required]],port:[e?e.port:null,[O.required,O.min(1),O.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[O.required,O.min(1),O.max(200)]],clientId:[e?e.clientId:null,[]],appendClientIdSuffix:[{value:!!e&&e.appendClientIdSuffix,disabled:!(e&&me(e.clientId))},[]],cleanSession:[!!e&&e.cleanSession,[]],retainedMessage:[!!e&&e.retainedMessage,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]})}updateValidators(e){me(this.mqttConfigForm.get("clientId").value)?this.mqttConfigForm.get("appendClientIdSuffix").enable({emitEvent:!1}):this.mqttConfigForm.get("appendClientIdSuffix").disable({emitEvent:!1}),this.mqttConfigForm.get("appendClientIdSuffix").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["clientId"]}}e("MqttConfigComponent",sr),sr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:sr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),sr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:sr,selector:"tb-external-node-mqtt-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n {{\'tb.rulenode.client-id-hint\' | translate}}\n \n \n {{ \'tb.rulenode.append-client-id-suffix\' | translate }}\n \n
{{ "tb.rulenode.client-id-suffix-hint" | translate }}
\n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ "tb.rulenode.retained-message" | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n"],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:wn,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRequired"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:sr,decorators:[{type:n,args:[{selector:"tb-external-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n {{\'tb.rulenode.client-id-hint\' | translate}}\n \n \n {{ \'tb.rulenode.append-client-id-suffix\' | translate }}\n \n
{{ "tb.rulenode.client-id-suffix-hint" | translate }}
\n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ "tb.rulenode.retained-message" | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class mr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.notificationType=D,this.entityType=F}configForm(){return this.notificationConfigForm}onConfigurationSet(e){this.notificationConfigForm=this.fb.group({templateId:[e?e.templateId:null,[O.required]],targets:[e?e.targets:[],[O.required]]})}}e("NotificationConfigComponent",mr),mr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:mr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),mr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:mr,selector:"tb-external-node-notification-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n
\n',dependencies:[{kind:"component",type:tt.EntityListComponent,selector:"tb-entity-list",inputs:["entityType","subType","labelText","placeholderText","requiredText","required","disabled","subscriptSizing","hint"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:nt.TemplateAutocompleteComponent,selector:"tb-template-autocomplete",inputs:["required","allowCreate","allowEdit","disabled","notificationTypes"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:mr,decorators:[{type:n,args:[{selector:"tb-external-node-notification-config",template:'
\n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class pr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.pubSubConfigForm}onConfigurationSet(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[O.required]],topicName:[e?e.topicName:null,[O.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[O.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[O.required]],messageAttributes:[e?e.messageAttributes:null,[]]})}}e("PubSubConfigComponent",pr),pr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:pr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),pr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:pr,selector:"tb-external-node-pub-sub-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Ze.FileInputComponent,selector:"tb-file-input",inputs:["label","hint","accept","noFileText","inputId","allowedExtensions","dropLabel","maxSizeByte","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:pr,decorators:[{type:n,args:[{selector:"tb-external-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class dr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"]}configForm(){return this.rabbitMqConfigForm}onConfigurationSet(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[O.required]],port:[e?e.port:null,[O.required,O.min(1),O.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[O.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[O.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})}}e("RabbitMqConfigComponent",dr),dr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),dr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:dr,selector:"tb-external-node-rabbit-mq-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dr,decorators:[{type:n,args:[{selector:"tb-external-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ur extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.proxySchemes=["http","https"],this.httpRequestTypes=Object.keys($t)}configForm(){return this.restApiCallConfigForm}onConfigurationSet(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[O.required]],requestMethod:[e?e.requestMethod:null,[O.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],parseToPlainText:[!!e&&e.parseToPlainText,[]],ignoreRequestBody:[!!e&&e.ignoreRequestBody,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[O.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]],credentials:[e?e.credentials:null,[]]})}validatorTriggers(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]}updateValidators(e){const t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,n=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,r=this.restApiCallConfigForm.get("enableProxy").value,o=this.restApiCallConfigForm.get("useSystemProxyProperties").value;r&&!o?(this.restApiCallConfigForm.get("proxyHost").setValidators(r?[O.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(r?[O.required,O.min(1),O.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([O.min(0)])),n?this.restApiCallConfigForm.get("maxQueueSize").setValidators([O.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})}}e("RestApiCallConfigComponent",ur),ur.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ur,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ur.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ur,selector:"tb-external-node-rest-api-call-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n \n {{ \'tb.rulenode.parse-to-plain-text\' | translate }}\n \n
tb.rulenode.parse-to-plain-text-hint
\n \n {{ \'tb.rulenode.ignore-request-body\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:wn,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRequired"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ur,decorators:[{type:n,args:[{selector:"tb-external-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n \n {{ \'tb.rulenode.parse-to-plain-text\' | translate }}\n \n
tb.rulenode.parse-to-plain-text-hint
\n \n {{ \'tb.rulenode.ignore-request-body\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class cr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.smtpProtocols=["smtp","smtps"],this.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"]}configForm(){return this.sendEmailConfigForm}onConfigurationSet(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})}validatorTriggers(){return["useSystemSmtpSettings","enableProxy"]}updateValidators(e){const t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,n=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([O.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([O.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([O.required,O.min(1),O.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([O.required,O.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(n?[O.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(n?[O.required,O.min(1),O.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})}}e("SendEmailConfigComponent",cr),cr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),cr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:cr,selector:"tb-external-node-send-email-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n
\n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:rt.TbCheckboxComponent,selector:"tb-checkbox",inputs:["disabled","trueValue","falseValue"],outputs:["valueChange"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cr,decorators:[{type:n,args:[{selector:"tb-external-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class fr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.sendSmsConfigForm}onConfigurationSet(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[O.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[O.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})}validatorTriggers(){return["useSystemSmsSettings"]}updateValidators(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([O.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})}}e("SendSmsConfigComponent",fr),fr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),fr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:fr,selector:"tb-external-node-send-sms-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ot.SmsProviderConfigurationComponent,selector:"tb-sms-provider-configuration",inputs:["required","disabled"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fr,decorators:[{type:n,args:[{selector:"tb-external-node-send-sms-config",template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class gr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.slackChanelTypes=Object.keys(w),this.slackChanelTypesTranslateMap=V}configForm(){return this.slackConfigForm}onConfigurationSet(e){this.slackConfigForm=this.fb.group({botToken:[e?e.botToken:null],useSystemSettings:[!!e&&e.useSystemSettings],messageTemplate:[e?e.messageTemplate:null,[O.required]],conversationType:[e?e.conversationType:null,[O.required]],conversation:[e?e.conversation:null,[O.required]]})}validatorTriggers(){return["useSystemSettings"]}updateValidators(e){this.slackConfigForm.get("useSystemSettings").value?this.slackConfigForm.get("botToken").clearValidators():this.slackConfigForm.get("botToken").setValidators([O.required]),this.slackConfigForm.get("botToken").updateValueAndValidity({emitEvent:e})}}e("SlackConfigComponent",gr),gr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),gr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:gr,selector:"tb-external-node-slack-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.message-template\n \n \n {{ \'tb.rulenode.message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-slack-settings\' | translate }}\n \n \n tb.rulenode.slack-api-token\n \n \n {{ \'tb.rulenode.slack-api-token-required\' | translate }}\n \n \n \n \n \n {{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }}\n \n \n \n \n
\n',styles:[":host .tb-title{display:block;padding-bottom:6px}:host ::ng-deep .mat-mdc-radio-group{display:flex;flex-direction:row;margin-bottom:22px;gap:12px}:host ::ng-deep .mat-mdc-radio-group .mat-mdc-radio-button{flex:1 1 100%;padding:4px;border:1px solid rgba(0,0,0,.12);border-radius:6px}@media screen and (max-width: 599px){:host ::ng-deep .mat-mdc-radio-group{flex-direction:column}}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:at.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{kind:"component",type:at.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:it.SlackConversationAutocompleteComponent,selector:"tb-slack-conversation-autocomplete",inputs:["labelText","requiredText","required","disabled","slackChanelType","token"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gr,decorators:[{type:n,args:[{selector:"tb-external-node-slack-config",template:'
\n \n tb.rulenode.message-template\n \n \n {{ \'tb.rulenode.message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-slack-settings\' | translate }}\n \n \n tb.rulenode.slack-api-token\n \n \n {{ \'tb.rulenode.slack-api-token-required\' | translate }}\n \n \n \n \n \n {{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }}\n \n \n \n \n
\n',styles:[":host .tb-title{display:block;padding-bottom:6px}:host ::ng-deep .mat-mdc-radio-group{display:flex;flex-direction:row;margin-bottom:22px;gap:12px}:host ::ng-deep .mat-mdc-radio-group .mat-mdc-radio-button{flex:1 1 100%;padding:4px;border:1px solid rgba(0,0,0,.12);border-radius:6px}@media screen and (max-width: 599px){:host ::ng-deep .mat-mdc-radio-group{flex-direction:column}}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class yr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.snsConfigForm}onConfigurationSet(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[O.required]],accessKeyId:[e?e.accessKeyId:null,[O.required]],secretAccessKey:[e?e.secretAccessKey:null,[O.required]],region:[e?e.region:null,[O.required]]})}}e("SnsConfigComponent",yr),yr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),yr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:yr,selector:"tb-external-node-sns-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yr,decorators:[{type:n,args:[{selector:"tb-external-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class xr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.sqsQueueType=Bt,this.sqsQueueTypes=Object.keys(Bt),this.sqsQueueTypeTranslationsMap=Kt}configForm(){return this.sqsConfigForm}onConfigurationSet(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[O.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[O.required]],delaySeconds:[e?e.delaySeconds:null,[O.min(0),O.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[O.required]],secretAccessKey:[e?e.secretAccessKey:null,[O.required]],region:[e?e.region:null,[O.required]]})}}e("SqsConfigComponent",xr),xr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),xr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:xr,selector:"tb-external-node-sqs-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xr,decorators:[{type:n,args:[{selector:"tb-external-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class br{}e("RulenodeCoreConfigExternalModule",br),br.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:br,deps:[],target:t.ɵɵFactoryTarget.NgModule}),br.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:br,declarations:[yr,xr,pr,lr,sr,mr,dr,ur,cr,ir,fr,gr],imports:[$,M,_e,$n],exports:[yr,xr,pr,lr,sr,mr,dr,ur,cr,ir,fr,gr]}),br.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:br,imports:[$,M,_e,$n]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:br,decorators:[{type:c,args:[{declarations:[yr,xr,pr,lr,sr,mr,dr,ur,cr,ir,fr,gr],imports:[$,M,_e,$n],exports:[yr,xr,pr,lr,sr,mr,dr,ur,cr,ir,fr,gr]}]}]});class hr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.searchText=""}configForm(){return this.alarmStatusConfigForm}prepareInputConfig(e){return{alarmStatusList:ae(e?.alarmStatusList)?e.alarmStatusList:null}}onConfigurationSet(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e.alarmStatusList,[O.required]]})}}e("CheckAlarmStatusComponent",hr),hr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),hr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:hr,selector:"tb-filter-node-check-alarm-status-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
tb.rulenode.alarm-status
\n
\n tb.rulenode.alarm-required\n
\n
\n \n
\n\n\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:jn,selector:"tb-alarm-status-select"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hr,decorators:[{type:n,args:[{selector:"tb-filter-node-check-alarm-status-config",template:'
\n
\n
tb.rulenode.alarm-status
\n
\n tb.rulenode.alarm-required\n
\n
\n \n
\n\n\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class vr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.checkMessageConfigForm}prepareInputConfig(e){return{messageNames:ae(e?.messageNames)?e.messageNames:[],metadataNames:ae(e?.metadataNames)?e.metadataNames:[],checkAllKeys:!!ae(e?.checkAllKeys)&&e.checkAllKeys}}prepareOutputConfig(e){return{messageNames:ae(e?.messageNames)?e.messageNames:[],metadataNames:ae(e?.metadataNames)?e.metadataNames:[],checkAllKeys:e.checkAllKeys}}atLeastOne(e,t=null){return n=>{t||(t=Object.keys(n.controls));return n?.controls&&t.some((t=>!e(n.controls[t])))?null:{atLeastOne:!0}}}onConfigurationSet(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e.messageNames,[]],metadataNames:[e.metadataNames,[]],checkAllKeys:[e.checkAllKeys,[]]},{validators:this.atLeastOne(O.required,["messageNames","metadataNames"])})}get touchedValidationControl(){return["messageNames","metadataNames"].some((e=>this.checkMessageConfigForm.get(e).touched))}}e("CheckMessageConfigComponent",vr),vr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),vr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:vr,selector:"tb-filter-node-check-message-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
tb.rulenode.fields-to-check
\n
\n tb.rulenode.at-least-one-field-required\n
\n
\n \n help\n \n \n help\n \n
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vr,decorators:[{type:n,args:[{selector:"tb-filter-node-check-message-config",template:'
\n
\n
tb.rulenode.fields-to-check
\n
\n tb.rulenode.at-least-one-field-required\n
\n
\n \n help\n \n \n help\n \n
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Cr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.entitySearchDirection=Object.values(v),this.entitySearchDirectionTranslationsMap=C}configForm(){return this.checkRelationConfigForm}prepareInputConfig(e){return{checkForSingleEntity:!!ae(e?.checkForSingleEntity)&&e.checkForSingleEntity,direction:ae(e?.direction)?e.direction:null,entityType:ae(e?.entityType)?e.entityType:null,entityId:ae(e?.entityId)?e.entityId:null,relationType:ae(e?.relationType)?e.relationType:null}}onConfigurationSet(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[e.checkForSingleEntity,[]],direction:[e.direction,[]],entityType:[e.entityType,e&&e.checkForSingleEntity?[O.required]:[]],entityId:[e.entityId,e&&e.checkForSingleEntity?[O.required]:[]],relationType:[e.relationType,[O.required]]})}validatorTriggers(){return["checkForSingleEntity"]}updateValidators(e){const t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[O.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[O.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})}}e("CheckRelationConfigComponent",Cr),Cr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Cr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Cr,selector:"tb-filter-node-check-relation-config",usesInheritance:!0,ngImport:t,template:'
\n
tb.rulenode.relation-search-parameters
\n
\n \n {{ \'relation.direction\' | translate }}\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n \n
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
\n
\n \n \n \n \n
\n
\n
\n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:lt.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","useFullEntityId","appearance","required","disabled"],outputs:["entityChanged"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:Je.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["showLabel","additionalClasses","appearance","required","disabled","subscriptSizing"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cr,decorators:[{type:n,args:[{selector:"tb-filter-node-check-relation-config",template:'
\n
tb.rulenode.relation-search-parameters
\n
\n \n {{ \'relation.direction\' | translate }}\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n \n
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
\n
\n \n \n \n \n
\n
\n
\n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Fr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Tt,this.perimeterTypes=Object.values(Tt),this.perimeterTypeTranslationMap=It,this.rangeUnits=Object.values(qt),this.rangeUnitTranslationMap=At,this.defaultPaddingEnable=!0}configForm(){return this.geoFilterConfigForm}prepareInputConfig(e){return{latitudeKeyName:ae(e?.latitudeKeyName)?e.latitudeKeyName:null,longitudeKeyName:ae(e?.longitudeKeyName)?e.longitudeKeyName:null,perimeterType:ae(e?.perimeterType)?e.perimeterType:null,fetchPerimeterInfoFromMessageMetadata:!!ae(e?.fetchPerimeterInfoFromMessageMetadata)&&e.fetchPerimeterInfoFromMessageMetadata,perimeterKeyName:ae(e?.perimeterKeyName)?e.perimeterKeyName:null,centerLatitude:ae(e?.centerLatitude)?e.centerLatitude:null,centerLongitude:ae(e?.centerLongitude)?e.centerLongitude:null,range:ae(e?.range)?e.range:null,rangeUnit:ae(e?.rangeUnit)?e.rangeUnit:null,polygonsDefinition:ae(e?.polygonsDefinition)?e.polygonsDefinition:null}}onConfigurationSet(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e.latitudeKeyName,[O.required]],longitudeKeyName:[e.longitudeKeyName,[O.required]],perimeterType:[e.perimeterType,[O.required]],fetchPerimeterInfoFromMessageMetadata:[e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e.perimeterKeyName,[]],centerLatitude:[e.centerLatitude,[]],centerLongitude:[e.centerLongitude,[]],range:[e.range,[]],rangeUnit:[e.rangeUnit,[]],polygonsDefinition:[e.polygonsDefinition,[]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterKeyName").setValidators([O.required]):this.geoFilterConfigForm.get("perimeterKeyName").setValidators([]),t||n!==Tt.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([]),this.defaultPaddingEnable=!0):(this.geoFilterConfigForm.get("centerLatitude").setValidators([O.required,O.min(-90),O.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([O.required,O.min(-180),O.max(180)]),this.geoFilterConfigForm.get("range").setValidators([O.required,O.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([O.required]),this.defaultPaddingEnable=!1),t||n!==Tt.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([O.required]),this.geoFilterConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoFilterConfigComponent",Fr),Fr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Fr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Fr,selector:"tb-filter-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
tb.rulenode.coordinate-field-names
\n
\n
\n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
\n
tb.rulenode.coordinate-field-hint
\n
\n
\n
\n
tb.rulenode.geofence-configuration
\n
\n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
\n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
\n
\n \n {{ \'tb.rulenode.polygon-definition\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-hint\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fr,decorators:[{type:n,args:[{selector:"tb-filter-node-gps-geofencing-config",template:'
\n
\n
tb.rulenode.coordinate-field-names
\n
\n
\n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
\n
tb.rulenode.coordinate-field-hint
\n
\n
\n
\n
tb.rulenode.geofence-configuration
\n
\n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
\n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
\n
\n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
\n
\n \n {{ \'tb.rulenode.polygon-definition\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-hint\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class kr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.messageTypeConfigForm}prepareInputConfig(e){return{messageTypes:ae(e?.messageTypes)?e.messageTypes:null}}onConfigurationSet(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e.messageTypes,[O.required]]})}}e("MessageTypeConfigComponent",kr),kr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),kr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:kr,selector:"tb-filter-node-message-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',dependencies:[{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Dn,selector:"tb-message-types-config",inputs:["required","label","placeholder","disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kr,decorators:[{type:n,args:[{selector:"tb-filter-node-message-type-config",template:'
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Lr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.allowedEntityTypes=[F.DEVICE,F.ASSET,F.ENTITY_VIEW,F.TENANT,F.CUSTOMER,F.USER,F.DASHBOARD,F.RULE_CHAIN,F.RULE_NODE,F.EDGE]}configForm(){return this.originatorTypeConfigForm}prepareInputConfig(e){return{originatorTypes:ae(e?.originatorTypes)?e.originatorTypes:null}}onConfigurationSet(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e.originatorTypes,[O.required]]})}}e("OriginatorTypeConfigComponent",Lr),Lr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Lr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Lr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Lr,selector:"tb-filter-node-originator-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n help\n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:st.EntityTypeListComponent,selector:"tb-entity-type-list",inputs:["required","additionalClasses","appearance","label","floatLabel","disabled","subscriptSizing","allowedEntityTypes","emptyInputPlaceholder","filledInputPlaceholder","ignoreAuthorityFilter"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Lr,decorators:[{type:n,args:[{selector:"tb-filter-node-originator-type-config",template:'
\n \n help\n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Tr extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-filter-function"}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e.scriptLang,[O.required]],jsScript:[e.jsScript,[]],tbelScript:[e.tbelScript,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),{scriptLang:ae(e?.scriptLang)?e.scriptLang:x.JS,jsScript:ae(e?.jsScript)?e.jsScript:null,tbelScript:ae(e?.tbelScript)?e.tbelScript:null}}testScript(e){const t=this.scriptConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/filter_node_script_fn":"rulenode/tbel/filter_node_script_fn",o=this.scriptConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.scriptConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("ScriptConfigComponent",Tr),Tr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tr,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Tr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Tr,selector:"tb-filter-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","hideBrackets","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tr,decorators:[{type:n,args:[{selector:"tb-filter-node-script-config",template:'
\n \n \n \n \n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class Ir extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-switch-function"}configForm(){return this.switchConfigForm}onConfigurationSet(e){this.switchConfigForm=this.fb.group({scriptLang:[e.scriptLang,[O.required]],jsScript:[e.jsScript,[]],tbelScript:[e.tbelScript,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.switchConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.switchConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.switchConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.switchConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.switchConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.switchConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.switchConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),{scriptLang:ae(e?.scriptLang)?e.scriptLang:x.JS,jsScript:ae(e?.jsScript)?e.jsScript:null,tbelScript:ae(e?.tbelScript)?e.tbelScript:null}}testScript(e){const t=this.switchConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/switch_node_script_fn":"rulenode/tbel/switch_node_script_fn",o=this.switchConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.switchConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.switchConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("SwitchConfigComponent",Ir),Ir.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ir,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Ir.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Ir,selector:"tb-filter-node-switch-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n \n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","hideBrackets","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ir,decorators:[{type:n,args:[{selector:"tb-filter-node-switch-config",template:'
\n \n \n \n \n \n \n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class Nr{}e("RuleNodeCoreConfigFilterModule",Nr),Nr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Nr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Nr,declarations:[vr,Cr,Fr,kr,Lr,Tr,Ir,hr],imports:[$,M,$n],exports:[vr,Cr,Fr,kr,Lr,Tr,Ir,hr]}),Nr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nr,imports:[$,M,$n]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nr,decorators:[{type:c,args:[{declarations:[vr,Cr,Fr,kr,Lr,Tr,Ir,hr],imports:[$,M,$n],exports:[vr,Cr,Fr,kr,Lr,Tr,Ir,hr]}]}]});class Sr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.originatorSource=vt,this.originatorSources=Object.keys(vt),this.originatorSourceTranslationMap=Ct,this.originatorSourceDescTranslationMap=Ft,this.allowedEntityTypes=[F.DEVICE,F.ASSET,F.ENTITY_VIEW,F.USER,F.EDGE]}configForm(){return this.changeOriginatorConfigForm}onConfigurationSet(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[O.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationsQuery:[e?e.relationsQuery:null,[]]})}validatorTriggers(){return["originatorSource"]}updateValidators(e){const t=this.changeOriginatorConfigForm.get("originatorSource").value;t===vt.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([O.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),t===vt.ENTITY?(this.changeOriginatorConfigForm.get("entityType").setValidators([O.required]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([O.required,O.pattern(/.*\S.*/)])):(this.changeOriginatorConfigForm.get("entityType").patchValue(null,{emitEvent:e}),this.changeOriginatorConfigForm.get("entityNamePattern").patchValue(null,{emitEvent:e}),this.changeOriginatorConfigForm.get("entityType").setValidators([]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([])),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e}),this.changeOriginatorConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.changeOriginatorConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}}e("ChangeOriginatorConfigComponent",Sr),Sr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Sr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Sr,selector:"tb-transformation-node-change-originator-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.new-originator\n \n \n \n {{ originatorSourceTranslationMap.get(changeOriginatorConfigForm.get(\'originatorSource\').value) | translate }}\n \n \n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n
\n \n {{ originatorSourceDescTranslationMap.get(source) | translate }}\n \n
\n
\n
\n
\n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n
\n
\n \n \n
\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:qe.MatListItemTitle,selector:"[matListItemTitle]"},{kind:"directive",type:qe.MatListItemMeta,selector:"[matListItemMeta]"},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Gn,selector:"tb-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sr,decorators:[{type:n,args:[{selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.new-originator\n \n \n \n {{ originatorSourceTranslationMap.get(changeOriginatorConfigForm.get(\'originatorSource\').value) | translate }}\n \n \n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n
\n \n {{ originatorSourceDescTranslationMap.get(source) | translate }}\n \n
\n
\n
\n
\n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n
\n
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class qr extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-transformer-function"}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],jsScript:[e?e.jsScript:null,[O.required]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.scriptConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/transformation_node_script_fn":"rulenode/tbel/transformation_node_script_fn",o=this.scriptConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.scriptConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("TransformScriptConfigComponent",qr),qr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:qr,deps:[{token:P.Store},{token:R.FormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),qr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:qr,selector:"tb-transformation-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n \n \n
\n \n
\n
\n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","hideBrackets","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:qr,decorators:[{type:n,args:[{selector:"tb-transformation-node-script-config",template:'
\n \n \n \n \n \n \n \n
\n \n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}}); +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +const Ar=mt({passive:!0});class Mr{constructor(e,t){this._platform=e,this._ngZone=t,this._monitoredElements=new Map}monitor(e){if(!this._platform.isBrowser)return He;const t=Te(e),n=this._monitoredElements.get(t);if(n)return n.subject;const r=new Ke,o="cdk-text-field-autofilled",a=e=>{"cdk-text-field-autofill-start"!==e.animationName||t.classList.contains(o)?"cdk-text-field-autofill-end"===e.animationName&&t.classList.contains(o)&&(t.classList.remove(o),this._ngZone.run((()=>r.next({target:e.target,isAutofilled:!1})))):(t.classList.add(o),this._ngZone.run((()=>r.next({target:e.target,isAutofilled:!0}))))};return this._ngZone.runOutsideAngular((()=>{t.addEventListener("animationstart",a,Ar),t.classList.add("cdk-text-field-autofill-monitored")})),this._monitoredElements.set(t,{subject:r,unlisten:()=>{t.removeEventListener("animationstart",a,Ar)}}),r}stopMonitoring(e){const t=Te(e),n=this._monitoredElements.get(t);n&&(n.unlisten(),n.subject.complete(),t.classList.remove("cdk-text-field-autofill-monitored"),t.classList.remove("cdk-text-field-autofilled"),this._monitoredElements.delete(t))}ngOnDestroy(){this._monitoredElements.forEach(((e,t)=>this.stopMonitoring(t)))}}Mr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Mr,deps:[{token:pt.Platform},{token:t.NgZone}],target:t.ɵɵFactoryTarget.Injectable}),Mr.ɵprov=t.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Mr,providedIn:"root"}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Mr,decorators:[{type:s,args:[{providedIn:"root"}]}],ctorParameters:function(){return[{type:pt.Platform},{type:t.NgZone}]}});class Er{constructor(e,t){this._elementRef=e,this._autofillMonitor=t,this.cdkAutofill=new r}ngOnInit(){this._autofillMonitor.monitor(this._elementRef).subscribe((e=>this.cdkAutofill.emit(e)))}ngOnDestroy(){this._autofillMonitor.stopMonitoring(this._elementRef)}}Er.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Er,deps:[{token:t.ElementRef},{token:Mr}],target:t.ɵɵFactoryTarget.Directive}),Er.ɵdir=t.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"15.2.0-rc.0",type:Er,selector:"[cdkAutofill]",outputs:{cdkAutofill:"cdkAutofill"},ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Er,decorators:[{type:d,args:[{selector:"[cdkAutofill]"}]}],ctorParameters:function(){return[{type:t.ElementRef},{type:Mr}]},propDecorators:{cdkAutofill:[{type:u}]}}); +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +class Gr{get minRows(){return this._minRows}set minRows(e){this._minRows=Ie(e),this._setMinHeight()}get maxRows(){return this._maxRows}set maxRows(e){this._maxRows=Ie(e),this._setMaxHeight()}get enabled(){return this._enabled}set enabled(e){e=Le(e),this._enabled!==e&&((this._enabled=e)?this.resizeToFitContent(!0):this.reset())}get placeholder(){return this._textareaElement.placeholder}set placeholder(e){this._cachedPlaceholderHeight=void 0,e?this._textareaElement.setAttribute("placeholder",e):this._textareaElement.removeAttribute("placeholder"),this._cacheTextareaPlaceholderHeight()}constructor(e,t,n,r){this._elementRef=e,this._platform=t,this._ngZone=n,this._destroyed=new Ke,this._enabled=!0,this._previousMinRows=-1,this._isViewInited=!1,this._handleFocusEvent=e=>{this._hasFocus="focus"===e.type},this._document=r,this._textareaElement=this._elementRef.nativeElement}_setMinHeight(){const e=this.minRows&&this._cachedLineHeight?this.minRows*this._cachedLineHeight+"px":null;e&&(this._textareaElement.style.minHeight=e)}_setMaxHeight(){const e=this.maxRows&&this._cachedLineHeight?this.maxRows*this._cachedLineHeight+"px":null;e&&(this._textareaElement.style.maxHeight=e)}ngAfterViewInit(){this._platform.isBrowser&&(this._initialHeight=this._textareaElement.style.height,this.resizeToFitContent(),this._ngZone.runOutsideAngular((()=>{const e=this._getWindow();je(e,"resize").pipe(Pe(16),Ve(this._destroyed)).subscribe((()=>this.resizeToFitContent(!0))),this._textareaElement.addEventListener("focus",this._handleFocusEvent),this._textareaElement.addEventListener("blur",this._handleFocusEvent)})),this._isViewInited=!0,this.resizeToFitContent(!0))}ngOnDestroy(){this._textareaElement.removeEventListener("focus",this._handleFocusEvent),this._textareaElement.removeEventListener("blur",this._handleFocusEvent),this._destroyed.next(),this._destroyed.complete()}_cacheTextareaLineHeight(){if(this._cachedLineHeight)return;let e=this._textareaElement.cloneNode(!1);e.rows=1,e.style.position="absolute",e.style.visibility="hidden",e.style.border="none",e.style.padding="0",e.style.height="",e.style.minHeight="",e.style.maxHeight="",e.style.overflow="hidden",this._textareaElement.parentNode.appendChild(e),this._cachedLineHeight=e.clientHeight,e.remove(),this._setMinHeight(),this._setMaxHeight()}_measureScrollHeight(){const e=this._textareaElement,t=e.style.marginBottom||"",n=this._platform.FIREFOX,r=n&&this._hasFocus,o=n?"cdk-textarea-autosize-measuring-firefox":"cdk-textarea-autosize-measuring";r&&(e.style.marginBottom=`${e.clientHeight}px`),e.classList.add(o);const a=e.scrollHeight-4;return e.classList.remove(o),r&&(e.style.marginBottom=t),a}_cacheTextareaPlaceholderHeight(){if(!this._isViewInited||null!=this._cachedPlaceholderHeight)return;if(!this.placeholder)return void(this._cachedPlaceholderHeight=0);const e=this._textareaElement.value;this._textareaElement.value=this._textareaElement.placeholder,this._cachedPlaceholderHeight=this._measureScrollHeight(),this._textareaElement.value=e}ngDoCheck(){this._platform.isBrowser&&this.resizeToFitContent()}resizeToFitContent(e=!1){if(!this._enabled)return;if(this._cacheTextareaLineHeight(),this._cacheTextareaPlaceholderHeight(),!this._cachedLineHeight)return;const t=this._elementRef.nativeElement,n=t.value;if(!e&&this._minRows===this._previousMinRows&&n===this._previousValue)return;const r=this._measureScrollHeight(),o=Math.max(r,this._cachedPlaceholderHeight||0);t.style.height=`${o}px`,this._ngZone.runOutsideAngular((()=>{"undefined"!=typeof requestAnimationFrame?requestAnimationFrame((()=>this._scrollToCaretPosition(t))):setTimeout((()=>this._scrollToCaretPosition(t)))})),this._previousValue=n,this._previousMinRows=this._minRows}reset(){void 0!==this._initialHeight&&(this._textareaElement.style.height=this._initialHeight)}_noopInputHandler(){}_getDocument(){return this._document||document}_getWindow(){return this._getDocument().defaultView||window}_scrollToCaretPosition(e){const{selectionStart:t,selectionEnd:n}=e;!this._destroyed.isStopped&&this._hasFocus&&e.setSelectionRange(t,n)}}Gr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Gr,deps:[{token:t.ElementRef},{token:pt.Platform},{token:t.NgZone},{token:j,optional:!0}],target:t.ɵɵFactoryTarget.Directive}),Gr.ɵdir=t.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"15.2.0-rc.0",type:Gr,selector:"textarea[cdkTextareaAutosize]",inputs:{minRows:["cdkAutosizeMinRows","minRows"],maxRows:["cdkAutosizeMaxRows","maxRows"],enabled:["cdkTextareaAutosize","enabled"],placeholder:"placeholder"},host:{attributes:{rows:"1"},listeners:{input:"_noopInputHandler()"},classAttribute:"cdk-textarea-autosize"},exportAs:["cdkTextareaAutosize"],ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Gr,decorators:[{type:d,args:[{selector:"textarea[cdkTextareaAutosize]",exportAs:"cdkTextareaAutosize",host:{class:"cdk-textarea-autosize",rows:"1","(input)":"_noopInputHandler()"}}]}],ctorParameters:function(){return[{type:t.ElementRef},{type:pt.Platform},{type:t.NgZone},{type:void 0,decorators:[{type:p},{type:m,args:[j]}]}]},propDecorators:{minRows:[{type:i,args:["cdkAutosizeMinRows"]}],maxRows:[{type:i,args:["cdkAutosizeMaxRows"]}],enabled:[{type:i,args:["cdkTextareaAutosize"]}],placeholder:[{type:i}]}}); +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +class Dr{}Dr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Dr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Dr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.0-rc.0",ngImport:t,type:Dr,declarations:[Er,Gr],exports:[Er,Gr]}),Dr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Dr}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Dr,decorators:[{type:c,args:[{declarations:[Er,Gr],exports:[Er,Gr]}]}]});class wr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.mailBodyTypes=[{name:"tb.mail-body-type.plain-text",description:"tb.mail-body-type.plain-text-description",value:"false"},{name:"tb.mail-body-type.html",description:"tb.mail-body-type.html-text-description",value:"true"},{name:"tb.mail-body-type.use-body-type-template",description:"tb.mail-body-type.dynamic-text-description",value:"dynamic"}]}configForm(){return this.toEmailConfigForm}onConfigurationSet(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[O.required]],toTemplate:[e?e.toTemplate:null,[O.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[O.required]],mailBodyType:[e?e.mailBodyType:null],isHtmlTemplate:[e?e.isHtmlTemplate:null,[O.required]],bodyTemplate:[e?e.bodyTemplate:null,[O.required]]})}prepareInputConfig(e){return{fromTemplate:ae(e?.fromTemplate)?e.fromTemplate:null,toTemplate:ae(e?.toTemplate)?e.toTemplate:null,ccTemplate:ae(e?.ccTemplate)?e.ccTemplate:null,bccTemplate:ae(e?.bccTemplate)?e.bccTemplate:null,subjectTemplate:ae(e?.subjectTemplate)?e.subjectTemplate:null,mailBodyType:ae(e?.mailBodyType)?e.mailBodyType:null,isHtmlTemplate:ae(e?.isHtmlTemplate)?e.isHtmlTemplate:null,bodyTemplate:ae(e?.bodyTemplate)?e.bodyTemplate:null}}updateValidators(e){"dynamic"===this.toEmailConfigForm.get("mailBodyType").value?this.toEmailConfigForm.get("isHtmlTemplate").enable({emitEvent:!1}):this.toEmailConfigForm.get("isHtmlTemplate").disable({emitEvent:!1}),this.toEmailConfigForm.get("isHtmlTemplate").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["mailBodyType"]}getBodyTypeName(){return this.mailBodyTypes.find((e=>e.value===this.toEmailConfigForm.get("mailBodyType").value)).name}}e("ToEmailConfigComponent",wr),wr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:wr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),wr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:wr,selector:"tb-transformation-node-to-email-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
tb.rulenode.email-sender
\n
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.email-from-template-hint\' | translate }}\n \n \n
\n
\n
\n
\n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n
\n
\n
\n
\n
\n
tb.rulenode.recipients
\n \n \n
\n
\n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n tb.rulenode.cc-template\n \n \n \n tb.rulenode.bcc-template\n \n \n
\n
\n
\n
tb.rulenode.message-subject-and-content
\n \n \n
\n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n tb.rulenode.mail-body-type\n \n \n \n {{ getBodyTypeName() | translate }}\n \n \n \n \n {{ type.name | translate }}\n \n
\n \n {{ type.description | translate }}\n \n
\n
\n
\n \n tb.rulenode.body-type-template\n \n tb.mail-body-type.after-template-evaluation-hint\n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n
\n
\n
\n',styles:[":host .input-bottom-double-hint{display:inline-flex}:host .input-bottom-double-hint .see-example{flex-shrink:0;padding-right:16px}:host textarea.tb-enable-vertical-resize{resize:vertical}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:$e.HelpPopupComponent,selector:"[tb-help-popup], [tb-help-popup-content]",inputs:["tb-help-popup","tb-help-popup-content","trigger-text","trigger-style","tb-help-popup-placement","tb-help-popup-style","hintMode"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Gr,selector:"textarea[cdkTextareaAutosize]",inputs:["cdkAutosizeMinRows","cdkAutosizeMaxRows","cdkTextareaAutosize","placeholder"],exportAs:["cdkTextareaAutosize"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:qe.MatListItemTitle,selector:"[matListItemTitle]"},{kind:"directive",type:qe.MatListItemMeta,selector:"[matListItemMeta]"},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:wr,decorators:[{type:n,args:[{selector:"tb-transformation-node-to-email-config",template:'
\n
\n
tb.rulenode.email-sender
\n
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.email-from-template-hint\' | translate }}\n \n \n
\n
\n
\n
\n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n
\n
\n
\n
\n
\n
tb.rulenode.recipients
\n \n \n
\n
\n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n tb.rulenode.cc-template\n \n \n \n tb.rulenode.bcc-template\n \n \n
\n
\n
\n
tb.rulenode.message-subject-and-content
\n \n \n
\n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n tb.rulenode.mail-body-type\n \n \n \n {{ getBodyTypeName() | translate }}\n \n \n \n \n {{ type.name | translate }}\n \n
\n \n {{ type.description | translate }}\n \n
\n
\n
\n \n tb.rulenode.body-type-template\n \n tb.mail-body-type.after-template-evaluation-hint\n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n
\n
\n
\n',styles:[":host .input-bottom-double-hint{display:inline-flex}:host .input-bottom-double-hint .see-example{flex-shrink:0;padding-right:16px}:host textarea.tb-enable-vertical-resize{resize:vertical}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Vr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.copyFrom=[],this.translation=tn;for(const e of this.translation.keys())this.copyFrom.push({value:e,name:this.translate.instant(this.translation.get(e))})}onConfigurationSet(e){this.copyKeysConfigForm=this.fb.group({copyFrom:[e.copyFrom,[O.required]],keys:[e?e.keys:null,[O.required]]})}configForm(){return this.copyKeysConfigForm}prepareInputConfig(e){let t;return t=ae(e?.fromMetadata)?e.copyFrom?en.METADATA:en.DATA:ae(e?.copyFrom)?e.copyFrom:en.DATA,{keys:ae(e?.keys)?e.keys:null,copyFrom:t}}}e("CopyKeysConfigComponent",Vr),Vr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Vr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Vr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Vr,selector:"tb-transformation-node-copy-keys-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n help\n \n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Vr,decorators:[{type:n,args:[{selector:"tb-transformation-node-copy-keys-config",template:'
\n \n \n \n \n help\n \n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Pr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.renameIn=[],this.translation=rn;for(const e of this.translation.keys())this.renameIn.push({value:e,name:this.translate.instant(this.translation.get(e))})}configForm(){return this.renameKeysConfigForm}onConfigurationSet(e){this.renameKeysConfigForm=this.fb.group({renameIn:[e?e.renameIn:null,[O.required]],renameKeysMapping:[e?e.renameKeysMapping:null,[O.required]]})}prepareInputConfig(e){let t;return t=ae(e?.fromMetadata)?e.fromMetadata?en.METADATA:en.DATA:ae(e?.renameIn)?e?.renameIn:en.DATA,{renameKeysMapping:ae(e?.renameKeysMapping)?e.renameKeysMapping:null,renameIn:t}}}e("RenameKeysConfigComponent",Pr),Pr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Pr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Pr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Pr,selector:"tb-transformation-node-rename-keys-config",usesInheritance:!0,ngImport:t,template:'
\n
tb.rulenode.rename-keys-in
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n \n \n
\n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}:host .fx-centered{display:flex;width:100%;justify-content:space-around}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Mn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Pr,decorators:[{type:n,args:[{selector:"tb-transformation-node-rename-keys-config",template:'
\n
tb.rulenode.rename-keys-in
\n
\n
\n \n \n {{ data.name }}\n \n \n
\n
\n \n \n
\n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}:host .fx-centered{display:flex;width:100%;justify-content:space-around}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Rr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.jsonPathConfigForm}onConfigurationSet(e){this.jsonPathConfigForm=this.fb.group({jsonPath:[e?e.jsonPath:null,[O.required]]})}}e("NodeJsonPathConfigComponent",Rr),Rr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Rr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Rr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Rr,selector:"tb-transformation-node-json-path-config",usesInheritance:!0,ngImport:t,template:"
\n \n {{ 'tb.rulenode.json-path-expression' | translate }}\n \n {{ 'tb.rulenode.json-path-expression-hint' | translate }}\n {{ 'tb.rulenode.json-path-expression-required' | translate }}\n \n
\n",dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Rr,decorators:[{type:n,args:[{selector:"tb-transformation-node-json-path-config",template:"
\n \n {{ 'tb.rulenode.json-path-expression' | translate }}\n \n {{ 'tb.rulenode.json-path-expression-hint' | translate }}\n {{ 'tb.rulenode.json-path-expression-required' | translate }}\n \n
\n"}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Or extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.deleteFrom=[],this.translation=nn;for(const e of this.translation.keys())this.deleteFrom.push({value:e,name:this.translate.instant(this.translation.get(e))})}onConfigurationSet(e){this.deleteKeysConfigForm=this.fb.group({deleteFrom:[e.deleteFrom,[O.required]],keys:[e?e.keys:null,[O.required]]})}prepareInputConfig(e){let t;return t=ae(e?.fromMetadata)?e.fromMetadata?en.METADATA:en.DATA:ae(e?.deleteFrom)?e?.deleteFrom:en.DATA,{keys:ae(e?.keys)?e.keys:null,deleteFrom:t}}configForm(){return this.deleteKeysConfigForm}}e("DeleteKeysConfigComponent",Or),Or.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Or,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Or.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Or,selector:"tb-transformation-node-delete-keys-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n help\n \n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Kn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Or,decorators:[{type:n,args:[{selector:"tb-transformation-node-delete-keys-config",template:'
\n \n \n \n \n help\n \n \n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class _r extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.deduplicationStrategie=Gt,this.deduplicationStrategies=Object.keys(this.deduplicationStrategie),this.deduplicationStrategiesTranslations=Dt}configForm(){return this.deduplicationConfigForm}onConfigurationSet(e){this.deduplicationConfigForm=this.fb.group({interval:[ae(e?.interval)?e.interval:null,[O.required,O.min(1)]],strategy:[ae(e?.strategy)?e.strategy:null,[O.required]],outMsgType:[ae(e?.outMsgType)?e.outMsgType:null,[O.required]],maxPendingMsgs:[ae(e?.maxPendingMsgs)?e.maxPendingMsgs:null,[O.required,O.min(1),O.max(1e3)]],maxRetries:[ae(e?.maxRetries)?e.maxRetries:null,[O.required,O.min(0),O.max(100)]]})}prepareInputConfig(e){return e||(e={}),e.outMsgType||(e.outMsgType="POST_TELEMETRY_REQUEST"),super.prepareInputConfig(e)}updateValidators(e){this.deduplicationConfigForm.get("strategy").value===this.deduplicationStrategie.ALL?this.deduplicationConfigForm.get("outMsgType").enable({emitEvent:!1}):this.deduplicationConfigForm.get("outMsgType").disable({emitEvent:!1}),this.deduplicationConfigForm.get("outMsgType").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["strategy"]}}e("DeduplicationConfigComponent",_r),_r.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_r,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),_r.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:_r,selector:"tb-action-node-msg-deduplication-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{\'tb.rulenode.interval\' | translate}}\n \n \n {{\'tb.rulenode.interval-required\' | translate}}\n \n \n {{\'tb.rulenode.interval-min-error\' | translate}}\n \n help\n \n
\n
\n
tb.rulenode.strategy
\n \n \n {{ deduplicationStrategiesTranslations.get(strategy) | translate }}\n \n \n \n \n \n \n \n \n
\n \n \n
\n
\n
\n \n \n tb.rulenode.advanced-settings\n \n
\n \n {{\'tb.rulenode.max-pending-msgs\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-required\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-min-error\' | translate}}\n \n help\n \n \n {{\'tb.rulenode.max-retries\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-required\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-min-error\' | translate}}\n \n help\n \n
\n
\n
\n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:We.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:We.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:We.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Bn,selector:"tb-output-message-type-autocomplete",inputs:["subscriptSizing","disabled","required"]},{kind:"component",type:An,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_r,decorators:[{type:n,args:[{selector:"tb-action-node-msg-deduplication-config",template:'
\n \n {{\'tb.rulenode.interval\' | translate}}\n \n \n {{\'tb.rulenode.interval-required\' | translate}}\n \n \n {{\'tb.rulenode.interval-min-error\' | translate}}\n \n help\n \n
\n
\n
tb.rulenode.strategy
\n \n \n {{ deduplicationStrategiesTranslations.get(strategy) | translate }}\n \n \n \n \n \n \n \n \n
\n \n \n
\n
\n
\n \n \n tb.rulenode.advanced-settings\n \n
\n \n {{\'tb.rulenode.max-pending-msgs\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-required\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-min-error\' | translate}}\n \n help\n \n \n {{\'tb.rulenode.max-retries\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-required\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-min-error\' | translate}}\n \n help\n \n
\n
\n
\n
\n
\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Br{}e("RulenodeCoreConfigTransformModule",Br),Br.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Br,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Br.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Br,declarations:[Sr,qr,wr,Vr,Pr,Rr,Or,_r],imports:[$,M,$n],exports:[Sr,qr,wr,Vr,Pr,Rr,Or,_r]}),Br.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Br,imports:[$,M,$n]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Br,decorators:[{type:c,args:[{declarations:[Sr,qr,wr,Vr,Pr,Rr,Or,_r],imports:[$,M,$n],exports:[Sr,qr,wr,Vr,Pr,Rr,Or,_r]}]}]});class Kr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.entityType=F}configForm(){return this.ruleChainInputConfigForm}onConfigurationSet(e){this.ruleChainInputConfigForm=this.fb.group({ruleChainId:[e?e.ruleChainId:null,[O.required]]})}}e("RuleChainInputComponent",Kr),Kr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Kr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Kr,selector:"tb-flow-node-rule-chain-input-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',dependencies:[{kind:"component",type:lt.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","useFullEntityId","appearance","required","disabled"],outputs:["entityChanged"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kr,decorators:[{type:n,args:[{selector:"tb-flow-node-rule-chain-input-config",template:'
\n \n \n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class zr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.ruleChainOutputConfigForm}onConfigurationSet(e){this.ruleChainOutputConfigForm=this.fb.group({})}}e("RuleChainOutputComponent",zr),zr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),zr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:zr,selector:"tb-flow-node-rule-chain-output-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
\n',dependencies:[{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zr,decorators:[{type:n,args:[{selector:"tb-flow-node-rule-chain-output-config",template:'
\n
\n
\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Ur{}e("RuleNodeCoreConfigFlowModule",Ur),Ur.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ur,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Ur.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Ur,declarations:[Kr,zr],imports:[$,M,$n],exports:[Kr,zr]}),Ur.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ur,imports:[$,M,$n]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ur,decorators:[{type:c,args:[{declarations:[Kr,zr],imports:[$,M,$n],exports:[Kr,zr]}]}]});class Hr{constructor(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{id:"Id","additional-info":"Additional Info","advanced-settings":"Advanced settings","create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","select-device-connectivity-event":"Select device connectivity event","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","copy-message-type":"Copy message type","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","message-type-value":"Message type value","message-type-value-required":"Message type value is required","message-type-value-max-length":"Message type value should be less than 256","output-message-type":"Output message type","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","interval-start":"Interval start","interval-end":"Interval end","time-unit":"Time unit","fetch-mode":"Fetch mode","order-by-timestamp":"Order by timestamp",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. If you want to fetch a single entry, select fetch mode 'First' or 'Last'.","limit-required":"Limit is required.","limit-range":"Limit should be in a range from 2 to 1000.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Allowing range from 1 to 2147483647.","start-interval-value-required":"Interval start is required.","end-interval-value-required":"Interval end is required.",filter:"Filter",switch:"Switch","math-templatization-tooltip":"This field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","add-message-type":"Add message type","select-message-types-required":"At least one message type should be selected.","select-message-types":"Select message types","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one.","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","attributes-keys":"Attributes keys","attributes-keys-required":"Attributes keys are required","notify-device":"Force notification to the device","send-attributes-updated-notification":"Send attributes updated notification","send-attributes-updated-notification-hint":"Send notification about updated attributes as a separate message to the rule engine queue.","send-attributes-deleted-notification":"Send attributes deleted notification","send-attributes-deleted-notification-hint":"Send notification about deleted attributes as a separate message to the rule engine queue.","update-attributes-only-on-value-change":"Save attributes only if the value changes","update-attributes-only-on-value-change-hint":"Updates the attributes on every incoming message disregarding if their value has changed. Increases API usage and reduces performance.","update-attributes-only-on-value-change-hint-enabled":"Updates the attributes only if their value has changed. If the value is not changed, no update to the attribute timestamp nor attribute change notification will be sent.","fetch-credentials-to-metadata":"Fetch credentials to metadata","notify-device-on-update-hint":"If enabled, force notification to the device about shared attributes update. If disabled, the notification behavior is controlled by the 'notifyDevice' parameter from the incoming message metadata. To turn off the notification, the message metadata must contain the 'notifyDevice' parameter set to 'false'. Any other case will trigger the notification to the device.","notify-device-on-delete-hint":"If enabled, force notification to the device about shared attributes removal. If disabled, the notification behavior is controlled by the 'notifyDevice' parameter from the incoming message metadata. To turn on the notification, the message metadata must contain the 'notifyDevice' parameter set to 'true'. In any other case, the notification will not be triggered to the device.","latest-timeseries":"Latest time-series data keys","timeseries-keys":"Timeseries keys","timeseries-keys-required":"At least one timeseries key should be selected.","add-timeseries-key":"Add timeseries key","add-message-field":"Add message field","relation-search-parameters":"Relation search parameters","add-metadata-field":"Add metadata field","data-keys":"Message field names","copy-from":"Copy from","data-to-metadata":"Data to metadata","metadata-to-data":"Metadata to data","use-regular-expression-hint":"Use regular expression to copy keys by pattern.\n\nTips & tricks:\nPress 'Enter' to complete field name input.\nPress 'Backspace' to delete field name. Multiple field names supported.",interval:"Interval","interval-required":"Interval is required","interval-hint":"Deduplication interval in seconds.","interval-min-error":"Min allowed value is 1","max-pending-msgs":"Max pending messages","max-pending-msgs-hint":"Maximum number of messages that are stored in memory for each unique deduplication id.","max-pending-msgs-required":"Max pending messages is required","max-pending-msgs-max-error":"Max allowed value is 1000","max-pending-msgs-min-error":"Min allowed value is 1","max-retries":"Max retries","max-retries-required":"Max retries is required","max-retries-hint":"Maximum number of retries to push the deduplicated messages into the queue. 10 seconds delay is used between retries","max-retries-max-error":"Max allowed value is 100","max-retries-min-error":"Min allowed value is 0",strategy:"Strategy","strategy-required":"Strategy is required","strategy-all-hint":"Return all messages that arrived during deduplication period as a single JSON array message. Where each element represents object with msg and metadata inner properties.","strategy-first-hint":"Return first message that arrived during deduplication period.","strategy-last-hint":"Return last message that arrived during deduplication period.",first:"First",last:"Last",all:"All","output-msg-type-hint":"The message type of the deduplication result.","queue-name-hint":"The queue name where the deduplication result will be published.",keys:"Keys","keys-required":"Keys are required","rename-keys-in":"Rename keys in",data:"Data",message:"Message",metadata:"Metadata","current-key-name":"Current key name","key-name-required":"Key name is required","new-key-name":"New key name","new-key-name-required":"New key name is required","metadata-keys":"Metadata field names","json-path-expression":"JSON path expression","json-path-expression-required":"JSON path expression is required","json-path-expression-hint":"JSONPath specifies a path to an element or a set of elements in a JSON structure. '$' represents the root object or array.","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","max-relation-level-error":"Value should be greater than 0 or unspecified.","relation-type":"Relation type","relation-type-pattern":"Relation type pattern","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","add-telemetry-key":"Add telemetry key","delete-from":"Delete from","use-regular-expression-delete-hint":"Use regular expression to delete keys by pattern.\n\nTips & tricks:\nPress 'Enter' to complete field name input.\nPress 'Backspace' to delete field name.\nMultiple field names supported.","fetch-into":"Fetch into","attr-mapping":"Attributes mapping:","source-attribute":"Source attribute key","source-attribute-required":"Source attribute key is required.","source-telemetry":"Source telemetry key","source-telemetry-required":"Source telemetry key is required.","target-key":"Target key","target-key-required":"Target key is required.","attr-mapping-required":"At least one mapping entry should be specified.","fields-mapping":"Fields mapping","relations-query-config-direction-suffix":"originator","profile-name":"Profile name","fetch-circle-parameter-info-from-metadata-hint":'Metadata field \'{{perimeterKeyName}}\' should be defined in next format: {"latitude":48.196, "longitude":24.6532, "radius":100.0, "radiusUnit":"METER"}',"fetch-poligon-parameter-info-from-metadata-hint":"Metadata field '{{perimeterKeyName}}' should be defined in next format: [[48.19736,24.65235],[48.19800,24.65060],...,[48.19849,24.65420]]","short-templatization-tooltip":"Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","fields-mapping-required":"At least one field mapping should be specified.","at-least-one-field-required":"At least one input field must have a value(s) provided.","originator-fields-sv-map-hint":"Target key fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","sv-map-hint":"Only target key fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","new-originator":"New originator","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related entity","originator-alarm-originator":"Alarm Originator","originator-entity":"Entity by name pattern","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","select-entity-types":"Select entity types","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-severity-pattern":"Alarm severity pattern","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate alarm to related entities","propagate-to-owner":"Propagate alarm to entity owner (Customer or Tenant)","propagate-to-tenant":"Propagate alarm to Tenant",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From","from-template-required":"From is required","message-to-metadata":"Message to metadata","metadata-to-message":"Metadata to message","from-message":"From message","from-metadata":"From metadata","to-template":"To","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc","bcc-template":"Bcc","subject-template":"Subject","subject-template-required":"Subject Template is required","body-template":"Body","body-template-required":"Body Template is required","dynamic-mail-body-type":"Dynamic mail body type","mail-body-type":"Mail body type","body-type-template":"Body type template","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","ignore-request-body":"Without request body","parse-to-plain-text":"Parse to plain text","parse-to-plain-text-hint":'If selected, request body message payload will be transformed from JSON string to plain text, e.g. msg = "Hello,\\t\\"world\\"" will be parsed to Hello, "world"',"read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","key-pattern":"Key pattern","key-pattern-hint":"Optional. If a valid partition number is specified, it will be used when sending the record. If no partition is specified, the key will be used instead. If neither is specified, a partition will be assigned in a round-robin fashion.","topic-pattern-required":"Topic pattern is required",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","client-id-hint":'Optional. Leave empty for auto-generated Client ID. Be careful when specifying the Client ID. Majority of the MQTT brokers will not allow multiple connections with the same Client ID. To connect to such brokers, your mqtt Client ID must be unique. When platform is running in a micro-services mode, the copy of rule node is launched in each micro-service. This will automatically lead to multiple mqtt clients with the same ID and may cause failures of the rule node. To avoid such failures enable "Add Service ID as suffix to Client ID" option below.',"append-client-id-suffix":"Add Service ID as suffix to Client ID","client-id-suffix-hint":'Optional. Applied when "Client ID" specified explicitly. If selected then Service ID will be added to Client ID as a suffix. Helps to avoid failures when platform is running in a micro-services mode.',"device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"Server CA certificate file","private-key":"Client private key file",cert:"Client certificate file","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-dynamic-interval":"Use dynamic interval","metadata-dynamic-interval-hint":"Interval start and end input fields support templatization. Note that the substituted template value should be set in milliseconds. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","overwrite-alarm-details":"Overwrite alarm details","use-alarm-severity-pattern":"Use alarm severity pattern","check-all-keys":"Check that all specified fields are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-to-specific-entity-tooltip":"If enabled, checks the presence of relation with a specific entity otherwise, checks the presence of relation with any entity. In both cases, relation lookup is based on configured direction and type.","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval":"Interval start","end-interval":"Interval end","start-interval-required":"Interval start is required.","end-interval-required":"Interval end is required.","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":'Comma separated Phone Numbers, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'Press "Enter" to complete field input.',"select-details":"Select details","entity-details-id":"Id","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-city":"City","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","email-sender":"Email sender","fields-to-check":"Fields to check","add-detail":"Add detail","check-all-keys-tooltip":"If enabled, checks the presence of all fields listed in the message and metadata field names within the incoming message and its metadata.","fields-to-check-hint":'Press "Enter" to complete field name input. Multiple field names supported.',"entity-details-list-empty":"At least one detail should be selected.","alarm-status":"Alarm status","alarm-required":"At least one alarm status should be selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"Enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-field-name":"Latitude field name","longitude-field-name":"Longitude field name","latitude-field-name-required":"Latitude field name is required.","longitude-field-name-required":"Longitude field name is required.","fetch-perimeter-info-from-metadata":"Fetch perimeter information from metadata","fetch-perimeter-info-from-metadata-tooltip":"If perimeter type is set to 'Polygon' the value of metadata field '{{perimeterKeyName}}' will be set as perimeter definition without additional parsing of the value. Otherwise, if perimeter type is set to 'Circle' the value of '{{perimeterKeyName}}' metadata field will be parsed to extract 'latitude', 'longitude', 'radius', 'radiusUnit' fields that uses for circle perimeter definition.","perimeter-key-name":"Perimeter key name","perimeter-key-name-hint":"Metadata field name that includes perimeter information.","perimeter-key-name-required":"Perimeter key name is required.","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units","range-units-required":"Range units is required.",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch timestamp for the latest telemetry values","get-latest-value-with-ts-hint":'If selected, the latest telemetry values will also include timestamp, e.g: "temp": "{"ts":1574329385897, "value":42}"',"use-redis-queue":"Use redis queue for message persistence","ignore-null-strings":"Ignore null strings","ignore-null-strings-hint":"If selected rule node will ignore entity fields with empty value.","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.","number-of-digits-after-floating-point":"Number of digits after floating point","number-of-digits-after-floating-point-range":"Number of digits after floating point should be in a range from 0 to 15.","failure-if-delta-negative":"Tell Failure if delta is negative","failure-if-delta-negative-tooltip":"Rule node forces failure of message processing if delta value is negative.","use-caching":"Use caching","use-caching-tooltip":'Rule node will cache the value of "{{inputValueKey}}" that arrives from the incoming message to improve performance. Note that the cache will not be updated if you modify the "{{inputValueKey}}" value elsewhere.',"add-time-difference-between-readings":'Add the time difference between "{{inputValueKey}}" readings',"add-time-difference-between-readings-tooltip":'If enabled, the rule node will add the "{{periodValueKey}}" to the outbound message.',"period-value-key":"Period value key","period-value-key-required":"Period value key is required.","general-pattern-hint":"Use ${metadataKey} for value from metadata, $[messageKey] for value from message body.","alarm-severity-pattern-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)',"output-node-name-hint":"The rule node name corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain.","skip-latest-persistence":"Skip latest persistence","use-server-ts":"Use server ts","use-server-ts-hint":"Enable this setting to use the timestamp of the message processing instead of the timestamp from the message. Useful for all sorts of sequential processing if you merge messages from multiple sources (devices, assets, etc).","kv-map-pattern-hint":"All input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","shared-scope":"Shared scope","server-scope":"Server scope","client-scope":"Client scope","attribute-type":"Attribute","constant-type":"Constant","time-series-type":"Time-series","message-body-type":"Message","message-metadata-type":"Metadata","argument-tile":"Arguments","no-arguments-prompt":"No arguments configured","result-title":"Result","functions-field-input":"Functions","no-option-found":"No option found","argument-source-field-input":"Source","argument-source-field-input-required":"Argument source is required.","argument-key-field-input":"Key","argument-key-field-input-required":"Argument key is required.","constant-value-field-input":"Constant value","constant-value-field-input-required":"Constant value is required.","attribute-scope-field-input":"Attribute scope","attribute-scope-field-input-required":"Attribute scope os required.","default-value-field-input":"Default value","type-field-input":"Type","type-field-input-required":"Type is required.","key-field-input":"Key","add-entity-type":"Add entity type","add-device-profile":"Add device profile","key-field-input-required":"Key is required.","number-floating-point-field-input":"Number of digits after floating point","number-floating-point-field-input-hint":"Use 0 to convert result to integer","add-to-message-field-input":"Add to message","add-to-metadata-field-input":"Add to metadata","custom-expression-field-input":"Mathematical Expression","custom-expression-field-input-required":"Mathematical expression is required","custom-expression-field-input-hint":"Specify a mathematical expression to evaluate. Default expression demonstrates how to transform Fahrenheit to Celsius","retained-message":"Retained","attributes-mapping":"Attributes mapping","latest-telemetry-mapping":"Latest telemetry mapping","add-mapped-attribute-to":"Add mapped attributes to","add-mapped-latest-telemetry-to":"Add mapped latest telemetry to","add-mapped-fields-to":"Add mapped fields to","add-selected-details-to":"Add selected details to","clear-selected-types":"Clear selected types","clear-selected-details":"Clear selected details","clear-selected-fields":"Clear selected fields","clear-selected-keys":"Clear selected keys","geofence-configuration":"Geofence configuration","coordinate-field-names":"Coordinate field names","coordinate-field-hint":"Rule node tries to retrieve the specified fields from the message. If they are not present, it will look them up in the metadata.","presence-monitoring-strategy":"Presence monitoring strategy","presence-monitoring-strategy-on-first-message":"On first message","presence-monitoring-strategy-on-each-message":"On each message","presence-monitoring-strategy-on-first-message-hint":"Reports presence status 'Inside' or 'Outside' on the first message after the configured minimal duration has passed since previous presence status 'Entered' or 'Left' update.","presence-monitoring-strategy-on-each-message-hint":"Reports presence status 'Inside' or 'Outside' on each message after presence status 'Entered' or 'Left' update.","fetch-credentials-to":"Fetch credentials to","add-originator-attributes-to":"Add originator attributes to","originator-attributes":"Originator attributes","fetch-latest-telemetry-with-timestamp":"Fetch latest telemetry with timestamp","fetch-latest-telemetry-with-timestamp-tooltip":'If selected, latest telemetry values will be added to the outbound metadata with timestamp, e.g: "{{latestTsKeyName}}": "{"ts":1574329385897, "value":42}"',"tell-failure":"Tell failure if any of the attributes are missing","tell-failure-tooltip":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"created-time":"Created time","chip-help":"Press 'Enter' to complete {{inputName}} input. \nPress 'Backspace' to delete {{inputName}}. \nMultiple values supported.",detail:"detail","field-name":"field name","device-profile":"device profile","entity-type":"entity type","message-type":"message type","timeseries-key":"timeseries key",type:"Type","first-name":"First name","last-name":"Last name",label:"Label","originator-fields-mapping":"Originator fields mapping","add-mapped-originator-fields-to":"Add mapped originator fields to",fields:"Fields","skip-empty-fields":"Skip empty fields","skip-empty-fields-tooltip":"Fields with empty values will not be added to the output message/output metadata.","fetch-interval":"Fetch interval","fetch-strategy":"Fetch strategy","fetch-timeseries-from-to":"Fetch timeseries from {{startInterval}} {{startIntervalTimeUnit}} ago to {{endInterval}} {{endIntervalTimeUnit}} ago.","fetch-timeseries-from-to-invalid":'Fetch timeseries invalid ("Interval start" should be less than "Interval end").',"use-metadata-dynamic-interval-tooltip":"If selected, the rule node will use dynamic interval start and end based on the message and metadata patterns.","all-mode-hint":'If selected fetch mode "All" rule node will retrieve telemetry from the fetch interval with configurable query parameters.',"first-mode-hint":'If selected fetch mode "First" rule node will retrieve the closest telemetry to the fetch interval\'s start.',"last-mode-hint":'If selected fetch mode "Last" rule node will retrieve the closest telemetry to the fetch interval\'s end.',ascending:"Ascending",descending:"Descending",min:"Min",max:"Max",average:"Average",sum:"Sum",count:"Count",none:"None","last-level-relation-tooltip":"If selected, the rule node will search related entities only on the level set in the max relation level.","last-level-device-relation-tooltip":"If selected, the rule node will search related devices only on the level set in the max relation level.","data-to-fetch":"Data to fetch","mapping-of-customers":"Mapping of customer's","map-fields-required":"All mapping fields are required.",attributes:"Attributes","related-device-attributes":"Related device attributes","add-selected-attributes-to":"Add selected attributes to","device-profiles":"Device profiles","mapping-of-tenant":"Mapping of tenant's","add-attribute-key":"Add attribute key","message-template":"Message template","message-template-required":"Message template is required","use-system-slack-settings":"Use system slack settings","slack-api-token":"Slack API token","slack-api-token-required":"Slack API token is required","keys-mapping":"keys mapping","add-key":"Add key",recipients:"Recipients","message-subject-and-content":"Message subject and content","template-rules-hint":"Both input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","originator-customer-desc":"Use customer of incoming message originator as new originator.","originator-tenant-desc":"Use current tenant as new originator.","originator-related-entity-desc":"Use related entity as new originator. Lookup based on configured relation type and direction.","originator-alarm-originator-desc":"Use alarm originator as new originator. Only if incoming message originator is alarm entity.","originator-entity-by-name-pattern-desc":"Use entity fetched from DB as new originator. Lookup based on entity type and specified name pattern.","email-from-template-hint":"Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","recipients-block-main-hint":"Comma-separated address list. All input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata."},"key-val":{key:"Key",value:"Value","see-examples":"See examples.","remove-entry":"Remove entry","remove-mapping-entry":"Remove mapping entry","add-mapping-entry":"Add mapping","add-entry":"Add entry","copy-key-values-from":"Copy key-values from","delete-key-values":"Delete key-values","delete-key-values-from":"Delete key-values from","at-least-one-key-error":"At least one key should be selected.","unique-key-value-pair-error":"'{{keyText}}' must be different from the '{{valText}}'!"},"mail-body-type":{"plain-text":"Plain text",html:"HTML",dynamic:"Dynamic","use-body-type-template":"Use body type template","plain-text-description":"Simple, unformatted text with no special styling or formating.","html-text-description":"Allows you to use HTML tags for formatting, links and images in your mai body.","dynamic-text-description":"Allows to use Plain Text or HTML body type dynamically based on templatization feature.","after-template-evaluation-hint":"After template evaluation value should be true for HTML, and false for Plain text."}}},!0)}(e)}}e("RuleNodeCoreConfigModule",Hr),Hr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hr,deps:[{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.NgModule}),Hr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Hr,declarations:[dt],imports:[$,M],exports:[Qn,Nr,ar,br,Br,Ur,dt]}),Hr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hr,imports:[$,M,Qn,Nr,ar,br,Br,Ur]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hr,decorators:[{type:c,args:[{declarations:[dt],imports:[$,M],exports:[Qn,Nr,ar,br,Br,Ur,dt]}]}],ctorParameters:function(){return[{type:Z.TranslateService}]}})}}}));//# sourceMappingURL=rulenode-core-config.js.map From f4c2791eb035fb809e908582032d98d3050dc55b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 18 Mar 2024 12:36:36 +0200 Subject: [PATCH 203/209] UI: update yarn.lock --- ....2.9.patch => @angular+core+15.2.10.patch} | 2 +- ui-ngx/pom.xml | 2 +- ui-ngx/yarn.lock | 4382 ++++++++++------- 3 files changed, 2541 insertions(+), 1845 deletions(-) rename ui-ngx/patches/{@angular+core+15.2.9.patch => @angular+core+15.2.10.patch} (97%) diff --git a/ui-ngx/patches/@angular+core+15.2.9.patch b/ui-ngx/patches/@angular+core+15.2.10.patch similarity index 97% rename from ui-ngx/patches/@angular+core+15.2.9.patch rename to ui-ngx/patches/@angular+core+15.2.10.patch index 3797e6b48c..3265415263 100644 --- a/ui-ngx/patches/@angular+core+15.2.9.patch +++ b/ui-ngx/patches/@angular+core+15.2.10.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/@angular/core/fesm2020/core.mjs b/node_modules/@angular/core/fesm2020/core.mjs -index 3e93015..9efcb96 100755 +index e9a9b75..17044d9 100755 --- a/node_modules/@angular/core/fesm2020/core.mjs +++ b/node_modules/@angular/core/fesm2020/core.mjs @@ -11053,13 +11053,13 @@ function findDirectiveDefMatches(tView, tNode) { diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index 944ca04846..750a303dfb 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -56,7 +56,7 @@ install-node-and-yarn - v16.20.2 + v20.11.1 v1.22.17 diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 4317883a2b..892b959e06 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -2,7 +2,12 @@ # yarn lockfile v1 -"@ampproject/remapping@2.2.0", "@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0": +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + +"@ampproject/remapping@2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== @@ -10,6 +15,14 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" +"@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + "@angular-builders/custom-webpack@~15.0.0": version "15.0.0" resolved "https://registry.yarnpkg.com/@angular-builders/custom-webpack/-/custom-webpack-15.0.0.tgz#bc05c5fdc1153ae2f48417f6529034282398940d" @@ -23,31 +36,23 @@ tsconfig-paths "^4.1.0" webpack-merge "^5.7.3" -"@angular-devkit/architect@0.1502.1", "@angular-devkit/architect@>=0.1500.0 < 0.1600.0": - version "0.1502.1" - resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1502.1.tgz#0ab9919638f61eaa962b1863d99581c52ccfa2a9" - integrity sha512-/rtR0U4TJCplpnIfenoTOIn4omPUDDL8iAl6le0qelL2CXdEl6UjMBYF1ILS8OVMLfpEGUXfMLeZgRn0yb7DjA== +"@angular-devkit/architect@0.1502.10", "@angular-devkit/architect@>=0.1500.0 < 0.1600.0": + version "0.1502.10" + resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1502.10.tgz#4c25ba881635937d922b18e7959b38a476badc82" + integrity sha512-S8lN73WYCfpEpw1Q41ZcUinw7JfDeSM8LyGs797OVshnW75QcOkOecWj/3CKR23G44IgFrHN6sqtzWxKmMxLig== dependencies: - "@angular-devkit/core" "15.2.1" + "@angular-devkit/core" "15.2.10" rxjs "6.6.7" -"@angular-devkit/architect@0.1502.8": - version "0.1502.8" - resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1502.8.tgz#9fd3fd27b3a7fc5f8eb65c92500b4d9d15b879e8" - integrity sha512-rTltw2ABHrcKc8EGimALvXmrDTP5hlNbEy6nYolJoXEI9EwHgriWrVLVPs3OEF+/ed47dbJi9EGOXUOgzgpB5A== - dependencies: - "@angular-devkit/core" "15.2.8" - rxjs "6.6.7" - -"@angular-devkit/build-angular@^15.0.0": - version "15.2.1" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-15.2.1.tgz#11dacec9f651e4491bb5d914e8b84dc2e245c955" - integrity sha512-EgJqlrig9iuMvqySb4h8h2AAd8OwhBqY2JMMvs+hIASZvJi3n+Qd12BdlqLuAzykRyIAdy3OeLSAaXZrmeSk0A== +"@angular-devkit/build-angular@^15.0.0", "@angular-devkit/build-angular@^15.2.8": + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-15.2.10.tgz#af4080a4811461bd1cab4f3b1b10edef53f31da8" + integrity sha512-3pCPVEJilVwHIJC6Su1/PIEqvFfU1Lxew9yItxX4s6dud8HY+fuKrsDnao4NNMFNqCLqL4el5QbSBKnnpWH1sg== dependencies: "@ampproject/remapping" "2.2.0" - "@angular-devkit/architect" "0.1502.1" - "@angular-devkit/build-webpack" "0.1502.1" - "@angular-devkit/core" "15.2.1" + "@angular-devkit/architect" "0.1502.10" + "@angular-devkit/build-webpack" "0.1502.10" + "@angular-devkit/core" "15.2.10" "@babel/core" "7.20.12" "@babel/generator" "7.20.14" "@babel/helper-annotate-as-pure" "7.18.6" @@ -59,7 +64,7 @@ "@babel/runtime" "7.20.13" "@babel/template" "7.20.7" "@discoveryjs/json-ext" "0.5.7" - "@ngtools/webpack" "15.2.1" + "@ngtools/webpack" "15.2.10" ansi-colors "4.1.3" autoprefixer "10.4.13" babel-loader "9.1.2" @@ -86,81 +91,13 @@ ora "5.4.1" parse5-html-rewriting-stream "7.0.0" piscina "3.2.0" - postcss "8.4.21" + postcss "8.4.31" postcss-loader "7.0.2" resolve-url-loader "5.0.0" rxjs "6.6.7" sass "1.58.1" sass-loader "13.2.0" - semver "7.3.8" - source-map-loader "4.0.1" - source-map-support "0.5.21" - terser "5.16.3" - text-table "0.2.0" - tree-kill "1.2.2" - tslib "2.5.0" - webpack "5.75.0" - webpack-dev-middleware "6.0.1" - webpack-dev-server "4.11.1" - webpack-merge "5.8.0" - webpack-subresource-integrity "5.1.0" - optionalDependencies: - esbuild "0.17.8" - -"@angular-devkit/build-angular@^15.2.8": - version "15.2.8" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-15.2.8.tgz#5412125b810fee084eb8afc20b9911606ad66170" - integrity sha512-TGDnXhhOG6h6TOrWWzfnkha7wYBOXi7iJc1o1w1VKCayE3T6TZZdF847aK66vL9KG7AKYVdGhWEGw2WBHUBUpg== - dependencies: - "@ampproject/remapping" "2.2.0" - "@angular-devkit/architect" "0.1502.8" - "@angular-devkit/build-webpack" "0.1502.8" - "@angular-devkit/core" "15.2.8" - "@babel/core" "7.20.12" - "@babel/generator" "7.20.14" - "@babel/helper-annotate-as-pure" "7.18.6" - "@babel/helper-split-export-declaration" "7.18.6" - "@babel/plugin-proposal-async-generator-functions" "7.20.7" - "@babel/plugin-transform-async-to-generator" "7.20.7" - "@babel/plugin-transform-runtime" "7.19.6" - "@babel/preset-env" "7.20.2" - "@babel/runtime" "7.20.13" - "@babel/template" "7.20.7" - "@discoveryjs/json-ext" "0.5.7" - "@ngtools/webpack" "15.2.8" - ansi-colors "4.1.3" - autoprefixer "10.4.13" - babel-loader "9.1.2" - babel-plugin-istanbul "6.1.1" - browserslist "4.21.5" - cacache "17.0.4" - chokidar "3.5.3" - copy-webpack-plugin "11.0.0" - critters "0.0.16" - css-loader "6.7.3" - esbuild-wasm "0.17.8" - glob "8.1.0" - https-proxy-agent "5.0.1" - inquirer "8.2.4" - jsonc-parser "3.2.0" - karma-source-map-support "1.4.0" - less "4.1.3" - less-loader "11.1.0" - license-webpack-plugin "4.0.2" - loader-utils "3.2.1" - magic-string "0.29.0" - mini-css-extract-plugin "2.7.2" - open "8.4.1" - ora "5.4.1" - parse5-html-rewriting-stream "7.0.0" - piscina "3.2.0" - postcss "8.4.21" - postcss-loader "7.0.2" - resolve-url-loader "5.0.0" - rxjs "6.6.7" - sass "1.58.1" - sass-loader "13.2.0" - semver "7.3.8" + semver "7.5.3" source-map-loader "4.0.1" source-map-support "0.5.21" terser "5.16.3" @@ -175,26 +112,18 @@ optionalDependencies: esbuild "0.17.8" -"@angular-devkit/build-webpack@0.1502.1": - version "0.1502.1" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1502.1.tgz#d0332b620a24a37ce132ef3fb771af3f991c6413" - integrity sha512-mQ9dbuoy0U2+caQCRMhUx88fA/WmBt+JkYt8nvtTUyhVTXm5XIVckTlIfqdVk0BCA1lT8AM4yFKmy97bFXuZeQ== +"@angular-devkit/build-webpack@0.1502.10": + version "0.1502.10" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1502.10.tgz#665dfa76a0c6548821fa372356e2c9b55e8eebac" + integrity sha512-55b9WZIGU4DNgiIV2lkkN6iQxJrgWY5CDaNu0kJC/qazotJgBbcN/8jgBx2DD8HNE1V3iXxWk66pt1h946Po+Q== dependencies: - "@angular-devkit/architect" "0.1502.1" + "@angular-devkit/architect" "0.1502.10" rxjs "6.6.7" -"@angular-devkit/build-webpack@0.1502.8": - version "0.1502.8" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1502.8.tgz#1b375480deef1b0920e1a63d952795bd33bbfb38" - integrity sha512-jWtNv+S03FFLDe/C8SPCcRvkz3bSb2R+919IT086Q9axIPQ1VowOEwzt2k3qXPSSrC7GSYuASM+X92dB47NTQQ== - dependencies: - "@angular-devkit/architect" "0.1502.8" - rxjs "6.6.7" - -"@angular-devkit/core@15.2.1", "@angular-devkit/core@^15.0.0": - version "15.2.1" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-15.2.1.tgz#78b03a134d354df6646a29c718bef6721ac9365e" - integrity sha512-m6lj5vvA/L21rUVm4PokK2SXZQE1Hjsw8AMNEaj+RlyVW36ISLm2TeeCdb441siBpLJkW+yA47bC2FUJ8HgsQQ== +"@angular-devkit/core@15.2.10", "@angular-devkit/core@^15.0.0": + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-15.2.10.tgz#e2c1fadaaa87ae62b3f3c752fa6fafc31197151b" + integrity sha512-bFPm7wjvfBds9km2rCJxUhzkqe4h3h/199yJtzC1bNvwRr2LMHvtyoQAzftda+gs7Ulqac5wzUEZX/cVV3WrsA== dependencies: ajv "8.12.0" ajv-formats "2.1.1" @@ -202,23 +131,12 @@ rxjs "6.6.7" source-map "0.7.4" -"@angular-devkit/core@15.2.8": - version "15.2.8" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-15.2.8.tgz#ff494ae7af137b0f0109deb8ee34f1550ed5cc1d" - integrity sha512-Lo4XrbDMtXarKnMrFgWLmQdSX+3QPNAg4otG8cmp/U4jJyjV4dAYKEAsb1sCNGUSM4h4v09EQU/5ugVjDU29lQ== +"@angular-devkit/schematics@15.2.10": + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-15.2.10.tgz#72ea6ac84082995221781bcb72df3143b4ffddc3" + integrity sha512-EeoDs4oKFpLZNa21G/8dqBdclEc4U2piI9EeXCVTaN6z5DYXIZ0G1WtCXU8nhD+GckS47rmfZ4/3lMaXAvED+g== dependencies: - ajv "8.12.0" - ajv-formats "2.1.1" - jsonc-parser "3.2.0" - rxjs "6.6.7" - source-map "0.7.4" - -"@angular-devkit/schematics@15.2.8": - version "15.2.8" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-15.2.8.tgz#c7dfc692e3f54e43085a8845d8c9f390a2519aa3" - integrity sha512-w6EUGC96kVsH9f8sEzajzbONMawezyVBiSo+JYp5r25rQArAz/a+KZntbuETWHQ0rQOEsKmUNKxwmr11BaptSQ== - dependencies: - "@angular-devkit/core" "15.2.8" + "@angular-devkit/core" "15.2.10" jsonc-parser "3.2.0" magic-string "0.29.0" ora "5.4.1" @@ -282,9 +200,9 @@ "@typescript-eslint/utils" "5.48.2" "@angular/animations@^15.2.9": - version "15.2.9" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-15.2.9.tgz#f0773d2071a5a17c03478d5838029b03bbab9a03" - integrity sha512-GQujLhI0cQFcl4Q8y0oSYKSRnW23GIeSL+Arl4eFufziJ9hGAAQNuesaNs/7i+9UlTHDMkPH3kd5ScXuYYz6wg== + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-15.2.10.tgz#c9194ba9a2b9b4e466e9c76e18591cde096a28e8" + integrity sha512-yxfN8qQpMaukRU5LjFkJBmy85rqrOp86tYVCsf+hmPEFRiXBMUj6xYLeCMcpk3Mt1JtnWGBR34ivGx+7bNeAow== dependencies: tslib "^2.3.0" @@ -298,14 +216,14 @@ parse5 "^7.1.2" "@angular/cli@^15.2.8": - version "15.2.8" - resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-15.2.8.tgz#612ffd69591aea0109db0a6dd8faec8044a4b80d" - integrity sha512-3VlTfm6DUZfFHBY43vQSAaqmFTxy3VtRd/iDBCHcEPhHwYLWBvNwReJuJfNja8O105QQ6DBiYVBExEBtPmjQ4w== - dependencies: - "@angular-devkit/architect" "0.1502.8" - "@angular-devkit/core" "15.2.8" - "@angular-devkit/schematics" "15.2.8" - "@schematics/angular" "15.2.8" + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-15.2.10.tgz#4035a64510e11894be2ff695e48ee0ef6badb494" + integrity sha512-/TSnm/ZQML6A4lvunyN2tjTB5utuvk3d1Pnfyehp/FXtV6YfZm6+EZrOpKkKPCxTuAgW6c9KK4yQtt3RuNVpwQ== + dependencies: + "@angular-devkit/architect" "0.1502.10" + "@angular-devkit/core" "15.2.10" + "@angular-devkit/schematics" "15.2.10" + "@schematics/angular" "15.2.10" "@yarnpkg/lockfile" "1.1.0" ansi-colors "4.1.3" ini "3.0.1" @@ -317,21 +235,21 @@ ora "5.4.1" pacote "15.1.0" resolve "1.22.1" - semver "7.3.8" + semver "7.5.3" symbol-observable "4.0.0" yargs "17.6.2" "@angular/common@^15.2.9": - version "15.2.9" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-15.2.9.tgz#5e1d47ce831935bcf545b172f88307aedacf1535" - integrity sha512-LM9/UHG2dRrOzlu2KovrFwWIziFMjRxHzSP3Igw6Symw/wIl0kXGq8Fn6RpFP78zmLqnv+IQOoRiby9MCXsI4g== + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-15.2.10.tgz#897923023c8ca4a361ce218bdee9a3f09060df75" + integrity sha512-jdBn3fctkqoNrJn9VLsUHpcCEhCxWSczdsR+BBbD6T0oLl6vMrAVNjPwfBejnlgfWN1KoRU9kgOYsMxa5apIWQ== dependencies: tslib "^2.3.0" "@angular/compiler-cli@^15.2.9": - version "15.2.9" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-15.2.9.tgz#d9e6013d6a8658e4a210aca7997e70d06f6976a8" - integrity sha512-zsbI8G2xHOeYWI0hjFzrI//ZhZV9il/uQW5dAimfwJp06KZDeXZ3PdwY9JQslf6F+saLwOObxy6QMrIVvfjy9w== + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-15.2.10.tgz#e51013aa0f3da303fc74f8e1948c550d8e74ead5" + integrity sha512-mCFIxrs60XicKfA2o42hA7LrQvhybi9BQveWuZn/2iIEOXx7R62Iemz8E21pLWftAZHGxEW3NECfBrY1d3gVmA== dependencies: "@babel/core" "7.19.3" "@jridgewell/sourcemap-codec" "^1.4.14" @@ -345,16 +263,16 @@ yargs "^17.2.1" "@angular/compiler@^15.2.9": - version "15.2.9" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-15.2.9.tgz#3f55e206b0e380c28336d2a233b7132f21d72644" - integrity sha512-MoKugbjk+E0wRBj12uvIyDLELlVLonnqjA2+XiF+7FxALIeyds3/qQeEoMmYIqAbN3NnTT5pV92RxWwG4tHFwA== + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-15.2.10.tgz#bd78f327d12eb5978f9dd05440aa23d4b5b925a9" + integrity sha512-M0XkeU0O73UlJZwDvOyp8/apetz9UKj78eTFDseMYJDLcxe6MpkbkxqpsGZnKYDj7LIep8PmCAKEkhtenE82zw== dependencies: tslib "^2.3.0" "@angular/core@^15.2.9": - version "15.2.9" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-15.2.9.tgz#7cb12cc83fcc92f23196ceac82e07b67b2e02203" - integrity sha512-w46Z1yUXCQfKV7XfnamOoLA2VD0MVUUYVrUjO73mHSskDXSXxfZAEHO9kfUS71Cj35PvhP3mbkqWscpea2WeYg== + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-15.2.10.tgz#93c1e0d460d21711654c578d2709a402e1822023" + integrity sha512-meGGidnitQJGDxYd9/LrqYiVlId+vGaLoiLgJdKBz+o2ZO6OmXQGuNw2VBqf17/Cc0/UjzrOY7+kILNFKkk/WQ== dependencies: tslib "^2.3.0" @@ -366,16 +284,16 @@ tslib "^2.3.0" "@angular/forms@^15.2.9": - version "15.2.9" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-15.2.9.tgz#c3b4b0108f4eb4966ddc5a7ec9913c2ca2c94f00" - integrity sha512-sk0pC2EFi2Ohg5J0q0NYptbT+2WOkoiERSMYA39ncDvlSZBWsNlxpkbGUSck7NIxjK2QfcVN1ldGbHlZTFvtqg== + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-15.2.10.tgz#09308e887df2fa4d349300c9d1f05cadfb3872b3" + integrity sha512-NIntGsNcN6o8L1txsbWXOf6f3K/CUBizdKsxsYVYGJIXEW5qU6UnWmfAZffNNXsT/XvbgUCjgDwT0cAwcqZPuQ== dependencies: tslib "^2.3.0" "@angular/language-service@^15.2.9": - version "15.2.9" - resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-15.2.9.tgz#7a94e3394093a425c757f7b385b4a94edb09178a" - integrity sha512-B7lP4q/eHge2lZezOXS96EYzVf4stMCWfOnz7+pUUi0HbF+A5QCV65SWQddS/M+NM2jj8N2L/j+6UCH8lJjTQA== + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-15.2.10.tgz#829a802aaf40bfab21d71463023a3b517500ffa9" + integrity sha512-G0g0teF4pBqLTgfyLcoBl55g91sCZvBK+V4VgTD/hXGpXyMNlNpOsgECSMliGQoJlsRLEugFsSlBNqy7CRoBtw== "@angular/material@^15.2.9": version "15.2.9" @@ -432,23 +350,23 @@ tslib "^2.3.0" "@angular/platform-browser-dynamic@^15.2.9": - version "15.2.9" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-15.2.9.tgz#aa31ba63d535ee49fdf3a60fe771503565b4e3c9" - integrity sha512-ZIYDM6MShblb8OyV1m4+18lJJ2LCeICmeg2uSbpFYptYBSOClrTiYOOFVDJvn7HLvNzljLs16XPrgyaYVqNpcw== + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-15.2.10.tgz#cc9ad3dcded6cb945ee8c4eef14db081dc6c3dfd" + integrity sha512-JHP6W+FX715Qv7DhqvfZLuBZXSDJrboiQsR06gUAgDSjAUyhbqmpVg/2YOtgeWpPkzNDtXdPU2PhcRdIv5J3Yg== dependencies: tslib "^2.3.0" "@angular/platform-browser@^15.2.9": - version "15.2.9" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-15.2.9.tgz#9150645843cc18b084fb5bf7025e6e320c2abe1e" - integrity sha512-ufCHeSX+U6d43YOMkn3igwfqtlozoCXADcbyfUEG8m2y9XASobqmCKvdSk/zfl62oyiA8msntWBJVBE2l4xKXg== + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-15.2.10.tgz#ca5a904b4da9e0cf719414db89514ee4221cb93d" + integrity sha512-9tbgVGSJqwfrOzT8aA/kWBLNhJSQ9gUg0CJxwFBSJm8VkBUJrszoBlDsnSvlxx8/W2ejNULKHFTXeUzq0O/+RQ== dependencies: tslib "^2.3.0" "@angular/router@^15.2.9": - version "15.2.9" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-15.2.9.tgz#c3879be22bda236eacf97a18a1e8619b51a53d47" - integrity sha512-UCbh5DLSDhybv0xKYT7kGQMfOVdyhHIHOZz5EYVebbhste6S+W1LE57vTHq7QtxJsyKBa/WSkaUkCLXD6ntCAg== + version "15.2.10" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-15.2.10.tgz#a5d32d769b930e905582ed6c7aa8122e63655738" + integrity sha512-LmuqEg0iIXSw7bli6HKJ19cbxP91v37GtRwbGKswyLihqzTgvjBYpvcfMnB5FRQ5LWkTwq5JclkX03dZw290Yg== dependencies: tslib "^2.3.0" @@ -458,23 +376,24 @@ integrity sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg== "@auth0/angular-jwt@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@auth0/angular-jwt/-/angular-jwt-5.1.2.tgz#e89ece98b0f6ef6407f35b8b64bcb77c53b0d8e5" - integrity sha512-8ulz24cPpEkZb9/AdAaWfYIkomQDbZqvB9LproF/48Klnr30EJx09AYF9sbKTN4qLSgIZSlCb/Y7XQJZ51vSzA== + version "5.2.0" + resolved "https://registry.yarnpkg.com/@auth0/angular-jwt/-/angular-jwt-5.2.0.tgz#3a734be51f7f4b1bb85ac2535e987b7e6e706481" + integrity sha512-9FS2L0QwGNlxA/zgeehCcsR9CZscouyXkoIj1fODM36A8BLfdzg9k9DWAXUQ2Drjk0AypGAFzeNZR4vsLMhdeQ== dependencies: tslib "^2.0.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== dependencies: - "@babel/highlight" "^7.18.6" + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.0.tgz#c241dc454e5b5917e40d37e525e2f4530c399298" - integrity sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== "@babel/core@7.19.3": version "7.19.3" @@ -519,25 +438,25 @@ semver "^6.3.0" "@babel/core@^7.12.3": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.0.tgz#1341aefdcc14ccc7553fcc688dd8986a2daffc13" - integrity sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA== + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.0.tgz#56cbda6b185ae9d9bed369816a8f4423c5f2ff1b" + integrity sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.21.0" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-module-transforms" "^7.21.0" - "@babel/helpers" "^7.21.0" - "@babel/parser" "^7.21.0" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.0" - "@babel/types" "^7.21.0" - convert-source-map "^1.7.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.24.0" + "@babel/parser" "^7.24.0" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.0" + "@babel/types" "^7.24.0" + convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.2" - semver "^6.3.0" + json5 "^2.2.3" + semver "^6.3.1" "@babel/generator@7.20.14": version "7.20.14" @@ -548,63 +467,71 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/generator@^7.19.3", "@babel/generator@^7.20.7", "@babel/generator@^7.21.0", "@babel/generator@^7.21.1": - version "7.21.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.1.tgz#951cc626057bc0af2c35cd23e9c64d384dea83dd" - integrity sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA== +"@babel/generator@^7.19.3", "@babel/generator@^7.20.7", "@babel/generator@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== dependencies: - "@babel/types" "^7.21.0" + "@babel/types" "^7.23.6" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@7.18.6", "@babel/helper-annotate-as-pure@^7.18.6": +"@babel/helper-annotate-as-pure@7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== dependencies: "@babel/types" "^7.18.6" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" - integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== +"@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== dependencies: - "@babel/helper-explode-assignable-expression" "^7.18.6" - "@babel/types" "^7.18.9" + "@babel/types" "^7.22.5" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.3", "@babel/helper-compilation-targets@^7.20.0", "@babel/helper-compilation-targets@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" - integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" + integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== dependencies: - "@babel/compat-data" "^7.20.5" - "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.21.3" - lru-cache "^5.1.1" - semver "^6.3.0" + "@babel/types" "^7.22.15" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz#64f49ecb0020532f19b1d014b03bccaa1ab85fb9" - integrity sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.19.3", "@babel/helper-compilation-targets@^7.20.0", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-member-expression-to-functions" "^7.21.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.20.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.0.tgz#53ff78472e5ce10a52664272a239787107603ebb" - integrity sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.0.tgz#fc7554141bdbfa2d17f7b4b80153b9b090e5d158" + integrity sha512-QAH+vfvts51BCsNZ2PhY6HAggnlS6omLLFTsIpeqZk/MmJ6cW7tgz5yRv0fMJThcr6FmbMrENh1RgrWPTYA76g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.20" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" + integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" regexpu-core "^5.3.1" + semver "^6.3.1" "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" @@ -618,179 +545,171 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== +"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== -"@babel/helper-explode-assignable-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" - integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== +"@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== dependencies: - "@babel/types" "^7.18.6" + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" - integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== dependencies: - "@babel/template" "^7.20.7" - "@babel/types" "^7.21.0" + "@babel/types" "^7.22.5" -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== +"@babel/helper-member-expression-to-functions@^7.22.15", "@babel/helper-member-expression-to-functions@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366" + integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.23.0" -"@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5" - integrity sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q== +"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== dependencies: - "@babel/types" "^7.21.0" + "@babel/types" "^7.22.15" -"@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== +"@babel/helper-module-transforms@^7.19.0", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== dependencies: - "@babel/types" "^7.18.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.0", "@babel/helper-module-transforms@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" - integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.20.2" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.2" - "@babel/types" "^7.21.2" + "@babel/types" "^7.22.5" -"@babel/helper-optimise-call-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" - integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" - integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" + integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== -"@babel/helper-remap-async-to-generator@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" - integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== +"@babel/helper-remap-async-to-generator@^7.18.9", "@babel/helper-remap-async-to-generator@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" + integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-wrap-function" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-wrap-function" "^7.22.20" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" - integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== +"@babel/helper-replace-supers@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" + integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.20.7" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.7" - "@babel/types" "^7.20.7" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-optimise-call-expression" "^7.22.5" -"@babel/helper-simple-access@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" - integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== dependencies: - "@babel/types" "^7.20.2" + "@babel/types" "^7.22.5" -"@babel/helper-skip-transparent-expression-wrappers@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" - integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== +"@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== dependencies: - "@babel/types" "^7.20.0" + "@babel/types" "^7.22.5" -"@babel/helper-split-export-declaration@7.18.6", "@babel/helper-split-export-declaration@^7.18.6": +"@babel/helper-split-export-declaration@7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== dependencies: "@babel/types" "^7.18.6" -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== -"@babel/helper-validator-option@^7.18.6": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" - integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== -"@babel/helper-wrap-function@^7.18.9": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz#75e2d84d499a0ab3b31c33bcfe59d6b8a45f62e3" - integrity sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q== +"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helper-wrap-function@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" + integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw== dependencies: - "@babel/helper-function-name" "^7.19.0" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.5" - "@babel/types" "^7.20.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/types" "^7.22.19" -"@babel/helpers@^7.19.0", "@babel/helpers@^7.20.7", "@babel/helpers@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" - integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== +"@babel/helpers@^7.19.0", "@babel/helpers@^7.20.7", "@babel/helpers@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.0.tgz#a3dd462b41769c95db8091e49cfe019389a9409b" + integrity sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA== dependencies: - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.0" - "@babel/types" "^7.21.0" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.0" + "@babel/types" "^7.24.0" -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.14.7", "@babel/parser@^7.19.3", "@babel/parser@^7.20.7", "@babel/parser@^7.21.0", "@babel/parser@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3" - integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ== +"@babel/parser@^7.14.7", "@babel/parser@^7.19.3", "@babel/parser@^7.20.7", "@babel/parser@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.0.tgz#26a3d1ff49031c53a97d03b604375f028746a9ac" + integrity sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" - integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz#5cd1c87ba9380d0afb78469292c954fee5d2411a" + integrity sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz#d9c85589258539a22a901033853101a6198d4ef1" - integrity sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz#f6652bb16b94f8f9c20c50941e16e9756898dc5d" + integrity sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/plugin-proposal-optional-chaining" "^7.20.7" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.23.3" "@babel/plugin-proposal-async-generator-functions@7.20.7", "@babel/plugin-proposal-async-generator-functions@^7.20.1": version "7.20.7" @@ -886,7 +805,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.18.9", "@babel/plugin-proposal-optional-chaining@^7.20.7": +"@babel/plugin-proposal-optional-chaining@^7.18.9": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== @@ -904,9 +823,9 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-proposal-private-property-in-object@^7.18.6": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz#19496bd9883dd83c23c7d7fc45dcd9ad02dfa1dc" - integrity sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw== + version "7.21.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz#69d597086b6760c4126525cfa154f34631ff272c" + integrity sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-create-class-features-plugin" "^7.21.0" @@ -957,11 +876,11 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-import-assertions@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" - integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz#9c05a7f592982aff1a2768260ad84bcd3f0c77fc" + integrity sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" @@ -1027,13 +946,13 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-arrow-functions@^7.18.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz#bea332b0e8b2dab3dafe55a163d8227531ab0551" - integrity sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz#94c6dcfd731af90f27a79509f9ab7fb2120fc38b" + integrity sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-async-to-generator@7.20.7", "@babel/plugin-transform-async-to-generator@^7.18.6": +"@babel/plugin-transform-async-to-generator@7.20.7": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354" integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q== @@ -1042,189 +961,207 @@ "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-remap-async-to-generator" "^7.18.9" +"@babel/plugin-transform-async-to-generator@^7.18.6": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz#d1f513c7a8a506d43f47df2bf25f9254b0b051fa" + integrity sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw== + dependencies: + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.20" + "@babel/plugin-transform-block-scoped-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" - integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz#fe1177d715fb569663095e04f3598525d98e8c77" + integrity sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-block-scoping@^7.20.2": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02" - integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ== + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz#b2d38589531c6c80fbe25e6b58e763622d2d3cf5" + integrity sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-classes@^7.20.2": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665" - integrity sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-replace-supers" "^7.20.7" - "@babel/helper-split-export-declaration" "^7.18.6" + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz#d08ae096c240347badd68cdf1b6d1624a6435d92" + integrity sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.20" + "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" "@babel/plugin-transform-computed-properties@^7.18.9": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz#704cc2fd155d1c996551db8276d55b9d46e4d0aa" - integrity sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz#652e69561fcc9d2b50ba4f7ac7f60dcf65e86474" + integrity sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/template" "^7.20.7" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/template" "^7.22.15" "@babel/plugin-transform-destructuring@^7.20.2": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz#8bda578f71620c7de7c93af590154ba331415454" - integrity sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz#8c9ee68228b12ae3dff986e56ed1ba4f3c446311" + integrity sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" - integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz#3f7af6054882ede89c378d0cf889b854a993da50" + integrity sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-duplicate-keys@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" - integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz#664706ca0a5dfe8d066537f99032fc1dc8b720ce" + integrity sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-exponentiation-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" - integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz#ea0d978f6b9232ba4722f3dbecdd18f450babd18" + integrity sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-for-of@^7.18.8": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz#964108c9988de1a60b4be2354a7d7e245f36e86e" - integrity sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ== + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz#81c37e24171b37b370ba6aaffa7ac86bcb46f94e" + integrity sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-transform-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" - integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz#8f424fcd862bf84cb9a1a6b42bc2f47ed630f8dc" + integrity sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw== dependencies: - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" - integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz#8214665f00506ead73de157eba233e7381f3beb4" + integrity sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-member-expression-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" - integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz#e37b3f0502289f477ac0e776b05a833d853cabcc" + integrity sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-modules-amd@^7.19.6": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz#3daccca8e4cc309f03c3a0c4b41dc4b26f55214a" - integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz#e19b55436a1416829df0a1afc495deedfae17f7d" + integrity sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw== dependencies: - "@babel/helper-module-transforms" "^7.20.11" - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-modules-commonjs@^7.19.6": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz#6ff5070e71e3192ef2b7e39820a06fb78e3058e7" - integrity sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz#661ae831b9577e52be57dd8356b734f9700b53b4" + integrity sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA== dependencies: - "@babel/helper-module-transforms" "^7.21.2" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" "@babel/plugin-transform-modules-systemjs@^7.19.6": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz#467ec6bba6b6a50634eea61c9c232654d8a4696e" - integrity sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw== + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz#105d3ed46e4a21d257f83a2f9e2ee4203ceda6be" + integrity sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw== dependencies: - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.20.11" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-validator-identifier" "^7.19.1" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" "@babel/plugin-transform-modules-umd@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" - integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz#5d4395fccd071dfefe6585a4411aa7d6b7d769e9" + integrity sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg== dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8" - integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA== + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" + integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.20.5" - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-new-target@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" - integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz#5491bb78ed6ac87e990957cea367eab781c4d980" + integrity sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-object-super@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" - integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz#81fdb636dcb306dd2e4e8fd80db5b2362ed2ebcd" + integrity sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.20" + +"@babel/plugin-transform-optional-chaining@^7.23.3": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz#6acf61203bdfc4de9d4e52e64490aeb3e52bd017" + integrity sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-transform-parameters@^7.20.1", "@babel/plugin-transform-parameters@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz#0ee349e9d1bc96e78e3b37a7af423a4078a7083f" - integrity sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz#83ef5d1baf4b1072fa6e54b2b0999a7b2527e2af" + integrity sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-property-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" - integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz#54518f14ac4755d22b92162e4a852d308a560875" + integrity sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-regenerator@^7.18.6": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz#57cda588c7ffb7f4f8483cc83bdcea02a907f04d" - integrity sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz#141afd4a2057298602069fce7f2dc5173e6c561c" + integrity sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - regenerator-transform "^0.15.1" + "@babel/helper-plugin-utils" "^7.22.5" + regenerator-transform "^0.15.2" "@babel/plugin-transform-reserved-words@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" - integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz#4130dcee12bd3dd5705c587947eb715da12efac8" + integrity sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-runtime@7.19.6": version "7.19.6" @@ -1239,55 +1176,55 @@ semver "^6.3.0" "@babel/plugin-transform-shorthand-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" - integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz#97d82a39b0e0c24f8a981568a8ed851745f59210" + integrity sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-spread@^7.19.0": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e" - integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz#41d17aacb12bde55168403c6f2d6bdca563d362c" + integrity sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-transform-sticky-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" - integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz#dec45588ab4a723cb579c609b294a3d1bd22ff04" + integrity sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-template-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" - integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz#5f0f028eb14e50b5d0f76be57f90045757539d07" + integrity sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-typeof-symbol@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" - integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz#9dfab97acc87495c0c449014eb9c547d8966bca4" + integrity sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-unicode-escapes@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" - integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz#1f66d16cab01fab98d784867d24f70c1ca65b925" + integrity sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-unicode-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" - integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz#26897708d8f42654ca4ce1b73e96140fbad879dc" + integrity sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/preset-env@7.20.2": version "7.20.2" @@ -1371,9 +1308,9 @@ semver "^6.3.0" "@babel/preset-modules@^0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" - integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== + version "0.1.6" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6.tgz#31bcdd8f19538437339d17af00d177d854d9d458" + integrity sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" @@ -1394,13 +1331,13 @@ regenerator-runtime "^0.13.11" "@babel/runtime@^7.10.1", "@babel/runtime@^7.11.1", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" - integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" + integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== dependencies: - regenerator-runtime "^0.13.11" + regenerator-runtime "^0.14.0" -"@babel/template@7.20.7", "@babel/template@^7.18.10", "@babel/template@^7.20.7": +"@babel/template@7.20.7": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== @@ -1409,35 +1346,44 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" -"@babel/traverse@^7.19.3", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.2.tgz#ac7e1f27658750892e815e60ae90f382a46d8e75" - integrity sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.21.1" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.21.2" - "@babel/types" "^7.21.2" - debug "^4.1.0" +"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.22.15", "@babel/template@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + +"@babel/traverse@^7.19.3", "@babel/traverse@^7.20.12", "@babel/traverse@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.0.tgz#4a408fbf364ff73135c714a2ab46a5eab2831b1e" + integrity sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.3", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.4.4": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.2.tgz#92246f6e00f91755893c2876ad653db70c8310d1" - integrity sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw== +"@babel/types@^7.18.6", "@babel/types@^7.19.3", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.24.0", "@babel/types@^7.4.4": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" "@braintree/sanitize-url@^6.0.0": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz#6110f918d273fe2af8ea1c4398a88774bb9fc12f" - integrity sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg== + version "6.0.4" + resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783" + integrity sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A== "@colors/colors@1.5.0": version "1.5.0" @@ -1473,14 +1419,14 @@ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== -"@es-joy/jsdoccomment@~0.36.1": - version "0.36.1" - resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz#c37db40da36e4b848da5fd427a74bae3b004a30f" - integrity sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg== +"@es-joy/jsdoccomment@~0.42.0": + version "0.42.0" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.42.0.tgz#59e878708336aaee88c2b34c894f73dbf77ae2b0" + integrity sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw== dependencies: - comment-parser "1.3.1" - esquery "^1.4.0" - jsdoc-type-pratt-parser "~3.1.0" + comment-parser "1.4.1" + esquery "^1.5.0" + jsdoc-type-pratt-parser "~4.0.0" "@esbuild/android-arm64@0.17.8": version "0.17.8" @@ -1599,19 +1545,19 @@ dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.4.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.0.tgz#f6f729b02feee2c749f57e334b7a1b5f40a81724" - integrity sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ== +"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== -"@eslint/eslintrc@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02" - integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ== +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.5.1" + espree "^9.6.0" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -1619,10 +1565,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.37.0": - version "8.37.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.37.0.tgz#cf1b5fa24217fe007f6487a26d765274925efa7d" - integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A== +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== "@flowjs/flow.js@^2.14.1": version "2.14.1" @@ -1642,9 +1588,9 @@ integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== "@geoman-io/leaflet-geoman-free@^2.13.0": - version "2.14.2" - resolved "https://registry.yarnpkg.com/@geoman-io/leaflet-geoman-free/-/leaflet-geoman-free-2.14.2.tgz#c84c2115c263f34d11dc0b43859551639fe3d56b" - integrity sha512-6lIyG8RvSVdFjVjiQgBPyNASjymSyqzsiUeBW0pA+q41lB5fAg4SDC6SfJvWdEyDHa81Jb5FWjUkCc9O+u0gbg== + version "2.16.0" + resolved "https://registry.yarnpkg.com/@geoman-io/leaflet-geoman-free/-/leaflet-geoman-free-2.16.0.tgz#c8a92fcc2cdd5770ebf43f8ef9f03cc8ef842359" + integrity sha512-BnKAAoTXraWVFfhX/0gT/iBgAz1BPfpbdQ9dJamEFI4lIku9UNXXluu/E0k7YkZETq0tENX2GPnKLB4p+VgrSw== dependencies: "@turf/boolean-contains" "^6.5.0" "@turf/kinks" "^6.5.0" @@ -1653,13 +1599,13 @@ lodash "4.17.21" polygon-clipping "0.15.3" -"@humanwhocodes/config-array@^0.11.8": - version "0.11.8" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" - integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" minimatch "^3.0.5" "@humanwhocodes/module-importer@^1.0.1": @@ -1667,18 +1613,30 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" + integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== "@iplab/ngx-color-picker@^15.0.2": - version "15.0.2" - resolved "https://registry.yarnpkg.com/@iplab/ngx-color-picker/-/ngx-color-picker-15.0.2.tgz#856bf2571378e792e5e42b566ac1aa79a9262763" - integrity sha512-wum0Hg4Ky/6mhvzolEpFpjGckOjN8L2nkXvy3mWUVclFHP9MZqqgpJaxghtax7/tMdEWQnwQI3PYsNyKfF15ug== + version "15.1.0" + resolved "https://registry.yarnpkg.com/@iplab/ngx-color-picker/-/ngx-color-picker-15.1.0.tgz#7520cb4c58f06ec68006b103214f9a649d14b15f" + integrity sha512-pL/GxDcMQpOe5CVoRa2iNgpnYS4N+S6Gr/7RkCnOJsWEm+OOcf4K4X2BxhKZK+uF6jBFata+u8pwJv9VgCitnQ== dependencies: tslib "^2.3.0" +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1708,37 +1666,37 @@ "@jridgewell/set-array" "^1.0.0" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== +"@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== dependencies: - "@jridgewell/set-array" "^1.0.1" + "@jridgewell/set-array" "^1.2.1" "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== +"@jridgewell/source-map@^0.3.2", "@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" -"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" @@ -1748,13 +1706,13 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" "@juggle/resize-observer@^3.4.0": version "3.4.0" @@ -2558,18 +2516,18 @@ tslib "^2.1.0" "@mdi/svg@^7.2.96": - version "7.2.96" - resolved "https://registry.yarnpkg.com/@mdi/svg/-/svg-7.2.96.tgz#6c182628ed722a85055c3795e7d4ca6d4f2f139c" - integrity sha512-rxzuSL2RSt/pWWnFnUFQi5GJArm2tHMhx20Gee3Ydn+xT2bqbR4syfgdPrq2b+j+n5LjC7C8Fb1QDM6LKeF0cA== + version "7.4.47" + resolved "https://registry.yarnpkg.com/@mdi/svg/-/svg-7.4.47.tgz#f8e5516aae129764a76d1bb2f27e55bee03e6e90" + integrity sha512-WQ2gDll12T9WD34fdRFgQVgO8bag3gavrAgJ0frN4phlwdJARpE6gO1YvLEMJR0KKgoc+/Ea/A0Pp11I00xBvw== "@messageformat/core@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@messageformat/core/-/core-3.1.0.tgz#d4d2f5c3555228a6b5980b122a02b53dfc6458bd" - integrity sha512-UxAnjecnRG4u2iaggwIyylYPHmk5BTErJcKmWyAKTXqYgSW1bFLp4D7fIzuh6bk17Qfcmf3qtufdrstCB23nBA== + version "3.3.0" + resolved "https://registry.yarnpkg.com/@messageformat/core/-/core-3.3.0.tgz#31edd52a5f7d017adad85c929809f07741dcfd3f" + integrity sha512-YcXd3remTDdeMxAlbvW6oV9d/01/DZ8DHUFwSttO3LMzIZj3iO0NRw+u1xlsNNORFI+u0EQzD52ZX3+Udi0T3g== dependencies: "@messageformat/date-skeleton" "^1.0.0" "@messageformat/number-skeleton" "^1.0.0" - "@messageformat/parser" "^5.0.0" + "@messageformat/parser" "^5.1.0" "@messageformat/runtime" "^3.0.1" make-plural "^7.0.0" safe-identifier "^0.4.1" @@ -2580,14 +2538,14 @@ integrity sha512-jPXy8fg+WMPIgmGjxSlnGJn68h/2InfT0TNSkVx0IGXgp4ynnvYkbZ51dGWmGySEK+pBiYUttbQdu5XEqX5CRg== "@messageformat/number-skeleton@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@messageformat/number-skeleton/-/number-skeleton-1.1.0.tgz#eb636738da8abbd35ccbeb84f7d84d63302aeb61" - integrity sha512-F0Io+GOSvFFxvp9Ze3L5kAoZ2NnOAT0Mr/jpGNd3fqo8A0t4NxNIAcCdggtl2B/gN2ErkIKSBVPrF7xcW1IGvA== + version "1.2.0" + resolved "https://registry.yarnpkg.com/@messageformat/number-skeleton/-/number-skeleton-1.2.0.tgz#e7c245c41a1b2722bc59dad68f4d454f761bc9b4" + integrity sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg== -"@messageformat/parser@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@messageformat/parser/-/parser-5.0.0.tgz#5737e69d7d4a469998b527710f1891174fc1b262" - integrity sha512-WiDKhi8F0zQaFU8cXgqq69eYFarCnTVxKcvhAONufKf0oUxbqLMW6JX6rV4Hqh+BEQWGyKKKHY4g1XA6bCLylA== +"@messageformat/parser@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@messageformat/parser/-/parser-5.1.0.tgz#05e4851c782d633ad735791dd0a68ee65d2a7201" + integrity sha512-jKlkls3Gewgw6qMjKZ9SFfHUpdzEVdovKFtW1qRhJ3WI4FW5R/NnGDqr8SDGz+krWDO3ki94boMmQvGke1HwUQ== dependencies: moo "^0.5.1" @@ -2624,10 +2582,10 @@ resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-15.2.1.tgz#439ac075b2dcb9f304f0b7009182cc5049c7988a" integrity sha512-YtA8rWAglPuf4CSStrFAxaprTSYE+DREGrJFc3WvZLcF5XrwVK+H4CC4Pmz07iYsG1TXShR4bWp1fbGw1cmBKw== -"@ngtools/webpack@15.2.8": - version "15.2.8" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-15.2.8.tgz#df8fb9300ccf94cab8f8ad69fb16fd31181e6c82" - integrity sha512-BJexeT4FxMtToVBGa3wdl6rrkYXgilP0kkSH4Qzu4MPlLPbeBSr4XQalQriewlpC2uzG0r2SJfrAe2eDhtSykA== +"@ngtools/webpack@15.2.10": + version "15.2.10" + resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-15.2.10.tgz#8118450206ae9398d81ca2eebe1b369321ac5583" + integrity sha512-ZExB4rKh/Saad31O/Ofd2XvRuILuCNTYs0+qJL697Be2pzeewvzBhE4Xe1Mm7Jg13aWSPeuIdzSGOqCdwxxxFQ== "@ngx-translate/core@^14.0.0": version "14.0.0" @@ -2680,13 +2638,12 @@ semver "^7.3.5" "@npmcli/git@^4.0.0": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-4.0.3.tgz#354db5fe1f29696303638e191d8538ee9b01b4bb" - integrity sha512-8cXNkDIbnXPVbhXMmQ7/bklCAjtmPaXfI9aEM4iH+xSuEHINLMHhlfESvVwdqmHJRJkR48vNJTSUvoF6GRPSFA== + version "4.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-4.1.0.tgz#ab0ad3fd82bc4d8c1351b6c62f0fa56e8fe6afa6" + integrity sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ== dependencies: "@npmcli/promise-spawn" "^6.0.0" lru-cache "^7.4.4" - mkdirp "^1.0.4" npm-pick-manifest "^8.0.0" proc-log "^3.0.0" promise-inflight "^1.0.1" @@ -2723,9 +2680,9 @@ which "^3.0.0" "@npmcli/run-script@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-6.0.0.tgz#f89e322c729e26ae29db6cc8cc76559074aac208" - integrity sha512-ql+AbRur1TeOdl1FY+RAwGW9fcr4ZwiVKabdvm93mujGREVuVLbdkXRJDrkTXSdCjaxYydr1wlA2v67jxWG5BQ== + version "6.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-6.0.2.tgz#a25452d45ee7f7fb8c16dfaf9624423c0c0eb885" + integrity sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA== dependencies: "@npmcli/node-gyp" "^3.0.0" "@npmcli/promise-spawn" "^6.0.0" @@ -2733,15 +2690,54 @@ read-package-json-fast "^3.0.0" which "^3.0.0" -"@schematics/angular@15.2.8": - version "15.2.8" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-15.2.8.tgz#d845903f1cc477d299f968eb5bc40a9855cfd911" - integrity sha512-F49IEzCFxQlpaMIgTO/wF1l/CLQKif7VaiDdyiTKOeT22IMmyd61FUmWDyZYfCBqMlvBmvDGx64HaHWes1HYCg== +"@one-ini/wasm@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@one-ini/wasm/-/wasm-0.1.1.tgz#6013659736c9dbfccc96e8a9c2b3de317df39323" + integrity sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw== + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@schematics/angular@15.2.10": + version "15.2.10" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-15.2.10.tgz#fa6c05f37ba82422abd6b3f13a2fc78ec7a4eb3d" + integrity sha512-eLdyP+T1TueNQ8FCP7sP+tt8z+YQ1BINsJsyAyoJT/XZjcCV7LUxgDIU94/kuvIotmJ2xTuFWHFPfAY+CN3duQ== dependencies: - "@angular-devkit/core" "15.2.8" - "@angular-devkit/schematics" "15.2.8" + "@angular-devkit/core" "15.2.10" + "@angular-devkit/schematics" "15.2.10" jsonc-parser "3.2.0" +"@sigstore/bundle@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@sigstore/bundle/-/bundle-1.1.0.tgz#17f8d813b09348b16eeed66a8cf1c3d6bd3d04f1" + integrity sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog== + dependencies: + "@sigstore/protobuf-specs" "^0.2.0" + +"@sigstore/protobuf-specs@^0.2.0": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz#be9ef4f3c38052c43bd399d3f792c97ff9e2277b" + integrity sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A== + +"@sigstore/sign@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@sigstore/sign/-/sign-1.0.0.tgz#6b08ebc2f6c92aa5acb07a49784cb6738796f7b4" + integrity sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA== + dependencies: + "@sigstore/bundle" "^1.1.0" + "@sigstore/protobuf-specs" "^0.2.0" + make-fetch-happen "^11.0.1" + +"@sigstore/tuf@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@sigstore/tuf/-/tuf-1.0.3.tgz#2a65986772ede996485728f027b0514c0b70b160" + integrity sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg== + dependencies: + "@sigstore/protobuf-specs" "^0.2.0" + tuf-js "^1.1.7" + "@socket.io/component-emitter@~3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" @@ -2788,16 +2784,22 @@ integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" - integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== -"@tufjs/models@1.0.0": +"@tufjs/canonical-json@1.0.0": version "1.0.0" - resolved "https://registry.yarnpkg.com/@tufjs/models/-/models-1.0.0.tgz#5a5784e8770b7d014b5f87bff35af1f71fdebf1f" - integrity sha512-RRMu4uMxWnZlxaIBxahSb2IssFZiu188sndesZflWOe1cA/qUqtemSIoBWbuVKPvvdktapImWNnKpBcc+VrCQw== + resolved "https://registry.yarnpkg.com/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz#eade9fd1f537993bc1f0949f3aea276ecc4fab31" + integrity sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ== + +"@tufjs/models@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tufjs/models/-/models-1.0.4.tgz#5a689630f6b9dbda338d4b208019336562f176ef" + integrity sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A== dependencies: - minimatch "^6.1.0" + "@tufjs/canonical-json" "1.0.0" + minimatch "^9.0.0" "@turf/bbox@*", "@turf/bbox@^6.5.0": version "6.5.0" @@ -2950,42 +2952,42 @@ "@turf/meta" "^6.5.0" "@types/ace-diff@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@types/ace-diff/-/ace-diff-2.1.1.tgz#1c08919aae8f9c429fcb139dc564c89dd093cbee" - integrity sha512-O27fCo2Y0njNslOFSewyRhTyXfLhVhleEU5aTI6ZqFTKENJ8L/LA+Y+ZfcHsHTtwrTWjBXqORmqEHH6Qytqw1w== + version "2.1.4" + resolved "https://registry.yarnpkg.com/@types/ace-diff/-/ace-diff-2.1.4.tgz#0bf9952c9b23fb3cb6f5cf96f1e8bbed8f603fcd" + integrity sha512-Jd9J35hi9PdsQzAAXWzPlXTuxRxnxuaNBMzBNts3EUDoQ8yuWp4Y36GjPASke8D6yVwKrapOnAaVp3km/CxPRw== "@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== dependencies: "@types/connect" "*" "@types/node" "*" "@types/bonjour@^3.5.9": - version "3.5.10" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" - integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== + version "3.5.13" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" + integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== dependencies: "@types/node" "*" "@types/canvas-gauges@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@types/canvas-gauges/-/canvas-gauges-2.1.4.tgz#063881264597d098e78cf5ad921e8ed20ae2ad16" - integrity sha512-JTvqQWrqcrgzCp/9+uzwUvPef2qAEnBJvm+bL9kvulzhXapDeNaGQXCIAZp+hOryusjOyndvP1za2HZooUV0XA== + version "2.1.8" + resolved "https://registry.yarnpkg.com/@types/canvas-gauges/-/canvas-gauges-2.1.8.tgz#5bdd199c598fd45f8618127bec2e2f4e49d7477f" + integrity sha512-sbxlEPEPnEYfvzC/yPIJZNkxKfAJaI45dxnQrkg3g2l3+1lWX6lwduRo8+hrsDw7VdJ5ijuRi1sQ7XBFu1R2qg== "@types/connect-history-api-fallback@^1.3.5": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" - integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" + integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== dependencies: "@types/express-serve-static-core" "*" "@types/node" "*" "@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== dependencies: "@types/node" "*" @@ -2995,32 +2997,32 @@ integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== "@types/cors@^2.8.12": - version "2.8.13" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.13.tgz#b8ade22ba455a1b8cb3b5d3f35910fd204f84f94" - integrity sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA== + version "2.8.17" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b" + integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== dependencies: "@types/node" "*" "@types/eslint-scope@^3.7.3": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" - integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== dependencies: "@types/eslint" "*" "@types/estree" "*" "@types/eslint@*": - version "8.21.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.21.1.tgz#110b441a210d53ab47795124dbc3e9bb993d1e7c" - integrity sha512-rc9K8ZpVjNcLs8Fp0dkozd5Pt2Apk1glO4Vgz8ix1u6yFByxfqo5Yavpy65o+93TAe24jr7v+eSBtFLvOQtCRQ== + version "8.56.5" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.5.tgz#94b88cab77588fcecdd0771a6d576fa1c0af9d02" + integrity sha512-u5/YPJHo1tvkSF2CE0USEkxon82Z5DBy2xR+qfyYNszpX9qcs4sT6uq2kBbj4BXY1+DBGDPnrhMZV3pKWGNukw== dependencies: "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== +"@types/estree@*", "@types/estree@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== "@types/estree@^0.0.51": version "0.0.51" @@ -3028,18 +3030,19 @@ integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.17.33" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543" - integrity sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA== + version "4.17.43" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz#10d8444be560cb789c4735aea5eac6e5af45df54" + integrity sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" + "@types/send" "*" "@types/express@*", "@types/express@^4.17.13": - version "4.17.17" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" - integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^4.17.33" @@ -3054,14 +3057,14 @@ "@types/jquery" "*" "@types/flowjs@^2.13.9": - version "2.13.9" - resolved "https://registry.yarnpkg.com/@types/flowjs/-/flowjs-2.13.9.tgz#b280e977a7b9d8e26d4a09ceab43e23ad2f148e5" - integrity sha512-iItTP2tOL9NalAR3C9o+psuvoBLwmEFPf1xr9BlDojyynizLBTtLw2aqrlaiZJW4z/QUwz7ryXFm4FjxjWatTQ== + version "2.13.13" + resolved "https://registry.yarnpkg.com/@types/flowjs/-/flowjs-2.13.13.tgz#91bb164ad4d63be6b9ad569171122c761526dc4b" + integrity sha512-wDAWxKbtsCXzlIqKMyy+lN3co9PIhJyQBRvx0tUefG0FDRAdFd4hvL8RFyLj8NxvBNoORWZ2exzNH5Gg+QpaAQ== "@types/geojson@*": - version "7946.0.10" - resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249" - integrity sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA== + version "7946.0.14" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.14.tgz#319b63ad6df705ee2a65a73ef042c8271e696613" + integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg== "@types/geojson@7946.0.8": version "7946.0.8" @@ -3069,50 +3072,55 @@ integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA== "@types/hammerjs@^2.0.40": - version "2.0.41" - resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.41.tgz#f6ecf57d1b12d2befcce00e928a6a097c22980aa" - integrity sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA== + version "2.0.45" + resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.45.tgz#ffa764bb68a66c08db6efb9c816eb7be850577b1" + integrity sha512-qkcUlZmX6c4J8q45taBKTL3p+LbITgyx7qhlPYOdOHZB7B31K0mXbP5YA7i7SgDeEGuI9MnumiKPEMrxg8j3KQ== + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== "@types/http-proxy@^1.17.8": - version "1.17.10" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.10.tgz#e576c8e4a0cc5c6a138819025a88e167ebb38d6c" - integrity sha512-Qs5aULi+zV1bwKAg5z1PWnDXWmsn+LxIvUGv6E2+OOMYhclZMO+OXd9pYVf2gLykf2I7IV2u7oTHwChPNsvJ7g== + version "1.17.14" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.14.tgz#57f8ccaa1c1c3780644f8a94f9c6b5000b5e2eec" + integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w== dependencies: "@types/node" "*" "@types/jasmine@*": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-4.3.1.tgz#2d8ab5601c2fe7d9673dcb157e03f128ab5c5fff" - integrity sha512-Vu8l+UGcshYmV1VWwULgnV/2RDbBaO6i2Ptx7nd//oJPIZGhoI1YLST4VKagD2Pq/Bc2/7zvtvhM7F3p4SN7kQ== + version "5.1.4" + resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-5.1.4.tgz#0de3f6ca753e10d1600ce1864ae42cfd47cf9924" + integrity sha512-px7OMFO/ncXxixDe1zR13V1iycqWae0MxTaw62RpFlksUi5QuNWgQJFkTQjIOvrmutJbI7Fp2Y2N1F6D2R4G6w== "@types/jasmine@~3.10.2": - version "3.10.7" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.10.7.tgz#57b8c6892fa2f4696fbb56851cd99649c781376c" - integrity sha512-brLuHhITMz4YV2IxLstAJtyRJgtWfLqFKiqiJFvFWMSmydpAmn42CE4wfw7ywkSk02UrufhtzipTcehk8FctoQ== + version "3.10.18" + resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.10.18.tgz#d442369fd2d06d5b2c607fae51c257b2f9caa94e" + integrity sha512-jOk52a1Kz+1oU5fNWwAcNe64/GsE7r/Q6ronwDox0D3ETo/cr4ICMQyeXrj7G6FPW1n8YjRoAZA2F0XBr6GicQ== "@types/jasminewd2@^2.0.10": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@types/jasminewd2/-/jasminewd2-2.0.10.tgz#ae31c237aa6421bde30f1058b1d20f4577e54443" - integrity sha512-J7mDz7ovjwjc+Y9rR9rY53hFWKATcIkrr9DwQWmOas4/pnIPJTXawnzjwpHm3RSxz/e3ZVUvQ7cRbd5UQLo10g== + version "2.0.13" + resolved "https://registry.yarnpkg.com/@types/jasminewd2/-/jasminewd2-2.0.13.tgz#0b60c1fcd06277ea97efbbad5a02e0c1a4a8996a" + integrity sha512-aJ3wj8tXMpBrzQ5ghIaqMisD8C3FIrcO6sDKHqFbuqAsI7yOxj0fA7MrRCPLZHIVUjERIwsMmGn/vB0UQ9u0Hg== dependencies: "@types/jasmine" "*" -"@types/jquery@*", "@types/jquery@^3.5.14", "@types/jquery@^3.5.16": - version "3.5.16" - resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.16.tgz#632131baf30951915b0317d48c98e9890bdf051d" - integrity sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw== +"@types/jquery@*", "@types/jquery@^3.5.16", "@types/jquery@^3.5.29": + version "3.5.29" + resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.29.tgz#3c06a1f519cd5fc3a7a108971436c00685b5dcea" + integrity sha512-oXQQC9X9MOPRrMhPHHOsXqeQDnWeCDT3PelUIg/Oy8FAbzSZtFHRjc7IpbfFVmpLtJ+UOoywpRsuO5Jxjybyeg== dependencies: "@types/sizzle" "*" "@types/js-beautify@^1.13.3": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@types/js-beautify/-/js-beautify-1.13.3.tgz#53839bb5b766d0fb45e87386100bb3bcbb7dca9d" - integrity sha512-ucIPw5gmNyvRKi6mpeojlqp+T+6ZBJeU+kqMDnIEDlijEU4QhLTon90sZ3cz9HZr+QTwXILjNsMZImzA7+zuJA== + version "1.14.3" + resolved "https://registry.yarnpkg.com/@types/js-beautify/-/js-beautify-1.14.3.tgz#6ced76f79935e37e0d613110dea369881d93c1ff" + integrity sha512-FMbQHz+qd9DoGvgLHxeqqVPaNRffpIu5ZjozwV8hf9JAGpIOzuAf4wGbRSo8LNITHqGjmmVjaMggTT5P4v4IHg== "@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/json5@^0.0.29": version "0.0.29" @@ -3120,86 +3128,93 @@ integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/leaflet-polylinedecorator@^1.6.1": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@types/leaflet-polylinedecorator/-/leaflet-polylinedecorator-1.6.1.tgz#b6522f9dae52146bf73da249e4bedfbab200c6e4" - integrity sha512-9etweJ2U4SWqcV/AR3i0NdWJByeMn6+zMUNlO6jVbpL8UI6qrMKybu8v9/s6UR4oXvsV4lZT6vzAsNAAMq5Ssg== + version "1.6.4" + resolved "https://registry.yarnpkg.com/@types/leaflet-polylinedecorator/-/leaflet-polylinedecorator-1.6.4.tgz#2270b84585bd28bbf1fef237be8480196c44ac06" + integrity sha512-meH/jC58Drt/9aqLhuzMCUQjHgMTud+UuQBK5gT6E8M6OfM/rftz/VgvdxOCCAQC9isrp4pqahDgEswesb5X3A== dependencies: "@types/leaflet" "*" "@types/leaflet-providers@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@types/leaflet-providers/-/leaflet-providers-1.2.1.tgz#620669b828959740a2d8572e0c0288a2382d3564" - integrity sha512-uNyuXiNV2q3fmgNjQji2P6RjQISmL40bbOL91/3OAwiE3XhkLKPmSAtAcfe11MAIz45iEjdFZJWppq9QyfnPIw== + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/leaflet-providers/-/leaflet-providers-1.2.4.tgz#284e8a197d4c2dbfeda436842e03b2adb502be16" + integrity sha512-4wYEpreixp+G5t510s202eQ5eubOmxHevIfCNrzUZbzp50XQsC9lSetX7MnPl3ANRJnUBbCWOzZ9EQFiH0Jm+g== dependencies: "@types/leaflet" "*" "@types/leaflet.gridlayer.googlemutant@^0.4.6": - version "0.4.6" - resolved "https://registry.yarnpkg.com/@types/leaflet.gridlayer.googlemutant/-/leaflet.gridlayer.googlemutant-0.4.6.tgz#86d3ba9d432dec29b4796e37d815c233680e7fcb" - integrity sha512-L0J7NadcZp5bcKQrv4DVlsEbQ90xLsOKScckAMnxoghh/wogk0GVkauYOYHBKeKDkx9qkMRzTf8oO+fKeYD7oQ== + version "0.4.9" + resolved "https://registry.yarnpkg.com/@types/leaflet.gridlayer.googlemutant/-/leaflet.gridlayer.googlemutant-0.4.9.tgz#9090ddf4578ce631d22b8aafc5ed12525b4fbf90" + integrity sha512-u/5Avs1KKkeABRDixGbGp2ldFs67+fYIATpkvvFgN+LQPNfq7dFd5adkksshiQBfYJ4SaQg1VJgN2dNirIC0aQ== dependencies: "@types/leaflet" "*" "@types/leaflet.markercluster@^1.5.1": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.1.tgz#d039ada408a30bda733b19a24cba89b81f0ace4b" - integrity sha512-gzJzP10qO6Zkts5QNVmSAEDLYicQHTEBLT9HZpFrJiSww9eDAs5OWHvIskldf41MvDv1gbMukuEBQEawHn+wtA== + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.4.tgz#2ab43417cf3f6a42d0f1baf4e1c8f659cf1dc3a1" + integrity sha512-tfMP8J62+wfsVLDLGh5Zh1JZxijCaBmVsMAX78MkLPwvPitmZZtSin5aWOVRhZrCS+pEOZwNzexbfWXlY+7yjg== dependencies: "@types/leaflet" "*" -"@types/leaflet@*": - version "1.9.1" - resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.9.1.tgz#a220706b02c6024951d7fc2959d530c6941c4224" - integrity sha512-lYawM3I3lLO6rmBASaqdGgY6zUL4YHr3H79/axx7FNYyPXuj0P1DZHbkNo8Itbv0i7Y9EryLWtDXXROMygXhRA== - dependencies: - "@types/geojson" "*" - -"@types/leaflet@^1.8.0": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.9.3.tgz#7aac302189eb3aa283f444316167995df42a5467" - integrity sha512-Caa1lYOgKVqDkDZVWkto2Z5JtVo09spEaUt2S69LiugbBpoqQu92HYFMGUbYezZbnBkyOxMNPXHSgRrRY5UyIA== +"@types/leaflet@*", "@types/leaflet@^1.8.0": + version "1.9.8" + resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.9.8.tgz#32162a8eaf305c63267e99470b9603b5883e63e8" + integrity sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg== dependencies: "@types/geojson" "*" "@types/lodash@^4.14.192": - version "4.14.192" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.192.tgz#5790406361a2852d332d41635d927f1600811285" - integrity sha512-km+Vyn3BYm5ytMO13k9KTp27O75rbQ0NFw+U//g+PX7VZyjCioXaRFisqSIJRECljcTv73G3i6BpglNGHgUQ5A== + version "4.17.0" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.0.tgz#d774355e41f372d5350a4d0714abb48194a489c3" + integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA== "@types/marked@^4.0.8": - version "4.0.8" - resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.0.8.tgz#b316887ab3499d0a8f4c70b7bd8508f92d477955" - integrity sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw== + version "4.3.2" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.3.2.tgz#e2e0ad02ebf5626bd215c5bae2aff6aff0ce9eac" + integrity sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w== "@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45" + integrity sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== "@types/mousetrap@^1.6.9": - version "1.6.11" - resolved "https://registry.yarnpkg.com/@types/mousetrap/-/mousetrap-1.6.11.tgz#ef9620160fdcefcb85bccda8aaa3e84d7429376d" - integrity sha512-F0oAily9Q9QQpv9JKxKn0zMKfOo36KHCW7myYsmUyf2t0g+sBTbG3UleTPoguHdE1z3GLFr3p7/wiOio52QFjQ== + version "1.6.15" + resolved "https://registry.yarnpkg.com/@types/mousetrap/-/mousetrap-1.6.15.tgz#f144a0c539a4cef553a631824651d48267e53c86" + integrity sha512-qL0hyIMNPow317QWW/63RvL1x5MVMV+Ru3NaY9f/CuEpCqrmb7WeuK2071ZY5hczOnm38qExWM2i2WtkXLSqFw== + +"@types/node-forge@^1.3.0": + version "1.3.11" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" + integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== + dependencies: + "@types/node" "*" "@types/node@*", "@types/node@>=10.0.0": - version "18.14.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.5.tgz#4a13a6445862159303fc38586598a9396fc408b3" - integrity sha512-CRT4tMK/DHYhw1fcCEBwME9CSaZNclxfzVMe7GsO6ULSwsttbj70wSiX6rZdIjGblu93sTJxLdhNIT85KKI7Qw== + version "20.11.28" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.28.tgz#4fd5b2daff2e580c12316e457473d68f15ee6f66" + integrity sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA== + dependencies: + undici-types "~5.26.4" "@types/node@~18.15.11": - version "18.15.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" - integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== + version "18.15.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" + integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== "@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== "@types/prop-types@*": - version "15.7.5" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + version "15.7.11" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" + integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== "@types/q@^0.0.32": version "0.0.32" @@ -3207,19 +3222,19 @@ integrity sha512-qYi3YV9inU/REEfxwVcGZzbS3KG/Xs90lv0Pr+lDtuVjBPGd1A+eciXzVSaRvLify132BfcvhvEjeVahrUl0Ug== "@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + version "6.9.12" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.12.tgz#afa96b383a3a6fdc859453a1892d41b607fc7756" + integrity sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg== "@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== "@types/raphael@^2.3.2": - version "2.3.3" - resolved "https://registry.yarnpkg.com/@types/raphael/-/raphael-2.3.3.tgz#d264b148bc100ef401a5e13159fd97861cd69e17" - integrity sha512-Rhvq0q6wzyvipejki/9w87/pgapyE+s3gO66tdl1oD3qDrow+ek+4vVYAbRkeL58HCCK9EOZKwyjqYJ/TFkmtQ== + version "2.3.9" + resolved "https://registry.yarnpkg.com/@types/raphael/-/raphael-2.3.9.tgz#d53bb8930431524f42987a8a19815c0d42a61eb5" + integrity sha512-K1dZwoLNvEN+mvleFU/t2swG9Z4SE5Vub7dA5wDYojH0bVTQ8ZAP+lNsl91t1njdu/B+roSEL4QXC67I7Hpiag== "@types/react-dom@17.0.11": version "17.0.11" @@ -3229,9 +3244,9 @@ "@types/react" "*" "@types/react-transition-group@^4.2.0": - version "4.4.5" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416" - integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA== + version "4.4.10" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac" + integrity sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q== dependencies: "@types/react" "*" @@ -3250,44 +3265,53 @@ integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== "@types/scheduler@*": - version "0.16.2" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" - integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + version "0.16.8" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff" + integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A== "@types/selenium-webdriver@^3.0.0": - version "3.0.20" - resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.20.tgz#448771a0608ebf1c86cb5885914da6311e323c3a" - integrity sha512-6d8Q5fqS9DWOXEhMDiF6/2FjyHdmP/jSTAUyeQR7QwrFeNmYyzmvGxD5aLIHL445HjWgibs0eAig+KPnbaesXA== + version "3.0.26" + resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.26.tgz#fc7d87d580affa2e52685b2e881bc201819a5836" + integrity sha512-dyIGFKXfUFiwkMfNGn1+F6b80ZjR3uSYv1j6xVJSDlft5waZ2cwkHW4e7zNzvq7hiEackcgvBpmnXZrI1GltPg== "@types/semver@^7.3.12": - version "7.3.13" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" - integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" "@types/serve-index@^1.9.1": - version "1.9.1" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" - integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== + version "1.9.4" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" + integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== dependencies: "@types/express" "*" "@types/serve-static@*", "@types/serve-static@^1.13.10": - version "1.15.1" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.1.tgz#86b1753f0be4f9a1bee68d459fcda5be4ea52b5d" - integrity sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ== + version "1.15.5" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.5.tgz#15e67500ec40789a1e8c9defc2d32a896f05b033" + integrity sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ== dependencies: + "@types/http-errors" "*" "@types/mime" "*" "@types/node" "*" "@types/sizzle@*": - version "2.3.3" - resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" - integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== + version "2.3.8" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.8.tgz#518609aefb797da19bf222feb199e8f653ff7627" + integrity sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg== "@types/sockjs@^0.3.33": - version "0.3.33" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" - integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== + version "0.3.36" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" + integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== dependencies: "@types/node" "*" @@ -3304,9 +3328,9 @@ integrity sha512-Jxo2/uif1WpkabfyvWpFmPWFPDdwKUmyL7xWzjtxNALEu2pgce+eISjbf0Vr+SsK/D9savO5kTRcf+COLK5eiQ== "@types/tinycolor2@^1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.3.tgz#ed4a0901f954b126e6a914b4839c77462d56e706" - integrity sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ== + version "1.4.6" + resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.6.tgz#670cbc0caf4e58dd61d1e3a6f26386e473087f06" + integrity sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw== "@types/tooltipster@^0.0.31": version "0.0.31" @@ -3316,9 +3340,9 @@ "@types/jquery" "*" "@types/ws@^8.5.1": - version "8.5.4" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" - integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== + version "8.5.10" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== dependencies: "@types/node" "*" @@ -3464,6 +3488,11 @@ "@typescript-eslint/types" "5.57.0" eslint-visitor-keys "^3.3.0" +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -3472,21 +3501,44 @@ "@webassemblyjs/helper-numbers" "1.11.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.1" +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.11.5": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/floating-point-hex-parser@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== + "@webassemblyjs/helper-api-error@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== + "@webassemblyjs/helper-buffer@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== + "@webassemblyjs/helper-numbers@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" @@ -3496,11 +3548,25 @@ "@webassemblyjs/helper-api-error" "1.11.1" "@xtuc/long" "4.2.2" +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@xtuc/long" "4.2.2" + "@webassemblyjs/helper-wasm-bytecode@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== + "@webassemblyjs/helper-wasm-section@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" @@ -3511,6 +3577,16 @@ "@webassemblyjs/helper-wasm-bytecode" "1.11.1" "@webassemblyjs/wasm-gen" "1.11.1" +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/ieee754@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" @@ -3518,6 +3594,13 @@ dependencies: "@xtuc/ieee754" "^1.2.0" +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + "@webassemblyjs/leb128@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" @@ -3525,11 +3608,23 @@ dependencies: "@xtuc/long" "4.2.2" +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== + dependencies: + "@xtuc/long" "4.2.2" + "@webassemblyjs/utf8@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + "@webassemblyjs/wasm-edit@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" @@ -3544,6 +3639,20 @@ "@webassemblyjs/wasm-parser" "1.11.1" "@webassemblyjs/wast-printer" "1.11.1" +"@webassemblyjs/wasm-edit@^1.11.5": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + "@webassemblyjs/wasm-gen@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" @@ -3555,6 +3664,17 @@ "@webassemblyjs/leb128" "1.11.1" "@webassemblyjs/utf8" "1.11.1" +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + "@webassemblyjs/wasm-opt@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" @@ -3565,6 +3685,16 @@ "@webassemblyjs/wasm-gen" "1.11.1" "@webassemblyjs/wasm-parser" "1.11.1" +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wasm-parser@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" @@ -3577,6 +3707,18 @@ "@webassemblyjs/leb128" "1.11.1" "@webassemblyjs/utf8" "1.11.1" +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.11.5": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + "@webassemblyjs/wast-printer@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" @@ -3585,6 +3727,14 @@ "@webassemblyjs/ast" "1.11.1" "@xtuc/long" "4.2.2" +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@xtuc/long" "4.2.2" + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -3610,6 +3760,11 @@ abbrev@^1.0.0: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abbrev@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" + integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== + accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -3630,10 +3785,10 @@ ace-diff@^3.0.3: dependencies: diff-match-patch "^1.0.5" -acorn-import-assertions@^1.7.6: - version "1.8.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" - integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== +acorn-import-assertions@^1.7.6, acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== acorn-jsx@^5.3.2: version "5.3.2" @@ -3641,14 +3796,14 @@ acorn-jsx@^5.3.2: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + version "8.3.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== -acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== +acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== adjust-sourcemap-loader@^4.0.0: version "4.0.0" @@ -3659,9 +3814,9 @@ adjust-sourcemap-loader@^4.0.0: regex-parser "^2.2.11" adm-zip@^0.5.2: - version "0.5.10" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.10.tgz#4a51d5ab544b1f5ce51e1b9043139b639afff45b" - integrity sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ== + version "0.5.12" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.12.tgz#87786328e91d54b37358d8a50f954c4cd73ba60b" + integrity sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ== agent-base@6, agent-base@^6.0.2: version "6.0.2" @@ -3678,12 +3833,10 @@ agent-base@^4.3.0: es6-promisify "^5.0.0" agentkeepalive@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" - integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== + version "4.5.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" + integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== dependencies: - debug "^4.1.0" - depd "^1.1.2" humanize-ms "^1.2.1" aggregate-error@^3.0.0: @@ -3706,14 +3859,14 @@ ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv-keywords@^5.0.0: +ajv-keywords@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== dependencies: fast-deep-equal "^3.1.3" -ajv@8.12.0, ajv@^8.0.0, ajv@^8.8.0: +ajv@8.12.0, ajv@^8.0.0, ajv@^8.9.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== @@ -3723,7 +3876,7 @@ ajv@8.12.0, ajv@^8.0.0, ajv@^8.8.0: require-from-string "^2.0.2" uri-js "^4.2.2" -ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3741,9 +3894,9 @@ angular-gridster2@~15.0.4: tslib "^2.4.0" angular2-hotkeys@^13.1.0: - version "13.1.0" - resolved "https://registry.yarnpkg.com/angular2-hotkeys/-/angular2-hotkeys-13.1.0.tgz#fdbb30b72dc512379e3dea5d44d98593cdfb18a9" - integrity sha512-irsQLLiHCHqz73ocDV8N5K7Zel7mJyLQHwLrRePOwUumQfyBc2TTuO+ccdQAAM7/RK+IdT6P5YoiP0FEbA19Uw== + version "13.4.0" + resolved "https://registry.yarnpkg.com/angular2-hotkeys/-/angular2-hotkeys-13.4.0.tgz#a96676466936556655cd64f92e1f5cd3aeac8e10" + integrity sha512-WvkouvdXtTYw3tpuaoEVF+ue41pvI2XSa8m4tVRPLzAblT/f7PG0uQO4npyjVw3oDIc7qnFkQR+oqGl1KM1eow== dependencies: "@types/mousetrap" "^1.6.9" mousetrap "^1.6.5" @@ -3776,6 +3929,11 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -3795,6 +3953,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + ansidec@^0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/ansidec/-/ansidec-0.3.4.tgz#e12d267d6b1f122d2da5b98fe0de1f98d14ac62b" @@ -3813,6 +3976,11 @@ anymatch@~3.1.2: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== +are-docs-informative@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz#387f0e93f5d45280373d387a59d34c96db321963" + integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig== + are-we-there-yet@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" @@ -3855,25 +4023,28 @@ array-back@^4.0.1, array-back@^4.0.2: resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== +array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== + dependencies: + call-bind "^1.0.5" + is-array-buffer "^3.0.4" + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -array-flatten@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - -array-includes@^3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" - integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== +array-includes@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" + integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" is-string "^1.0.7" array-union@^1.0.1: @@ -3893,26 +4064,62 @@ array-uniq@^1.0.1: resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== -array.prototype.flat@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" - integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== +array.prototype.filter@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz#423771edeb417ff5914111fff4277ea0624c0d0e" + integrity sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.7" + +array.prototype.findlastindex@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz#d1c50f0b3a9da191981ff8942a0aedd82794404f" + integrity sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.3.0" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" - integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" + is-shared-array-buffer "^1.0.2" + arrify@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -3964,10 +4171,12 @@ autoprefixer@10.4.13: picocolors "^1.0.0" postcss-value-parser "^4.2.0" -available-typed-arrays@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" - integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" aws-sign2@~0.7.0: version "0.7.0" @@ -4067,9 +4276,9 @@ big.js@^5.2.2: integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== bl@^4.1.0: version "4.1.0" @@ -4087,25 +4296,7 @@ blocking-proxy@^1.0.0: dependencies: minimist "^1.2.0" -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -body-parser@^1.19.0: +body-parser@1.20.2, body-parser@^1.19.0: version "1.20.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== @@ -4124,12 +4315,10 @@ body-parser@^1.19.0: unpipe "1.0.0" bonjour-service@^1.0.11: - version "1.1.0" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.1.0.tgz#424170268d68af26ff83a5c640b95def01803a13" - integrity sha512-LVRinRB3k1/K0XzZ2p58COnWvkQknIY6sf0zF2rpErvcJXpMBttEPQSxK+HEXSS9VmpZlDoDnQWv8ftJT20B0Q== + version "1.2.1" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02" + integrity sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw== dependencies: - array-flatten "^2.1.2" - dns-equal "^1.0.0" fast-deep-equal "^3.1.3" multicast-dns "^7.2.5" @@ -4160,7 +4349,7 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -browserslist@4.21.5, browserslist@^4.14.5, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4.21.5: +browserslist@4.21.5: version "4.21.5" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== @@ -4170,6 +4359,16 @@ browserslist@4.21.5, browserslist@^4.14.5, browserslist@^4.21.3, browserslist@^4 node-releases "^2.0.8" update-browserslist-db "^1.0.10" +browserslist@^4.14.5, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.22.2, browserslist@^4.22.3: + version "4.23.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + browserstack@^1.5.1: version "1.6.1" resolved "https://registry.yarnpkg.com/browserstack/-/browserstack-1.6.1.tgz#e051f9733ec3b507659f395c7a4765a1b1e358b3" @@ -4190,6 +4389,11 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + builtins@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" @@ -4207,7 +4411,7 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cacache@17.0.4, cacache@^17.0.0: +cacache@17.0.4: version "17.0.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.0.4.tgz#5023ed892ba8843e3b7361c26d0ada37e146290c" integrity sha512-Z/nL3gU+zTUjz5pCA5vVjYM8pmaw2kxM7JEiE0fv3w77Wj+sFbi70CrBruUWH0uNcEdvLDixFpgA2JM4F4DBjA== @@ -4250,13 +4454,34 @@ cacache@^16.1.0: tar "^6.1.11" unique-filename "^2.0.0" -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== +cacache@^17.0.0: + version "17.1.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.1.4.tgz#b3ff381580b47e85c6e64f801101508e26604b35" + integrity sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A== dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" + "@npmcli/fs" "^3.1.0" + fs-minipass "^3.0.0" + glob "^10.2.2" + lru-cache "^7.7.1" + minipass "^7.0.3" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + p-map "^4.0.0" + ssri "^10.0.0" + tar "^6.1.11" + unique-filename "^3.0.0" + +call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" callsites@^3.0.0: version "3.1.0" @@ -4268,10 +4493,10 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001449: - version "1.0.30001460" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001460.tgz#31d2e26f0a2309860ed3eff154e03890d9d851a7" - integrity sha512-Bud7abqjvEjipUkpLs4D7gR0l8hBYBHoa+tGtKJHvT2AYzLp1z7EmVkUT4ERpVUfca8S2HGIVs883D8pUH1ZzQ== +caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001587: + version "1.0.30001599" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz#571cf4f3f1506df9bf41fcbb6d10d5d017817bce" + integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA== canvas-gauges@^2.1.7: version "2.1.7" @@ -4294,7 +4519,7 @@ chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.4.2: +chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -4316,7 +4541,7 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.5.1, chokidar@^3.5.3: +chokidar@3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -4331,6 +4556,21 @@ chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.5.1, cho optionalDependencies: fsevents "~2.3.2" +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.5.1, chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -4347,9 +4587,9 @@ ci-info@^2.0.0: integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== classnames@2.x, classnames@^2.2.1, classnames@^2.2.6: - version "2.3.2" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" - integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== clean-stack@^2.0.0: version "2.2.0" @@ -4364,9 +4604,9 @@ cli-cursor@^3.1.0: restore-cursor "^3.1.0" cli-spinners@^2.5.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a" - integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== + version "2.9.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== cli-width@^3.0.0: version "3.0.0" @@ -4458,16 +4698,16 @@ color-support@^1.1.3: integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== colorette@^2.0.10: - version "2.0.19" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== colors@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -4499,20 +4739,25 @@ commander@7: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^2.19.0, commander@^2.20.0: +commander@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^8.0.0: +commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -comment-parser@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.1.tgz#3d7ea3adaf9345594aedee6563f422348f165c1b" - integrity sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA== +comment-parser@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.1.tgz#bdafead37961ac079be11eb7ec65c4d021eaf9cc" + integrity sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg== commondir@^1.0.1: version "1.0.1" @@ -4597,6 +4842,11 @@ convert-source-map@^1.5.1, convert-source-map@^1.7.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -4632,16 +4882,16 @@ copy-webpack-plugin@11.0.0: serialize-javascript "^6.0.0" core-js-compat@^3.25.1: - version "3.29.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.29.0.tgz#1b8d9eb4191ab112022e7f6364b99b65ea52f528" - integrity sha512-ScMn3uZNAFhK2DGoEfErguoiAHhV2Ju+oJo/jK08p7B3f3UhocUrCCkTvnZaiS+edl5nlIoiBXKcwMc6elv4KQ== + version "3.36.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.36.0.tgz#087679119bc2fdbdefad0d45d8e5d307d45ba190" + integrity sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw== dependencies: - browserslist "^4.21.5" + browserslist "^4.22.3" core-js@^3.29.1: - version "3.29.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.29.1.tgz#40ff3b41588b091aaed19ca1aa5cb111803fa9a6" - integrity sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw== + version "3.36.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.36.0.tgz#e752fa0b0b462a0787d56e9d73f80b0f7c0dde68" + integrity sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw== core-util-is@1.0.2: version "1.0.2" @@ -4686,6 +4936,17 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +coveralls-next@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/coveralls-next/-/coveralls-next-4.2.0.tgz#821fb802f98284a3c11c96577b7fd0eaf4d4473e" + integrity sha512-zg41a/4QDSASPtlV6gp+6owoU43U5CguxuPZR3nPZ26M5ZYdEK3MdUe7HwE+AnCZPkucudfhqqJZehCNkz2rYg== + dependencies: + form-data "4.0.0" + js-yaml "4.1.0" + lcov-parse "1.0.0" + log-driver "1.2.7" + minimist "1.2.7" + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -4714,7 +4975,7 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -4779,9 +5040,9 @@ csstype@^2.5.2: integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w== csstype@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" - integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== custom-event@~1.0.0: version "1.0.1" @@ -4803,17 +5064,17 @@ cytoscape-fcose@^2.1.0: cose-base "^2.2.0" cytoscape@^3.23.0: - version "3.23.0" - resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.23.0.tgz#054ee05a6d0aa3b4f139382bbf2f4e5226df3c6d" - integrity sha512-gRZqJj/1kiAVPkrVFvz/GccxsXhF3Qwpptl32gKKypO4IlqnKBjTOu+HbXtEggSGzC5KCaHp3/F7GgENrtsFkA== + version "3.28.1" + resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.28.1.tgz#f32c3e009bdf32d47845a16a4cd2be2bbc01baf7" + integrity sha512-xyItz4O/4zp9/239wCcH8ZcFuuZooEeF8KHRmzjDfGdXsj3OG9MFSMA0pJE0uX3uCN/ygof6hHf4L7lst+JaDg== dependencies: heap "^0.2.6" lodash "^4.17.21" "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.2.tgz#f8ac4705c5b06914a7e0025bbf8d5f1513f6a86e" - integrity sha512-yEEyEAbDrF8C6Ob2myOBLjwBLck1Z89jMGFee0oPsn95GqjerpaOA4ch+vc2l0FNFFwMD5N7OCSEN5eAlsUbgQ== + version "3.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== dependencies: internmap "1 - 2" @@ -4853,9 +5114,9 @@ d3-contour@4: d3-array "^3.2.0" d3-delaunay@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.2.tgz#7fd3717ad0eade2fc9939f4260acfb503f984e92" - integrity sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ== + version "6.0.4" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" + integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== dependencies: delaunator "5" @@ -4908,9 +5169,9 @@ d3-force@3: integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== d3-geo@3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.0.tgz#74fd54e1f4cebd5185ac2039217a98d39b0a4c0e" - integrity sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA== + version "3.1.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.1.tgz#6027cf51246f9b2ebd64f99e01dc7c3364033a4d" + integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== dependencies: d3-array "2.5.0 - 3" @@ -4947,9 +5208,9 @@ d3-random@3: integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== d3-scale-chromatic@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a" - integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g== + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" + integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== dependencies: d3-color "1 - 3" d3-interpolate "1 - 3" @@ -5018,10 +5279,10 @@ d3-zoom@3: d3-selection "2 - 3" d3-transition "2 - 3" -d3@^7.0.0, d3@^7.8.2: - version "7.8.2" - resolved "https://registry.yarnpkg.com/d3/-/d3-7.8.2.tgz#2bdb3c178d095ae03b107a18837ae049838e372d" - integrity sha512-WXty7qOGSHb7HR7CfOzwN1Gw04MUOzN8qh9ZUsvwycIMb4DYMpY9xczZ6jUorGtO6bR9BPMPaueIKwiDxu9uiQ== +d3@^7.4.0, d3@^7.8.2: + version "7.9.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" + integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== dependencies: d3-array "3" d3-axis "3" @@ -5054,10 +5315,10 @@ d3@^7.0.0, d3@^7.8.2: d3-transition "3" d3-zoom "3" -dagre-d3-es@7.0.8: - version "7.0.8" - resolved "https://registry.yarnpkg.com/dagre-d3-es/-/dagre-d3-es-7.0.8.tgz#14c309c3c08ba8329a7cf51000bd56a369c513d1" - integrity sha512-eykdoYQ4FwCJinEYS0gPL2f2w+BPbSLvnQSJ3Ye1vAoPjdkq6xIMKBv+UkICd3qZE26wBKIn3p+6n0QC7R1LyA== +dagre-d3-es@7.0.9: + version "7.0.9" + resolved "https://registry.yarnpkg.com/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz#aca12fccd9d09955a4430029ba72ee6934542a8d" + integrity sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w== dependencies: d3 "^7.8.2" lodash-es "^4.17.21" @@ -5067,7 +5328,34 @@ dashdash@^1.12.0: resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== dependencies: - assert-plus "^1.0.0" + assert-plus "^1.0.0" + +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" date-fns@2.0.0-alpha.27: version "2.0.0-alpha.27" @@ -5084,10 +5372,10 @@ dayjs@1.11.4: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.4.tgz#3b3c10ca378140d8917e06ebc13a4922af4f433e" integrity sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g== -dayjs@^1.11.5: - version "1.11.7" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" - integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== +dayjs@^1.11.5, dayjs@^1.11.7: + version "1.11.10" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== debug@2.6.9: version "2.6.9" @@ -5096,14 +5384,14 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@^3.1.0, debug@^3.2.6, debug@^3.2.7: +debug@^3.1.0, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -5116,15 +5404,16 @@ decamelize@^1.2.0: integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== deep-equal@^2.0.5: - version "2.2.0" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.0.tgz#5caeace9c781028b9ff459f33b779346637c43e6" - integrity sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw== + version "2.2.3" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1" + integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA== dependencies: - call-bind "^1.0.2" - es-get-iterator "^1.1.2" - get-intrinsic "^1.1.3" + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.5" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.2" is-arguments "^1.1.1" - is-array-buffer "^3.0.1" + is-array-buffer "^3.0.2" is-date-object "^1.0.5" is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" @@ -5132,11 +5421,11 @@ deep-equal@^2.0.5: object-is "^1.1.5" object-keys "^1.1.1" object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" + regexp.prototype.flags "^1.5.1" side-channel "^1.0.4" which-boxed-primitive "^1.0.2" which-collection "^1.0.1" - which-typed-array "^1.1.9" + which-typed-array "^1.1.13" deep-extend@~0.6.0: version "0.6.0" @@ -5167,16 +5456,26 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== -define-properties@^1.1.3, define-properties@^1.1.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" - integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== dependencies: + define-data-property "^1.0.1" has-property-descriptors "^1.0.0" object-keys "^1.1.1" @@ -5194,11 +5493,11 @@ del@^2.2.0: rimraf "^2.2.8" delaunator@5: - version "5.0.0" - resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b" - integrity sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw== + version "5.0.1" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278" + integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== dependencies: - robust-predicates "^3.0.0" + robust-predicates "^3.0.2" delayed-stream@~1.0.0: version "1.0.0" @@ -5220,7 +5519,7 @@ depd@2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -depd@^1.1.2, depd@~1.1.2: +depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== @@ -5256,9 +5555,9 @@ diff@^4.0.1: integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== dijkstrajs@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257" - integrity sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg== + version "1.0.3" + resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23" + integrity sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA== dir-glob@^3.0.1: version "3.0.1" @@ -5275,15 +5574,10 @@ directory-tree@^3.5.1: command-line-args "^5.2.0" command-line-usage "^6.1.1" -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== - dns-packet@^5.2.2: - version "5.4.0" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.4.0.tgz#1f88477cf9f27e78a213fb6d118ae38e759a879b" - integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g== + version "5.6.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== dependencies: "@leichtgewicht/ip-codec" "^2.0.1" @@ -5359,6 +5653,11 @@ domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -5375,25 +5674,25 @@ echarts@^5.5.0: tslib "2.3.0" zrender "5.5.0" -editorconfig@^0.15.3: - version "0.15.3" - resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" - integrity sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g== +editorconfig@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-1.0.4.tgz#040c9a8e9a6c5288388b87c2db07028aa89f53a3" + integrity sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q== dependencies: - commander "^2.19.0" - lru-cache "^4.1.5" - semver "^5.6.0" - sigmund "^1.0.1" + "@one-ini/wasm" "0.1.1" + commander "^10.0.0" + minimatch "9.0.1" + semver "^7.5.3" ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.284: - version "1.4.317" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.317.tgz#9a3d38a1a37f26a417d3d95dafe198ff11ed072b" - integrity sha512-JhCRm9v30FMNzQSsjl4kXaygU+qHBD0Yh7mKxyjmF0V8VwYVB6qpBRX28GyAucrM9wDCpSUctT6FpMUQxbyKuA== +electron-to-chromium@^1.4.284, electron-to-chromium@^1.4.668: + version "1.4.708" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.708.tgz#d54d3b47cb44ae6b190067439c42135456907893" + integrity sha512-iWgEEvREL4GTXXHKohhh33+6Y8XkPI5eHihDmm8zUk5Zo7HICEW+wI/j5kJ2tbuNUCXJ/sNXa03ajW635DiJXA== elkjs@^0.8.2: version "0.8.2" @@ -5405,6 +5704,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + emoji-toolkit@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/emoji-toolkit/-/emoji-toolkit-7.0.1.tgz#4ea2a78fe4b40c7cdbe7ef5725c7011299932f09" @@ -5432,15 +5736,15 @@ encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" -engine.io-parser@~5.0.3: - version "5.0.6" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.6.tgz#7811244af173e157295dec9b2718dfe42a64ef45" - integrity sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw== +engine.io-parser@~5.2.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.2.tgz#37b48e2d23116919a3453738c5720455e64e1c49" + integrity sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw== -engine.io@~6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.4.1.tgz#8056b4526a88e779f9c280d820422d4e3eeaaae5" - integrity sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw== +engine.io@~6.5.2: + version "6.5.4" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.4.tgz#6822debf324e781add2254e912f8568508850cdc" + integrity sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg== dependencies: "@types/cookie" "^0.4.1" "@types/cors" "^2.8.12" @@ -5450,13 +5754,13 @@ engine.io@~6.4.1: cookie "~0.4.1" cors "~2.8.5" debug "~4.3.1" - engine.io-parser "~5.0.3" + engine.io-parser "~5.2.1" ws "~8.11.0" -enhanced-resolve@^5.10.0: - version "5.12.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634" - integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ== +enhanced-resolve@^5.10.0, enhanced-resolve@^5.15.0: + version "5.16.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787" + integrity sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -5472,9 +5776,9 @@ entities@^2.0.0: integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== entities@^4.3.0, entities@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" - integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== env-paths@^2.2.0: version "2.2.1" @@ -5500,46 +5804,123 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.21.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.1.tgz#e6105a099967c08377830a0c9cb589d570dd86c6" - integrity sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-set-tostringtag "^2.0.1" +es-abstract@^1.22.1, es-abstract@^1.22.3: + version "1.22.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.5.tgz#1417df4e97cc55f09bf7e58d1e614bc61cb8df46" + integrity sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" es-to-primitive "^1.2.1" - function-bind "^1.1.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.1.3" - get-symbol-description "^1.0.0" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" globalthis "^1.0.3" gopd "^1.0.1" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" has-symbols "^1.0.3" - internal-slot "^1.0.4" - is-array-buffer "^3.0.1" + hasown "^2.0.1" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" is-callable "^1.2.7" - is-negative-zero "^2.0.2" + is-negative-zero "^2.0.3" is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" + is-shared-array-buffer "^1.0.3" is-string "^1.0.7" - is-typed-array "^1.1.10" + is-typed-array "^1.1.13" is-weakref "^1.0.2" - object-inspect "^1.12.2" + object-inspect "^1.13.1" object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" - safe-regex-test "^1.0.0" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" - typed-array-length "^1.0.4" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.0" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.5" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.14" + +es-abstract@^1.23.0: + version "1.23.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.2.tgz#693312f3940f967b8dd3eebacb590b01712622e0" + integrity sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + is-typed-array "^1.1.13" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.7" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.5" unbox-primitive "^1.0.2" - which-typed-array "^1.1.9" + which-typed-array "^1.1.15" + +es-array-method-boxes-properly@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" + integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.0.0, es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -es-get-iterator@^1.1.2: +es-get-iterator@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== @@ -5559,21 +5940,33 @@ es-module-lexer@^0.9.0: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== -es-set-tostringtag@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" - integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== - dependencies: - get-intrinsic "^1.1.3" - has "^1.0.3" - has-tostringtag "^1.0.0" +es-module-lexer@^1.2.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5" + integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w== -es-shim-unscopables@^1.0.0: +es-object-atoms@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" - integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== dependencies: - has "^1.0.3" + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" + +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" es-to-primitive@^1.2.1: version "1.2.1" @@ -5630,9 +6023,9 @@ esbuild@0.17.8: "@esbuild/win32-x64" "0.17.8" escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== escape-html@~1.0.3: version "1.0.3" @@ -5649,55 +6042,59 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-import-resolver-node@^0.3.7: - version "0.3.7" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" - integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== dependencies: debug "^3.2.7" - is-core-module "^2.11.0" - resolve "^1.22.1" + is-core-module "^2.13.0" + resolve "^1.22.4" -eslint-module-utils@^2.7.4: - version "2.7.4" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" - integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== +eslint-module-utils@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34" + integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q== dependencies: debug "^3.2.7" eslint-plugin-import@latest: - version "2.27.5" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65" - integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow== - dependencies: - array-includes "^3.1.6" - array.prototype.flat "^1.3.1" - array.prototype.flatmap "^1.3.1" + version "2.29.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" + integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== + dependencies: + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" debug "^3.2.7" doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.7" - eslint-module-utils "^2.7.4" - has "^1.0.3" - is-core-module "^2.11.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.8.0" + hasown "^2.0.0" + is-core-module "^2.13.1" is-glob "^4.0.3" minimatch "^3.1.2" - object.values "^1.1.6" - resolve "^1.22.1" - semver "^6.3.0" - tsconfig-paths "^3.14.1" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" + semver "^6.3.1" + tsconfig-paths "^3.15.0" eslint-plugin-jsdoc@latest: - version "40.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-40.0.1.tgz#5f028b4928d5c77f54bfd3c42c00acb61d27bb9f" - integrity sha512-KkiRInury7YrjjV5aCHDxwsPy6XFt5p2b2CnpDMITnWs8patNPf5kj24+VXIWw45kP6z/B0GOKfrYczB56OjQQ== + version "48.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.1.tgz#9334a05555a95fdc192980627142177963b668b4" + integrity sha512-iUvbcyDZSO/9xSuRv2HQBw++8VkV/pt3UWtX9cpPH0l7GKPq78QC/6+PmyQHHvNZaTjAce6QVciEbnc6J/zH5g== dependencies: - "@es-joy/jsdoccomment" "~0.36.1" - comment-parser "1.3.1" + "@es-joy/jsdoccomment" "~0.42.0" + are-docs-informative "^0.0.2" + comment-parser "1.4.1" debug "^4.3.4" escape-string-regexp "^4.0.0" - esquery "^1.4.0" - semver "^7.3.8" - spdx-expression-parse "^3.0.1" + esquery "^1.5.0" + is-builtin-module "^3.2.1" + semver "^7.6.0" + spdx-expression-parse "^4.0.0" eslint-plugin-prefer-arrow@latest: version "1.2.3" @@ -5712,10 +6109,10 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.0.0, eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== +eslint-scope@^7.0.0, eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -5732,37 +6129,33 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== - -eslint-visitor-keys@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" - integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@^8.37.0: - version "8.37.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.37.0.tgz#1f660ef2ce49a0bfdec0b0d698e0b8b627287412" - integrity sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw== + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.2" - "@eslint/js" "8.37.0" - "@humanwhocodes/config-array" "^0.11.8" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" - ajv "^6.10.0" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-visitor-keys "^3.4.0" - espree "^9.5.1" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -5770,39 +6163,36 @@ eslint@^8.37.0: find-up "^5.0.0" glob-parent "^6.0.2" globals "^13.19.0" - grapheme-splitter "^1.0.4" + graphemer "^1.4.0" ignore "^5.2.0" - import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" - js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" minimatch "^3.1.2" natural-compare "^1.4.0" - optionator "^0.9.1" + optionator "^0.9.3" strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.5.1: - version "9.5.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4" - integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg== +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: - acorn "^8.8.0" + acorn "^8.9.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.0" + eslint-visitor-keys "^3.4.1" esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0, esquery@^1.4.2: +esquery@^1.4.2, esquery@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== @@ -5876,14 +6266,19 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== +exponential-backoff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" + integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== + express@^4.17.3: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + version "4.18.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.3.tgz#6870746f3ff904dee1819b82e4b51509afffb0d4" + integrity sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.1" + body-parser "1.20.2" content-disposition "0.5.4" content-type "~1.0.4" cookie "0.5.0" @@ -5942,10 +6337,10 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.11, fast-glob@^3.2.9: - version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== +fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -5964,9 +6359,9 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== dependencies: reusify "^1.0.4" @@ -6071,17 +6466,23 @@ find-yarn-workspace-root@^2.0.0: micromatch "^4.0.2" flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== dependencies: - flatted "^3.1.0" + flatted "^3.2.9" + keyv "^4.5.3" rimraf "^3.0.2" -flatted@^3.1.0, flatted@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +flatted@^3.2.7, flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== "flot.curvedlines@https://github.com/MichaelZinsmaier/CurvedLines.git#master": version "1.1.1" @@ -6092,9 +6493,9 @@ flatted@^3.1.0, flatted@^3.2.7: resolved "https://github.com/thingsboard/flot.git#c2734540477d8b261d04ee18d4d38af3b0ecb81b" follow-redirects@^1.0.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== font-awesome@^4.7.0: version "4.7.0" @@ -6108,11 +6509,28 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== +form-data@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -6128,9 +6546,9 @@ forwarded@0.2.0: integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== fraction.js@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" - integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== fresh@0.5.2: version "0.5.2" @@ -6164,16 +6582,16 @@ fs-minipass@^2.0.0, fs-minipass@^2.1.0: minipass "^3.0.0" fs-minipass@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.1.tgz#853809af15b6d03e27638d1ab6432e6b378b085d" - integrity sha512-MhaJDcFRTuLidHrIttu0RDGyyXs/IYHVmlcxfLAEFIWjc1vdLAkdwT7Ace2u7DbitWC0toKMl5eJZRYNVreIMw== + version "3.0.3" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.3.tgz#79a85981c4dc120065e96f62086bf6f9dc26cc54" + integrity sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw== dependencies: - minipass "^4.0.0" + minipass "^7.0.3" -fs-monkey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" - integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== +fs-monkey@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788" + integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew== fs.realpath@^1.0.0: version "1.0.0" @@ -6181,26 +6599,26 @@ fs.realpath@^1.0.0: integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@^2.3.2, fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" -functions-have-names@^1.2.2: +functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== @@ -6240,14 +6658,16 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" - integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== dependencies: - function-bind "^1.1.1" - has "^1.0.3" + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" has-symbols "^1.0.3" + hasown "^2.0.0" get-package-type@^0.1.0: version "0.1.0" @@ -6259,13 +6679,14 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" getpass@^0.1.1: version "0.1.7" @@ -6293,7 +6714,7 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@8.1.0, glob@^8.0.1, glob@^8.0.3: +glob@8.1.0, glob@^8.0.1: version "8.1.0" resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== @@ -6304,6 +6725,17 @@ glob@8.1.0, glob@^8.0.1, glob@^8.0.3: minimatch "^5.0.1" once "^1.3.0" +glob@^10.2.2, glob@^10.3.3: + version "10.3.10" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" + integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.3.5" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" + glob@^7.0.3, glob@^7.0.6, glob@^7.1.3, glob@^7.1.4, glob@^7.1.7: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -6322,9 +6754,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: type-fest "^0.20.2" @@ -6348,13 +6780,13 @@ globby@^11.1.0: slash "^3.0.0" globby@^13.1.1: - version "13.1.3" - resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.3.tgz#f62baf5720bcb2c1330c8d4ef222ee12318563ff" - integrity sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw== + version "13.2.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" + integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== dependencies: dir-glob "^3.0.1" - fast-glob "^3.2.11" - ignore "^5.2.0" + fast-glob "^3.3.0" + ignore "^5.2.4" merge2 "^1.4.1" slash "^4.0.0" @@ -6385,15 +6817,20 @@ gopd@^1.0.1: get-intrinsic "^1.1.3" graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== grapheme-splitter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + hammerjs@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" @@ -6439,41 +6876,41 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== dependencies: - get-intrinsic "^1.1.1" + es-define-property "^1.0.0" -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: - has-symbols "^1.0.2" + has-symbols "^1.0.3" has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: - function-bind "^1.1.1" + function-bind "^1.1.2" hdr-histogram-js@^2.0.1: version "2.0.3" @@ -6519,9 +6956,9 @@ hpack.js@^2.1.6: wbuf "^1.1.0" html-entities@^2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" - integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA== + version "2.5.2" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" + integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA== html-escaper@^2.0.0: version "2.0.2" @@ -6668,17 +7105,22 @@ ieee754@^1.1.13: integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore-walk@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-6.0.1.tgz#f05e232992ebf25fef13613668fea99857e7e8cf" - integrity sha512-/c8MxUAqpRccq+LyDOecwF+9KqajueJHh8fz7g3YqjMZt+NSfJzx05zrKiXwa2sKwFCzaiZ5qUVfRj0pmxixEA== + version "6.0.4" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-6.0.4.tgz#89950be94b4f522225eb63a13c56badb639190e9" + integrity sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw== dependencies: - minimatch "^6.1.6" + minimatch "^9.0.0" -ignore@5.2.4, ignore@^5.2.0: +ignore@5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== +ignore@^5.2.0, ignore@^5.2.4: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + image-size@~0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" @@ -6690,11 +7132,11 @@ immediate@~3.0.5: integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== immutable@^4.0.0: - version "4.2.4" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.2.4.tgz#83260d50889526b4b531a5e293709a77f7c55a2a" - integrity sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w== + version "4.3.5" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0" + integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw== -import-fresh@^3.0.0, import-fresh@^3.2.1: +import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -6766,13 +7208,13 @@ inquirer@8.2.4: through "^2.3.6" wrap-ansi "^7.0.0" -internal-slot@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" - integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== +internal-slot@^1.0.4, internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== dependencies: - get-intrinsic "^1.2.0" - has "^1.0.3" + es-errors "^1.3.0" + hasown "^2.0.0" side-channel "^1.0.4" "internmap@1 - 2": @@ -6780,10 +7222,13 @@ internal-slot@^1.0.4: resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== -ip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" - integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" ipaddr.js@1.9.1: version "1.9.1" @@ -6791,9 +7236,9 @@ ipaddr.js@1.9.1: integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== ipaddr.js@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" - integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== + version "2.1.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f" + integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== is-arguments@^1.1.1: version "1.1.1" @@ -6803,14 +7248,13 @@ is-arguments@^1.1.1: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-array-buffer@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" - integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== +is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== dependencies: call-bind "^1.0.2" - get-intrinsic "^1.2.0" - is-typed-array "^1.1.10" + get-intrinsic "^1.2.1" is-arrayish@^0.2.1: version "0.2.1" @@ -6839,6 +7283,13 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -6851,12 +7302,19 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.11.0, is-core-module@^2.8.1, is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== +is-core-module@^2.13.0, is-core-module@^2.13.1, is-core-module@^2.8.1, is-core-module@^2.9.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== dependencies: - has "^1.0.3" + is-typed-array "^1.1.13" is-date-object@^1.0.1, is-date-object@^1.0.5: version "1.0.5" @@ -6902,15 +7360,15 @@ is-lambda@^1.0.1: resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== -is-map@^2.0.1, is-map@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" - integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== +is-map@^2.0.2, is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== is-number-object@^1.0.4: version "1.0.7" @@ -6968,17 +7426,17 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-set@^2.0.1, is-set@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" - integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== +is-set@^2.0.2, is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" is-stream@^2.0.0: version "2.0.1" @@ -6999,16 +7457,12 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.9: - version "1.1.10" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" - integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== +is-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" + which-typed-array "^1.1.14" is-typedarray@~1.0.0: version "1.0.0" @@ -7020,10 +7474,10 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== is-weakref@^1.0.2: version "1.0.2" @@ -7032,13 +7486,13 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" -is-weakset@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" - integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== +is-weakset@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" + integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" + call-bind "^1.0.7" + get-intrinsic "^1.2.4" is-what@^3.14.1: version "3.14.1" @@ -7088,9 +7542,9 @@ istanbul-lib-coverage@^2.0.5: integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== istanbul-lib-instrument@^5.0.4: version "5.2.1" @@ -7104,12 +7558,12 @@ istanbul-lib-instrument@^5.0.4: semver "^6.3.0" istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== dependencies: istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" + make-dir "^4.0.0" supports-color "^7.1.0" istanbul-lib-source-maps@^3.0.6: @@ -7124,13 +7578,22 @@ istanbul-lib-source-maps@^3.0.6: source-map "^0.6.1" istanbul-reports@^3.0.2: - version "3.1.5" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" - integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jackspeak@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" + integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jasmine-core@^3.6.0: version "3.99.1" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.99.1.tgz#5bfa4b2d76618868bfac4c8ff08bb26fffa4120d" @@ -7177,50 +7640,54 @@ jest-worker@^27.4.5: supports-color "^8.0.0" jquery.terminal@^2.35.3: - version "2.35.3" - resolved "https://registry.yarnpkg.com/jquery.terminal/-/jquery.terminal-2.35.3.tgz#64c9291c9fdfa845083a5a66c6052c96f5cb5f81" - integrity sha512-McYaOivaUB2Gubn8IBhQY7zxjGWXg4ENSofL11rt7HACWUDjqncXxakShuqq7Ma0y+BwCcYdltPl1e+WpDJkeg== + version "2.39.0" + resolved "https://registry.yarnpkg.com/jquery.terminal/-/jquery.terminal-2.39.0.tgz#defc2062dadebc41f0b97eebbfd0e5b4cd165616" + integrity sha512-5uOeJY8dxVJPdeGlaUuRFAcPlw3GzSxLLTmCSaqP9vJhSAu3Amgkr7e7LZxBvup8oQDYF8jRjQSvtIrkn1XsWw== dependencies: "@jcubic/lily" "^0.3.0" - "@types/jquery" "^3.5.14" + "@types/jquery" "^3.5.29" ansidec "^0.3.4" + coveralls-next "^4.2.0" iconv-lite "^0.6.3" - jquery "^3.6.0" + jquery "^3.7.1" prismjs "^1.27.0" wcwidth "^1.0.1" optionalDependencies: fsevents "^2.3.2" -jquery@>=1.9.1, jquery@^3.5.0, jquery@^3.6.0: - version "3.6.3" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.3.tgz#23ed2ffed8a19e048814f13391a19afcdba160e6" - integrity sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg== - -jquery@^3.6.3: - version "3.6.4" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.4.tgz#ba065c188142100be4833699852bf7c24dc0252f" - integrity sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ== +jquery@>=1.9.1, jquery@^3.5.0, jquery@^3.6.3, jquery@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" + integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== js-beautify@^1.14.7: - version "1.14.7" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.7.tgz#9206296de33f86dc106d3e50a35b7cf8729703b2" - integrity sha512-5SOX1KXPFKx+5f6ZrPsIPEY7NwKeQz47n3jm2i+XeHx9MoRsfQenlOP13FQhWvg8JRS0+XLO6XYUQ2GX+q+T9A== + version "1.15.1" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.15.1.tgz#4695afb508c324e1084ee0b952a102023fc65b64" + integrity sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA== dependencies: config-chain "^1.1.13" - editorconfig "^0.15.3" - glob "^8.0.3" - nopt "^6.0.0" + editorconfig "^1.0.4" + glob "^10.3.3" + js-cookie "^3.0.5" + nopt "^7.2.0" -js-sdsl@^4.1.4: - version "4.3.0" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711" - integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== +js-cookie@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" + integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@4.1.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -7229,22 +7696,20 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== -jsdoc-type-pratt-parser@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz#a4a56bdc6e82e5865ffd9febc5b1a227ff28e67e" - integrity sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw== +jsdoc-type-pratt-parser@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz#136f0571a99c184d84ec84662c45c29ceff71114" + integrity sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ== jsesc@^2.5.1: version "2.5.2" @@ -7256,15 +7721,20 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-parse-even-better-errors@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz#2cb2ee33069a78870a0c7e3da560026b89669cf7" - integrity sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz#02bb29fb5da90b5444581749c22cedd3597c6cb0" + integrity sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg== json-schema-defaults@^0.4.0: version "0.4.0" @@ -7305,7 +7775,7 @@ json5@^1.0.2: dependencies: minimist "^1.2.0" -json5@^2.1.2, json5@^2.2.1, json5@^2.2.2: +json5@^2.1.2, json5@^2.2.1, json5@^2.2.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -7424,9 +7894,9 @@ jstree-bootstrap-theme@^1.0.1: jquery ">=1.9.1" jstree@^3.3.15: - version "3.3.15" - resolved "https://registry.yarnpkg.com/jstree/-/jstree-3.3.15.tgz#364bfa0fc4c0b1b7b5bc122cda44a0f09e59eba4" - integrity sha512-fNK2EBgGjaJQ3cJuINX/80vDeAufYWtM0csudgYl3eJG+eRAH/1r1IJVUOvAlJIa+uSgg+Fi8uGrt+Xbs92eKg== + version "3.3.16" + resolved "https://registry.yarnpkg.com/jstree/-/jstree-3.3.16.tgz#43e3a8a1ee270ddff514317250d5e85b95d63c52" + integrity sha512-yeeIJffi2WAqyMeHufXj/Ozy7GqgKdDkxfN8L8lwbG0h1cw/TgDafWmyhroH4AKgDSk9yW1W6jiJZu4zXAqzXw== dependencies: jquery "^3.5.0" @@ -7508,16 +7978,23 @@ karma@~6.3.9: yargs "^16.1.1" katex@^0.16.0: - version "0.16.4" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.4.tgz#87021bc3bbd80586ef715aeb476794cba6a49ad4" - integrity sha512-WudRKUj8yyBeVDI4aYMNxhx5Vhh2PjpzQw1GRu/LVGqL4m1AxwD1GcUp0IMbdJaf5zsjtj8ghP0DOQRYhroNkw== + version "0.16.9" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.9.tgz#bc62d8f7abfea6e181250f85a56e4ef292dcb1fa" + integrity sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ== + dependencies: + commander "^8.3.0" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== dependencies: - commander "^8.0.0" + json-buffer "3.0.1" khroma@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/khroma/-/khroma-2.0.0.tgz#7577de98aed9f36c7a474c4d453d94c0d6c6588b" - integrity sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g== + version "2.1.0" + resolved "https://registry.yarnpkg.com/khroma/-/khroma-2.1.0.tgz#45f2ce94ce231a437cf5b63c2e886e6eb42bbbb1" + integrity sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw== kind-of@^6.0.2: version "6.0.3" @@ -7546,6 +8023,11 @@ layout-base@^2.0.0: resolved "https://registry.yarnpkg.com/layout-base/-/layout-base-2.0.1.tgz#d0337913586c90f9c2c075292069f5c2da5dd285" integrity sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg== +lcov-parse@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-1.0.0.tgz#eb0d46b54111ebc561acb4c408ef9363bdc8f7e0" + integrity sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ== + leaflet-polylinedecorator@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/leaflet-polylinedecorator/-/leaflet-polylinedecorator-1.6.0.tgz#9ef79fd1b5302d67b72efe959a8ecd2553f27266" @@ -7611,9 +8093,9 @@ levn@^0.4.1: type-check "~0.4.0" libphonenumber-js@^1.10.4: - version "1.10.21" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.21.tgz#860312cbec1389e36e28389161025c7817a3ae32" - integrity sha512-/udZhx49av2r2gZR/+xXSrwcR8smX/sDNrVpOFrvW+CA26TfYTVZfwb3MIDvmwAYMLs7pXuJjZX0VxxGpqPhsA== + version "1.10.58" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.58.tgz#2015877bd47fd3d32d9fbfcedd75df35be230c9a" + integrity sha512-53A0IpJFL9LdHbpeatwizf8KSwPICrqn9H0g3Y7WQ+Jgeu9cQ4Ew3WrRtrLBu/CX2lXd5+rgT01/tGlkbkzOjw== license-webpack-plugin@4.0.2: version "4.0.2" @@ -7702,6 +8184,11 @@ lodash@4.17.21, lodash@^4.0.1, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-driver@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" + integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg== + log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -7711,9 +8198,9 @@ log-symbols@^4.1.0: is-unicode-supported "^0.1.0" log4js@^6.4.1: - version "6.8.0" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.8.0.tgz#f0fe9b2b82725aaf97f20692e23381a5c5722448" - integrity sha512-g+V8gZyurIexrOvWQ+AcZsIvuK/lBnx2argejZxL4gVZ4Hq02kUYH6WZOnqxgBml+zzQZYdaEoTN84B6Hzm8Fg== + version "6.9.1" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz#aba5a3ff4e7872ae34f8b4c533706753709e38b6" + integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g== dependencies: date-format "^4.0.14" debug "^4.3.4" @@ -7728,14 +8215,6 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lru-cache@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -7751,9 +8230,14 @@ lru-cache@^6.0.0: yallist "^4.0.0" lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: - version "7.18.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.1.tgz#4716408dec51d5d0104732647f584d1f6738b109" - integrity sha512-8/HcIENyQnfUTCDizRu9rrDyG6XG/21M4X7/YEGZeD76ZJilFPAUVb/2zysFf7VVO1LEjCDFyHp8pMMvozIrvg== + version "7.18.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + +"lru-cache@^9.1.1 || ^10.0.0": + version "10.2.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" + integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== magic-string@0.29.0: version "0.29.0" @@ -7777,13 +8261,20 @@ make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0, make-dir@^3.0.2: +make-dir@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -7811,10 +8302,10 @@ make-fetch-happen@^10.0.3: socks-proxy-agent "^7.0.0" ssri "^9.0.0" -make-fetch-happen@^11.0.0, make-fetch-happen@^11.0.1: - version "11.0.3" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-11.0.3.tgz#ed83dd3685b97f75607156d2721848f6eca561b9" - integrity sha512-oPLh5m10lRNNZDjJ2kP8UpboUx2uFXVaVweVe/lWut4iHWcQEmfqSVJt2ihZsFI8HbpwyyocaXbCAWf0g1ukIA== +make-fetch-happen@^11.0.0, make-fetch-happen@^11.0.1, make-fetch-happen@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz#85ceb98079584a9523d4bf71d32996e7e208549f" + integrity sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w== dependencies: agentkeepalive "^4.2.1" cacache "^17.0.0" @@ -7823,7 +8314,7 @@ make-fetch-happen@^11.0.0, make-fetch-happen@^11.0.1: https-proxy-agent "^5.0.0" is-lambda "^1.0.1" lru-cache "^7.7.1" - minipass "^4.0.0" + minipass "^5.0.0" minipass-fetch "^3.0.0" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" @@ -7833,14 +8324,14 @@ make-fetch-happen@^11.0.0, make-fetch-happen@^11.0.1: ssri "^10.0.0" make-plural@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-7.2.0.tgz#93174b1419672a48a2340db6c1d3fb217530c684" - integrity sha512-WkdI+iaWaBCFM2wUXwos8Z7spg5Dt64Xe/VI6NpRaly21cDtD76N6S97K//UtzV0dHOiXX+E90TnszdXHG0aMg== + version "7.3.0" + resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-7.3.0.tgz#2889dbafca2fb097037c47967d3e3afa7e48a52c" + integrity sha512-/K3BC0KIsO+WK2i94LkMPv3wslMrazrQhfi5We9fMbLlLjzoOSJWr7TAdupLlDWaJcWxwoNosBkhFDejiu5VDw== marked@^4.0.17: - version "4.2.12" - resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.12.tgz#d69a64e21d71b06250da995dcd065c11083bebb5" - integrity sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw== + version "4.3.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" + integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== media-typer@0.3.0: version "0.3.0" @@ -7848,11 +8339,11 @@ media-typer@0.3.0: integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== memfs@^3.4.12, memfs@^3.4.3: - version "3.4.13" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.13.tgz#248a8bd239b3c240175cd5ec548de5227fc4f345" - integrity sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg== + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== dependencies: - fs-monkey "^1.0.3" + fs-monkey "^1.0.4" merge-descriptors@1.0.1: version "1.0.1" @@ -7870,25 +8361,26 @@ merge2@^1.3.0, merge2@^1.4.1: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== mermaid@^9.1.2: - version "9.4.0" - resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.4.0.tgz#ff9afcac9f565a358fa8fc39135dec2c842c3b8f" - integrity sha512-4PWbOND7CNRbjHrdG3WUUGBreKAFVnMhdlPjttuUkeHbCQmAHkwzSh5dGwbrKmXGRaR4uTvfFVYzUcg++h0DkA== + version "9.4.3" + resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.4.3.tgz#62cf210c246b74972ea98c19837519b6f03427f2" + integrity sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw== dependencies: "@braintree/sanitize-url" "^6.0.0" cytoscape "^3.23.0" cytoscape-cose-bilkent "^4.1.0" cytoscape-fcose "^2.1.0" - d3 "^7.0.0" - dagre-d3-es "7.0.8" + d3 "^7.4.0" + dagre-d3-es "7.0.9" + dayjs "^1.11.7" dompurify "2.4.3" elkjs "^0.8.2" khroma "^2.0.0" lodash-es "^4.17.21" - moment "^2.29.4" non-layered-tidy-tree-layout "^2.0.2" stylis "^4.1.2" ts-dedent "^2.2.0" uuid "^9.0.0" + web-worker "^1.2.0" methods@~1.1.2: version "1.1.2" @@ -7942,6 +8434,13 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== +minimatch@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253" + integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w== + dependencies: + brace-expansion "^2.0.1" + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -7956,13 +8455,18 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minimatch@^6.1.0, minimatch@^6.1.6: - version "6.2.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-6.2.0.tgz#2b70fd13294178c69c04dfc05aebdb97a4e79e42" - integrity sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg== +minimatch@^9.0.0, minimatch@^9.0.1: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== dependencies: brace-expansion "^2.0.1" +minimist@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -7987,11 +8491,11 @@ minipass-fetch@^2.0.3: encoding "^0.1.13" minipass-fetch@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.1.tgz#bae3789f668d82ffae3ea47edc6b78b8283b3656" - integrity sha512-t9/wowtf7DYkwz8cfMSt0rMwiyNIBXf5CKZ3S5ZMqRqMYT0oLTp0x1WorMI9WTwvaPg21r1JbFxJMum8JrLGfw== + version "3.0.4" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.4.tgz#4d4d9b9f34053af6c6e597a64be8e66e42bf45b7" + integrity sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg== dependencies: - minipass "^4.0.0" + minipass "^7.0.3" minipass-sized "^1.0.3" minizlib "^2.1.2" optionalDependencies: @@ -8034,9 +8538,19 @@ minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: yallist "^4.0.0" minipass@^4.0.0: - version "4.2.4" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.4.tgz#7d0d97434b6a19f59c5c3221698b48bbf3b2cd06" - integrity sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ== + version "4.2.8" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" + integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.3: + version "7.0.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" + integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" @@ -8059,16 +8573,16 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== moment-timezone@^0.5.42: - version "0.5.42" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.42.tgz#c59f2aa00442d0dcd1d258d2182873d637b4e17b" - integrity sha512-tjI9goqwzkflKSTxJo+jC/W8riTFwEjjunssmFvAWlvNVApjbkJM7UHggyKO0q1Fd/kZVKY77H7C9A0XKhhAFw== + version "0.5.45" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.45.tgz#cb685acd56bac10e69d93c536366eb65aa6bcf5c" + integrity sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ== dependencies: moment "^2.29.4" moment@^2.29.4: - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== moo@^0.5.1: version "0.5.2" @@ -8108,10 +8622,10 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nanoid@^3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== +nanoid@^3.3.6, nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== natural-compare-lite@^1.4.0: version "1.4.0" @@ -8124,11 +8638,10 @@ natural-compare@^1.4.0: integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== needle@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-3.2.0.tgz#07d240ebcabfd65c76c03afae7f6defe6469df44" - integrity sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ== + version "3.3.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-3.3.1.tgz#63f75aec580c2e77e209f3f324e2cdf3d29bd049" + integrity sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q== dependencies: - debug "^3.2.6" iconv-lite "^0.6.3" sax "^1.2.4" @@ -8166,9 +8679,9 @@ ngx-daterangepicker-material@^6.0.4: tslib "^2.0.0" ngx-drag-drop@^15.0.1: - version "15.0.1" - resolved "https://registry.yarnpkg.com/ngx-drag-drop/-/ngx-drag-drop-15.0.1.tgz#e751fa0f11a4c86456fbb241bc8c2c530149cee0" - integrity sha512-x0YgsVCk95x3YSNZT/EmJvo1sRjra+BtQuZMbRYKc88E0GHIOlmWNp/1Z7w5UdznEp0240KDwYp3crR4FuDGew== + version "15.1.0" + resolved "https://registry.yarnpkg.com/ngx-drag-drop/-/ngx-drag-drop-15.1.0.tgz#ddc208202ea6576e27c5362b87e87e9a4b081877" + integrity sha512-ZTAMrKMv7Fqdybvt+2sEtbT0LlOwsaFCNwFQ/CnKXrXEBkbs5K1jx2rN8HXx9BMlVAfa4ovOmtkJXtx/9XYqrA== dependencies: tslib "^2.3.0" @@ -8208,16 +8721,16 @@ ngx-sharebuttons@^12.0.0: tslib "^2.0.0" ngx-translate-messageformat-compiler@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-6.2.0.tgz#54cd3dc151e29d035d483ad3ac8951fb1e074a25" - integrity sha512-niGhub53gMw8GbmP3u3OB7SVJn7z5JZodQxQxDb6ZE0A9c0WeVBzz30WbekQshBF7ijzLYE/qcM+lzTCpWkRmg== + version "6.5.1" + resolved "https://registry.yarnpkg.com/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-6.5.1.tgz#dfb51a83df5ec41bb6c3fff3b2e3f640ecb4a324" + integrity sha512-jJHlEYfE6myYQsILhXHNp2QIOgP3B19BE+NFN9H2a9sgqWgV/CoHBzhEvnt0suf1og/drgHFSkugp5N0Im0xHg== dependencies: - tslib "^2.4.1" + tslib "^2.5.0" ngx-window-token@>=6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/ngx-window-token/-/ngx-window-token-6.0.0.tgz#8b4704242df93a302422a23163359c09d6bb34a0" - integrity sha512-IeLKO1jzfzSvZ6vlAt4QSY/B5XcHEhdOwTjqvWEPt6/esWV9T3mA2ln10kj6SCc9pUSx4NybxE10gcyyYroImg== + version "7.0.0" + resolved "https://registry.yarnpkg.com/ngx-window-token/-/ngx-window-token-7.0.0.tgz#2e1bc76846411a388188b802154e4841d3e27856" + integrity sha512-5+XfRVSY7Dciu8xyCNMkOlH2UfwR9W2P1Pirz7caaZgOZDjFbL8aEO2stjfJJm2FFf1D6dlVHNzhLWGk9HGkqA== dependencies: tslib "^2.0.0" @@ -8245,16 +8758,17 @@ node-forge@^1: integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== node-gyp-build@^4.2.2: - version "4.6.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" - integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== + version "4.8.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.0.tgz#3fee9c1731df4581a3f9ead74664369ff00d26dd" + integrity sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og== node-gyp@^9.0.0: - version "9.3.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.3.1.tgz#1e19f5f290afcc9c46973d68700cbd21a96192e4" - integrity sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg== + version "9.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.1.tgz#8a1023e0d6766ecb52764cc3a734b36ff275e185" + integrity sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ== dependencies: env-paths "^2.2.0" + exponential-backoff "^3.1.1" glob "^7.1.4" graceful-fs "^4.2.6" make-fetch-happen "^10.0.3" @@ -8265,10 +8779,10 @@ node-gyp@^9.0.0: tar "^6.1.2" which "^2.0.2" -node-releases@^2.0.8: - version "2.0.10" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" - integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== +node-releases@^2.0.14, node-releases@^2.0.8: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== non-layered-tidy-tree-layout@^2.0.2: version "2.0.2" @@ -8282,6 +8796,13 @@ nopt@^6.0.0: dependencies: abbrev "^1.0.0" +nopt@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.0.tgz#067378c68116f602f552876194fd11f1292503d7" + integrity sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA== + dependencies: + abbrev "^2.0.0" + normalize-package-data@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-5.0.0.tgz#abcb8d7e724c40d88462b84982f7cbf6859b4588" @@ -8310,16 +8831,16 @@ npm-bundled@^3.0.0: npm-normalize-package-bin "^3.0.0" npm-install-checks@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-6.0.0.tgz#9a021d8e8b3956d61fd265c2eda4735bcd3d9b83" - integrity sha512-SBU9oFglRVZnfElwAtF14NivyulDqF1VKqqwNsFW9HDcbHMAPHpRSsVFgKuwFGq/hVvWZExz62Th0kvxn/XE7Q== + version "6.3.0" + resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-6.3.0.tgz#046552d8920e801fa9f919cad569545d60e826fe" + integrity sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw== dependencies: semver "^7.1.1" npm-normalize-package-bin@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.0.tgz#6097436adb4ef09e2628b59a7882576fe53ce485" - integrity sha512-g+DPQSkusnk7HYXr75NtzkIP4+N81i3RPsGFidF3DzHd9MT9wWngmqoeg/fnHFz5MNdtG4w03s+QnhewSLTT2Q== + version "3.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz#25447e32a9a7de1f51362c61a559233b89947832" + integrity sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ== npm-package-arg@10.1.0, npm-package-arg@^10.0.0: version "10.1.0" @@ -8338,7 +8859,7 @@ npm-packlist@^7.0.0: dependencies: ignore-walk "^6.0.0" -npm-pick-manifest@8.0.1, npm-pick-manifest@^8.0.0: +npm-pick-manifest@8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz#c6acd97d1ad4c5dbb80eac7b386b03ffeb289e5f" integrity sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA== @@ -8348,13 +8869,23 @@ npm-pick-manifest@8.0.1, npm-pick-manifest@^8.0.0: npm-package-arg "^10.0.0" semver "^7.3.5" +npm-pick-manifest@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz#2159778d9c7360420c925c1a2287b5a884c713aa" + integrity sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg== + dependencies: + npm-install-checks "^6.0.0" + npm-normalize-package-bin "^3.0.0" + npm-package-arg "^10.0.0" + semver "^7.3.5" + npm-registry-fetch@^14.0.0: - version "14.0.3" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-14.0.3.tgz#8545e321c2b36d2c6fe6e009e77e9f0e527f547b" - integrity sha512-YaeRbVNpnWvsGOjX2wk5s85XJ7l1qQBGAp724h8e2CZFFhMSuw9enom7K1mWVUtvXO1uUSFIAPofQK0pPN0ZcA== + version "14.0.5" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz#fe7169957ba4986a4853a650278ee02e568d115d" + integrity sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA== dependencies: make-fetch-happen "^11.0.0" - minipass "^4.0.0" + minipass "^5.0.0" minipass-fetch "^3.0.0" minipass-json-stream "^1.0.1" minizlib "^2.1.2" @@ -8395,42 +8926,62 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.12.2, object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== +object-inspect@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== object-is@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" - integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + version "1.1.6" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" + call-bind "^1.0.7" + define-properties "^1.2.1" object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== +object.assign@^4.1.4, object.assign@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" + call-bind "^1.0.5" + define-properties "^1.2.1" has-symbols "^1.0.3" object-keys "^1.1.1" -object.values@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" - integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== +object.fromentries@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" + integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +object.groupby@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.2.tgz#494800ff5bab78fd0eff2835ec859066e00192ec" + integrity sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw== + dependencies: + array.prototype.filter "^1.0.3" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.0.0" + +object.values@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" + integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" objectpath@^2.0.0: version "2.0.0" @@ -8501,17 +9052,17 @@ open@^8.0.9: is-docker "^2.1.1" is-wsl "^2.2.0" -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" - word-wrap "^1.2.3" ora@5.4.1, ora@^5.4.1: version "5.4.1" @@ -8722,6 +9273,14 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== + dependencies: + lru-cache "^9.1.1 || ^10.0.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -8804,6 +9363,11 @@ popper.js@1.16.1-lts: resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05" integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA== +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + postcss-loader@7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.0.2.tgz#b53ff44a26fba3688eee92a048c7f2d4802e23bb" @@ -8819,18 +9383,18 @@ postcss-modules-extract-imports@^3.0.0: integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== + version "4.0.4" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz#7cbed92abd312b94aaea85b68226d3dec39a14e6" + integrity sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q== dependencies: icss-utils "^5.0.0" postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== + version "3.1.1" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz#32cfab55e84887c079a19bbb215e721d683ef134" + integrity sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA== dependencies: postcss-selector-parser "^6.0.4" @@ -8842,9 +9406,9 @@ postcss-modules-values@^4.0.0: icss-utils "^5.0.0" postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.11" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" - integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== + version "6.0.16" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz#3b88b9f5c5abd989ef4e2fc9ec8eedd34b20fb04" + integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -8854,15 +9418,24 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@8.4.21, postcss@^8.2.14, postcss@^8.3.7, postcss@^8.4.19: - version "8.4.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" - integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== +postcss@8.4.31: + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== dependencies: - nanoid "^3.3.4" + nanoid "^3.3.6" picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.2.14, postcss@^8.3.7, postcss@^8.4.19: + version "8.4.36" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.36.tgz#dba513c3c3733c44e0288a712894f8910bbaabc6" + integrity sha512-/n7eumA6ZjFHAsbX30yhHup/IMkOmlmvtEi7P+6RMYf+bGJSUHc3geH4a0NSZxAz/RJfiS9tooCTs9LAVYUZKw== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.1.0" + postinstall-prepare@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postinstall-prepare/-/postinstall-prepare-2.0.0.tgz#2a6867c1a13a05502aa115d0495efbbd778769cb" @@ -8874,9 +9447,9 @@ prelude-ls@^1.2.1: integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prettier@^2.8.3: - version "2.8.4" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3" - integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw== + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== pretty-bytes@^5.3.0: version "5.6.0" @@ -8959,20 +9532,15 @@ prr@~1.0.1: resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== - psl@^1.1.28: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== punycode@^2.1.0, punycode@^2.1.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== q@1.4.1: version "1.4.1" @@ -8990,9 +9558,9 @@ qjobs@^1.2.0: integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== qrcode@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.1.tgz#0103f97317409f7bc91772ef30793a54cd59f0cb" - integrity sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg== + version "1.5.3" + resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170" + integrity sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg== dependencies: dijkstrajs "^1.0.1" encode-utf8 "^1.0.3" @@ -9040,16 +9608,6 @@ raphael@^2.3.0: dependencies: eve-raphael "0.5.0" -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - raw-body@2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" @@ -9087,32 +9645,32 @@ rc-align@^4.0.0: resize-observer-polyfill "^1.5.1" rc-motion@^2.0.0, rc-motion@^2.0.1: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.6.3.tgz#e6d8ca06591c2c1bcd3391a8e7a822ebc4d94e9c" - integrity sha512-xFLkes3/7VL/J+ah9jJruEW/Akbx5F6jVa2wG5o/ApGKQKSOd5FR3rseHLL9+xtJg4PmCwo6/1tqhDO/T+jFHA== + version "2.9.0" + resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.9.0.tgz#9e18a1b8d61e528a97369cf9a7601e9b29205710" + integrity sha512-XIU2+xLkdIr1/h6ohPZXyPBMvOmuyFZQ/T0xnawz+Rh+gh4FINcnZmMT5UTIj6hgI0VLDjTaPeRd+smJeSPqiQ== dependencies: "@babel/runtime" "^7.11.1" classnames "^2.2.1" rc-util "^5.21.0" rc-overflow@^1.0.0: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc-overflow/-/rc-overflow-1.2.8.tgz#40f140fabc244118543e627cdd1ef750d9481a88" - integrity sha512-QJ0UItckWPQ37ZL1dMEBAdY1dhfTXFL9k6oTTcyydVwoUNMnMqCGqnRNA98axSr/OeDKqR6DVFyi8eA5RQI/uQ== + version "1.3.2" + resolved "https://registry.yarnpkg.com/rc-overflow/-/rc-overflow-1.3.2.tgz#72ee49e85a1308d8d4e3bd53285dc1f3e0bcce2c" + integrity sha512-nsUm78jkYAoPygDAcGZeC2VwIg/IBGSodtOY3pMof4W3M9qRJgqaDYm03ZayHlde3I6ipliAxbN0RUcGf5KOzw== dependencies: "@babel/runtime" "^7.11.1" classnames "^2.2.1" rc-resize-observer "^1.0.0" - rc-util "^5.19.2" + rc-util "^5.37.0" rc-resize-observer@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-1.3.1.tgz#b61b9f27048001243617b81f95e53d7d7d7a6a3d" - integrity sha512-iFUdt3NNhflbY3mwySv5CA1TC06zdJ+pfo0oc27xpf4PIOvfZwZGtD9Kz41wGYqC4SLio93RVAirSSpYlV/uYg== + version "1.4.0" + resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-1.4.0.tgz#7bba61e6b3c604834980647cce6451914750d0cc" + integrity sha512-PnMVyRid9JLxFavTjeDXEXo65HCRqbmLBw9xX9gfC4BZiSzbLXKzW3jPz+J0P71pLbD5tBMTT+mkstV5gD0c9Q== dependencies: "@babel/runtime" "^7.20.7" classnames "^2.2.1" - rc-util "^5.27.0" + rc-util "^5.38.0" resize-observer-polyfill "^1.5.1" rc-select@13.2.1: @@ -9139,23 +9697,23 @@ rc-trigger@^5.0.4: rc-motion "^2.0.0" rc-util "^5.19.2" -rc-util@^5.15.0, rc-util@^5.19.2, rc-util@^5.21.0, rc-util@^5.26.0, rc-util@^5.27.0, rc-util@^5.9.8: - version "5.28.0" - resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.28.0.tgz#9e5e441d5875b8bf0ba56c2f295042a28dcff580" - integrity sha512-KYDjhGodswVj29v0TRciKTqRPgumIFvFDndbCD227pitQ+0Cei196rxk+OXb/blu6V8zdTRK5RjCJn+WmHLvBA== +rc-util@^5.19.2, rc-util@^5.21.0, rc-util@^5.26.0, rc-util@^5.36.0, rc-util@^5.37.0, rc-util@^5.38.0, rc-util@^5.9.8: + version "5.39.1" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.39.1.tgz#7bca4fb55e20add0eef5c23166cd9f9e5f51a8a1" + integrity sha512-OW/ERynNDgNr4y0oiFmtes3rbEamXw7GHGbkbNd9iRr7kgT03T6fT0b9WpJ3mbxKhyOcAHnGcIoh5u/cjrC2OQ== dependencies: "@babel/runtime" "^7.18.3" - react-is "^16.12.0" + react-is "^18.2.0" rc-virtual-list@^3.2.0: - version "3.4.13" - resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.4.13.tgz#20acc934b263abcf7b7c161f50ef82281b2f7e8d" - integrity sha512-cPOVDmcNM7rH6ANotanMDilW/55XnFPw0Jh/GQYtrzZSy3AmWvCnqVNyNC/pgg3lfVmX2994dlzAhuUrd4jG7w== + version "3.11.4" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.11.4.tgz#d0a8937843160b7b00d5586854290bf56d396af7" + integrity sha512-NbBi0fvyIu26gP69nQBiWgUMTPX3mr4FcuBQiVqagU0BnuX8WQkiivnMs105JROeuUIFczLrlgUhLQwTWV1XDA== dependencies: "@babel/runtime" "^7.20.0" classnames "^2.2.6" rc-resize-observer "^1.0.0" - rc-util "^5.15.0" + rc-util "^5.36.0" react-ace@9.5.0: version "9.5.0" @@ -9186,7 +9744,7 @@ react-dropzone@^11.4.2: file-selector "^0.4.0" prop-types "^15.8.1" -react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -9196,6 +9754,11 @@ react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-is@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + react-transition-group@^4.0.0, react-transition-group@^4.4.0: version "4.4.5" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" @@ -9230,11 +9793,11 @@ read-package-json-fast@^3.0.0: npm-normalize-package-bin "^3.0.0" read-package-json@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-6.0.0.tgz#6a741841ad72a40e77a82b9c3c8c10e865bbc519" - integrity sha512-b/9jxWJ8EwogJPpv99ma+QwtqB7FSl3+V6UXS7Aaay8/5VwMY50oIFooY1UKXMWpfNCM6T/PoGqa5GD1g9xf9w== + version "6.0.4" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-6.0.4.tgz#90318824ec456c287437ea79595f4c2854708836" + integrity sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw== dependencies: - glob "^8.0.1" + glob "^10.2.2" json-parse-even-better-errors "^3.0.0" normalize-package-data "^5.0.0" npm-normalize-package-bin "^3.0.0" @@ -9253,9 +9816,9 @@ readable-stream@^2.0.1, readable-stream@~2.3.6: util-deprecate "~1.0.1" readable-stream@^3.0.6, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.1.tgz#f9f9b5f536920253b3d26e7660e7da4ccff9bb62" - integrity sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ== + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" @@ -9274,14 +9837,14 @@ reduce-flatten@^2.0.0: integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== reflect-metadata@^0.1.2: - version "0.1.13" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" - integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + version "0.1.14" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.14.tgz#24cf721fe60677146bb77eeb0e1f9dece3d65859" + integrity sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A== regenerate-unicode-properties@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" - integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== + version "10.1.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" + integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== dependencies: regenerate "^1.4.2" @@ -9295,31 +9858,37 @@ regenerator-runtime@^0.13.11: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== -regenerator-transform@^0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" - integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== dependencies: "@babel/runtime" "^7.8.4" regex-parser@^2.2.11: - version "2.2.11" - resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.11.tgz#3b37ec9049e19479806e878cabe7c1ca83ccfe58" - integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== + version "2.3.0" + resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.3.0.tgz#4bb61461b1a19b8b913f3960364bb57887f920ee" + integrity sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg== -regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== +regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" regexpu-core@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.1.tgz#66900860f88def39a5cb79ebd9490e84f17bcdfb" - integrity sha512-nCOzW2V/X15XpLsK2rlgdwrysrBq+AauCn+omItIz4R1pIcmeot5zvjdmOBRLzEH/CkC6IxMJVmxDe3QcMuNVQ== + version "5.3.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== dependencies: "@babel/regjsgen" "^0.8.0" regenerate "^1.4.2" @@ -9407,7 +9976,7 @@ resolve-url-loader@5.0.0: postcss "^8.2.14" source-map "0.6.1" -resolve@1.22.1, resolve@^1.14.2, resolve@^1.22.1: +resolve@1.22.1: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -9416,6 +9985,15 @@ resolve@1.22.1, resolve@^1.14.2, resolve@^1.22.1: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^1.14.2, resolve@^1.22.4: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -9440,9 +10018,9 @@ reusify@^1.0.4: integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + version "1.3.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.1.tgz#2b6d4df52dffe8bb346992a10ea9451f24373a8f" + integrity sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg== rifm@^0.7.0: version "0.7.0" @@ -9465,10 +10043,10 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" -robust-predicates@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a" - integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g== +robust-predicates@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" + integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== run-async@^2.4.0: version "2.4.1" @@ -9495,12 +10073,22 @@ rxjs@6.6.7: tslib "^1.9.0" rxjs@^7.5.5, rxjs@~7.8.0: - version "7.8.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" - integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== dependencies: tslib "^2.1.0" +safe-array-concat@^1.1.0, safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -9516,13 +10104,13 @@ safe-identifier@^0.4.1: resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb" integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w== -safe-regex-test@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" - integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" + call-bind "^1.0.6" + es-errors "^1.3.0" is-regex "^1.1.4" "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: @@ -9560,9 +10148,9 @@ saucelabs@^1.5.0: https-proxy-agent "^2.2.1" sax@>=0.6.0, sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + version "1.3.0" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" + integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== scheduler@^0.20.2: version "0.20.2" @@ -9573,30 +10161,30 @@ scheduler@^0.20.2: object-assign "^4.1.1" schema-inspector@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/schema-inspector/-/schema-inspector-2.0.2.tgz#3b902e9095f4636428a890203d469b0d404ada5b" - integrity sha512-phq0/I55VGzl4kmq3Tp1jlY75Xtc1o7wfGmOEFTgGyucI6zIdEsiM7MJu9jjQf2SfMreqSbTi/ktUsEMs6pV7A== + version "2.1.0" + resolved "https://registry.yarnpkg.com/schema-inspector/-/schema-inspector-2.1.0.tgz#85096fbc78162a420262ed41b82e60ac927767b2" + integrity sha512-3bmQVhbA01/EW8cZin4vIpqlpNU2SIy4BhKCfCgogJ3T/L76dLx3QAE+++4o+dNT33sa+SN9vOJL7iHiHFjiNg== dependencies: async "~2.6.3" -schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== +schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== dependencies: "@types/json-schema" "^7.0.8" ajv "^6.12.5" ajv-keywords "^3.5.2" schema-utils@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" - integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== + version "4.2.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" + integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== dependencies: "@types/json-schema" "^7.0.9" - ajv "^8.8.0" + ajv "^8.9.0" ajv-formats "^2.1.1" - ajv-keywords "^5.0.0" + ajv-keywords "^5.1.0" screenfull@^6.0.2: version "6.0.2" @@ -9624,28 +10212,36 @@ selenium-webdriver@3.6.0, selenium-webdriver@^3.0.1: xml2js "^0.4.17" selfsigned@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61" - integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== + version "2.4.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" + integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== dependencies: + "@types/node-forge" "^1.3.0" node-forge "^1" -semver@7.3.8, semver@^7.0.0, semver@^7.1.1, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== +semver@7.5.3: + version "7.5.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" + integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== dependencies: lru-cache "^6.0.0" semver@^5.3.0, semver@^5.5.0, semver@^5.6.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.0.0, semver@^7.1.1, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.6.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" send@0.18.0: version "0.18.0" @@ -9666,10 +10262,10 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" -serialize-javascript@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" - integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== +serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" @@ -9701,6 +10297,28 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -9748,31 +10366,35 @@ shebang-regex@^3.0.0: integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -sigmund@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" - integrity sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g== + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sigstore@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-1.0.0.tgz#3c7a8bbacf99d0f978276bd29bd94911006b72c7" - integrity sha512-e+qfbn/zf1+rCza/BhIA//Awmf0v1pa5HQS8Xk8iXrn9bgytytVLqYD0P7NSqZ6IELTgq+tcDvLPkQjNHyWLNg== + version "1.9.0" + resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-1.9.0.tgz#1e7ad8933aa99b75c6898ddd0eeebc3eb0d59875" + integrity sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A== dependencies: + "@sigstore/bundle" "^1.1.0" + "@sigstore/protobuf-specs" "^0.2.0" + "@sigstore/sign" "^1.0.0" + "@sigstore/tuf" "^1.0.3" make-fetch-happen "^11.0.1" - tuf-js "^1.0.0" slash@^2.0.0: version "2.0.0" @@ -9795,31 +10417,33 @@ smart-buffer@^4.2.0: integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== socket.io-adapter@~2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" - integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA== + version "2.5.4" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz#4fdb1358667f6d68f25343353bd99bd11ee41006" + integrity sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg== dependencies: + debug "~4.3.4" ws "~8.11.0" -socket.io-parser@~4.2.1: - version "4.2.2" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.2.tgz#1dd384019e25b7a3d374877f492ab34f2ad0d206" - integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw== +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== dependencies: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" socket.io@^4.4.1: - version "4.6.1" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.6.1.tgz#62ec117e5fce0692fa50498da9347cfb52c3bc70" - integrity sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA== + version "4.7.5" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.5.tgz#56eb2d976aef9d1445f373a62d781a41c7add8f8" + integrity sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA== dependencies: accepts "~1.3.4" base64id "~2.0.0" + cors "~2.8.5" debug "~4.3.2" - engine.io "~6.4.1" + engine.io "~6.5.2" socket.io-adapter "~2.5.2" - socket.io-parser "~4.2.1" + socket.io-parser "~4.2.4" sockjs@^0.3.24: version "0.3.24" @@ -9840,11 +10464,11 @@ socks-proxy-agent@^7.0.0: socks "^2.6.2" socks@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" - integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + version "2.8.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.1.tgz#22c7d9dd7882649043cba0eafb49ae144e3457af" + integrity sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ== dependencies: - ip "^2.0.0" + ip-address "^9.0.5" smart-buffer "^4.2.0" sorted-btree@^1.8.1: @@ -9852,10 +10476,10 @@ sorted-btree@^1.8.1: resolved "https://registry.yarnpkg.com/sorted-btree/-/sorted-btree-1.8.1.tgz#6e6275f7955e5892bb8737149cbe495be10f426f" integrity sha512-395+XIP+wqNn3USkFSrNz7G3Ss/MXlZEqesxvzCRFwL14h6e8LukDHdLBePn5pwbm5OQ9vGu8mDyz2lLDIqamQ== -"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2, source-map-js@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.1.0.tgz#9e7d5cb46f0689fb6691b30f226937558d0fa94b" + integrity sha512-9vC2SfsJzlej6MAaMPLu8HiBSHGdRAJ9hVFYN1ibZoNkeanmDmLUcIrj6G9DGL7XMJ54AKg/G75akXl1/izTOw== source-map-loader@4.0.1: version "4.0.1" @@ -9897,19 +10521,19 @@ source-map@^0.5.6: integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + version "2.5.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== -spdx-expression-parse@^3.0.0, spdx-expression-parse@^3.0.1: +spdx-expression-parse@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== @@ -9917,10 +10541,18 @@ spdx-expression-parse@^3.0.0, spdx-expression-parse@^3.0.1: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" +spdx-expression-parse@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz#a23af9f3132115465dac215c099303e4ceac5794" + integrity sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + spdx-license-ids@^3.0.0: - version "3.0.12" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz#69077835abe2710b65f03969898b6637b505a779" - integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== + version "3.0.17" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c" + integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== spdy-transport@^3.0.0: version "3.0.0" @@ -9946,24 +10578,29 @@ spdy@^4.0.2: spdy-transport "^3.0.0" splaytree@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/splaytree/-/splaytree-3.1.1.tgz#e1bc8e68e64ef5a9d5f09d36e6d9f3621795a438" - integrity sha512-9FaQ18FF0+sZc/ieEeXHt+Jw2eSpUgUtTLDYB/HXKWvhYVyOc7h1hzkn5MMO3GPib9MmXG1go8+OsBBzs/NMww== + version "3.1.2" + resolved "https://registry.yarnpkg.com/splaytree/-/splaytree-3.1.2.tgz#d1db2691665a3c69d630de98d55145a6546dc166" + integrity sha512-4OM2BJgC5UzrhVnnJA4BkHKGtjXNzzUfpQjCO8I05xYPsfS/VuQDwjCGGMi8rYQilHEV4j8NBqTFbls/PZEE7A== split.js@^1.6.5: version "1.6.5" resolved "https://registry.yarnpkg.com/split.js/-/split.js-1.6.5.tgz#f7f61da1044c9984cb42947df4de4fadb5a3f300" integrity sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw== +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== sshpk@^1.7.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" - integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== + version "1.18.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" + integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -9976,11 +10613,11 @@ sshpk@^1.7.0: tweetnacl "~0.14.0" ssri@^10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.1.tgz#c61f85894bbc6929fc3746f05e31cf5b44c030d5" - integrity sha512-WVy6di9DlPOeBWEjMScpNipeSX2jIZBGEn5Uuo8Q7aIuFEuDX0pw8RxcOjlD1TWP4obi24ki7m/13+nFpcbXrw== + version "10.0.5" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.5.tgz#e49efcd6e36385196cb515d3a2ad6c3f0265ef8c" + integrity sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A== dependencies: - minipass "^4.0.0" + minipass "^7.0.3" ssri@^9.0.0: version "9.0.1" @@ -10015,7 +10652,7 @@ streamroller@^3.1.5: debug "^4.3.4" fs-extra "^8.1.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10024,23 +10661,42 @@ streamroller@^3.1.5: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.trimend@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" -string.prototype.trimstart@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== +string.prototype.trim@^1.2.8, string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" + +string.prototype.trimend@^1.0.7, string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" string_decoder@^1.1.1: version "1.3.0" @@ -10056,6 +10712,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -10063,12 +10726,12 @@ strip-ansi@^3.0.0: dependencies: ansi-regex "^2.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== dependencies: - ansi-regex "^5.0.1" + ansi-regex "^6.0.1" strip-bom@^3.0.0: version "3.0.0" @@ -10080,15 +10743,15 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-json-comments@3.1.1, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== stylis@^4.1.2: - version "4.1.3" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7" - integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA== + version "4.3.1" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.1.tgz#ed8a9ebf9f76fe1e12d462f5cc3c4c980b23a7eb" + integrity sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ== supports-color@^2.0.0: version "2.0.0" @@ -10147,27 +10810,27 @@ tapable@^2.1.1, tapable@^2.2.0: integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== tar@^6.1.11, tar@^6.1.2: - version "6.1.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" - integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== + version "6.2.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" + integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" - minipass "^4.0.0" + minipass "^5.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" -terser-webpack-plugin@^5.1.3: - version "5.3.6" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz#5590aec31aa3c6f771ce1b1acca60639eab3195c" - integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ== +terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.10: + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== dependencies: - "@jridgewell/trace-mapping" "^0.3.14" + "@jridgewell/trace-mapping" "^0.3.20" jest-worker "^27.4.5" schema-utils "^3.1.1" - serialize-javascript "^6.0.0" - terser "^5.14.1" + serialize-javascript "^6.0.1" + terser "^5.26.0" terser@5.16.3: version "5.16.3" @@ -10179,13 +10842,13 @@ terser@5.16.3: commander "^2.20.0" source-map-support "~0.5.20" -terser@^5.14.1: - version "5.16.5" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.5.tgz#1c285ca0655f467f92af1bbab46ab72d1cb08e5a" - integrity sha512-qcwfg4+RZa3YvlFh0qjifnzBHjKGNbtDo9yivMqMFDy9Q6FSaQWSB/j1xKhsoUFJIqDOM3TsN6D5xbrMrFcHbg== +terser@^5.26.0: + version "5.29.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.29.2.tgz#c17d573ce1da1b30f21a877bffd5655dd86fdb35" + integrity sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw== dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" commander "^2.20.0" source-map-support "~0.5.20" @@ -10236,14 +10899,14 @@ tinycolor2@^1.6.0: integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== "tinymce@^6.0.0 || ^5.5.0": - version "6.3.1" - resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-6.3.1.tgz#cc5cf2b9f702d429cf5d1d21ed62245d768bfc4f" - integrity sha512-+oCwXuTxAdJXVJ0130OxQz0JDNsqg3deuzgeUo8X5Vb27EzCJgXwO5eWvCxvkxpQo4oiHMVlM4tUIpTUHufHGQ== + version "6.8.3" + resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-6.8.3.tgz#0025a4aaa4c24dc2a3e32e83dfda705d196fd802" + integrity sha512-3fCHKAeqT+xNwBVESf6iDbDV0VNwZNmfrkx9c/6Gz5iB8piMfaO6s7FvoiTrj1hf1gVbfyLTnz1DooI6DhgINQ== tinymce@~5.10.7: - version "5.10.7" - resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-5.10.7.tgz#d89d446f1962f2a1df6b2b70018ce475ec7ffb80" - integrity sha512-9UUjaO0R7FxcFo0oxnd1lMs7H+D0Eh+dDVo5hKbVe1a+VB0nit97vOqlinj+YwgoBDt6/DSCUoWqAYlLI8BLYA== + version "5.10.9" + resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-5.10.9.tgz#1dfacb3231c71a688d90ff44a0b3f2e91b3b9edf" + integrity sha512-5bkrors87X9LhYX2xq8GgPHrIgJYHl87YNs+kBcjQ5I3CiUgzo/vFcGvT3MZQ9QHsEeYMhYO6a5CLGGffR8hMg== tmp@0.0.30: version "0.0.30" @@ -10252,7 +10915,7 @@ tmp@0.0.30: dependencies: os-tmpdir "~1.0.1" -tmp@0.2.1, tmp@^0.2.1: +tmp@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== @@ -10266,6 +10929,11 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@^0.2.1: + version "0.2.3" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" + integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -10307,9 +10975,9 @@ ts-dedent@^2.2.0: integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== ts-node@^10.0.0, ts-node@^10.9.1: - version "10.9.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" - integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== dependencies: "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" @@ -10330,10 +10998,10 @@ ts-transformer-keys@^0.4.4: resolved "https://registry.yarnpkg.com/ts-transformer-keys/-/ts-transformer-keys-0.4.4.tgz#c185508b3ae9b79236aac58f788c85ca3ac807d7" integrity sha512-LrqgvaFvar01/5mbunRyeLTSIkqoC2xfcpL/90aDY6vR07DGyH+UaYGdIEsUudnlAw2Sr0pxFgdZvE0QIyI4qA== -tsconfig-paths@^3.14.1: - version "3.14.2" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" - integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== dependencies: "@types/json5" "^0.0.29" json5 "^1.0.2" @@ -10341,9 +11009,9 @@ tsconfig-paths@^3.14.1: strip-bom "^3.0.0" tsconfig-paths@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.1.2.tgz#4819f861eef82e6da52fb4af1e8c930a39ed979a" - integrity sha512-uhxiMgnXQp1IR622dUXI+9Ehnws7i/y6xvpZB9IbUVOPy0muvdvgXeZOn88UcGPiT98Vp3rJPTa8bFoalZ3Qhw== + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== dependencies: json5 "^2.2.2" minimist "^1.2.6" @@ -10354,7 +11022,7 @@ tslib@2.3.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== -tslib@2.5.0, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.5.0: +tslib@2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== @@ -10364,6 +11032,11 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -10371,13 +11044,14 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" -tuf-js@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/tuf-js/-/tuf-js-1.1.0.tgz#25dd680bce9e3819e1fe4640d85186f862efb827" - integrity sha512-Tsqlm419OAlrkCE6rsf1WuPvww44vfK1ZHz+Uq9Mpq5JiV5qnJ9LLItvsbM9OipIIeSG3rydVBS4BmD40ts2uA== +tuf-js@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/tuf-js/-/tuf-js-1.1.7.tgz#21b7ae92a9373015be77dfe0cb282a80ec3bbe43" + integrity sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg== dependencies: - "@tufjs/models" "1.0.0" - make-fetch-happen "^11.0.1" + "@tufjs/models" "1.0.4" + debug "^4.3.4" + make-fetch-happen "^11.1.1" tunnel-agent@^0.6.0: version "0.6.0" @@ -10421,14 +11095,49 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typed-array-length@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" - integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.5.tgz#57d44da160296d8663fd63180a1802ebf25905d5" + integrity sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA== + dependencies: + call-bind "^1.0.7" for-each "^0.3.3" - is-typed-array "^1.1.9" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" typed-assert@^1.0.8: version "1.0.9" @@ -10456,9 +11165,9 @@ typical@^5.2.0: integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== ua-parser-js@^0.7.30: - version "0.7.33" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532" - integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw== + version "0.7.37" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.37.tgz#e464e66dac2d33a7a1251d7d7a99d6157ec27832" + integrity sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA== unbox-primitive@^1.0.2: version "1.0.2" @@ -10470,6 +11179,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -10527,19 +11241,19 @@ universalify@^0.1.0: integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== +update-browserslist-db@^1.0.10, update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== dependencies: escalade "^3.1.1" picocolors "^1.0.0" @@ -10579,9 +11293,9 @@ uuid@^8.3.2: integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== uuid@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" - integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== v8-compile-cache-lib@^3.0.1: version "3.0.1" @@ -10623,9 +11337,9 @@ void-elements@^2.0.0: integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung== watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + version "2.4.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff" + integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -10644,6 +11358,11 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-worker@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.3.0.tgz#e5f2df5c7fe356755a5fb8f8410d4312627e6776" + integrity sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA== + webdriver-js-extender@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz#57d7a93c00db4cc8d556e4d3db4b5db0a80c3bb7" @@ -10726,7 +11445,7 @@ webpack-dev-server@4.11.1: webpack-dev-middleware "^5.3.1" ws "^8.4.2" -webpack-merge@5.8.0, webpack-merge@^5.7.3: +webpack-merge@5.8.0: version "5.8.0" resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== @@ -10734,6 +11453,15 @@ webpack-merge@5.8.0, webpack-merge@^5.7.3: clone-deep "^4.0.1" wildcard "^2.0.0" +webpack-merge@^5.7.3: + version "5.10.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.0" + webpack-sources@^3.0.0, webpack-sources@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" @@ -10746,36 +11474,6 @@ webpack-subresource-integrity@5.1.0: dependencies: typed-assert "^1.0.8" -webpack@5.75.0: - version "5.75.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152" - integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.7.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.10.0" - es-module-lexer "^0.9.0" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.1.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - watchpack "^2.4.0" - webpack-sources "^3.2.3" - webpack@5.76.1: version "5.76.1" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.1.tgz#7773de017e988bccb0f13c7d75ec245f377d295c" @@ -10807,21 +11505,21 @@ webpack@5.76.1: webpack-sources "^3.2.3" webpack@^5.77.0: - version "5.77.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.77.0.tgz#dea3ad16d7ea6b84aa55fa42f4eac9f30e7eb9b4" - integrity sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q== + version "5.90.3" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.90.3.tgz#37b8f74d3ded061ba789bb22b31e82eed75bd9ac" + integrity sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA== dependencies: "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.11.5" + "@webassemblyjs/wasm-edit" "^1.11.5" + "@webassemblyjs/wasm-parser" "^1.11.5" acorn "^8.7.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" + acorn-import-assertions "^1.9.0" + browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.10.0" - es-module-lexer "^0.9.0" + enhanced-resolve "^5.15.0" + es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" @@ -10830,9 +11528,9 @@ webpack@^5.77.0: loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.1.0" + schema-utils "^3.2.0" tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" + terser-webpack-plugin "^5.3.10" watchpack "^2.4.0" webpack-sources "^3.2.3" @@ -10862,31 +11560,30 @@ which-boxed-primitive@^1.0.2: is-symbol "^1.0.3" which-collection@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which-typed-array@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" - integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== +which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" for-each "^0.3.3" gopd "^1.0.1" - has-tostringtag "^1.0.0" - is-typed-array "^1.1.10" + has-tostringtag "^1.0.2" which@^1.2.1, which@^1.2.9: version "1.3.1" @@ -10903,9 +11600,9 @@ which@^2.0.1, which@^2.0.2: isexe "^2.0.0" which@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/which/-/which-3.0.0.tgz#a9efd016db59728758a390d23f1687b6e8f59f8e" - integrity sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ== + version "3.0.1" + resolved "https://registry.yarnpkg.com/which/-/which-3.0.1.tgz#89f1cd0c23f629a8105ffe69b8172791c87b4be1" + integrity sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg== dependencies: isexe "^2.0.0" @@ -10917,14 +11614,9 @@ wide-align@^1.1.5: string-width "^1.0.2 || 2 || 3 || 4" wildcard@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" - integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== - -word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== wordwrapjs@^4.0.0: version "4.0.1" @@ -10934,6 +11626,15 @@ wordwrapjs@^4.0.0: reduce-flatten "^2.0.0" typical "^5.2.0" +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -10943,14 +11644,14 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" wrappy@1: version "1.0.2" @@ -10958,9 +11659,9 @@ wrappy@1: integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== ws@^8.4.2: - version "8.12.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.1.tgz#c51e583d79140b5e42e39be48c934131942d4a8f" - integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew== + version "8.16.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" + integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== ws@~8.11.0: version "8.11.0" @@ -10990,11 +11691,6 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== - yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" @@ -11072,9 +11768,9 @@ yargs@^16.1.1: yargs-parser "^20.2.2" yargs@^17.2.1: - version "17.7.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967" - integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw== + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" escalade "^3.1.1" @@ -11095,9 +11791,9 @@ yocto-queue@^0.1.0: integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== zone.js@~0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.13.0.tgz#4c735cb8ef49312b58c0ad13451996dc2b202a6d" - integrity sha512-7m3hNNyswsdoDobCkYNAy5WiUulkMd3+fWaGT9ij6iq3Zr/IwJo4RMCYPSDjT+r7tnPErmY9sZpKhWQ8S5k6XQ== + version "0.13.3" + resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.13.3.tgz#344c24098fa047eda6427a4c7ed486e391fd67b5" + integrity sha512-MKPbmZie6fASC/ps4dkmIhaT5eonHkEt6eAy80K42tAm0G2W+AahLJjbfi6X9NPdciOE9GRFTTM8u2IiF6O3ww== dependencies: tslib "^2.3.0" From f4733a44b41ef5442c60bf936bcaa1037a393fd3 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 18 Mar 2024 12:47:32 +0200 Subject: [PATCH 204/209] Fix existingNotificationTypes for Edge --- .../dao/notification/DefaultNotificationSettingsService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java index 1bdc835b29..b719de0192 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java @@ -222,7 +222,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS } else { var requiredNotificationTypes = List.of(NotificationType.EDGE_CONNECTION, NotificationType.EDGE_COMMUNICATION_FAILURE); var existingNotificationTypes = notificationTemplateService.findNotificationTemplatesByTenantIdAndNotificationTypes( - tenantId, requiredNotificationTypes, new PageLink(1)) + tenantId, requiredNotificationTypes, new PageLink(2)) .getData() .stream() .map(NotificationTemplate::getNotificationType) From 7ead550348f92ee3c550b62aaaa7d39a2539f6c3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 18 Mar 2024 13:28:11 +0200 Subject: [PATCH 205/209] UI: Update minor versions. --- ui-ngx/package.json | 33 +++---- ui-ngx/yarn.lock | 216 ++++++++++---------------------------------- 2 files changed, 63 insertions(+), 186 deletions(-) diff --git a/ui-ngx/package.json b/ui-ngx/package.json index c39817749f..0ba5880ed3 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -14,17 +14,17 @@ }, "private": true, "dependencies": { - "@angular/animations": "^15.2.9", + "@angular/animations": "^15.2.10", "@angular/cdk": "^15.2.9", - "@angular/common": "^15.2.9", - "@angular/compiler": "^15.2.9", - "@angular/core": "^15.2.9", + "@angular/common": "^15.2.10", + "@angular/compiler": "^15.2.10", + "@angular/core": "^15.2.10", "@angular/flex-layout": "^15.0.0-beta.42", - "@angular/forms": "^15.2.9", + "@angular/forms": "^15.2.10", "@angular/material": "^15.2.9", - "@angular/platform-browser": "^15.2.9", - "@angular/platform-browser-dynamic": "^15.2.9", - "@angular/router": "^15.2.9", + "@angular/platform-browser": "^15.2.10", + "@angular/platform-browser-dynamic": "^15.2.10", + "@angular/router": "^15.2.10", "@auth0/angular-jwt": "^5.1.2", "@date-io/core": "1.3.7", "@date-io/date-fns": "1.3.7", @@ -112,16 +112,16 @@ }, "devDependencies": { "@angular-builders/custom-webpack": "~15.0.0", - "@angular-devkit/build-angular": "^15.2.8", + "@angular-devkit/build-angular": "^15.2.10", "@angular-eslint/builder": "15.2.1", "@angular-eslint/eslint-plugin": "15.2.1", "@angular-eslint/eslint-plugin-template": "15.2.1", "@angular-eslint/schematics": "15.2.1", "@angular-eslint/template-parser": "15.2.1", - "@angular/cli": "^15.2.8", - "@angular/compiler-cli": "^15.2.9", - "@angular/language-service": "^15.2.9", - "@ngtools/webpack": "15.2.1", + "@angular/cli": "^15.2.10", + "@angular/compiler-cli": "^15.2.10", + "@angular/language-service": "^15.2.10", + "@ngtools/webpack": "15.2.10", "@types/ace-diff": "^2.1.1", "@types/canvas-gauges": "^2.1.4", "@types/flot": "^0.0.32", @@ -130,7 +130,7 @@ "@types/jasminewd2": "^2.0.10", "@types/jquery": "^3.5.16", "@types/js-beautify": "^1.13.3", - "@types/leaflet": "^1.8.0", + "@types/leaflet": "~1.8.0", "@types/leaflet-polylinedecorator": "^1.6.1", "@types/leaflet-providers": "^1.2.1", "@types/leaflet.gridlayer.googlemutant": "^0.4.6", @@ -166,11 +166,12 @@ "raw-loader": "^4.0.2", "ts-node": "^10.9.1", "typescript": "~4.9.5", - "webpack": "^5.77.0" + "webpack": "5.77.0" }, "resolutions": { "@types/react": "17.0.37", "ace-builds": "1.4.13", - "@date-io/core": "1.3.7" + "@date-io/core": "1.3.7", + "rc-virtual-list": "3.4.13" } } diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 892b959e06..ef25146805 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -44,7 +44,7 @@ "@angular-devkit/core" "15.2.10" rxjs "6.6.7" -"@angular-devkit/build-angular@^15.0.0", "@angular-devkit/build-angular@^15.2.8": +"@angular-devkit/build-angular@^15.0.0", "@angular-devkit/build-angular@^15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-15.2.10.tgz#af4080a4811461bd1cab4f3b1b10edef53f31da8" integrity sha512-3pCPVEJilVwHIJC6Su1/PIEqvFfU1Lxew9yItxX4s6dud8HY+fuKrsDnao4NNMFNqCLqL4el5QbSBKnnpWH1sg== @@ -199,7 +199,7 @@ "@angular-eslint/bundled-angular-compiler" "15.2.1" "@typescript-eslint/utils" "5.48.2" -"@angular/animations@^15.2.9": +"@angular/animations@^15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-15.2.10.tgz#c9194ba9a2b9b4e466e9c76e18591cde096a28e8" integrity sha512-yxfN8qQpMaukRU5LjFkJBmy85rqrOp86tYVCsf+hmPEFRiXBMUj6xYLeCMcpk3Mt1JtnWGBR34ivGx+7bNeAow== @@ -215,7 +215,7 @@ optionalDependencies: parse5 "^7.1.2" -"@angular/cli@^15.2.8": +"@angular/cli@^15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-15.2.10.tgz#4035a64510e11894be2ff695e48ee0ef6badb494" integrity sha512-/TSnm/ZQML6A4lvunyN2tjTB5utuvk3d1Pnfyehp/FXtV6YfZm6+EZrOpKkKPCxTuAgW6c9KK4yQtt3RuNVpwQ== @@ -239,14 +239,14 @@ symbol-observable "4.0.0" yargs "17.6.2" -"@angular/common@^15.2.9": +"@angular/common@^15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@angular/common/-/common-15.2.10.tgz#897923023c8ca4a361ce218bdee9a3f09060df75" integrity sha512-jdBn3fctkqoNrJn9VLsUHpcCEhCxWSczdsR+BBbD6T0oLl6vMrAVNjPwfBejnlgfWN1KoRU9kgOYsMxa5apIWQ== dependencies: tslib "^2.3.0" -"@angular/compiler-cli@^15.2.9": +"@angular/compiler-cli@^15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-15.2.10.tgz#e51013aa0f3da303fc74f8e1948c550d8e74ead5" integrity sha512-mCFIxrs60XicKfA2o42hA7LrQvhybi9BQveWuZn/2iIEOXx7R62Iemz8E21pLWftAZHGxEW3NECfBrY1d3gVmA== @@ -262,14 +262,14 @@ tslib "^2.3.0" yargs "^17.2.1" -"@angular/compiler@^15.2.9": +"@angular/compiler@^15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-15.2.10.tgz#bd78f327d12eb5978f9dd05440aa23d4b5b925a9" integrity sha512-M0XkeU0O73UlJZwDvOyp8/apetz9UKj78eTFDseMYJDLcxe6MpkbkxqpsGZnKYDj7LIep8PmCAKEkhtenE82zw== dependencies: tslib "^2.3.0" -"@angular/core@^15.2.9": +"@angular/core@^15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@angular/core/-/core-15.2.10.tgz#93c1e0d460d21711654c578d2709a402e1822023" integrity sha512-meGGidnitQJGDxYd9/LrqYiVlId+vGaLoiLgJdKBz+o2ZO6OmXQGuNw2VBqf17/Cc0/UjzrOY7+kILNFKkk/WQ== @@ -283,14 +283,14 @@ dependencies: tslib "^2.3.0" -"@angular/forms@^15.2.9": +"@angular/forms@^15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-15.2.10.tgz#09308e887df2fa4d349300c9d1f05cadfb3872b3" integrity sha512-NIntGsNcN6o8L1txsbWXOf6f3K/CUBizdKsxsYVYGJIXEW5qU6UnWmfAZffNNXsT/XvbgUCjgDwT0cAwcqZPuQ== dependencies: tslib "^2.3.0" -"@angular/language-service@^15.2.9": +"@angular/language-service@^15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-15.2.10.tgz#829a802aaf40bfab21d71463023a3b517500ffa9" integrity sha512-G0g0teF4pBqLTgfyLcoBl55g91sCZvBK+V4VgTD/hXGpXyMNlNpOsgECSMliGQoJlsRLEugFsSlBNqy7CRoBtw== @@ -349,21 +349,21 @@ "@material/typography" "15.0.0-canary.684e33d25.0" tslib "^2.3.0" -"@angular/platform-browser-dynamic@^15.2.9": +"@angular/platform-browser-dynamic@^15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-15.2.10.tgz#cc9ad3dcded6cb945ee8c4eef14db081dc6c3dfd" integrity sha512-JHP6W+FX715Qv7DhqvfZLuBZXSDJrboiQsR06gUAgDSjAUyhbqmpVg/2YOtgeWpPkzNDtXdPU2PhcRdIv5J3Yg== dependencies: tslib "^2.3.0" -"@angular/platform-browser@^15.2.9": +"@angular/platform-browser@^15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-15.2.10.tgz#ca5a904b4da9e0cf719414db89514ee4221cb93d" integrity sha512-9tbgVGSJqwfrOzT8aA/kWBLNhJSQ9gUg0CJxwFBSJm8VkBUJrszoBlDsnSvlxx8/W2ejNULKHFTXeUzq0O/+RQ== dependencies: tslib "^2.3.0" -"@angular/router@^15.2.9": +"@angular/router@^15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@angular/router/-/router-15.2.10.tgz#a5d32d769b930e905582ed6c7aa8122e63655738" integrity sha512-LmuqEg0iIXSw7bli6HKJ19cbxP91v37GtRwbGKswyLihqzTgvjBYpvcfMnB5FRQ5LWkTwq5JclkX03dZw290Yg== @@ -2577,11 +2577,6 @@ dependencies: tslib "^2.0.0" -"@ngtools/webpack@15.2.1": - version "15.2.1" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-15.2.1.tgz#439ac075b2dcb9f304f0b7009182cc5049c7988a" - integrity sha512-YtA8rWAglPuf4CSStrFAxaprTSYE+DREGrJFc3WvZLcF5XrwVK+H4CC4Pmz07iYsG1TXShR4bWp1fbGw1cmBKw== - "@ngtools/webpack@15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-15.2.10.tgz#8118450206ae9398d81ca2eebe1b369321ac5583" @@ -3019,7 +3014,7 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^1.0.5": +"@types/estree@*": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== @@ -3155,13 +3150,20 @@ dependencies: "@types/leaflet" "*" -"@types/leaflet@*", "@types/leaflet@^1.8.0": +"@types/leaflet@*": version "1.9.8" resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.9.8.tgz#32162a8eaf305c63267e99470b9603b5883e63e8" integrity sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg== dependencies: "@types/geojson" "*" +"@types/leaflet@~1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.8.0.tgz#dc92d3e868fb6d5067b4b59fa08cd4441f84fabe" + integrity sha512-+sXFmiJTFdhaXXIGFlV5re9AdqtAODoXbGAvxx02e5SHXL3ir7ClP5J7pahO8VmzKY3dth4RUS1nf2BTT+DW1A== + dependencies: + "@types/geojson" "*" + "@types/lodash@^4.14.192": version "4.17.0" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.0.tgz#d774355e41f372d5350a4d0714abb48194a489c3" @@ -3501,44 +3503,21 @@ "@webassemblyjs/helper-numbers" "1.11.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.1" -"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.11.5": - version "1.12.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" - integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/floating-point-hex-parser@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== -"@webassemblyjs/floating-point-hex-parser@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" - integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== - "@webassemblyjs/helper-api-error@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== -"@webassemblyjs/helper-api-error@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" - integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== - "@webassemblyjs/helper-buffer@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== -"@webassemblyjs/helper-buffer@1.12.1": - version "1.12.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" - integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== - "@webassemblyjs/helper-numbers@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" @@ -3548,25 +3527,11 @@ "@webassemblyjs/helper-api-error" "1.11.1" "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-numbers@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" - integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.6" - "@webassemblyjs/helper-api-error" "1.11.6" - "@xtuc/long" "4.2.2" - "@webassemblyjs/helper-wasm-bytecode@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== -"@webassemblyjs/helper-wasm-bytecode@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" - integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== - "@webassemblyjs/helper-wasm-section@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" @@ -3577,16 +3542,6 @@ "@webassemblyjs/helper-wasm-bytecode" "1.11.1" "@webassemblyjs/wasm-gen" "1.11.1" -"@webassemblyjs/helper-wasm-section@1.12.1": - version "1.12.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" - integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== - dependencies: - "@webassemblyjs/ast" "1.12.1" - "@webassemblyjs/helper-buffer" "1.12.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/wasm-gen" "1.12.1" - "@webassemblyjs/ieee754@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" @@ -3594,13 +3549,6 @@ dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/ieee754@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" - integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== - dependencies: - "@xtuc/ieee754" "^1.2.0" - "@webassemblyjs/leb128@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" @@ -3608,23 +3556,11 @@ dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/leb128@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" - integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== - dependencies: - "@xtuc/long" "4.2.2" - "@webassemblyjs/utf8@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== -"@webassemblyjs/utf8@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" - integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== - "@webassemblyjs/wasm-edit@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" @@ -3639,20 +3575,6 @@ "@webassemblyjs/wasm-parser" "1.11.1" "@webassemblyjs/wast-printer" "1.11.1" -"@webassemblyjs/wasm-edit@^1.11.5": - version "1.12.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" - integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== - dependencies: - "@webassemblyjs/ast" "1.12.1" - "@webassemblyjs/helper-buffer" "1.12.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/helper-wasm-section" "1.12.1" - "@webassemblyjs/wasm-gen" "1.12.1" - "@webassemblyjs/wasm-opt" "1.12.1" - "@webassemblyjs/wasm-parser" "1.12.1" - "@webassemblyjs/wast-printer" "1.12.1" - "@webassemblyjs/wasm-gen@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" @@ -3664,17 +3586,6 @@ "@webassemblyjs/leb128" "1.11.1" "@webassemblyjs/utf8" "1.11.1" -"@webassemblyjs/wasm-gen@1.12.1": - version "1.12.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" - integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== - dependencies: - "@webassemblyjs/ast" "1.12.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/ieee754" "1.11.6" - "@webassemblyjs/leb128" "1.11.6" - "@webassemblyjs/utf8" "1.11.6" - "@webassemblyjs/wasm-opt@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" @@ -3685,16 +3596,6 @@ "@webassemblyjs/wasm-gen" "1.11.1" "@webassemblyjs/wasm-parser" "1.11.1" -"@webassemblyjs/wasm-opt@1.12.1": - version "1.12.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" - integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== - dependencies: - "@webassemblyjs/ast" "1.12.1" - "@webassemblyjs/helper-buffer" "1.12.1" - "@webassemblyjs/wasm-gen" "1.12.1" - "@webassemblyjs/wasm-parser" "1.12.1" - "@webassemblyjs/wasm-parser@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" @@ -3707,18 +3608,6 @@ "@webassemblyjs/leb128" "1.11.1" "@webassemblyjs/utf8" "1.11.1" -"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.12.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" - integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== - dependencies: - "@webassemblyjs/ast" "1.12.1" - "@webassemblyjs/helper-api-error" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/ieee754" "1.11.6" - "@webassemblyjs/leb128" "1.11.6" - "@webassemblyjs/utf8" "1.11.6" - "@webassemblyjs/wast-printer@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" @@ -3727,14 +3616,6 @@ "@webassemblyjs/ast" "1.11.1" "@xtuc/long" "4.2.2" -"@webassemblyjs/wast-printer@1.12.1": - version "1.12.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" - integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== - dependencies: - "@webassemblyjs/ast" "1.12.1" - "@xtuc/long" "4.2.2" - "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -3785,7 +3666,7 @@ ace-diff@^3.0.3: dependencies: diff-match-patch "^1.0.5" -acorn-import-assertions@^1.7.6, acorn-import-assertions@^1.9.0: +acorn-import-assertions@^1.7.6: version "1.9.0" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== @@ -4359,7 +4240,7 @@ browserslist@4.21.5: node-releases "^2.0.8" update-browserslist-db "^1.0.10" -browserslist@^4.14.5, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.22.2, browserslist@^4.22.3: +browserslist@^4.14.5, browserslist@^4.21.4, browserslist@^4.22.2, browserslist@^4.22.3: version "4.23.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== @@ -5757,7 +5638,7 @@ engine.io@~6.5.2: engine.io-parser "~5.2.1" ws "~8.11.0" -enhanced-resolve@^5.10.0, enhanced-resolve@^5.15.0: +enhanced-resolve@^5.10.0: version "5.16.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787" integrity sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA== @@ -5940,11 +5821,6 @@ es-module-lexer@^0.9.0: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== -es-module-lexer@^1.2.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5" - integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w== - es-object-atoms@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" @@ -9697,7 +9573,7 @@ rc-trigger@^5.0.4: rc-motion "^2.0.0" rc-util "^5.19.2" -rc-util@^5.19.2, rc-util@^5.21.0, rc-util@^5.26.0, rc-util@^5.36.0, rc-util@^5.37.0, rc-util@^5.38.0, rc-util@^5.9.8: +rc-util@^5.15.0, rc-util@^5.19.2, rc-util@^5.21.0, rc-util@^5.26.0, rc-util@^5.37.0, rc-util@^5.38.0, rc-util@^5.9.8: version "5.39.1" resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.39.1.tgz#7bca4fb55e20add0eef5c23166cd9f9e5f51a8a1" integrity sha512-OW/ERynNDgNr4y0oiFmtes3rbEamXw7GHGbkbNd9iRr7kgT03T6fT0b9WpJ3mbxKhyOcAHnGcIoh5u/cjrC2OQ== @@ -9705,15 +9581,15 @@ rc-util@^5.19.2, rc-util@^5.21.0, rc-util@^5.26.0, rc-util@^5.36.0, rc-util@^5.3 "@babel/runtime" "^7.18.3" react-is "^18.2.0" -rc-virtual-list@^3.2.0: - version "3.11.4" - resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.11.4.tgz#d0a8937843160b7b00d5586854290bf56d396af7" - integrity sha512-NbBi0fvyIu26gP69nQBiWgUMTPX3mr4FcuBQiVqagU0BnuX8WQkiivnMs105JROeuUIFczLrlgUhLQwTWV1XDA== +rc-virtual-list@3.4.13, rc-virtual-list@^3.2.0: + version "3.4.13" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.4.13.tgz#20acc934b263abcf7b7c161f50ef82281b2f7e8d" + integrity sha512-cPOVDmcNM7rH6ANotanMDilW/55XnFPw0Jh/GQYtrzZSy3AmWvCnqVNyNC/pgg3lfVmX2994dlzAhuUrd4jG7w== dependencies: "@babel/runtime" "^7.20.0" classnames "^2.2.6" rc-resize-observer "^1.0.0" - rc-util "^5.36.0" + rc-util "^5.15.0" react-ace@9.5.0: version "9.5.0" @@ -10167,7 +10043,7 @@ schema-inspector@^2.0.2: dependencies: async "~2.6.3" -schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1, schema-utils@^3.2.0: +schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: version "3.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== @@ -10821,7 +10697,7 @@ tar@^6.1.11, tar@^6.1.2: mkdirp "^1.0.3" yallist "^4.0.0" -terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.10: +terser-webpack-plugin@^5.1.3: version "5.3.10" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== @@ -11504,22 +11380,22 @@ webpack@5.76.1: watchpack "^2.4.0" webpack-sources "^3.2.3" -webpack@^5.77.0: - version "5.90.3" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.90.3.tgz#37b8f74d3ded061ba789bb22b31e82eed75bd9ac" - integrity sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA== +webpack@5.77.0: + version "5.77.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.77.0.tgz#dea3ad16d7ea6b84aa55fa42f4eac9f30e7eb9b4" + integrity sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q== dependencies: "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.5" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" acorn "^8.7.1" - acorn-import-assertions "^1.9.0" - browserslist "^4.21.10" + acorn-import-assertions "^1.7.6" + browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.15.0" - es-module-lexer "^1.2.1" + enhanced-resolve "^5.10.0" + es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" @@ -11528,9 +11404,9 @@ webpack@^5.77.0: loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.2.0" + schema-utils "^3.1.0" tapable "^2.1.1" - terser-webpack-plugin "^5.3.10" + terser-webpack-plugin "^5.1.3" watchpack "^2.4.0" webpack-sources "^3.2.3" From 2a2d09be7d5d3d1c46d0e244ae1ce333ad1f08e3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 18 Mar 2024 15:05:51 +0200 Subject: [PATCH 206/209] UI: Fix build --- ui-ngx/package.json | 6 +- ui-ngx/yarn.lock | 330 +++++++++----------------------------------- 2 files changed, 68 insertions(+), 268 deletions(-) diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 0ba5880ed3..a74a1f2953 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -62,7 +62,7 @@ "html2canvas": "^1.4.1", "jquery": "^3.6.3", "jquery.terminal": "^2.35.3", - "js-beautify": "^1.14.7", + "js-beautify": "1.14.7", "json-schema-defaults": "^0.4.0", "jstree": "^3.3.15", "jstree-bootstrap-theme": "^1.0.1", @@ -172,6 +172,8 @@ "@types/react": "17.0.37", "ace-builds": "1.4.13", "@date-io/core": "1.3.7", - "rc-virtual-list": "3.4.13" + "rc-virtual-list": "3.4.13", + "read-package-json": "6.0.0", + "cacache": "17.0.4" } } diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index ef25146805..ca5c179ad5 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -1582,11 +1582,6 @@ dependencies: tslib "^2.2.0" -"@gar/promisify@^1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" - integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== - "@geoman-io/leaflet-geoman-free@^2.13.0": version "2.16.0" resolved "https://registry.yarnpkg.com/@geoman-io/leaflet-geoman-free/-/leaflet-geoman-free-2.16.0.tgz#c8a92fcc2cdd5770ebf43f8ef9f03cc8ef842359" @@ -1625,18 +1620,6 @@ dependencies: tslib "^2.3.0" -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -2617,14 +2600,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@npmcli/fs@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" - integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== - dependencies: - "@gar/promisify" "^1.1.3" - semver "^7.3.5" - "@npmcli/fs@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.0.tgz#233d43a25a91d68c3a863ba0da6a3f00924a173e" @@ -2654,14 +2629,6 @@ npm-bundled "^3.0.0" npm-normalize-package-bin "^3.0.0" -"@npmcli/move-file@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" - integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - "@npmcli/node-gyp@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz#101b2d0490ef1aa20ed460e4c0813f0db560545a" @@ -2685,16 +2652,6 @@ read-package-json-fast "^3.0.0" which "^3.0.0" -"@one-ini/wasm@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@one-ini/wasm/-/wasm-0.1.1.tgz#6013659736c9dbfccc96e8a9c2b3de317df39323" - integrity sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw== - -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - "@schematics/angular@15.2.10": version "15.2.10" resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-15.2.10.tgz#fa6c05f37ba82422abd6b3f13a2fc78ec7a4eb3d" @@ -3641,11 +3598,6 @@ abbrev@^1.0.0: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -abbrev@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" - integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== - accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -3810,11 +3762,6 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -3834,11 +3781,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - ansidec@^0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/ansidec/-/ansidec-0.3.4.tgz#e12d267d6b1f122d2da5b98fe0de1f98d14ac62b" @@ -4292,7 +4234,7 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cacache@17.0.4: +cacache@17.0.4, cacache@^16.1.0, cacache@^17.0.0: version "17.0.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.0.4.tgz#5023ed892ba8843e3b7361c26d0ada37e146290c" integrity sha512-Z/nL3gU+zTUjz5pCA5vVjYM8pmaw2kxM7JEiE0fv3w77Wj+sFbi70CrBruUWH0uNcEdvLDixFpgA2JM4F4DBjA== @@ -4311,48 +4253,6 @@ cacache@17.0.4: tar "^6.1.11" unique-filename "^3.0.0" -cacache@^16.1.0: - version "16.1.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" - integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== - dependencies: - "@npmcli/fs" "^2.1.0" - "@npmcli/move-file" "^2.0.0" - chownr "^2.0.0" - fs-minipass "^2.1.0" - glob "^8.0.1" - infer-owner "^1.0.4" - lru-cache "^7.7.1" - minipass "^3.1.6" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - mkdirp "^1.0.4" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^9.0.0" - tar "^6.1.11" - unique-filename "^2.0.0" - -cacache@^17.0.0: - version "17.1.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.1.4.tgz#b3ff381580b47e85c6e64f801101508e26604b35" - integrity sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A== - dependencies: - "@npmcli/fs" "^3.1.0" - fs-minipass "^3.0.0" - glob "^10.2.2" - lru-cache "^7.7.1" - minipass "^7.0.3" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - p-map "^4.0.0" - ssri "^10.0.0" - tar "^6.1.11" - unique-filename "^3.0.0" - call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -4620,12 +4520,7 @@ commander@7: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -commander@^2.20.0: +commander@^2.19.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -4856,7 +4751,7 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -5534,11 +5429,6 @@ domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -5555,15 +5445,15 @@ echarts@^5.5.0: tslib "2.3.0" zrender "5.5.0" -editorconfig@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-1.0.4.tgz#040c9a8e9a6c5288388b87c2db07028aa89f53a3" - integrity sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q== +editorconfig@^0.15.3: + version "0.15.3" + resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" + integrity sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g== dependencies: - "@one-ini/wasm" "0.1.1" - commander "^10.0.0" - minimatch "9.0.1" - semver "^7.5.3" + commander "^2.19.0" + lru-cache "^4.1.5" + semver "^5.6.0" + sigmund "^1.0.1" ee-first@1.1.1: version "1.1.1" @@ -5585,11 +5475,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - emoji-toolkit@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/emoji-toolkit/-/emoji-toolkit-7.0.1.tgz#4ea2a78fe4b40c7cdbe7ef5725c7011299932f09" @@ -6385,14 +6270,6 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -foreground-child@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" - integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== - dependencies: - cross-spawn "^7.0.0" - signal-exit "^4.0.1" - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -6450,7 +6327,7 @@ fs-extra@^9.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-minipass@^2.0.0, fs-minipass@^2.1.0: +fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== @@ -6590,7 +6467,7 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@8.1.0, glob@^8.0.1: +glob@8.1.0, glob@^8.0.1, glob@^8.0.3: version "8.1.0" resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== @@ -6601,17 +6478,6 @@ glob@8.1.0, glob@^8.0.1: minimatch "^5.0.1" once "^1.3.0" -glob@^10.2.2, glob@^10.3.3: - version "10.3.10" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" - integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== - dependencies: - foreground-child "^3.1.0" - jackspeak "^2.3.5" - minimatch "^9.0.1" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry "^1.10.1" - glob@^7.0.3, glob@^7.0.6, glob@^7.1.3, glob@^7.1.4, glob@^7.1.7: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -7030,11 +6896,6 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -7461,15 +7322,6 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jackspeak@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" - integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - jasmine-core@^3.6.0: version "3.99.1" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.99.1.tgz#5bfa4b2d76618868bfac4c8ff08bb26fffa4120d" @@ -7536,21 +7388,15 @@ jquery@>=1.9.1, jquery@^3.5.0, jquery@^3.6.3, jquery@^3.7.1: resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== -js-beautify@^1.14.7: - version "1.15.1" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.15.1.tgz#4695afb508c324e1084ee0b952a102023fc65b64" - integrity sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA== +js-beautify@1.14.7: + version "1.14.7" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.7.tgz#9206296de33f86dc106d3e50a35b7cf8729703b2" + integrity sha512-5SOX1KXPFKx+5f6ZrPsIPEY7NwKeQz47n3jm2i+XeHx9MoRsfQenlOP13FQhWvg8JRS0+XLO6XYUQ2GX+q+T9A== dependencies: config-chain "^1.1.13" - editorconfig "^1.0.4" - glob "^10.3.3" - js-cookie "^3.0.5" - nopt "^7.2.0" - -js-cookie@^3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" - integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== + editorconfig "^0.15.3" + glob "^8.0.3" + nopt "^6.0.0" "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -8091,6 +7937,14 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lru-cache@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -8110,11 +7964,6 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== -"lru-cache@^9.1.1 || ^10.0.0": - version "10.2.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" - integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== - magic-string@0.29.0: version "0.29.0" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.29.0.tgz#f034f79f8c43dba4ae1730ffb5e8c4e084b16cf3" @@ -8310,13 +8159,6 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253" - integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w== - dependencies: - brace-expansion "^2.0.1" - minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -8331,7 +8173,7 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.0, minimatch@^9.0.1: +minimatch@^9.0.0: version "9.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== @@ -8423,7 +8265,7 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.3: +minipass@^7.0.3: version "7.0.4" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== @@ -8443,7 +8285,7 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.6" -mkdirp@^1.0.3, mkdirp@^1.0.4: +mkdirp@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -8672,13 +8514,6 @@ nopt@^6.0.0: dependencies: abbrev "^1.0.0" -nopt@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.0.tgz#067378c68116f602f552876194fd11f1292503d7" - integrity sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA== - dependencies: - abbrev "^2.0.0" - normalize-package-data@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-5.0.0.tgz#abcb8d7e724c40d88462b84982f7cbf6859b4588" @@ -9149,14 +8984,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.10.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" - integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== - dependencies: - lru-cache "^9.1.1 || ^10.0.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -9408,6 +9235,11 @@ prr@~1.0.1: resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== + psl@^1.1.28: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" @@ -9668,12 +9500,12 @@ read-package-json-fast@^3.0.0: json-parse-even-better-errors "^3.0.0" npm-normalize-package-bin "^3.0.0" -read-package-json@^6.0.0: - version "6.0.4" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-6.0.4.tgz#90318824ec456c287437ea79595f4c2854708836" - integrity sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw== +read-package-json@6.0.0, read-package-json@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-6.0.0.tgz#6a741841ad72a40e77a82b9c3c8c10e865bbc519" + integrity sha512-b/9jxWJ8EwogJPpv99ma+QwtqB7FSl3+V6UXS7Aaay8/5VwMY50oIFooY1UKXMWpfNCM6T/PoGqa5GD1g9xf9w== dependencies: - glob "^10.2.2" + glob "^8.0.1" json-parse-even-better-errors "^3.0.0" normalize-package-data "^5.0.0" npm-normalize-package-bin "^3.0.0" @@ -10251,16 +10083,16 @@ side-channel@^1.0.4: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +sigmund@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + integrity sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g== + signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - sigstore@^1.0.0: version "1.9.0" resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-1.9.0.tgz#1e7ad8933aa99b75c6898ddd0eeebc3eb0d59875" @@ -10528,7 +10360,7 @@ streamroller@^3.1.5: debug "^4.3.4" fs-extra "^8.1.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10537,15 +10369,6 @@ streamroller@^3.1.5: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - string.prototype.trim@^1.2.8, string.prototype.trim@^1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" @@ -10588,13 +10411,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -10602,12 +10418,12 @@ strip-ansi@^3.0.0: dependencies: ansi-regex "^2.0.0" -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: - ansi-regex "^6.0.1" + ansi-regex "^5.0.1" strip-bom@^3.0.0: version "3.0.0" @@ -11083,13 +10899,6 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== -unique-filename@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" - integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== - dependencies: - unique-slug "^3.0.0" - unique-filename@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea" @@ -11097,13 +10906,6 @@ unique-filename@^3.0.0: dependencies: unique-slug "^4.0.0" -unique-slug@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" - integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== - dependencies: - imurmurhash "^0.1.4" - unique-slug@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-4.0.0.tgz#6bae6bb16be91351badd24cdce741f892a6532e3" @@ -11502,15 +11304,6 @@ wordwrapjs@^4.0.0: reduce-flatten "^2.0.0" typical "^5.2.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -11520,14 +11313,14 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrappy@1: version "1.0.2" @@ -11567,6 +11360,11 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== + yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" From 2ee4d10621b25b67a5e051d8835f0d346902a271 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 19 Mar 2024 16:24:06 +0200 Subject: [PATCH 207/209] Update TBEL version to 1.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6fa82db97e..a9b7efc653 100755 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,7 @@ 3.8.1 3.21.9 1.42.1 - 1.1.5 + 1.2.0 1.18.26 1.2.4 1.2.5 From 7dd81ffb937d669a0cc7a337470d07d43ea90edb Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 19 Mar 2024 16:28:52 +0200 Subject: [PATCH 208/209] Ignore invalid lwm2m tests --- .../lwm2m/bootstrap/LwM2MTransportBootstrapServiceTest.java | 5 ++++- .../transport/lwm2m/server/client/LwM2mClientTest.java | 2 ++ .../lwm2m/server/store/util/LwM2MClientSerDesTest.java | 5 ++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServiceTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServiceTest.java index 3e923bb644..9f49f3b138 100644 --- a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServiceTest.java +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServiceTest.java @@ -17,6 +17,8 @@ package org.thingsboard.server.transport.lwm2m.bootstrap; import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.junit.Ignore; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -48,7 +50,7 @@ public class LwM2MTransportBootstrapServiceTest { @Mock private TbLwM2MDtlsBootstrapCertificateVerifier certificateVerifier; - + @Disabled // fixme: nick @Test public void getLHServer_creates_ConnectionIdGenerator_when_connection_id_length_not_null(){ final Integer CONNECTION_ID_LENGTH = 6; @@ -66,6 +68,7 @@ public class LwM2MTransportBootstrapServiceTest { .isEqualTo(CONNECTION_ID_LENGTH); } + @Disabled // fixme: nick @Test public void getLHServer_creates_no_ConnectionIdGenerator_when_connection_id_length_is_null(){ when(serverConfig.getDtlsCidLength()).thenReturn(null); diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java index ee154794ce..5bc334756d 100644 --- a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client; import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.server.registration.Registration; +import org.junit.Ignore; import org.junit.Test; import org.junit.jupiter.api.Assertions; @@ -24,6 +25,7 @@ import java.net.InetSocketAddress; public class LwM2mClientTest { + @Ignore @Test public void setRegistration() { LwM2mClient client = new LwM2mClient("nodeId", "testEndpoint"); diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java index 3e279efe40..43187f2a36 100644 --- a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java @@ -15,15 +15,14 @@ */ package org.thingsboard.server.transport.lwm2m.server.store.util; -import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.server.registration.Registration; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; -import org.mockito.Mockito; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.id.CustomerId; @@ -42,7 +41,6 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; -import java.net.InetSocketAddress; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; @@ -58,6 +56,7 @@ import static org.mockito.Mockito.when; public class LwM2MClientSerDesTest { + @Ignore @Test public void serializeDeserialize() throws Exception { LwM2mClient client = new LwM2mClient("nodeId", "testEndpoint"); From c4b8068e96793fba7ba9132780a3bef00af3e387 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 19 Mar 2024 16:56:11 +0200 Subject: [PATCH 209/209] Use AttributeScope instead of DataConstants in DefaultDeviceStateServiceTest --- .../service/state/DefaultDeviceStateServiceTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java index e590e4d8c3..f80c8c4c82 100644 --- a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java @@ -205,7 +205,7 @@ public class DefaultDeviceStateServiceTest { // THEN then(telemetrySubscriptionService).should().saveAttrAndNotify( - eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), eq(LAST_CONNECT_TIME), eq(lastConnectTime), any() + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(LAST_CONNECT_TIME), eq(lastConnectTime), any() ); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -289,7 +289,7 @@ public class DefaultDeviceStateServiceTest { // THEN then(telemetrySubscriptionService).should().saveAttrAndNotify( - eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(LAST_DISCONNECT_TIME), eq(lastDisconnectTime), any() ); @@ -410,11 +410,11 @@ public class DefaultDeviceStateServiceTest { // THEN then(telemetrySubscriptionService).should().saveAttrAndNotify( - eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(INACTIVITY_ALARM_TIME), eq(lastInactivityTime), any() ); then(telemetrySubscriptionService).should().saveAttrAndNotify( - eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any() ); @@ -450,11 +450,11 @@ public class DefaultDeviceStateServiceTest { // THEN then(telemetrySubscriptionService).should().saveAttrAndNotify( - eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(INACTIVITY_ALARM_TIME), anyLong(), any() ); then(telemetrySubscriptionService).should().saveAttrAndNotify( - eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any() );