diff --git a/application/src/main/data/json/edge/install_instructions/docker/localhost_warning.md b/application/src/main/data/json/edge/install_instructions/docker/localhost_warning.md
deleted file mode 100644
index d2a5bad26b..0000000000
--- a/application/src/main/data/json/edge/install_instructions/docker/localhost_warning.md
+++ /dev/null
@@ -1,3 +0,0 @@
-###### WARNING NOTE: 'localhost' can not be used as CLOUD_RPC_HOST
-
-Please note that your ThingsBoard base URL is **'localhost'** at the moment. **'localhost'** cannot be used for docker containers - please update **CLOUD_RPC_HOST** environment variable below to the IP address of your machine (*docker **host** machine*). IP address must be `192.168.1.XX` or similar format. In other case - ThingsBoard Edge service, that is running in docker container, will not be able to connect to the cloud.
diff --git a/application/src/main/data/json/edge/install_instructions/centos/instructions.md b/application/src/main/data/json/edge/instructions/install/centos/instructions.md
similarity index 91%
rename from application/src/main/data/json/edge/install_instructions/centos/instructions.md
rename to application/src/main/data/json/edge/instructions/install/centos/instructions.md
index 126c79b641..22bb3f63a0 100644
--- a/application/src/main/data/json/edge/install_instructions/centos/instructions.md
+++ b/application/src/main/data/json/edge/instructions/install/centos/instructions.md
@@ -1,4 +1,4 @@
-Here is the list of commands, that can be used to quickly install ThingsBoard Edge on RHEL/CentOS 7/8 and connect to the cloud.
+Here is the list of commands, that can be used to quickly install ThingsBoard Edge on RHEL/CentOS 7/8 and connect to the server.
#### Prerequisites
Before continue to installation execute the following commands in order to install necessary tools:
@@ -56,13 +56,13 @@ sudo yum update
sudo yum -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
# Install packages
sudo yum -y install epel-release yum-utils
-sudo yum-config-manager --enable pgdg12
-sudo yum install postgresql12-server postgresql12
+sudo yum-config-manager --enable pgdg15
+sudo yum install postgresql15-server postgresql15
# Initialize your PostgreSQL DB
-sudo /usr/pgsql-12/bin/postgresql-12-setup initdb
-sudo systemctl start postgresql-12
+sudo /usr/pgsql-15/bin/postgresql-15-setup initdb
+sudo systemctl start postgresql-15
# Optional: Configure PostgreSQL to start on boot
-sudo systemctl enable --now postgresql-12
+sudo systemctl enable --now postgresql-15
{:copy-code}
```
@@ -74,12 +74,12 @@ sudo systemctl enable --now postgresql-12
sudo yum -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm
# Install packages
sudo dnf -qy module disable postgresql
-sudo dnf -y install postgresql12 postgresql12-server
+sudo dnf -y install postgresql15 postgresql15-server
# Initialize your PostgreSQL DB
-sudo /usr/pgsql-12/bin/postgresql-12-setup initdb
-sudo systemctl start postgresql-12
+sudo /usr/pgsql-15/bin/postgresql-15-setup initdb
+sudo systemctl start postgresql-15
# Optional: Configure PostgreSQL to start on boot
-sudo systemctl enable --now postgresql-12
+sudo systemctl enable --now postgresql-15
{:copy-code}
```
@@ -101,7 +101,7 @@ After configuring the password, edit the pg_hba.conf to use MD5 authentication w
Edit pg_hba.conf file:
```bash
-sudo nano /var/lib/pgsql/12/data/pg_hba.conf
+sudo nano /var/lib/pgsql/15/data/pg_hba.conf
{:copy-code}
```
@@ -121,7 +121,7 @@ host all all 127.0.0.1/32 md5
Finally, you should restart the PostgreSQL service to initialize the new configuration:
```bash
-sudo systemctl restart postgresql-12.service
+sudo systemctl restart postgresql-15.service
{:copy-code}
```
diff --git a/application/src/main/data/json/edge/install_instructions/docker/instructions.md b/application/src/main/data/json/edge/instructions/install/docker/instructions.md
similarity index 77%
rename from application/src/main/data/json/edge/install_instructions/docker/instructions.md
rename to application/src/main/data/json/edge/instructions/install/docker/instructions.md
index d8f9731889..9910124b3e 100644
--- a/application/src/main/data/json/edge/install_instructions/docker/instructions.md
+++ b/application/src/main/data/json/edge/instructions/install/docker/instructions.md
@@ -1,25 +1,11 @@
-Here is the list of commands, that can be used to quickly install ThingsBoard Edge using docker compose and connect to the cloud.
+Here is the list of commands, that can be used to quickly install ThingsBoard Edge using docker compose and connect to the server.
#### Prerequisites
Install Docker CE and Docker Compose .
-#### Create data and logs folders
-
-Run following commands, before starting docker container(s), to create folders for storing data and logs.
-These commands additionally will change owner of newly created folders to docker container user.
-To do this (to change user) **chown** command is used, and this command requires *sudo* permissions (command will request password for a *sudo* access):
-
-```bash
-mkdir -p ~/.mytb-edge-data && sudo chown -R 799:799 ~/.mytb-edge-data
-mkdir -p ~/.mytb-edge-logs && sudo chown -R 799:799 ~/.mytb-edge-logs
-{:copy-code}
-```
-
#### Running ThingsBoard Edge as docker service
-${LOCALHOST_WARNING}
-
Create docker compose file for ThingsBoard Edge service:
```bash
@@ -30,7 +16,7 @@ nano docker-compose.yml
Add the following lines to the yml file:
```bash
-version: '3.0'
+version: '3.8'
services:
mytbedge:
restart: always
@@ -47,8 +33,9 @@ services:
CLOUD_RPC_PORT: ${CLOUD_RPC_PORT}
CLOUD_RPC_SSL_ENABLED: ${CLOUD_RPC_SSL_ENABLED}
volumes:
- - ~/.mytb-edge-data:/data
- - ~/.mytb-edge-logs:/var/log/tb-edge
+ - tb-edge-data:/data
+ - tb-edge-logs:/var/log/tb-edge
+ ${EXTRA_HOSTS}
postgres:
restart: always
image: "postgres:15"
@@ -58,7 +45,15 @@ services:
POSTGRES_DB: tb-edge
POSTGRES_PASSWORD: postgres
volumes:
- - ~/.mytb-edge-data/db:/var/lib/postgresql/data
+ - tb-edge-postgres-data:/var/lib/postgresql/data
+
+volumes:
+ tb-edge-data:
+ name: tb-edge-data
+ tb-edge-logs:
+ name: tb-edge-logs
+ tb-edge-postgres-data:
+ name: tb-edge-postgres-data
{:copy-code}
```
diff --git a/application/src/main/data/json/edge/install_instructions/ubuntu/instructions.md b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md
similarity index 97%
rename from application/src/main/data/json/edge/install_instructions/ubuntu/instructions.md
rename to application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md
index 425c08662b..6fcdcd50f2 100644
--- a/application/src/main/data/json/edge/install_instructions/ubuntu/instructions.md
+++ b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md
@@ -1,4 +1,4 @@
-Here is the list of commands, that can be used to quickly install ThingsBoard Edge on Ubuntu Server and connect to the cloud.
+Here is the list of commands, that can be used to quickly install ThingsBoard Edge on Ubuntu Server and connect to the server.
#### Install Java 11 (OpenJDK)
ThingsBoard service is running on Java 11. Follow these instructions to install OpenJDK 11:
@@ -49,7 +49,7 @@ echo "deb http://apt.postgresql.org/pub/repos/apt/ ${RELEASE}"-pgdg main | sudo
# install and launch the postgresql service:
sudo apt update
-sudo apt -y install postgresql-12
+sudo apt -y install postgresql-15
sudo service postgresql start
{:copy-code}
```
diff --git a/application/src/main/data/json/edge/instructions/upgrade/centos/instructions.md b/application/src/main/data/json/edge/instructions/upgrade/centos/instructions.md
new file mode 100644
index 0000000000..c0e721ace2
--- /dev/null
+++ b/application/src/main/data/json/edge/instructions/upgrade/centos/instructions.md
@@ -0,0 +1,15 @@
+#### Upgrading to ${TB_EDGE_VERSION}EDGE
+
+**ThingsBoard Edge package download:**
+```bash
+wget https://github.com/thingsboard/thingsboard-edge/releases/download/v${TB_EDGE_TAG}/tb-edge-${TB_EDGE_TAG}.rpm
+{:copy-code}
+```
+##### ThingsBoard Edge service upgrade
+
+Install package:
+```bash
+sudo rpm -Uvh tb-edge-${TB_EDGE_TAG}.rpm
+{:copy-code}
+```
+${UPGRADE_DB}
diff --git a/application/src/main/data/json/edge/instructions/upgrade/docker/instructions.md b/application/src/main/data/json/edge/instructions/upgrade/docker/instructions.md
new file mode 100644
index 0000000000..f1b9931e8b
--- /dev/null
+++ b/application/src/main/data/json/edge/instructions/upgrade/docker/instructions.md
@@ -0,0 +1,10 @@
+#### Upgrading to ${TB_EDGE_VERSION}
+
+Execute the following command to pull **${TB_EDGE_VERSION}** image:
+
+```bash
+docker pull thingsboard/tb-edge:${TB_EDGE_VERSION}
+{:copy-code}
+```
+
+${UPGRADE_DB}
diff --git a/application/src/main/data/json/edge/instructions/upgrade/docker/start_service.md b/application/src/main/data/json/edge/instructions/upgrade/docker/start_service.md
new file mode 100644
index 0000000000..a6c9c6731c
--- /dev/null
+++ b/application/src/main/data/json/edge/instructions/upgrade/docker/start_service.md
@@ -0,0 +1,23 @@
+Modify ‘main’ docker compose (`docker-compose.yml`) file for ThingsBoard Edge and update version of the image:
+```bash
+nano docker-compose.yml
+{:copy-code}
+```
+
+```text
+version: '3.8'
+services:
+ mytbedge:
+ restart: always
+ image: "thingsboard/tb-edge:${TB_EDGE_VERSION}"
+...
+```
+
+Make sure your image is the set to **tb-edge-${TB_EDGE_VERSION}**.
+Execute the following commands to up this docker compose directly:
+
+```bash
+docker compose up -d
+docker compose logs -f mytbedge
+{:copy-code}
+```
diff --git a/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md
new file mode 100644
index 0000000000..d922fa9155
--- /dev/null
+++ b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md
@@ -0,0 +1,61 @@
+Create docker compose file for ThingsBoard Edge upgrade process:
+
+```bash
+> docker-compose-upgrade.yml && nano docker-compose-upgrade.yml
+{:copy-code}
+```
+
+Add the following lines to the yml file:
+
+```bash
+version: '3.8'
+services:
+ mytbedge:
+ restart: on-failure
+ image: "thingsboard/tb-edge:${TB_EDGE_VERSION}"
+ environment:
+ SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/tb-edge
+ volumes:
+ - tb-edge-data:/data
+ - tb-edge-logs:/var/log/tb-edge
+ entrypoint: upgrade-tb-edge.sh
+ postgres:
+ restart: always
+ image: "postgres:15"
+ ports:
+ - "5432"
+ environment:
+ POSTGRES_DB: tb-edge
+ POSTGRES_PASSWORD: postgres
+ volumes:
+ - tb-edge-postgres-data:/var/lib/postgresql/data
+
+volumes:
+ tb-edge-data:
+ name: tb-edge-data
+ tb-edge-logs:
+ name: tb-edge-logs
+ tb-edge-postgres-data:
+ name: tb-edge-postgres-data
+{:copy-code}
+```
+
+Execute the following command to start upgrade process:
+
+```bash
+docker compose -f docker-compose-upgrade.yml up
+{:copy-code}
+```
+
+Once upgrade process successfully completed, exit from the docker-compose shell by this combination:
+
+```text
+Ctrl + C
+```
+
+Execute the following command to stop TB Edge upgrade container:
+
+```bash
+docker compose -f docker-compose-upgrade.yml stop
+{:copy-code}
+```
diff --git a/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_preparing.md b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_preparing.md
new file mode 100644
index 0000000000..bb3ce268b8
--- /dev/null
+++ b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_preparing.md
@@ -0,0 +1,83 @@
+Here is the list of commands, that can be used to quickly upgrade ThingsBoard Edge on Docker (Linux or MacOS).
+
+#### Prepare for upgrading ThingsBoard Edge
+Set the terminal in the directory which contains the `docker-compose.yml` file and execute the following command
+to stop and remove currently running TB Edge container:
+
+```bash
+docker compose stop
+docker compose rm mytbedge
+{:copy-code}
+```
+
+**OPTIONAL:** If you still rely on Docker Compose as docker-compose (with a hyphen) here is the list of the above commands:
+```text
+docker-compose stop
+docker-compose rm mytbedge
+```
+
+##### Migrating Data from Docker Bind Mount Folders to Docker Volumes
+Starting with the **3.6.2** release, the ThingsBoard team has transitioned from using Docker bind mount folders to Docker volumes.
+This change aims to enhance security and efficiency in storing data for Docker containers and to mitigate permission issues across various environments.
+
+To migrate from Docker bind mounts to Docker volumes, please execute the following commands:
+
+```bash
+docker run --rm -v tb-edge-data:/volume -v ~/.mytb-edge-data:/backup busybox sh -c "cp -a /backup/. /volume"
+docker run --rm -v tb-edge-logs:/volume -v ~/.mytb-edge-logs:/backup busybox sh -c "cp -a /backup/. /volume"
+docker run --rm -v tb-edge-postgres-data:/volume -v ~/.mytb-edge-data/db:/backup busybox sh -c "cp -a /backup/. /volume"
+{:copy-code}
+```
+
+After completing the data migration to the newly created Docker volumes, you'll need to update the volume mounts in your Docker Compose configuration.
+Modify the `docker-compose.yml` file for ThingsBoard Edge to update the volume settings.
+
+First, please update docker compose file version. Find next snippet:
+```text
+version: '3.0'
+...
+```
+
+And replace it with:
+```text
+version: '3.8'
+...
+```
+
+Then update volume mounts. Locate the following snippet:
+```text
+ volumes:
+ - ~/.mytb-edge-data:/data
+ - ~/.mytb-edge-logs:/var/log/tb-edge
+...
+```
+
+And replace it with:
+```text
+ volumes:
+ - tb-edge-data:/data
+ - tb-edge-logs:/var/log/tb-edge
+...
+```
+
+Apply a similar update for the PostgreSQL service. Find the section:
+```text
+ volumes:
+ - ~/.mytb-edge-data/db:/var/lib/postgresql/data
+...
+```
+
+And replace it with:
+```text
+ volumes:
+ - tb-edge-postgres-data/:/var/lib/postgresql/data
+...
+```
+
+##### Backup Database
+Make a copy of the database volume before upgrading:
+
+```bash
+docker run --rm -v tb-edge-postgres-data:/source -v tb-edge-postgres-data-backup:/backup busybox sh -c "cp -a /source/. /backup"
+{:copy-code}
+```
diff --git a/application/src/main/data/json/edge/instructions/upgrade/start_service.md b/application/src/main/data/json/edge/instructions/upgrade/start_service.md
new file mode 100644
index 0000000000..c26c183154
--- /dev/null
+++ b/application/src/main/data/json/edge/instructions/upgrade/start_service.md
@@ -0,0 +1,6 @@
+Start the service
+
+```bash
+sudo systemctl tb-edge start
+{:copy-code}
+```
diff --git a/application/src/main/data/json/edge/instructions/upgrade/ubuntu/instructions.md b/application/src/main/data/json/edge/instructions/upgrade/ubuntu/instructions.md
new file mode 100644
index 0000000000..59d243c63c
--- /dev/null
+++ b/application/src/main/data/json/edge/instructions/upgrade/ubuntu/instructions.md
@@ -0,0 +1,15 @@
+#### Upgrading to ${TB_EDGE_VERSION}EDGE
+
+**ThingsBoard Edge package download:**
+```bash
+wget https://github.com/thingsboard/thingsboard-edge/releases/download/v${TB_EDGE_TAG}/tb-edge-${TB_EDGE_TAG}.deb
+{:copy-code}
+```
+##### ThingsBoard Edge service upgrade
+
+Install package:
+```bash
+sudo dpkg -i tb-edge-${TB_EDGE_TAG}.deb
+{:copy-code}
+```
+${UPGRADE_DB}
diff --git a/application/src/main/data/json/edge/instructions/upgrade/upgrade_db.md b/application/src/main/data/json/edge/instructions/upgrade/upgrade_db.md
new file mode 100644
index 0000000000..6e7beb1425
--- /dev/null
+++ b/application/src/main/data/json/edge/instructions/upgrade/upgrade_db.md
@@ -0,0 +1,8 @@
+**NOTE**: Package installer may ask you to merge your tb-edge configuration. It is preferred to use **merge option** to make sure that all your previous parameters will not be overwritten.
+
+Execute regular upgrade script:
+
+```bash
+sudo /usr/share/tb-edge/bin/install/upgrade.sh --fromVersion=${FROM_TB_EDGE_VERSION}
+{:copy-code}
+```
diff --git a/application/src/main/data/json/edge/instructions/upgrade/upgrade_preparing.md b/application/src/main/data/json/edge/instructions/upgrade/upgrade_preparing.md
new file mode 100644
index 0000000000..bdd6c4ea18
--- /dev/null
+++ b/application/src/main/data/json/edge/instructions/upgrade/upgrade_preparing.md
@@ -0,0 +1,36 @@
+Here is the list of commands, that can be used to quickly upgrade ThingsBoard Edge on ${OS}
+
+#### Prepare for upgrading ThingsBoard Edge
+
+Stop ThingsBoard Edge service:
+
+```bash
+sudo systemctl stop tb-edge
+{:copy-code}
+```
+
+##### Backup Database
+Make a backup of the database before upgrading. **Make sure you have enough space to place a backup of the database.**
+
+Check database size:
+
+```bash
+sudo -u postgres psql -c "SELECT pg_size_pretty( pg_database_size('tb_edge') );"
+{:copy-code}
+```
+
+Check free space:
+
+```bash
+df -h /
+{:copy-code}
+```
+
+If there is enough free space - make a backup:
+
+```bash
+sudo -Hiu postgres pg_dump tb_edge > tb_edge.sql.bak
+{:copy-code}
+```
+
+Check backup file created successfully.
diff --git a/application/src/main/data/json/system/widget_types/battery_level.json b/application/src/main/data/json/system/widget_types/battery_level.json
index dc53143caa..1f52bb8462 100644
--- a/application/src/main/data/json/system/widget_types/battery_level.json
+++ b/application/src/main/data/json/system/widget_types/battery_level.json
@@ -17,7 +17,7 @@
"settingsDirective": "tb-battery-level-widget-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-battery-level-basic-config",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"batteryLevel\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 7;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 0;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"layout\":\"vertical_solid\",\"showValue\":true,\"autoScaleValueSize\":true,\"valueFont\":{\"family\":\"Roboto\",\"size\":20,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"24px\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"batteryLevelColor\":{\"color\":\"rgba(92, 223, 144, 1)\",\"type\":\"range\",\"rangeList\":[{\"from\":0,\"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)\"}],\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"batteryShapeColor\":{\"color\":\"rgba(92, 223, 144, 0.32)\",\"type\":\"range\",\"rangeList\":[{\"from\":0,\"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)\"}],\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"sectionsCount\":4},\"title\":\"Battery level\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"%\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"mdi:battery-high\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"18px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"batteryLevel\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 7;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 0;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"layout\":\"vertical_solid\",\"showValue\":true,\"autoScaleValueSize\":true,\"valueFont\":{\"family\":\"Roboto\",\"size\":20,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"24px\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"batteryLevelColor\":{\"type\":\"range\",\"color\":\"rgb(224, 224, 224)\",\"rangeList\":[{\"from\":0,\"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)\"}],\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"batteryShapeColor\":{\"type\":\"range\",\"color\":\"rgba(224, 224, 224, 0.32)\",\"rangeList\":[{\"from\":0,\"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)\"}],\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"sectionsCount\":4},\"title\":\"Battery level\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"%\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"mdi:battery-high\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"18px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
},
"externalId": null,
"tags": [
diff --git a/application/src/main/data/json/tenant/dashboards/gateways.json b/application/src/main/data/json/tenant/dashboards/gateways.json
index 972aed299f..13902a9158 100644
--- a/application/src/main/data/json/tenant/dashboards/gateways.json
+++ b/application/src/main/data/json/tenant/dashboards/gateways.json
@@ -250,9 +250,9 @@
"useShowWidgetActionFunction": null,
"showWidgetActionFunction": "return true;",
"type": "customPretty",
- "customHtml": "
\r\n",
+ "customHtml": "\r\n",
"customCss": ".add-entity-form {\r\n min-width: 400px !important;\r\n}\r\n\r\n.add-entity-form .boolean-value-input {\r\n padding-left: 5px;\r\n}\r\n\r\n.add-entity-form .boolean-value-input .checkbox-label {\r\n margin-bottom: 8px;\r\n color: rgba(0,0,0,0.54);\r\n font-size: 12px;\r\n}\r\n\r\n.relations-list .header {\r\n padding-right: 5px;\r\n padding-bottom: 5px;\r\n padding-left: 5px;\r\n}\r\n\r\n.relations-list .header .cell {\r\n padding-right: 5px;\r\n padding-left: 5px;\r\n font-size: 12px;\r\n font-weight: 700;\r\n color: rgba(0, 0, 0, .54);\r\n white-space: nowrap;\r\n}\r\n\r\n.relations-list .mat-form-field-infix {\r\n width: auto !important;\r\n}\r\n\r\n.relations-list .body {\r\n padding-right: 5px;\r\n padding-bottom: 15px;\r\n padding-left: 5px;\r\n}\r\n\r\n.relations-list .body .row {\r\n padding-top: 5px;\r\n}\r\n\r\n.relations-list .body .cell {\r\n padding-right: 5px;\r\n padding-left: 5px;\r\n}\r\n\r\n.relations-list .body .md-button {\r\n margin: 0;\r\n}\r\n\r\n",
- "customFunction": "let $injector = widgetContext.$scope.$injector;\r\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\r\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\r\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\r\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\r\nlet entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));\r\nlet userSettingsService = $injector.get(widgetContext.servicesMap.get('userSettingsService'));\r\n\r\nopenAddEntityDialog();\r\n\r\nfunction openAddEntityDialog() {\r\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\r\n}\r\n\r\nfunction AddEntityDialogController(instance) {\r\n let vm = instance;\r\n let userSettings;\r\n userSettingsService.loadUserSettings().subscribe(settings=> {\r\n userSettings = settings;\r\n if (!userSettings.createdGatewaysCount) userSettings.createdGatewaysCount = 0;\r\n });\r\n \r\n\r\n vm.addEntityFormGroup = vm.fb.group({\r\n entityName: ['', [vm.validators.required]],\r\n entityType: ['DEVICE'],\r\n entityLabel: [''],\r\n type: ['', [vm.validators.required]],\r\n });\r\n\r\n vm.cancel = function() {\r\n vm.dialogRef.close(null);\r\n };\r\n\r\n\r\n vm.save = function() {\r\n vm.addEntityFormGroup.markAsPristine();\r\n saveEntityObservable().subscribe(\r\n function (device) {\r\n widgetContext.updateAliases();\r\n userSettingsService.putUserSettings({ createdGatewaysCount: ++userSettings.createdGatewaysCount }).subscribe(_=>{\r\n });\r\n vm.dialogRef.close(null);\r\n goToConfigState(device);\r\n }\r\n );\r\n };\r\n \r\n function goToConfigState(device) {\r\n const stateParams = {};\r\n stateParams.entityId = device.id;\r\n stateParams.entityName = device.name;\r\n const newStateParams = {\r\n targetEntityParamName: 'default',\r\n new_gateway: {\r\n entityId: device.id,\r\n entityName: device.name\r\n }\r\n }\r\n const params = {...stateParams, ...newStateParams};\r\n widgetContext.stateController.openState('gateway_details', params, false);\r\n }\r\n\r\n function saveEntityObservable() {\r\n const formValues = vm.addEntityFormGroup.value;\r\n let entity = {\r\n name: formValues.entityName,\r\n type: formValues.type,\r\n label: formValues.entityLabel,\r\n additionalInfo: {\r\n gateway: true\r\n }\r\n };\r\n return deviceService.saveDevice(entity);\r\n }\r\n}\r\n",
+ "customFunction": "let $injector = widgetContext.$scope.$injector;\r\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\r\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\r\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\r\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\r\nlet entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));\r\nlet userSettingsService = $injector.get(widgetContext.servicesMap.get('userSettingsService'));\r\n\r\nopenAddEntityDialog();\r\n\r\nfunction openAddEntityDialog() {\r\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\r\n}\r\n\r\nfunction AddEntityDialogController(instance) {\r\n let vm = instance;\r\n let userSettings;\r\n userSettingsService.loadUserSettings().subscribe(settings=> {\r\n userSettings = settings;\r\n if (!userSettings.createdGatewaysCount) userSettings.createdGatewaysCount = 0;\r\n });\r\n \r\n\r\n vm.addEntityFormGroup = vm.fb.group({\r\n entityName: ['', [vm.validators.required]],\r\n entityType: ['DEVICE'],\r\n entityLabel: [''],\r\n type: ['', [vm.validators.required]],\r\n });\r\n\r\n vm.cancel = function() {\r\n vm.dialogRef.close(null);\r\n };\r\n\r\n\r\n vm.save = function($event) {\r\n vm.addEntityFormGroup.markAsPristine();\r\n saveEntityObservable().subscribe(\r\n function (device) {\r\n widgetContext.updateAliases();\r\n userSettingsService.putUserSettings({ createdGatewaysCount: ++userSettings.createdGatewaysCount }).subscribe(_=>{\r\n });\r\n vm.dialogRef.close(null);\r\n openCommandDialog(device, $event);\r\n }\r\n );\r\n };\r\n \r\n function openCommandDialog(device, $event) {\r\n vm.device = device;\r\n let openCommandAction = widgetContext.actionsApi.getActionDescriptors(\"actionCellButton\").find(action => action.name == \"Docker commands\");\r\n widgetContext.actionsApi.handleWidgetAction($event, openCommandAction, device.id, device.name, {newDevice: true});\r\n setTimeout(function() {\r\n document.querySelector(\".dashboard-state-dialog .mat-mdc-button-touch-target\").addEventListener('click', goToConfigState);\r\n document.querySelector(\".dashboard-state-dialog .mat-mdc-button.mat-primary\").addEventListener('click', goToConfigState);\r\n }, 500);\r\n }\r\n\r\n \r\n function goToConfigState() {\r\n document.querySelector(\".dashboard-state-dialog .mat-mdc-button-touch-target\").removeEventListener('click', goToConfigState);\r\n document.querySelector(\".dashboard-state-dialog .mat-mdc-button.mat-primary\").removeEventListener('click', goToConfigState);\r\n const stateParams = {};\r\n stateParams.entityId = vm.device.id;\r\n stateParams.entityName = vm.device.name;\r\n const newStateParams = {\r\n targetEntityParamName: 'default',\r\n new_gateway: {\r\n entityId: vm.device.id,\r\n entityName: vm.device.name\r\n }\r\n }\r\n const params = {...stateParams, ...newStateParams};\r\n widgetContext.stateController.openState('gateway_details', params, false);\r\n }\r\n\r\n function saveEntityObservable() {\r\n const formValues = vm.addEntityFormGroup.value;\r\n let entity = {\r\n name: formValues.entityName,\r\n type: formValues.type,\r\n label: formValues.entityLabel,\r\n additionalInfo: {\r\n gateway: true\r\n }\r\n };\r\n return deviceService.saveDevice(entity);\r\n }\r\n}\r\n",
"customResources": [],
"openInSeparateDialog": false,
"openInPopover": false,
@@ -2142,7 +2142,7 @@
"settings": {
"useMarkdownTextFunction": false,
"markdownTextPattern": "\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n",
- "applyDefaultMarkdownStyle": true,
+ "applyDefaultMarkdownStyle": false,
"markdownCss": ".mat-mdc-form-field-subscript-wrapper {\n display: none !important;\n}"
},
"title": "Gateway devices",
@@ -2316,7 +2316,22 @@
"pageSize": 1024,
"noDataDisplayMessage": "",
"enableDataExport": false,
- "borderRadius": ""
+ "borderRadius": "",
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "45e4507d-3adc-bb31-8b2b-1ba09bbd56ac"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -2468,7 +2483,22 @@
"pageSize": 1024,
"noDataDisplayMessage": "",
"enableDataExport": false,
- "borderRadius": "4px"
+ "borderRadius": "4px",
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "852eccce-98eb-24db-c783-bdd62566f906"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -2619,7 +2649,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "3c31ba62-e760-2bea-4c8d-d32784a86c24"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -2770,7 +2815,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "4b55ea81-93bf-4206-9166-3e0bdc1dd9f3"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -2921,7 +2981,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "babf88d0-a118-e2b5-f10e-3a5970c8a65b"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -3072,7 +3147,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "94de7690-f91d-b032-6771-85af99abd749"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -3223,7 +3313,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "18414f44-1c65-536a-14de-eaf21a7d56bd"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -3374,7 +3479,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "794974da-c9d2-a9f7-be47-c9eb642094e8"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -3525,7 +3645,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "2add705b-3e53-8559-8126-380cac686fb0"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -3676,7 +3811,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "7e1ba820-9992-d52a-579b-20485abb3926"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -3827,7 +3977,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "91af27c1-b37c-2276-6022-a332e41b2b33"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -3978,7 +4143,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "26cf8696-054b-13ec-7984-6fc5df20e6f1"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -4129,7 +4309,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "1dcfaf24-32be-cd19-62d6-86d12cc6a7ef"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -4280,7 +4475,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "ad2bc817-f3c4-150c-4672-8fe0c38aee8d"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -4431,7 +4641,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "d1ad84cd-bd9c-4dca-e4a0-f444ae8598bd"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -4582,7 +4807,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "bf80eef9-b879-9a08-40a4-488dbdefa125"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -4733,7 +4973,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "b5a406b3-cc0a-8a09-9aec-3f8befae5fb8"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -4884,7 +5139,22 @@
"widgetCss": ".status {\r\n border-radius: 20px;\r\n font-weight: 500;\r\n padding: 5px 15px;\r\n }\r\n\r\n .status-active {\r\n color: green;\r\n background: rgba(0, 128, 0, 0.1);\r\n }\r\n\r\n .status-inactive {\r\n color: red;\r\n background: rgba(255, 0, 0, 0.1);\r\n }\r\n",
"pageSize": 1024,
"noDataDisplayMessage": "",
- "enableDataExport": false
+ "enableDataExport": false,
+ "actions": {
+ "actionCellButton": [
+ {
+ "name": "Show Device Info",
+ "icon": "info",
+ "useShowWidgetActionFunction": null,
+ "showWidgetActionFunction": "return true;",
+ "type": "custom",
+ "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');",
+ "openInSeparateDialog": false,
+ "openInPopover": false,
+ "id": "ec1dfba3-4b43-2491-8948-f602337f8a3b"
+ }
+ ]
+ }
},
"row": 0,
"col": 0,
@@ -6596,4 +6866,4 @@
},
"externalId": null,
"name": "Gateway"
-}
+}
\ No newline at end of file
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 55370b04b8..6b29ba0b28 100644
--- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
+++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
@@ -32,14 +32,12 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
@@ -63,7 +61,7 @@ import java.util.List;
@Configuration
@EnableWebSecurity
-@EnableGlobalMethodSecurity(prePostEnabled=true)
+@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(SecurityProperties.BASIC_AUTH_ORDER)
@TbCoreComponent
public class ThingsboardSecurityConfiguration {
@@ -79,7 +77,7 @@ public class ThingsboardSecurityConfiguration {
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/**"};
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
- public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**";
+ public static final String WS_ENTRY_POINT = "/api/ws/**";
public static final String MAIL_OAUTH2_PROCESSING_ENTRY_POINT = "/api/admin/mail/oauth2/code";
public static final String DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT = "/api/device-connectivity/mqtts/certificate/download";
@@ -115,10 +113,6 @@ public class ThingsboardSecurityConfiguration {
@Qualifier("jwtHeaderTokenExtractor")
private TokenExtractor jwtHeaderTokenExtractor;
- @Autowired
- @Qualifier("jwtQueryTokenExtractor")
- private TokenExtractor jwtQueryTokenExtractor;
-
@Autowired private AuthenticationManager authenticationManager;
@Autowired private RateLimitProcessingFilter rateLimitProcessingFilter;
@@ -150,7 +144,7 @@ public class ThingsboardSecurityConfiguration {
protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
List pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS));
- pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT,
+ pathsToSkip.addAll(Arrays.asList(WS_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT,
PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT,
DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT));
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
@@ -167,15 +161,6 @@ public class ThingsboardSecurityConfiguration {
return filter;
}
- @Bean
- protected JwtTokenAuthenticationProcessingFilter buildWsJwtTokenAuthenticationProcessingFilter() throws Exception {
- AntPathRequestMatcher matcher = new AntPathRequestMatcher(WS_TOKEN_BASED_AUTH_ENTRY_POINT);
- JwtTokenAuthenticationProcessingFilter filter
- = new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtQueryTokenExtractor, matcher);
- filter.setAuthenticationManager(this.authenticationManager);
- return filter;
- }
-
@Bean
public AuthenticationManager authenticationManager(ObjectPostProcessor objectPostProcessor) throws Exception {
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
@@ -229,7 +214,7 @@ public class ThingsboardSecurityConfiguration {
.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(WS_ENTRY_POINT).permitAll() // WebSocket API End-points
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
.and()
.exceptionHandling().accessDeniedHandler(restAccessDeniedHandler)
@@ -238,7 +223,6 @@ public class ThingsboardSecurityConfiguration {
.addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
- .addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
if (oauth2Configuration != null) {
http.oauth2Login()
diff --git a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
index 9a6ffb0e32..467e20adb8 100644
--- a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
+++ b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
@@ -19,25 +19,13 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.server.ServerHttpRequest;
-import org.springframework.http.server.ServerHttpResponse;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
-import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
-import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
-import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
-import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.controller.plugin.TbWebSocketHandler;
import org.thingsboard.server.queue.util.TbCoreComponent;
-import org.thingsboard.server.service.security.model.SecurityUser;
-
-import java.util.Map;
@Configuration
@TbCoreComponent
@@ -46,8 +34,9 @@ import java.util.Map;
@Slf4j
public class WebSocketConfiguration implements WebSocketConfigurer {
- public static final String WS_PLUGIN_PREFIX = "/api/ws/plugins/";
- private static final String WS_PLUGIN_MAPPING = WS_PLUGIN_PREFIX + "**";
+ public static final String WS_API_ENDPOINT = "/api/ws";
+ public static final String WS_PLUGINS_ENDPOINT = "/api/ws/plugins/";
+ private static final String WS_API_MAPPING = "/api/ws/**";
private final WebSocketHandler wsHandler;
@@ -65,39 +54,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
log.error("TbWebSocketHandler expected but [{}] provided", wsHandler);
throw new RuntimeException("TbWebSocketHandler expected but " + wsHandler + " provided");
}
- registry.addHandler(wsHandler, WS_PLUGIN_MAPPING).setAllowedOriginPatterns("*")
- .addInterceptors(new HttpSessionHandshakeInterceptor(), new HandshakeInterceptor() {
-
- @Override
- public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
- Map attributes) throws Exception {
- SecurityUser user = null;
- try {
- user = getCurrentUser();
- } catch (ThingsboardException ex) {
- }
- if (user == null) {
- response.setStatusCode(HttpStatus.UNAUTHORIZED);
- return false;
- } else {
- return true;
- }
- }
-
- @Override
- public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
- Exception exception) {
- //Do nothing
- }
- });
+ registry.addHandler(wsHandler, WS_API_MAPPING).setAllowedOriginPatterns("*");
}
- protected SecurityUser getCurrentUser() throws ThingsboardException {
- Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
- if (authentication != null && authentication.getPrincipal() instanceof SecurityUser) {
- return (SecurityUser) authentication.getPrincipal();
- } else {
- throw new ThingsboardException("You aren't authorized to perform this operation!", ThingsboardErrorCode.AUTHENTICATION);
- }
- }
}
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 7e64ec428a..58b9376f1f 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java
@@ -397,10 +397,12 @@ 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 = "Deprecated. See 'getAssetProfileNames' API from Asset Profile Controller instead." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
+ produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset/types", method = RequestMethod.GET)
@ResponseBody
+ @Deprecated(since = "3.6.2")
public List getAssetTypes() throws ThingsboardException, ExecutionException, InterruptedException {
SecurityUser user = getCurrentUser();
TenantId tenantId = user.getTenantId();
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 d9a2923d52..59c9670a0c 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
@@ -29,18 +29,23 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
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.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.asset.profile.TbAssetProfileService;
+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.List;
+
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;
@@ -212,4 +217,19 @@ public class AssetProfileController extends BaseController {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(assetProfileService.findAssetProfileInfos(getTenantId(), pageLink));
}
+
+ @ApiOperation(value = "Get Asset Profile names (getAssetProfileNames)",
+ notes = "Returns a set of unique asset profile names owned by the tenant."
+ + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/assetProfile/names", method = RequestMethod.GET)
+ @ResponseBody
+ public List getAssetProfileNames(
+ @ApiParam(value = "Flag indicating whether to retrieve exclusively the names of asset profiles that are referenced by tenant's assets.")
+ @RequestParam(value = "activeOnly", required = false, defaultValue = "false") boolean activeOnly) throws ThingsboardException {
+ SecurityUser user = getCurrentUser();
+ TenantId tenantId = user.getTenantId();
+ return checkNotNull(assetProfileService.findAssetProfileNamesByTenantId(tenantId, activeOnly));
+ }
+
}
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 c05cf5cd6c..1761056051 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
@@ -194,14 +194,14 @@ public class DeviceController extends BaseController {
"Requires to provide the Device Credentials object as well as an existing device profile ID or use \"default\".\n" +
"You may find the example of device with different type of credentials below: \n\n" +
"- Credentials type: \"Access token\" with device profile ID below: \n\n" +
- DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN + "\n\n" +
+ DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN + "\n\n" +
"- Credentials type: \"Access token\" with device profile default below: \n\n" +
- DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DEFAULT_DESCRIPTION_MARKDOWN + "\n\n" +
+ DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DEFAULT_DESCRIPTION_MARKDOWN + "\n\n" +
"- Credentials type: \"X509\" with device profile ID below: \n\n" +
"Note: credentialsId - format Sha3Hash , certificateValue - format PEM (with \"--BEGIN CERTIFICATE----\" and -\"----END CERTIFICATE-\").\n\n" +
- DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN + "\n\n" +
+ DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN + "\n\n" +
"- Credentials type: \"MQTT_BASIC\" with device profile ID below: \n\n" +
- DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN + "\n\n" +
+ DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN + "\n\n" +
"- You may find the example of LwM2M device and RPK credentials below: \n\n" +
"Note: LwM2M device - only existing device profile ID (Transport configuration -> Transport type: \"LWM2M\".\n\n" +
DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION_MARKDOWN + "\n\n" +
@@ -304,12 +304,12 @@ public class DeviceController extends BaseController {
"The structure of device credentials id and value is simple for the 'ACCESS_TOKEN' but is much more complex for the 'MQTT_BASIC' or 'LWM2M_CREDENTIALS'.\n" +
"You may find the example of device with different type of credentials below: \n\n" +
"- Credentials type: \"Access token\" with device ID and with device ID below: \n\n" +
- DEVICE_UPDATE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN + "\n\n" +
+ DEVICE_UPDATE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN + "\n\n" +
"- Credentials type: \"X509\" with device profile ID below: \n\n" +
"Note: credentialsId - format Sha3Hash , certificateValue - format PEM (with \"--BEGIN CERTIFICATE----\" and -\"----END CERTIFICATE-\").\n\n" +
- DEVICE_UPDATE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN + "\n\n" +
+ DEVICE_UPDATE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN + "\n\n" +
"- Credentials type: \"MQTT_BASIC\" with device profile ID below: \n\n" +
- DEVICE_UPDATE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN + "\n\n" +
+ DEVICE_UPDATE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN + "\n\n" +
"- You may find the example of LwM2M device and RPK credentials below: \n\n" +
"Note: LwM2M device - only existing device profile ID (Transport configuration -> Transport type: \"LWM2M\".\n\n" +
DEVICE_UPDATE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION_MARKDOWN + "\n\n" +
@@ -534,11 +534,12 @@ public class DeviceController extends BaseController {
}
@ApiOperation(value = "Get Device Types (getDeviceTypes)",
- notes = "Returns a set of unique device profile names based on devices that are either owned by the tenant or assigned to the customer which user is performing the request."
- + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
+ notes = "Deprecated. See 'getDeviceProfileNames' API from Device Profile Controller instead." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
+ produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device/types", method = RequestMethod.GET)
@ResponseBody
+ @Deprecated(since = "3.6.2")
public List getDeviceTypes() throws ThingsboardException, ExecutionException, InterruptedException {
SecurityUser user = getCurrentUser();
TenantId tenantId = user.getTenantId();
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 3c07946309..c16e201bad 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
@@ -32,15 +32,18 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.StringUtils;
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.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.device.profile.TbDeviceProfileService;
+import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@@ -273,4 +276,19 @@ public class DeviceProfileController extends BaseController {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink, transportType));
}
+
+ @ApiOperation(value = "Get Device Profile names (getDeviceProfileNames)",
+ notes = "Returns a set of unique device profile names owned by the tenant."
+ + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/deviceProfile/names", method = RequestMethod.GET)
+ @ResponseBody
+ public List getDeviceProfileNames(
+ @ApiParam(value = "Flag indicating whether to retrieve exclusively the names of device profiles that are referenced by tenant's devices.")
+ @RequestParam(value = "activeOnly", required = false, defaultValue = "false") boolean activeOnly) throws ThingsboardException {
+ SecurityUser user = getCurrentUser();
+ TenantId tenantId = user.getTenantId();
+ return checkNotNull(deviceProfileService.findDeviceProfileNamesByTenantId(tenantId, activeOnly));
+ }
+
}
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 fa16fbff81..7556ab589b 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
@@ -39,7 +39,7 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeInfo;
-import org.thingsboard.server.common.data.edge.EdgeInstallInstructions;
+import org.thingsboard.server.common.data.edge.EdgeInstructions;
import org.thingsboard.server.common.data.edge.EdgeSearchQuery;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@@ -59,7 +59,8 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeBulkImportService;
-import org.thingsboard.server.service.edge.instructions.EdgeInstallService;
+import org.thingsboard.server.service.edge.instructions.EdgeInstallInstructionsService;
+import org.thingsboard.server.service.edge.instructions.EdgeUpgradeInstructionsService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.entitiy.edge.TbEdgeService;
import org.thingsboard.server.service.security.model.SecurityUser;
@@ -101,7 +102,8 @@ public class EdgeController extends BaseController {
private final EdgeBulkImportService edgeBulkImportService;
private final TbEdgeService tbEdgeService;
private final Optional edgeRpcServiceOpt;
- private final Optional edgeInstallServiceOpt;
+ private final Optional edgeInstallServiceOpt;
+ private final Optional edgeUpgradeServiceOpt;
public static final String EDGE_ID = "edgeId";
public static final String EDGE_SECURITY_CHECK = "If the user has the authority of 'Tenant Administrator', the server checks that the edge is owned by the same tenant. " +
@@ -553,23 +555,41 @@ public class EdgeController extends BaseController {
return edgeBulkImportService.processBulkImport(request, user);
}
- @ApiOperation(value = "Get Edge Docker Install Instructions (getEdgeDockerInstallInstructions)",
- notes = "Get a docker install instructions for provided edge id." + TENANT_AUTHORITY_PARAGRAPH,
+ @ApiOperation(value = "Get Edge Install Instructions (getEdgeInstallInstructions)",
+ notes = "Get an install instructions for provided edge id." + TENANT_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
- @RequestMapping(value = "/edge/instructions/{edgeId}/{method}", method = RequestMethod.GET)
+ @RequestMapping(value = "/edge/instructions/install/{edgeId}/{method}", method = RequestMethod.GET)
@ResponseBody
- public EdgeInstallInstructions getEdgeDockerInstallInstructions(
+ public EdgeInstructions getEdgeInstallInstructions(
@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("edgeId") String strEdgeId,
- @ApiParam(value = "Installation method ('docker', 'ubuntu' or 'centos')")
+ @ApiParam(value = "Installation method ('docker', 'ubuntu' or 'centos')", allowableValues = "docker, ubuntu, centos")
@PathVariable("method") String installationMethod,
HttpServletRequest request) throws ThingsboardException {
if (isEdgesEnabled() && edgeInstallServiceOpt.isPresent()) {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
edgeId = checkNotNull(edgeId);
Edge edge = checkEdgeId(edgeId, Operation.READ);
- return checkNotNull(edgeInstallServiceOpt.get().getInstallInstructions(getTenantId(), edge, installationMethod, request));
+ return checkNotNull(edgeInstallServiceOpt.get().getInstallInstructions(edge, installationMethod, request));
+ } else {
+ throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL);
+ }
+ }
+
+ @ApiOperation(value = "Get Edge Upgrade Instructions (getEdgeUpgradeInstructions)",
+ notes = "Get an upgrade instructions for provided edge version." + TENANT_AUTHORITY_PARAGRAPH,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/edge/instructions/upgrade/{edgeVersion}/{method}", method = RequestMethod.GET)
+ @ResponseBody
+ public EdgeInstructions getEdgeUpgradeInstructions(
+ @ApiParam(value = "Edge version", required = true)
+ @PathVariable("edgeVersion") String edgeVersion,
+ @ApiParam(value = "Upgrade method ('docker', 'ubuntu' or 'centos')", allowableValues = "docker, ubuntu, centos")
+ @PathVariable("method") String method) throws Exception {
+ if (isEdgesEnabled() && edgeUpgradeServiceOpt.isPresent()) {
+ return checkNotNull(edgeUpgradeServiceOpt.get().getUpgradeInstructions(edgeVersion, method));
} else {
throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL);
}
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 e1f20be0e4..3af6ff2ed0 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,12 +15,17 @@
*/
package org.thingsboard.server.controller.plugin;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.RemovalCause;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.BeanCreationNotAllowedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
-import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PongMessage;
@@ -28,6 +33,7 @@ import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.adapter.NativeWebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.id.CustomerId;
@@ -39,49 +45,59 @@ import org.thingsboard.server.config.WebSocketConfiguration;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
+import org.thingsboard.server.service.subscription.SubscriptionErrorCode;
+import org.thingsboard.server.service.ws.AuthCmd;
import org.thingsboard.server.service.ws.SessionEvent;
import org.thingsboard.server.service.ws.WebSocketMsgEndpoint;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.WebSocketSessionType;
+import org.thingsboard.server.service.ws.WsCommandsWrapper;
+import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper;
+import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryCmdsWrapper;
+import javax.annotation.PostConstruct;
import javax.websocket.RemoteEndpoint;
import javax.websocket.SendHandler;
import javax.websocket.SendResult;
import javax.websocket.Session;
import java.io.IOException;
-import java.net.URI;
import java.security.InvalidParameterException;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import static org.thingsboard.server.service.ws.DefaultWebSocketService.NUMBER_OF_PING_ATTEMPTS;
@Service
@TbCoreComponent
@Slf4j
+@RequiredArgsConstructor
public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocketMsgEndpoint {
private final ConcurrentMap internalSessionMap = new ConcurrentHashMap<>();
private final ConcurrentMap externalSessionMap = new ConcurrentHashMap<>();
-
@Autowired @Lazy
private WebSocketService webSocketService;
-
@Autowired
private TbTenantProfileCache tenantProfileCache;
-
@Autowired
private RateLimitService rateLimitService;
+ @Autowired
+ private JwtAuthenticationProvider authenticationProvider;
@Value("${server.ws.send_timeout:5000}")
private long sendTimeout;
@@ -89,6 +105,8 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
private long pingTimeout;
@Value("${server.ws.max_queue_messages_per_session:1000}")
private int wsMaxQueueMessagesPerSession;
+ @Value("${server.ws.auth_timeout_ms:10000}")
+ private int authTimeoutMs;
private final ConcurrentMap blacklistedSessions = new ConcurrentHashMap<>();
@@ -97,28 +115,97 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
private final ConcurrentMap> regularUserSessionsMap = new ConcurrentHashMap<>();
private final ConcurrentMap> publicUserSessionsMap = new ConcurrentHashMap<>();
+ private Cache pendingSessions;
+
+ @PostConstruct
+ private void init() {
+ pendingSessions = Caffeine.newBuilder()
+ .expireAfterWrite(authTimeoutMs, TimeUnit.MILLISECONDS)
+ .removalListener((sessionId, sessionMd, removalCause) -> {
+ if (removalCause == RemovalCause.EXPIRED && sessionMd != null) {
+ try {
+ close(sessionMd.sessionRef, CloseStatus.POLICY_VIOLATION);
+ } catch (IOException e) {
+ log.warn("IO error", e);
+ }
+ }
+ })
+ .build();
+ }
+
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
try {
- SessionMetaData sessionMd = internalSessionMap.get(session.getId());
- if (sessionMd != null) {
- log.trace("[{}][{}] Processing {}", sessionMd.sessionRef.getSecurityCtx().getTenantId(), session.getId(), message.getPayload());
- webSocketService.handleWebSocketMsg(sessionMd.sessionRef, message.getPayload());
- } else {
+ SessionMetaData sessionMd = getSessionMd(session.getId());
+ if (sessionMd == null) {
log.trace("[{}] Failed to find session", session.getId());
session.close(CloseStatus.SERVER_ERROR.withReason("Session not found!"));
+ return;
}
+ sessionMd.onMsg(message.getPayload());
} catch (IOException e) {
log.warn("IO error", e);
}
}
+ void processMsg(SessionMetaData sessionMd, String msg) throws IOException {
+ WebSocketSessionRef sessionRef = sessionMd.sessionRef;
+ WsCommandsWrapper cmdsWrapper;
+ try {
+ switch (sessionRef.getSessionType()) {
+ case GENERAL:
+ cmdsWrapper = JacksonUtil.fromString(msg, WsCommandsWrapper.class);
+ break;
+ case TELEMETRY:
+ cmdsWrapper = JacksonUtil.fromString(msg, TelemetryCmdsWrapper.class).toCommonCmdsWrapper();
+ break;
+ case NOTIFICATIONS:
+ cmdsWrapper = JacksonUtil.fromString(msg, NotificationCmdsWrapper.class).toCommonCmdsWrapper();
+ break;
+ default:
+ return;
+ }
+ } catch (Exception e) {
+ log.debug("{} Failed to decode subscription cmd: {}", sessionRef, e.getMessage(), e);
+ if (sessionRef.getSecurityCtx() != null) {
+ webSocketService.sendError(sessionRef, 1, SubscriptionErrorCode.BAD_REQUEST, "Failed to parse the payload");
+ } else {
+ close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage()));
+ }
+ return;
+ }
+
+ if (sessionRef.getSecurityCtx() != null) {
+ log.trace("{} Processing {}", sessionRef, msg);
+ webSocketService.handleCommands(sessionRef, cmdsWrapper);
+ } else {
+ AuthCmd authCmd = cmdsWrapper.getAuthCmd();
+ if (authCmd == null) {
+ close(sessionRef, CloseStatus.POLICY_VIOLATION.withReason("Auth cmd is missing"));
+ return;
+ }
+ log.trace("{} Authenticating session", sessionRef);
+ SecurityUser securityCtx;
+ try {
+ securityCtx = authenticationProvider.authenticate(authCmd.getToken());
+ } catch (Exception e) {
+ close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage()));
+ return;
+ }
+ sessionRef.setSecurityCtx(securityCtx);
+ pendingSessions.invalidate(sessionMd.session.getId());
+ establishSession(sessionMd.session, sessionRef, sessionMd);
+
+ webSocketService.handleCommands(sessionRef, cmdsWrapper);
+ }
+ }
+
@Override
protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
try {
- SessionMetaData sessionMd = internalSessionMap.get(session.getId());
+ SessionMetaData sessionMd = getSessionMd(session.getId());
if (sessionMd != null) {
- log.trace("[{}][{}] Processing pong response {}", sessionMd.sessionRef.getSecurityCtx().getTenantId(), session.getId(), message.getPayload());
+ log.trace("{} Processing pong response {}", sessionMd.sessionRef, message.getPayload());
sessionMd.processPongMessage(System.currentTimeMillis());
} else {
log.trace("[{}] Failed to find session", session.getId());
@@ -139,23 +226,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
nativeSession.getAsyncRemote().setSendTimeout(sendTimeout);
}
}
- String internalSessionId = session.getId();
WebSocketSessionRef sessionRef = toRef(session);
- String externalSessionId = sessionRef.getSessionId();
-
- if (!checkLimits(session, sessionRef)) {
- return;
- }
- var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
- int wsTenantProfileQueueLimit = tenantProfileConfiguration != null ?
- tenantProfileConfiguration.getWsMsgQueueLimitPerSession() : wsMaxQueueMessagesPerSession;
- internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef,
- (wsTenantProfileQueueLimit > 0 && wsTenantProfileQueueLimit < wsMaxQueueMessagesPerSession) ?
- wsTenantProfileQueueLimit : wsMaxQueueMessagesPerSession));
-
- externalSessionMap.put(externalSessionId, internalSessionId);
- processInWebSocketService(sessionRef, SessionEvent.onEstablished());
- log.info("[{}][{}][{}] Session is opened from address: {}", sessionRef.getSecurityCtx().getTenantId(), externalSessionId, session.getId(), session.getRemoteAddress());
+ log.debug("[{}][{}] Session opened from address: {}", sessionRef.getSessionId(), session.getId(), session.getRemoteAddress());
+ establishSession(session, sessionRef, null);
} catch (InvalidParameterException e) {
log.warn("[{}] Failed to start session", session.getId(), e);
session.close(CloseStatus.BAD_DATA.withReason(e.getMessage()));
@@ -165,10 +238,36 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
+ private void establishSession(WebSocketSession session, WebSocketSessionRef sessionRef, SessionMetaData sessionMd) throws IOException {
+ if (sessionRef.getSecurityCtx() != null) {
+ if (!checkLimits(session, sessionRef)) {
+ return;
+ }
+ int maxMsgQueueSize = Optional.ofNullable(getTenantProfileConfiguration(sessionRef))
+ .map(DefaultTenantProfileConfiguration::getWsMsgQueueLimitPerSession)
+ .filter(profileLimit -> profileLimit > 0 && profileLimit < wsMaxQueueMessagesPerSession)
+ .orElse(wsMaxQueueMessagesPerSession);
+ if (sessionMd == null) {
+ sessionMd = new SessionMetaData(session, sessionRef);
+ }
+ sessionMd.setMaxMsgQueueSize(maxMsgQueueSize);
+
+ internalSessionMap.put(session.getId(), sessionMd);
+ externalSessionMap.put(sessionRef.getSessionId(), session.getId());
+ processInWebSocketService(sessionRef, SessionEvent.onEstablished());
+ log.info("[{}][{}][{}][{}] Session established from address: {}", sessionRef.getSecurityCtx().getTenantId(),
+ sessionRef.getSecurityCtx().getId(), sessionRef.getSessionId(), session.getId(), session.getRemoteAddress());
+ } else {
+ sessionMd = new SessionMetaData(session, sessionRef);
+ pendingSessions.put(session.getId(), sessionMd);
+ externalSessionMap.put(sessionRef.getSessionId(), session.getId());
+ }
+ }
+
@Override
public void handleTransportError(WebSocketSession session, Throwable tError) throws Exception {
super.handleTransportError(session, tError);
- SessionMetaData sessionMd = internalSessionMap.get(session.getId());
+ SessionMetaData sessionMd = getSessionMd(session.getId());
if (sessionMd != null) {
processInWebSocketService(sessionMd.sessionRef, SessionEvent.onError(tError));
} else {
@@ -181,63 +280,87 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
super.afterConnectionClosed(session, closeStatus);
SessionMetaData sessionMd = internalSessionMap.remove(session.getId());
+ if (sessionMd == null) {
+ sessionMd = pendingSessions.asMap().remove(session.getId());
+ }
if (sessionMd != null) {
- cleanupLimits(session, sessionMd.sessionRef);
externalSessionMap.remove(sessionMd.sessionRef.getSessionId());
- processInWebSocketService(sessionMd.sessionRef, SessionEvent.onClosed());
- log.info("[{}][{}][{}] Session is closed", sessionMd.sessionRef.getSecurityCtx().getTenantId(), sessionMd.sessionRef.getSessionId(), session.getId());
+ if (sessionMd.sessionRef.getSecurityCtx() != null) {
+ cleanupLimits(session, sessionMd.sessionRef);
+ processInWebSocketService(sessionMd.sessionRef, SessionEvent.onClosed());
+ }
+ log.info("{} Session is closed", sessionMd.sessionRef);
} else {
log.info("[{}] Session is closed", session.getId());
}
}
private void processInWebSocketService(WebSocketSessionRef sessionRef, SessionEvent event) {
+ if (sessionRef.getSecurityCtx() == null) {
+ return;
+ }
try {
- webSocketService.handleWebSocketSessionEvent(sessionRef, event);
+ webSocketService.handleSessionEvent(sessionRef, event);
} catch (BeanCreationNotAllowedException e) {
- log.warn("[{}] Failed to close session due to possible shutdown state", sessionRef.getSessionId());
+ log.warn("{} Failed to close session due to possible shutdown state", sessionRef);
}
}
- private WebSocketSessionRef toRef(WebSocketSession session) throws IOException {
- URI sessionUri = session.getUri();
- String path = sessionUri.getPath();
- path = path.substring(WebSocketConfiguration.WS_PLUGIN_PREFIX.length());
- if (path.length() == 0) {
- throw new IllegalArgumentException("URL should contain plugin token!");
+ private WebSocketSessionRef toRef(WebSocketSession session) {
+ String path = session.getUri().getPath();
+ WebSocketSessionType sessionType;
+ if (path.equals(WebSocketConfiguration.WS_API_ENDPOINT)) {
+ sessionType = WebSocketSessionType.GENERAL;
+ } else {
+ String type = StringUtils.substringAfter(path, WebSocketConfiguration.WS_PLUGINS_ENDPOINT);
+ sessionType = WebSocketSessionType.forName(type)
+ .orElseThrow(() -> new InvalidParameterException("Unknown session type"));
}
- String[] pathElements = path.split("/");
- String serviceToken = pathElements[0];
- WebSocketSessionType sessionType = WebSocketSessionType.forName(serviceToken)
- .orElseThrow(() -> new InvalidParameterException("Can't find plugin with specified token!"));
- SecurityUser currentUser = (SecurityUser) ((Authentication) session.getPrincipal()).getPrincipal();
+ SecurityUser securityCtx = null;
+ String token = StringUtils.substringAfter(session.getUri().getQuery(), "token=");
+ if (StringUtils.isNotEmpty(token)) {
+ securityCtx = authenticationProvider.authenticate(token);
+ }
return WebSocketSessionRef.builder()
.sessionId(UUID.randomUUID().toString())
- .securityCtx(currentUser)
+ .securityCtx(securityCtx)
.localAddress(session.getLocalAddress())
.remoteAddress(session.getRemoteAddress())
.sessionType(sessionType)
.build();
}
+ private SessionMetaData getSessionMd(String internalSessionId) {
+ SessionMetaData sessionMd = internalSessionMap.get(internalSessionId);
+ if (sessionMd == null) {
+ sessionMd = pendingSessions.getIfPresent(internalSessionId);
+ }
+ return sessionMd;
+ }
+
class SessionMetaData implements SendHandler {
private final WebSocketSession session;
private final RemoteEndpoint.Async asyncRemote;
private final WebSocketSessionRef sessionRef;
final AtomicBoolean isSending = new AtomicBoolean(false);
- private final Queue> msgQueue;
+ private final Queue> outboundMsgQueue = new ConcurrentLinkedQueue<>();
+ private final AtomicInteger outboundMsgQueueSize = new AtomicInteger();
+ @Setter
+ private int maxMsgQueueSize = wsMaxQueueMessagesPerSession;
+
+ private final Queue inboundMsgQueue = new ConcurrentLinkedQueue<>();
+ private final Lock inboundMsgQueueProcessorLock = new ReentrantLock();
private volatile long lastActivityTime;
- SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef, int maxMsgQueuePerSession) {
+ SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef) {
super();
this.session = session;
Session nativeSession = ((NativeWebSocketSession) session).getNativeSession(Session.class);
this.asyncRemote = nativeSession.getAsyncRemote();
this.sessionRef = sessionRef;
- this.msgQueue = new LinkedBlockingQueue<>(maxMsgQueuePerSession);
this.lastActivityTime = System.currentTimeMillis();
}
@@ -245,13 +368,13 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
try {
long timeSinceLastActivity = currentTime - lastActivityTime;
if (timeSinceLastActivity >= pingTimeout) {
- log.warn("[{}] Closing session due to ping timeout", session.getId());
+ log.warn("{} Closing session due to ping timeout", sessionRef);
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
} else if (timeSinceLastActivity >= pingTimeout / NUMBER_OF_PING_ATTEMPTS) {
sendMsg(TbWebSocketPingMsg.INSTANCE);
}
} catch (Exception e) {
- log.trace("[{}] Failed to send ping msg", session.getId(), e);
+ log.trace("{} Failed to send ping msg", sessionRef, e);
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
}
}
@@ -260,9 +383,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
try {
close(this.sessionRef, reason);
} catch (IOException ioe) {
- log.trace("[{}] Session transport error", session.getId(), ioe);
+ log.trace("{} Session transport error", sessionRef, ioe);
} finally {
- msgQueue.clear();
+ outboundMsgQueue.clear();
}
}
@@ -275,19 +398,14 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
void sendMsg(TbWebSocketMsg> msg) {
- try {
- msgQueue.add(msg);
- } catch (RuntimeException e) {
- if (log.isTraceEnabled()) {
- log.trace("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId(), e);
- } else {
- log.info("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId());
- }
+ if (outboundMsgQueueSize.get() < maxMsgQueueSize) {
+ outboundMsgQueue.add(msg);
+ outboundMsgQueueSize.incrementAndGet();
+ processNextMsg();
+ } else {
+ log.info("{} Session closed due to updates queue size exceeded", sessionRef);
closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!"));
- return;
}
-
- processNextMsg();
}
private void sendMsgInternal(TbWebSocketMsg> msg) {
@@ -303,7 +421,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
processNextMsg();
}
} catch (Exception e) {
- log.trace("[{}] Failed to send msg", session.getId(), e);
+ log.trace("{} Failed to send msg", sessionRef, e);
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
}
}
@@ -311,7 +429,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
@Override
public void onResult(SendResult result) {
if (!result.isOK()) {
- log.trace("[{}] Failed to send msg", session.getId(), result.getException());
+ log.trace("{} Failed to send msg", sessionRef, result.getException());
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
return;
}
@@ -321,22 +439,45 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
private void processNextMsg() {
- if (msgQueue.isEmpty() || !isSending.compareAndSet(false, true)) {
+ if (outboundMsgQueue.isEmpty() || !isSending.compareAndSet(false, true)) {
return;
}
- TbWebSocketMsg> msg = msgQueue.poll();
+ TbWebSocketMsg> msg = outboundMsgQueue.poll();
if (msg != null) {
+ outboundMsgQueueSize.decrementAndGet();
sendMsgInternal(msg);
} else {
isSending.set(false);
}
}
+
+ public void onMsg(String msg) throws IOException {
+ inboundMsgQueue.add(msg);
+ tryProcessInboundMsgs();
+ }
+
+ void tryProcessInboundMsgs() throws IOException {
+ while (!inboundMsgQueue.isEmpty()) {
+ if (inboundMsgQueueProcessorLock.tryLock()) {
+ try {
+ String msg;
+ while ((msg = inboundMsgQueue.poll()) != null) {
+ processMsg(this, msg);
+ }
+ } finally {
+ inboundMsgQueueProcessorLock.unlock();
+ }
+ } else {
+ return;
+ }
+ }
+ }
}
@Override
public void send(WebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException {
+ log.debug("{} Sending {}", sessionRef, msg);
String externalId = sessionRef.getSessionId();
- log.debug("[{}] Processing {}", externalId, msg);
String internalId = externalSessionMap.get(externalId);
if (internalId != null) {
SessionMetaData sessionMd = internalSessionMap.get(internalId);
@@ -344,13 +485,12 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
TenantId tenantId = sessionRef.getSecurityCtx().getTenantId();
if (!rateLimitService.checkRateLimit(LimitedApi.WS_UPDATES_PER_SESSION, tenantId, (Object) sessionRef.getSessionId())) {
if (blacklistedSessions.putIfAbsent(externalId, sessionRef) == null) {
- log.info("[{}][{}][{}] Failed to process session update. Max session updates limit reached"
- , tenantId, sessionRef.getSecurityCtx().getId(), externalId);
+ log.info("{} Failed to process session update. Max session updates limit reached", sessionRef);
sessionMd.sendMsg("{\"subscriptionId\":" + subscriptionId + ", \"errorCode\":" + ThingsboardErrorCode.TOO_MANY_UPDATES.getErrorCode() + ", \"errorMsg\":\"Too many updates!\"}");
}
return;
} else {
- log.debug("[{}][{}][{}] Session is no longer blacklisted.", tenantId, sessionRef.getSecurityCtx().getId(), externalId);
+ log.debug("{} Session is no longer blacklisted.", sessionRef);
blacklistedSessions.remove(externalId);
}
sessionMd.sendMsg(msg);
@@ -381,10 +521,10 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
@Override
public void close(WebSocketSessionRef sessionRef, CloseStatus reason) throws IOException {
String externalId = sessionRef.getSessionId();
- log.debug("[{}] Processing close request", externalId);
+ log.debug("{} Processing close request", sessionRef.toString());
String internalId = externalSessionMap.get(externalId);
if (internalId != null) {
- SessionMetaData sessionMd = internalSessionMap.get(internalId);
+ SessionMetaData sessionMd = getSessionMd(internalId);
if (sessionMd != null) {
sessionMd.session.close(reason);
} else {
@@ -395,7 +535,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
- private boolean checkLimits(WebSocketSession session, WebSocketSessionRef sessionRef) throws Exception {
+ private boolean checkLimits(WebSocketSession session, WebSocketSessionRef sessionRef) throws IOException {
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
if (tenantProfileConfiguration == null) {
return true;
@@ -411,10 +551,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
if (!limitAllowed) {
- log.info("[{}][{}][{}] Failed to start session. Max tenant sessions limit reached"
- , sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId);
- session.close(CloseStatus.POLICY_VIOLATION.withReason("Max tenant sessions limit reached!"));
- return false;
+ log.info("{} Failed to start session. Max tenant sessions limit reached", sessionRef.toString());
+ session.close(CloseStatus.POLICY_VIOLATION.withReason("Max tenant sessions limit reached!"));
+ return false;
}
}
@@ -428,10 +567,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
if (!limitAllowed) {
- log.info("[{}][{}][{}] Failed to start session. Max customer sessions limit reached"
- , sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId);
- session.close(CloseStatus.POLICY_VIOLATION.withReason("Max customer sessions limit reached"));
- return false;
+ log.info("{} Failed to start session. Max customer sessions limit reached", sessionRef.toString());
+ session.close(CloseStatus.POLICY_VIOLATION.withReason("Max customer sessions limit reached"));
+ return false;
}
}
if (tenantProfileConfiguration.getMaxWsSessionsPerRegularUser() > 0
@@ -444,10 +582,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
if (!limitAllowed) {
- log.info("[{}][{}][{}] Failed to start session. Max regular user sessions limit reached"
- , sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId);
- session.close(CloseStatus.POLICY_VIOLATION.withReason("Max regular user sessions limit reached"));
- return false;
+ log.info("{} Failed to start session. Max regular user sessions limit reached", sessionRef.toString());
+ session.close(CloseStatus.POLICY_VIOLATION.withReason("Max regular user sessions limit reached"));
+ return false;
}
}
if (tenantProfileConfiguration.getMaxWsSessionsPerPublicUser() > 0
@@ -460,10 +597,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
if (!limitAllowed) {
- log.info("[{}][{}][{}] Failed to start session. Max public user sessions limit reached"
- , sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId);
- session.close(CloseStatus.POLICY_VIOLATION.withReason("Max public user sessions limit reached"));
- return false;
+ log.info("{} Failed to start session. Max public user sessions limit reached", sessionRef.toString());
+ session.close(CloseStatus.POLICY_VIOLATION.withReason("Max public user sessions limit reached"));
+ return false;
}
}
}
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/DefaultEdgeInstallInstructionsService.java
similarity index 78%
rename from application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java
rename to application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallInstructionsService.java
index dd17f83317..ba2cd06ee1 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/DefaultEdgeInstallInstructionsService.java
@@ -16,13 +16,14 @@
package org.thingsboard.server.service.edge.instructions;
import lombok.RequiredArgsConstructor;
+import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
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 org.thingsboard.server.common.data.edge.EdgeInstructions;
+import org.thingsboard.server.dao.util.DeviceConnectivityUtil;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.install.InstallScripts;
@@ -37,11 +38,11 @@ import java.nio.file.Paths;
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true")
@TbCoreComponent
-public class DefaultEdgeInstallService implements EdgeInstallService {
+public class DefaultEdgeInstallInstructionsService implements EdgeInstallInstructionsService {
private static final String EDGE_DIR = "edge";
-
- private static final String EDGE_INSTALL_INSTRUCTIONS_DIR = "install_instructions";
+ private static final String INSTRUCTIONS_DIR = "instructions";
+ private static final String INSTALL_DIR = "install";
private final InstallScripts installScripts;
@@ -52,10 +53,11 @@ public class DefaultEdgeInstallService implements EdgeInstallService {
private boolean sslEnabled;
@Value("${app.version:unknown}")
+ @Setter
private String appVersion;
@Override
- public EdgeInstallInstructions getInstallInstructions(TenantId tenantId, Edge edge, String installationMethod, HttpServletRequest request) {
+ public EdgeInstructions getInstallInstructions(Edge edge, String installationMethod, HttpServletRequest request) {
switch (installationMethod.toLowerCase()) {
case "docker":
return getDockerInstallInstructions(edge, request);
@@ -68,41 +70,41 @@ public class DefaultEdgeInstallService implements EdgeInstallService {
}
}
- private EdgeInstallInstructions getDockerInstallInstructions(Edge edge, HttpServletRequest request) {
+ private EdgeInstructions getDockerInstallInstructions(Edge edge, HttpServletRequest request) {
String dockerInstallInstructions = readFile(resolveFile("docker", "instructions.md"));
String baseUrl = request.getServerName();
- if (baseUrl.contains("localhost") || baseUrl.contains("127.0.0.1")) {
- String localhostWarning = readFile(resolveFile("docker", "localhost_warning.md"));
- dockerInstallInstructions = dockerInstallInstructions.replace("${LOCALHOST_WARNING}", localhostWarning);
- dockerInstallInstructions = dockerInstallInstructions.replace("${BASE_URL}", "!!!REPLACE_ME_TO_HOST_IP_ADDRESS!!!");
+
+ if (DeviceConnectivityUtil.isLocalhost(baseUrl)) {
+ dockerInstallInstructions = dockerInstallInstructions.replace("${EXTRA_HOSTS}", "extra_hosts:\n - \"host.docker.internal:host-gateway\"\n");
+ dockerInstallInstructions = dockerInstallInstructions.replace("${BASE_URL}", "host.docker.internal");
} else {
- dockerInstallInstructions = dockerInstallInstructions.replace("${LOCALHOST_WARNING}", "");
+ dockerInstallInstructions = dockerInstallInstructions.replace("${EXTRA_HOSTS}", "");
dockerInstallInstructions = dockerInstallInstructions.replace("${BASE_URL}", baseUrl);
}
String edgeVersion = appVersion + "EDGE";
edgeVersion = edgeVersion.replace("-SNAPSHOT", "");
dockerInstallInstructions = dockerInstallInstructions.replace("${TB_EDGE_VERSION}", edgeVersion);
dockerInstallInstructions = replacePlaceholders(dockerInstallInstructions, edge);
- return new EdgeInstallInstructions(dockerInstallInstructions);
+ return new EdgeInstructions(dockerInstallInstructions);
}
- private EdgeInstallInstructions getUbuntuInstallInstructions(Edge edge, HttpServletRequest request) {
+ private EdgeInstructions getUbuntuInstallInstructions(Edge edge, HttpServletRequest request) {
String ubuntuInstallInstructions = readFile(resolveFile("ubuntu", "instructions.md"));
ubuntuInstallInstructions = replacePlaceholders(ubuntuInstallInstructions, edge);
ubuntuInstallInstructions = ubuntuInstallInstructions.replace("${BASE_URL}", request.getServerName());
String edgeVersion = appVersion.replace("-SNAPSHOT", "");
ubuntuInstallInstructions = ubuntuInstallInstructions.replace("${TB_EDGE_VERSION}", edgeVersion);
- return new EdgeInstallInstructions(ubuntuInstallInstructions);
+ return new EdgeInstructions(ubuntuInstallInstructions);
}
- private EdgeInstallInstructions getCentosInstallInstructions(Edge edge, HttpServletRequest request) {
+ private EdgeInstructions getCentosInstallInstructions(Edge edge, HttpServletRequest request) {
String centosInstallInstructions = readFile(resolveFile("centos", "instructions.md"));
centosInstallInstructions = replacePlaceholders(centosInstallInstructions, edge);
centosInstallInstructions = centosInstallInstructions.replace("${BASE_URL}", request.getServerName());
String edgeVersion = appVersion.replace("-SNAPSHOT", "");
centosInstallInstructions = centosInstallInstructions.replace("${TB_EDGE_VERSION}", edgeVersion);
- return new EdgeInstallInstructions(centosInstallInstructions);
+ return new EdgeInstructions(centosInstallInstructions);
}
private String replacePlaceholders(String instructions, Edge edge) {
@@ -127,6 +129,6 @@ public class DefaultEdgeInstallService implements EdgeInstallService {
}
private Path getEdgeInstallInstructionsDir() {
- return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, EDGE_INSTALL_INSTRUCTIONS_DIR);
+ return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, INSTRUCTIONS_DIR, INSTALL_DIR);
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java
new file mode 100644
index 0000000000..940a813fc6
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java
@@ -0,0 +1,158 @@
+/**
+ * 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.edge.instructions;
+
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.EdgeUpgradeInfo;
+import org.thingsboard.server.common.data.edge.EdgeInstructions;
+import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.service.install.InstallScripts;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+@Slf4j
+@RequiredArgsConstructor
+@ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true")
+@TbCoreComponent
+public class DefaultEdgeUpgradeInstructionsService implements EdgeUpgradeInstructionsService {
+
+ private static final Map upgradeVersionHashMap = new HashMap<>();
+
+ private static final String EDGE_DIR = "edge";
+ private static final String INSTRUCTIONS_DIR = "instructions";
+ private static final String UPGRADE_DIR = "upgrade";
+
+ private final InstallScripts installScripts;
+
+ @Value("${app.version:unknown}")
+ @Setter
+ private String appVersion;
+
+ @Override
+ public EdgeInstructions getUpgradeInstructions(String edgeVersion, String upgradeMethod) {
+ String tbVersion = appVersion.replace("-SNAPSHOT", "");
+ String currentEdgeVersion = convertEdgeVersionToDocsFormat(edgeVersion);
+ switch (upgradeMethod.toLowerCase()) {
+ case "docker":
+ return getDockerUpgradeInstructions(tbVersion, currentEdgeVersion);
+ case "ubuntu":
+ case "centos":
+ return getLinuxUpgradeInstructions(tbVersion, currentEdgeVersion, upgradeMethod.toLowerCase());
+ default:
+ throw new IllegalArgumentException("Unsupported upgrade method for Edge: " + upgradeMethod);
+ }
+ }
+
+ @Override
+ public void updateInstructionMap(Map map) {
+ for (String key : map.keySet()) {
+ upgradeVersionHashMap.put(key, map.get(key));
+ }
+ }
+
+ private EdgeInstructions getDockerUpgradeInstructions(String tbVersion, String currentEdgeVersion) {
+ EdgeUpgradeInfo edgeUpgradeInfo = upgradeVersionHashMap.get(currentEdgeVersion);
+ if (edgeUpgradeInfo == null || edgeUpgradeInfo.getNextEdgeVersion() == null || tbVersion.equals(currentEdgeVersion)) {
+ return new EdgeInstructions("Edge upgrade instruction for " + currentEdgeVersion + "EDGE is not available.");
+ }
+ StringBuilder result = new StringBuilder(readFile(resolveFile("docker", "upgrade_preparing.md")));
+ while (edgeUpgradeInfo.getNextEdgeVersion() != null || !tbVersion.equals(currentEdgeVersion)) {
+ String edgeVersion = edgeUpgradeInfo.getNextEdgeVersion();
+ String dockerUpgradeInstructions = readFile(resolveFile("docker", "instructions.md"));
+ if (edgeUpgradeInfo.isRequiresUpdateDb()) {
+ String upgradeDb = readFile(resolveFile("docker", "upgrade_db.md"));
+ dockerUpgradeInstructions = dockerUpgradeInstructions.replace("${UPGRADE_DB}", upgradeDb);
+ } else {
+ dockerUpgradeInstructions = dockerUpgradeInstructions.replace("${UPGRADE_DB}", "");
+ }
+ dockerUpgradeInstructions = dockerUpgradeInstructions.replace("${TB_EDGE_VERSION}", edgeVersion + "EDGE");
+ dockerUpgradeInstructions = dockerUpgradeInstructions.replace("${FROM_TB_EDGE_VERSION}", currentEdgeVersion + "EDGE");
+ currentEdgeVersion = edgeVersion;
+ edgeUpgradeInfo = upgradeVersionHashMap.get(edgeUpgradeInfo.getNextEdgeVersion());
+ result.append(dockerUpgradeInstructions);
+ }
+ String startService = readFile(resolveFile("docker", "start_service.md"));
+ startService = startService.replace("${TB_EDGE_VERSION}", currentEdgeVersion + "EDGE");
+ result.append(startService);
+ return new EdgeInstructions(result.toString());
+ }
+
+ private EdgeInstructions getLinuxUpgradeInstructions(String tbVersion, String currentEdgeVersion, String os) {
+ EdgeUpgradeInfo edgeUpgradeInfo = upgradeVersionHashMap.get(currentEdgeVersion);
+ if (edgeUpgradeInfo == null || edgeUpgradeInfo.getNextEdgeVersion() == null || tbVersion.equals(currentEdgeVersion)) {
+ return new EdgeInstructions("Edge upgrade instruction for " + currentEdgeVersion + "EDGE is not available.");
+ }
+ String upgrade_preparing = readFile(resolveFile("upgrade_preparing.md"));
+ upgrade_preparing = upgrade_preparing.replace("${OS}", os.equals("centos") ? "RHEL/CentOS 7/8" : "Ubuntu");
+ StringBuilder result = new StringBuilder(upgrade_preparing);
+ while (edgeUpgradeInfo.getNextEdgeVersion() != null || !tbVersion.equals(currentEdgeVersion)) {
+ String edgeVersion = edgeUpgradeInfo.getNextEdgeVersion();
+ String linuxUpgradeInstructions = readFile(resolveFile(os, "instructions.md"));
+ if (edgeUpgradeInfo.isRequiresUpdateDb()) {
+ String upgradeDb = readFile(resolveFile("upgrade_db.md"));
+ linuxUpgradeInstructions = linuxUpgradeInstructions.replace("${UPGRADE_DB}", upgradeDb);
+ } else {
+ linuxUpgradeInstructions = linuxUpgradeInstructions.replace("${UPGRADE_DB}", "");
+ }
+ linuxUpgradeInstructions = linuxUpgradeInstructions.replace("${TB_EDGE_TAG}", getTagVersion(edgeVersion));
+ linuxUpgradeInstructions = linuxUpgradeInstructions.replace("${FROM_TB_EDGE_TAG}", getTagVersion(currentEdgeVersion));
+ linuxUpgradeInstructions = linuxUpgradeInstructions.replace("${TB_EDGE_VERSION}", edgeVersion);
+ linuxUpgradeInstructions = linuxUpgradeInstructions.replace("${FROM_TB_EDGE_VERSION}", currentEdgeVersion);
+ currentEdgeVersion = edgeVersion;
+ edgeUpgradeInfo = upgradeVersionHashMap.get(edgeUpgradeInfo.getNextEdgeVersion());
+ result.append(linuxUpgradeInstructions);
+ }
+ String startService = readFile(resolveFile("start_service.md"));
+ result.append(startService);
+ return new EdgeInstructions(result.toString());
+ }
+
+ private String getTagVersion(String version) {
+ return version.endsWith(".0") ? version.substring(0, version.length() - 2) : version;
+ }
+
+ private String convertEdgeVersionToDocsFormat(String edgeVersion) {
+ return edgeVersion.replace("_", ".").substring(2);
+ }
+
+ private String readFile(Path file) {
+ try {
+ return Files.readString(file);
+ } catch (IOException e) {
+ log.warn("Failed to read file: {}", file, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private Path resolveFile(String subDir, String... subDirs) {
+ return getEdgeInstallInstructionsDir().resolve(Paths.get(subDir, subDirs));
+ }
+
+ private Path getEdgeInstallInstructionsDir() {
+ return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, INSTRUCTIONS_DIR, UPGRADE_DIR);
+ }
+}
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/EdgeInstallInstructionsService.java
similarity index 72%
rename from application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallService.java
rename to application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallInstructionsService.java
index 20cac25e33..9df1f86e86 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/EdgeInstallInstructionsService.java
@@ -16,12 +16,13 @@
package org.thingsboard.server.service.edge.instructions;
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 org.thingsboard.server.common.data.edge.EdgeInstructions;
import javax.servlet.http.HttpServletRequest;
-public interface EdgeInstallService {
+public interface EdgeInstallInstructionsService {
- EdgeInstallInstructions getInstallInstructions(TenantId tenantId, Edge edge, String installationMethod, HttpServletRequest request);
+ EdgeInstructions getInstallInstructions(Edge edge, String installationMethod, HttpServletRequest request);
+
+ void setAppVersion(String version);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeInstructionsService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeInstructionsService.java
new file mode 100644
index 0000000000..be40bc6c89
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeInstructionsService.java
@@ -0,0 +1,30 @@
+/**
+ * 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.edge.instructions;
+
+import org.thingsboard.server.common.data.EdgeUpgradeInfo;
+import org.thingsboard.server.common.data.edge.EdgeInstructions;
+
+import java.util.Map;
+
+public interface EdgeUpgradeInstructionsService {
+
+ EdgeInstructions getUpgradeInstructions(String edgeVersion, String upgradeMethod);
+
+ void updateInstructionMap(Map upgradeVersions);
+
+ void setAppVersion(String version);
+}
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 e1dc1c667f..c9fb796831 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
@@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
+import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
@@ -794,6 +795,7 @@ public final class EdgeGrpcSession implements Closeable {
if (edge.getSecret().equals(request.getEdgeSecret())) {
sessionOpenListener.accept(edge.getId(), this);
this.edgeVersion = request.getEdgeVersion();
+ processSaveEdgeVersionAsAttribute(request.getEdgeVersion().name());
return ConnectResponseMsg.newBuilder()
.setResponseCode(ConnectResponseCode.ACCEPTED)
.setErrorMsg("")
@@ -819,6 +821,11 @@ public final class EdgeGrpcSession implements Closeable {
.setConfiguration(EdgeConfiguration.getDefaultInstance()).build();
}
+ private void processSaveEdgeVersionAsAttribute(String edgeVersion) {
+ AttributeKvEntry attributeKvEntry = new BaseAttributeKvEntry(new StringDataEntry("edgeVersion", edgeVersion), System.currentTimeMillis());
+ ctx.getAttributesService().save(this.tenantId, this.edge.getId(), DataConstants.SERVER_SCOPE, attributeKvEntry);
+ }
+
@Override
public void close() {
log.debug("[{}][{}] Closing session", this.tenantId, sessionId);
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 f404edb8b1..c2444390f6 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
@@ -766,6 +766,10 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
} catch (Exception e) {
log.warn("Failed to execute update script for save attributes rule nodes due to: ", e);
}
+ try {
+ connection.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_asset_profile_id ON asset(tenant_id, asset_profile_id);");
+ } catch (Exception e) {
+ }
});
break;
default:
diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java
index e711bf2682..8ff0124481 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java
@@ -17,17 +17,21 @@ package org.thingsboard.server.service.install.update;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.HasImage;
import org.thingsboard.server.common.data.id.EntityId;
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.dao.Dao;
import org.thingsboard.server.dao.asset.AssetProfileDao;
import org.thingsboard.server.dao.dashboard.DashboardDao;
import org.thingsboard.server.dao.device.DeviceProfileDao;
import org.thingsboard.server.dao.resource.ImageService;
+import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.widget.WidgetTypeDao;
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
@@ -41,6 +45,7 @@ public class ImagesUpdater {
private final ImageService imageService;
private final WidgetsBundleDao widgetsBundleDao;
private final WidgetTypeDao widgetTypeDao;
+ private final TenantDao tenantDao;
private final DashboardDao dashboardDao;
private final DeviceProfileDao deviceProfileDao;
private final AssetProfileDao assetProfileDao;
@@ -59,8 +64,7 @@ public class ImagesUpdater {
public void updateDashboardsImages() {
log.info("Updating dashboards images...");
- var dashboardsIds = new PageDataIterable<>(dashboardDao::findAllIds, 1024);
- updateImages(dashboardsIds, "dashboard", imageService::replaceBase64WithImageUrl, dashboardDao);
+ updateImages("dashboard", dashboardDao::findIdsByTenantId, imageService::replaceBase64WithImageUrl, dashboardDao);
}
public void createSystemImages(Dashboard defaultDashboard) {
@@ -108,11 +112,44 @@ public class ImagesUpdater {
private void updateImages(Iterable extends EntityId> entitiesIds, String type,
Function updater, Dao dao) {
+ int totalCount = 0;
int updatedCount = 0;
+ var counts = updateImages(entitiesIds, type, updater, dao, totalCount, updatedCount);
+ totalCount = counts[0];
+ updatedCount = counts[1];
+ log.info("Updated {} {}s out of {}", updatedCount, type, totalCount);
+ }
+
+ private void updateImages(String type, BiFunction> entityIdsByTenantId,
+ Function updater, Dao dao) {
+ int tenantCount = 0;
int totalCount = 0;
+ int updatedCount = 0;
+ var tenantIds = new PageDataIterable<>(tenantDao::findTenantsIds, 128);
+ for (var tenantId : tenantIds) {
+ tenantCount++;
+ var entitiesIds = new PageDataIterable<>(link -> entityIdsByTenantId.apply(tenantId, link), 128);
+ var counts = updateImages(entitiesIds, type, updater, dao, totalCount, updatedCount);
+ totalCount = counts[0];
+ updatedCount = counts[1];
+ if (tenantCount % 100 == 0) {
+ log.info("Update {}s images: processed {} tenants so far", type, tenantCount);
+ }
+ }
+ log.info("Updated {} {}s out of {}", updatedCount, type, totalCount);
+ }
+
+ private int[] updateImages(Iterable extends EntityId> entitiesIds, String type,
+ Function updater, Dao dao, int totalCount, int updatedCount) {
for (EntityId id : entitiesIds) {
totalCount++;
- E entity = dao.findById(TenantId.SYS_TENANT_ID, id.getId());
+ E entity;
+ try {
+ entity = dao.findById(TenantId.SYS_TENANT_ID, id.getId());
+ } catch (Exception e) {
+ log.error("Failed to update {} images: error fetching {} by id [{}]: {}", type, type, id.getId(), StringUtils.abbreviate(e.toString(), 1000));
+ continue;
+ }
try {
boolean updated = updater.apply(entity);
if (updated) {
@@ -127,7 +164,7 @@ public class ImagesUpdater {
log.info("Processed {} {}s so far", totalCount, type);
}
}
- log.info("Updated {} {}s out of {}", updatedCount, type, totalCount);
+ return new int[]{totalCount, updatedCount};
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java
index 9f33466793..02c712a640 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java
@@ -23,7 +23,6 @@ import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
-import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import java.util.Optional;
@@ -49,7 +48,7 @@ public class DefaultTokenOutdatingService implements TokenOutdatingService {
}
@Override
- public boolean isOutdated(JwtToken token, UserId userId) {
+ public boolean isOutdated(String token, UserId userId) {
Claims claims = tokenFactory.parseTokenClaims(token).getBody();
long issueTime = claims.getIssuedAt().getTime();
String sessionId = claims.get("sessionId", String.class);
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java
index 20df639619..f1d0c499ff 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java
@@ -16,10 +16,9 @@
package org.thingsboard.server.service.security.auth;
import org.thingsboard.server.common.data.id.UserId;
-import org.thingsboard.server.common.data.security.model.JwtToken;
public interface TokenOutdatingService {
- boolean isOutdated(JwtToken token, UserId userId);
+ boolean isOutdated(String token, UserId userId);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java
index e0242126fd..43389e2a09 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java
@@ -16,7 +16,9 @@
package org.thingsboard.server.service.security.auth.jwt;
import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
@@ -37,13 +39,19 @@ public class JwtAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
- SecurityUser securityUser = tokenFactory.parseAccessJwtToken(rawAccessToken);
+ SecurityUser securityUser = authenticate(rawAccessToken.getToken());
+ return new JwtAuthenticationToken(securityUser);
+ }
- if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) {
+ public SecurityUser authenticate(String accessToken) throws AuthenticationException {
+ if (StringUtils.isEmpty(accessToken)) {
+ throw new BadCredentialsException("Token is invalid");
+ }
+ SecurityUser securityUser = tokenFactory.parseAccessJwtToken(accessToken);
+ if (tokenOutdatingService.isOutdated(accessToken, securityUser.getId())) {
throw new JwtExpiredTokenException("Token is outdated");
}
-
- return new JwtAuthenticationToken(securityUser);
+ return securityUser;
}
@Override
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java
index e75d3aa4d1..527ff97ba5 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java
@@ -57,7 +57,7 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.notNull(authentication, "No authentication data provided");
RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
- SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken);
+ SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken.getToken());
UserPrincipal principal = unsafeUser.getUserPrincipal();
SecurityUser securityUser;
@@ -67,7 +67,7 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
securityUser = authenticateByPublicId(principal.getValue());
}
securityUser.setSessionId(unsafeUser.getSessionId());
- if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) {
+ if (tokenOutdatingService.isOutdated(rawAccessToken.getToken(), securityUser.getId())) {
throw new CredentialsExpiredException("Token is outdated");
}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java b/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java
index 87b7403a85..4e4430ceaa 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java
@@ -16,23 +16,22 @@
package org.thingsboard.server.service.security.exception;
import org.springframework.security.core.AuthenticationException;
-import org.thingsboard.server.common.data.security.model.JwtToken;
public class JwtExpiredTokenException extends AuthenticationException {
private static final long serialVersionUID = -5959543783324224864L;
- private JwtToken token;
+ private String token;
public JwtExpiredTokenException(String msg) {
super(msg);
}
- public JwtExpiredTokenException(JwtToken token, String msg, Throwable t) {
+ public JwtExpiredTokenException(String token, String msg, Throwable t) {
super(msg, t);
this.token = token;
}
public String token() {
- return this.token.getToken();
+ return this.token;
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
index a222a7e83a..a9d32c4d95 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
@@ -93,8 +93,8 @@ public class JwtTokenFactory {
return new AccessJwtToken(token);
}
- public SecurityUser parseAccessJwtToken(RawAccessJwtToken rawAccessToken) {
- Jws jwsClaims = parseTokenClaims(rawAccessToken);
+ public SecurityUser parseAccessJwtToken(String token) {
+ Jws jwsClaims = parseTokenClaims(token);
Claims claims = jwsClaims.getBody();
String subject = claims.getSubject();
@SuppressWarnings("unchecked")
@@ -145,8 +145,8 @@ public class JwtTokenFactory {
return new AccessJwtToken(token);
}
- public SecurityUser parseRefreshToken(RawAccessJwtToken rawAccessToken) {
- Jws jwsClaims = parseTokenClaims(rawAccessToken);
+ public SecurityUser parseRefreshToken(String token) {
+ Jws jwsClaims = parseTokenClaims(token);
Claims claims = jwsClaims.getBody();
String subject = claims.getSubject();
@SuppressWarnings("unchecked")
@@ -200,11 +200,11 @@ public class JwtTokenFactory {
.signWith(SignatureAlgorithm.HS512, jwtSettingsService.getJwtSettings().getTokenSigningKey());
}
- public Jws parseTokenClaims(JwtToken token) {
+ public Jws parseTokenClaims(String token) {
try {
return Jwts.parser()
.setSigningKey(jwtSettingsService.getJwtSettings().getTokenSigningKey())
- .parseClaimsJws(token.getToken());
+ .parseClaimsJws(token);
} catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException ex) {
log.debug("Invalid JWT Token", ex);
throw new BadCredentialsException("Invalid JWT token: ", ex);
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 102431f63b..b4468c0bd0 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
@@ -336,7 +336,7 @@ public abstract class TbAbstractSubCtx {
public void sendWsMsg(CmdUpdate update) {
wsLock.lock();
try {
- wsService.sendWsMsg(sessionRef.getSessionId(), update);
+ wsService.sendUpdate(sessionRef.getSessionId(), update);
} finally {
wsLock.unlock();
}
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java
index 1cda590a1f..04007e949c 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java
+++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java
@@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
@Data
@@ -35,6 +36,8 @@ public abstract class TbSubscription {
private final TbSubscriptionType type;
private final BiConsumer, T> updateProcessor;
+ protected final AtomicInteger sequence = new AtomicInteger();
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
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 f5e3daf05d..48a4187efc 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
@@ -27,17 +27,21 @@ import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
+import org.thingsboard.server.common.data.EdgeUpgradeMessage;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.service.edge.instructions.EdgeInstallInstructionsService;
+import org.thingsboard.server.service.edge.instructions.EdgeUpgradeInstructionsService;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -65,12 +69,20 @@ public class DefaultUpdateService implements UpdateService {
@Autowired
private NotificationRuleProcessor notificationRuleProcessor;
+ @Autowired(required = false)
+ private EdgeInstallInstructionsService edgeInstallInstructionsService;
+
+ @Autowired(required = false)
+ private EdgeUpgradeInstructionsService edgeUpgradeInstructionsService;
+
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service"));
private ScheduledFuture> checkUpdatesFuture = null;
private final RestTemplate restClient = new RestTemplate();
private UpdateMessage updateMessage;
+ private EdgeUpgradeMessage edgeUpgradeMessage;
+ private String edgeInstallVersion;
private String platform;
private String version;
@@ -82,6 +94,7 @@ public class DefaultUpdateService implements UpdateService {
updateMessage = new UpdateMessage(false, version, "", "",
"https://thingsboard.io/docs/reference/releases",
"https://thingsboard.io/docs/reference/releases");
+ edgeUpgradeMessage = new EdgeUpgradeMessage(new HashMap<>());
if (updatesEnabled) {
try {
platform = System.getProperty("platform", "unknown");
@@ -141,6 +154,16 @@ public class DefaultUpdateService implements UpdateService {
.updateInfo(updateMessage)
.build());
}
+ ObjectNode edgeRequest = JacksonUtil.newObjectNode().put(VERSION_PARAM, version);
+ String edgeInstallVersion = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/v1/edge/installMapping", new HttpEntity<>(edgeRequest.toString(), headers), String.class);
+ if (edgeInstallVersion != null) {
+ edgeInstallInstructionsService.setAppVersion(edgeInstallVersion);
+ edgeUpgradeInstructionsService.setAppVersion(edgeInstallVersion);
+ }
+ EdgeUpgradeMessage edgeUpgradeMessage = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/v1/edge/upgradeMapping", new HttpEntity<>(edgeRequest.toString(), headers), EdgeUpgradeMessage.class);
+ if (edgeUpgradeMessage != null) {
+ edgeUpgradeInstructionsService.updateInstructionMap(edgeUpgradeMessage.getEdgeVersions());
+ }
} catch (Exception e) {
log.trace(e.getMessage());
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/AuthCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/AuthCmd.java
new file mode 100644
index 0000000000..776cdd40d2
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/ws/AuthCmd.java
@@ -0,0 +1,33 @@
+/**
+ * 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.ws;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class AuthCmd implements WsCmd {
+ private int cmdId;
+ private String token;
+
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.AUTH;
+ }
+}
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 d0334f90b6..3e9ee69cfc 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
@@ -21,8 +21,10 @@ 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.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;
@@ -64,9 +66,6 @@ import org.thingsboard.server.service.subscription.TbEntityDataSubscriptionServi
import org.thingsboard.server.service.subscription.TbLocalSubscriptionService;
import org.thingsboard.server.service.subscription.TbTimeSeriesSubscription;
import org.thingsboard.server.service.ws.notification.NotificationCommandsHandler;
-import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper;
-import org.thingsboard.server.service.ws.notification.cmd.WsCmd;
-import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.SubscriptionCmd;
@@ -87,6 +86,7 @@ import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -123,7 +123,6 @@ public class DefaultWebSocketService implements WebSocketService {
private static final String FAILED_TO_FETCH_DATA = "Failed to fetch data!";
private static final String FAILED_TO_FETCH_ATTRIBUTES = "Failed to fetch attributes!";
private static final String SESSION_META_DATA_NOT_FOUND = "Session meta-data not found!";
- private static final String FAILED_TO_PARSE_WS_COMMAND = "Failed to parse websocket command!";
private final ConcurrentMap wsSessionsMap = new ConcurrentHashMap<>();
@@ -149,8 +148,7 @@ public class DefaultWebSocketService implements WebSocketService {
private ScheduledExecutorService pingExecutor;
private String serviceId;
- private List> telemetryCmdsHandlers;
- private List> notificationCmdsHandlers;
+ private Map> cmdsHandlers;
@PostConstruct
public void init() {
@@ -160,26 +158,23 @@ public class DefaultWebSocketService implements WebSocketService {
pingExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("telemetry-web-socket-ping"));
pingExecutor.scheduleWithFixedDelay(this::sendPing, pingTimeout / NUMBER_OF_PING_ATTEMPTS, pingTimeout / NUMBER_OF_PING_ATTEMPTS, TimeUnit.MILLISECONDS);
- telemetryCmdsHandlers = List.of(
- newCmdsHandler(TelemetryPluginCmdsWrapper::getAttrSubCmds, this::handleWsAttributesSubscriptionCmd),
- newCmdsHandler(TelemetryPluginCmdsWrapper::getTsSubCmds, this::handleWsTimeseriesSubscriptionCmd),
- newCmdsHandler(TelemetryPluginCmdsWrapper::getHistoryCmds, this::handleWsHistoryCmd),
- newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityDataCmds, this::handleWsEntityDataCmd),
- newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataCmds, this::handleWsAlarmDataCmd),
- newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountCmds, this::handleWsEntityCountCmd),
- newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmCountCmds, this::handleWsAlarmCountCmd),
- newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd),
- newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd),
- newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd),
- newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd)
- );
- notificationCmdsHandlers = List.of(
- newCmdHandler(NotificationCmdsWrapper::getUnreadSubCmd, notificationCmdsHandler::handleUnreadNotificationsSubCmd),
- newCmdHandler(NotificationCmdsWrapper::getUnreadCountSubCmd, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd),
- newCmdHandler(NotificationCmdsWrapper::getMarkAsReadCmd, notificationCmdsHandler::handleMarkAsReadCmd),
- newCmdHandler(NotificationCmdsWrapper::getMarkAllAsReadCmd, notificationCmdsHandler::handleMarkAllAsReadCmd),
- newCmdHandler(NotificationCmdsWrapper::getUnsubCmd, notificationCmdsHandler::handleUnsubCmd)
- );
+ cmdsHandlers = new EnumMap<>(WsCmdType.class);
+ cmdsHandlers.put(WsCmdType.ATTRIBUTES, newCmdHandler(this::handleWsAttributesSubscriptionCmd));
+ cmdsHandlers.put(WsCmdType.TIMESERIES, newCmdHandler(this::handleWsTimeseriesSubscriptionCmd));
+ cmdsHandlers.put(WsCmdType.TIMESERIES_HISTORY, newCmdHandler(this::handleWsHistoryCmd));
+ cmdsHandlers.put(WsCmdType.ENTITY_DATA, newCmdHandler(this::handleWsEntityDataCmd));
+ cmdsHandlers.put(WsCmdType.ALARM_DATA, newCmdHandler(this::handleWsAlarmDataCmd));
+ cmdsHandlers.put(WsCmdType.ENTITY_COUNT, newCmdHandler(this::handleWsEntityCountCmd));
+ cmdsHandlers.put(WsCmdType.ALARM_COUNT, newCmdHandler(this::handleWsAlarmCountCmd));
+ cmdsHandlers.put(WsCmdType.ENTITY_DATA_UNSUBSCRIBE, newCmdHandler(this::handleWsDataUnsubscribeCmd));
+ cmdsHandlers.put(WsCmdType.ALARM_DATA_UNSUBSCRIBE, newCmdHandler(this::handleWsDataUnsubscribeCmd));
+ cmdsHandlers.put(WsCmdType.ENTITY_COUNT_UNSUBSCRIBE, newCmdHandler(this::handleWsDataUnsubscribeCmd));
+ cmdsHandlers.put(WsCmdType.ALARM_COUNT_UNSUBSCRIBE, newCmdHandler(this::handleWsDataUnsubscribeCmd));
+ cmdsHandlers.put(WsCmdType.NOTIFICATIONS, newCmdHandler(notificationCmdsHandler::handleUnreadNotificationsSubCmd));
+ cmdsHandlers.put(WsCmdType.NOTIFICATIONS_COUNT, newCmdHandler(notificationCmdsHandler::handleUnreadNotificationsCountSubCmd));
+ cmdsHandlers.put(WsCmdType.MARK_NOTIFICATIONS_AS_READ, newCmdHandler(notificationCmdsHandler::handleMarkAsReadCmd));
+ cmdsHandlers.put(WsCmdType.MARK_ALL_NOTIFICATIONS_AS_READ, newCmdHandler(notificationCmdsHandler::handleMarkAllAsReadCmd));
+ cmdsHandlers.put(WsCmdType.NOTIFICATIONS_UNSUBSCRIBE, newCmdHandler(notificationCmdsHandler::handleUnsubCmd));
}
@PreDestroy
@@ -194,7 +189,7 @@ public class DefaultWebSocketService implements WebSocketService {
}
@Override
- public void handleWebSocketSessionEvent(WebSocketSessionRef sessionRef, SessionEvent event) {
+ public void handleSessionEvent(WebSocketSessionRef sessionRef, SessionEvent event) {
String sessionId = sessionRef.getSessionId();
log.debug(PROCESSING_MSG, sessionId, event);
switch (event.getEventType()) {
@@ -214,120 +209,75 @@ public class DefaultWebSocketService implements WebSocketService {
}
@Override
- public void handleWebSocketMsg(WebSocketSessionRef sessionRef, String msg) {
- if (log.isTraceEnabled()) {
- log.trace("[{}] Processing: {}", sessionRef.getSessionId(), msg);
- }
-
- try {
- switch (sessionRef.getSessionType()) {
- case TELEMETRY:
- processTelemetryCmds(sessionRef, msg);
- break;
- case NOTIFICATIONS:
- processNotificationCmds(sessionRef, msg);
- break;
- }
- } catch (IOException e) {
- log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e);
- sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(UNKNOWN_SUBSCRIPTION_ID, SubscriptionErrorCode.BAD_REQUEST, FAILED_TO_PARSE_WS_COMMAND));
- }
- }
-
- private void processTelemetryCmds(WebSocketSessionRef sessionRef, String msg) throws JsonProcessingException {
- TelemetryPluginCmdsWrapper cmdsWrapper = JacksonUtil.fromString(msg, TelemetryPluginCmdsWrapper.class);
- if (cmdsWrapper == null) {
+ public void handleCommands(WebSocketSessionRef sessionRef, WsCommandsWrapper commandsWrapper) {
+ if (commandsWrapper == null || CollectionUtils.isEmpty(commandsWrapper.getCmds())) {
return;
}
- for (WsCmdListHandler cmdHandler : telemetryCmdsHandlers) {
- List> cmds = cmdHandler.extractCmds(cmdsWrapper);
- if (cmds != null) {
- cmdHandler.handle(sessionRef, cmds);
- }
+ String sessionId = sessionRef.getSessionId();
+ if (!validateSessionMetadata(sessionRef, UNKNOWN_SUBSCRIPTION_ID, sessionId)) {
+ return;
}
- }
- private void processNotificationCmds(WebSocketSessionRef sessionRef, String msg) throws IOException {
- NotificationCmdsWrapper cmdsWrapper = JacksonUtil.fromString(msg, NotificationCmdsWrapper.class);
- for (WsCmdHandler cmdHandler : notificationCmdsHandlers) {
- WsCmd cmd = cmdHandler.extractCmd(cmdsWrapper);
- if (cmd != null) {
- String sessionId = sessionRef.getSessionId();
- if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) {
- try {
- cmdHandler.handle(sessionRef, cmd);
- } catch (Exception e) {
- log.error("[sessionId: {}, tenantId: {}, userId: {}] Failed to handle WS cmd: {}", sessionId,
- sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), cmd, e);
- }
- }
+ for (WsCmd cmd : commandsWrapper.getCmds()) {
+ log.debug("[{}][{}][{}] Processing cmd: {}", sessionId, cmd.getType(), cmd.getCmdId(), cmd);
+ try {
+ Optional.ofNullable(cmdsHandlers.get(cmd.getType()))
+ .ifPresent(cmdHandler -> cmdHandler.handle(sessionRef, cmd));
+ } catch (Exception e) {
+ log.error("[sessionId: {}, tenantId: {}, userId: {}] Failed to handle WS cmd: {}", sessionId,
+ sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), cmd, e);
}
}
}
private void handleWsEntityDataCmd(WebSocketSessionRef sessionRef, EntityDataCmd cmd) {
- String sessionId = sessionRef.getSessionId();
- log.debug("[{}] Processing: {}", sessionId, cmd);
-
- if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)
- && validateSubscriptionCmd(sessionRef, cmd)) {
+ if (validateSubscriptionCmd(sessionRef, cmd)) {
entityDataSubService.handleCmd(sessionRef, cmd);
}
}
private void handleWsEntityCountCmd(WebSocketSessionRef sessionRef, EntityCountCmd cmd) {
- String sessionId = sessionRef.getSessionId();
- log.debug("[{}] Processing: {}", sessionId, cmd);
-
- if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)
- && validateSubscriptionCmd(sessionRef, cmd)) {
+ if (validateSubscriptionCmd(sessionRef, cmd)) {
entityDataSubService.handleCmd(sessionRef, cmd);
}
}
private void handleWsAlarmDataCmd(WebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
- String sessionId = sessionRef.getSessionId();
- log.debug("[{}] Processing: {}", sessionId, cmd);
-
- if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)
- && validateSubscriptionCmd(sessionRef, cmd)) {
+ if (validateSubscriptionCmd(sessionRef, cmd)) {
entityDataSubService.handleCmd(sessionRef, cmd);
}
}
private void handleWsDataUnsubscribeCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) {
- String sessionId = sessionRef.getSessionId();
- log.debug("[{}] Processing: {}", sessionId, cmd);
-
- if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) {
- entityDataSubService.cancelSubscription(sessionRef.getSessionId(), cmd);
- }
+ entityDataSubService.cancelSubscription(sessionRef.getSessionId(), cmd);
}
private void handleWsAlarmCountCmd(WebSocketSessionRef sessionRef, AlarmCountCmd cmd) {
- String sessionId = sessionRef.getSessionId();
- log.debug("[{}] Processing: {}", sessionId, cmd);
-
- if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)
- && validateSubscriptionCmd(sessionRef, cmd)) {
+ if (validateCmd(sessionRef, cmd)) {
entityDataSubService.handleCmd(sessionRef, cmd);
}
}
@Override
- public void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update) {
- sendWsMsg(sessionId, update.getSubscriptionId(), update);
+ public void sendUpdate(String sessionId, TelemetrySubscriptionUpdate update) {
+ sendUpdate(sessionId, update.getSubscriptionId(), update);
+ }
+
+ @Override
+ public void sendUpdate(String sessionId, CmdUpdate update) {
+ sendUpdate(sessionId, update.getCmdId(), update);
}
@Override
- public void sendWsMsg(String sessionId, CmdUpdate update) {
- sendWsMsg(sessionId, update.getCmdId(), update);
+ public void sendError(WebSocketSessionRef sessionRef, int subId, SubscriptionErrorCode errorCode, String errorMsg) {
+ TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(subId, errorCode, errorMsg);
+ sendUpdate(sessionRef, update);
}
- private void sendWsMsg(String sessionId, int cmdId, T update) {
+ private void sendUpdate(String sessionId, int cmdId, T update) {
WsSessionMetaData md = wsSessionsMap.get(sessionId);
if (md != null) {
- sendWsMsg(md.getSessionRef(), cmdId, update);
+ sendUpdate(md.getSessionRef(), cmdId, update);
}
}
@@ -455,21 +405,17 @@ public class DefaultWebSocketService implements WebSocketService {
}
String sessionId = sessionRef.getSessionId();
- log.debug("[{}] Processing: {}", sessionId, cmd);
-
- if (validateSessionMetadata(sessionRef, cmd, sessionId)) {
- if (cmd.isUnsubscribe()) {
- unsubscribe(sessionRef, cmd, sessionId);
- } else if (validateSubscriptionCmd(sessionRef, cmd)) {
- EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
- log.debug("[{}] fetching latest attributes ({}) values for device: {}", sessionId, cmd.getKeys(), entityId);
- Optional> keysOptional = getKeys(cmd);
- if (keysOptional.isPresent()) {
- List keys = new ArrayList<>(keysOptional.get());
- handleWsAttributesSubscriptionByKeys(sessionRef, cmd, sessionId, entityId, keys);
- } else {
- handleWsAttributesSubscription(sessionRef, cmd, sessionId, entityId);
- }
+ if (cmd.isUnsubscribe()) {
+ unsubscribe(sessionRef, cmd, sessionId);
+ } else if (validateSubscriptionCmd(sessionRef, cmd)) {
+ EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
+ log.debug("[{}] fetching latest attributes ({}) values for device: {}", sessionId, cmd.getKeys(), entityId);
+ Optional> keysOptional = getKeys(cmd);
+ if (keysOptional.isPresent()) {
+ List keys = new ArrayList<>(keysOptional.get());
+ handleWsAttributesSubscriptionByKeys(sessionRef, cmd, sessionId, entityId, keys);
+ } else {
+ handleWsAttributesSubscription(sessionRef, cmd, sessionId, entityId);
}
}
}
@@ -503,7 +449,7 @@ public class DefaultWebSocketService implements WebSocketService {
.updateProcessor((subscription, update) -> {
subLock.lock();
try {
- sendWsMsg(subscription.getSessionId(), update);
+ sendUpdate(subscription.getSessionId(), update);
} finally {
subLock.unlock();
}
@@ -511,9 +457,9 @@ public class DefaultWebSocketService implements WebSocketService {
.build();
subLock.lock();
- try{
+ try {
oldSubService.addSubscription(sub);
- sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData));
+ sendUpdate(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData));
} finally {
subLock.unlock();
}
@@ -531,7 +477,7 @@ public class DefaultWebSocketService implements WebSocketService {
update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
FAILED_TO_FETCH_ATTRIBUTES);
}
- sendWsMsg(sessionRef, update);
+ sendUpdate(sessionRef, update);
}
};
@@ -543,27 +489,15 @@ public class DefaultWebSocketService implements WebSocketService {
}
private void handleWsHistoryCmd(WebSocketSessionRef sessionRef, GetHistoryCmd cmd) {
- String sessionId = sessionRef.getSessionId();
- WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId);
- if (sessionMD == null) {
- log.warn("[{}] Session meta data not found. ", sessionId);
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
- SESSION_META_DATA_NOT_FOUND);
- sendWsMsg(sessionRef, update);
- return;
- }
- if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty() || cmd.getEntityType() == null || cmd.getEntityType().isEmpty()) {
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
- "Device id is empty!");
- sendWsMsg(sessionRef, update);
- return;
- }
- if (cmd.getKeys() == null || cmd.getKeys().isEmpty()) {
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
- "Keys are empty!");
- sendWsMsg(sessionRef, update);
- return;
- }
+ if (!validateCmd(sessionRef, cmd, () -> {
+ if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty() || cmd.getEntityType() == null || cmd.getEntityType().isEmpty()) {
+ throw new IllegalArgumentException("Device id is empty!");
+ }
+ if (cmd.getKeys() == null || cmd.getKeys().isEmpty()) {
+ throw new IllegalArgumentException("Keys are empty!");
+ }
+ })) return;
+
EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
List keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
List queries = keys.stream().map(key -> new BaseReadTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg())))
@@ -572,7 +506,7 @@ public class DefaultWebSocketService implements WebSocketService {
FutureCallback> callback = new FutureCallback>() {
@Override
public void onSuccess(List data) {
- sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data));
+ sendUpdate(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data));
}
@Override
@@ -585,7 +519,7 @@ public class DefaultWebSocketService implements WebSocketService {
update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
FAILED_TO_FETCH_DATA);
}
- sendWsMsg(sessionRef, update);
+ sendUpdate(sessionRef, update);
}
};
accessValidator.validate(sessionRef.getSecurityCtx(), Operation.READ_TELEMETRY, entityId,
@@ -620,7 +554,7 @@ public class DefaultWebSocketService implements WebSocketService {
.updateProcessor((subscription, update) -> {
subLock.lock();
try {
- sendWsMsg(subscription.getSessionId(), update);
+ sendUpdate(subscription.getSessionId(), update);
} finally {
subLock.unlock();
}
@@ -631,7 +565,7 @@ public class DefaultWebSocketService implements WebSocketService {
subLock.lock();
try {
oldSubService.addSubscription(sub);
- sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData));
+ sendUpdate(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData));
} finally {
subLock.unlock();
}
@@ -640,9 +574,7 @@ public class DefaultWebSocketService implements WebSocketService {
@Override
public void onFailure(Throwable e) {
log.error(FAILED_TO_FETCH_ATTRIBUTES, e);
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
- FAILED_TO_FETCH_ATTRIBUTES);
- sendWsMsg(sessionRef, update);
+ sendError(sessionRef, cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, FAILED_TO_FETCH_ATTRIBUTES);
}
};
@@ -660,20 +592,16 @@ public class DefaultWebSocketService implements WebSocketService {
}
String sessionId = sessionRef.getSessionId();
- log.debug("[{}] Processing: {}", sessionId, cmd);
-
- if (validateSessionMetadata(sessionRef, cmd, sessionId)) {
- if (cmd.isUnsubscribe()) {
- unsubscribe(sessionRef, cmd, sessionId);
- } else if (validateSubscriptionCmd(sessionRef, cmd)) {
- EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
- Optional> keysOptional = getKeys(cmd);
-
- if (keysOptional.isPresent()) {
- handleWsTimeSeriesSubscriptionByKeys(sessionRef, cmd, sessionId, entityId);
- } else {
- handleWsTimeSeriesSubscription(sessionRef, cmd, sessionId, entityId);
- }
+ if (cmd.isUnsubscribe()) {
+ unsubscribe(sessionRef, cmd, sessionId);
+ } else if (validateSubscriptionCmd(sessionRef, cmd)) {
+ EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
+ Optional> keysOptional = getKeys(cmd);
+
+ if (keysOptional.isPresent()) {
+ handleWsTimeSeriesSubscriptionByKeys(sessionRef, cmd, sessionId, entityId);
+ } else {
+ handleWsTimeSeriesSubscription(sessionRef, cmd, sessionId, entityId);
}
}
}
@@ -721,7 +649,7 @@ public class DefaultWebSocketService implements WebSocketService {
.updateProcessor((subscription, update) -> {
subLock.lock();
try {
- sendWsMsg(subscription.getSessionId(), update);
+ sendUpdate(subscription.getSessionId(), update);
} finally {
subLock.unlock();
}
@@ -734,7 +662,7 @@ public class DefaultWebSocketService implements WebSocketService {
subLock.lock();
try {
oldSubService.addSubscription(sub);
- sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data));
+ sendUpdate(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data));
} finally {
subLock.unlock();
}
@@ -750,7 +678,7 @@ public class DefaultWebSocketService implements WebSocketService {
update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
FAILED_TO_FETCH_DATA);
}
- sendWsMsg(sessionRef, update);
+ sendUpdate(sessionRef, update);
}
};
accessValidator.validate(sessionRef.getSecurityCtx(), Operation.READ_TELEMETRY, entityId,
@@ -776,7 +704,7 @@ public class DefaultWebSocketService implements WebSocketService {
.updateProcessor((subscription, update) -> {
subLock.lock();
try {
- sendWsMsg(subscription.getSessionId(), update);
+ sendUpdate(subscription.getSessionId(), update);
} finally {
subLock.unlock();
}
@@ -787,9 +715,9 @@ public class DefaultWebSocketService implements WebSocketService {
.build();
subLock.lock();
- try{
+ try {
oldSubService.addSubscription(sub);
- sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data));
+ sendUpdate(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data));
} finally {
subLock.unlock();
}
@@ -802,9 +730,7 @@ public class DefaultWebSocketService implements WebSocketService {
} else {
log.info(FAILED_TO_FETCH_DATA, e);
}
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
- FAILED_TO_FETCH_DATA);
- sendWsMsg(sessionRef, update);
+ sendError(sessionRef, cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, FAILED_TO_FETCH_DATA);
}
};
}
@@ -818,95 +744,77 @@ public class DefaultWebSocketService implements WebSocketService {
}
private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, EntityDataCmd cmd) {
- if (cmd.getCmdId() < 0) {
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
- "Cmd id is negative value!");
- sendWsMsg(sessionRef, update);
- return false;
- } else if (cmd.getQuery() == null && !cmd.hasAnyCmd()) {
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
- "Query is empty!");
- sendWsMsg(sessionRef, update);
- return false;
- }
- return true;
+ return validateCmd(sessionRef, cmd, () -> {
+ if (cmd.getQuery() == null && !cmd.hasAnyCmd()) {
+ throw new IllegalArgumentException("Query is empty!");
+ }
+ });
}
private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, EntityCountCmd cmd) {
- if (cmd.getCmdId() < 0) {
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
- "Cmd id is negative value!");
- sendWsMsg(sessionRef, update);
- return false;
- } else if (cmd.getQuery() == null) {
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Query is empty!");
- sendWsMsg(sessionRef, update);
- return false;
- }
- return true;
+ return validateCmd(sessionRef, cmd, () -> {
+ if (cmd.getQuery() == null) {
+ throw new IllegalArgumentException("Query is empty!");
+ }
+ });
}
private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
- if (cmd.getCmdId() < 0) {
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
- "Cmd id is negative value!");
- sendWsMsg(sessionRef, update);
- return false;
- } else if (cmd.getQuery() == null) {
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
- "Query is empty!");
- sendWsMsg(sessionRef, update);
- return false;
- }
- return true;
+ return validateCmd(sessionRef, cmd, () -> {
+ if (cmd.getQuery() == null) {
+ throw new IllegalArgumentException("Query is empty!");
+ }
+ });
}
private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, SubscriptionCmd cmd) {
- if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
- "Device id is empty!");
- sendWsMsg(sessionRef, update);
- return false;
- }
- return true;
- }
-
- private boolean validateSessionMetadata(WebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
- return validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId);
+ return validateCmd(sessionRef, cmd, () -> {
+ if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
+ throw new IllegalArgumentException("Device id is empty!");
+ }
+ });
}
private boolean validateSessionMetadata(WebSocketSessionRef sessionRef, int cmdId, String sessionId) {
WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId);
if (sessionMD == null) {
log.warn("[{}] Session meta data not found. ", sessionId);
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmdId, SubscriptionErrorCode.INTERNAL_ERROR,
- SESSION_META_DATA_NOT_FOUND);
- sendWsMsg(sessionRef, update);
+ sendError(sessionRef, cmdId, SubscriptionErrorCode.INTERNAL_ERROR, SESSION_META_DATA_NOT_FOUND);
return false;
} else {
return true;
}
}
- private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, AlarmCountCmd cmd) {
+ private boolean validateCmd(WebSocketSessionRef sessionRef, WsCmd cmd) {
+ return validateCmd(sessionRef, cmd, null);
+ }
+
+ private boolean validateCmd(WebSocketSessionRef sessionRef, C cmd, Runnable validator) {
if (cmd.getCmdId() < 0) {
- TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
- "Cmd id is negative value!");
- sendWsMsg(sessionRef, update);
+ sendError(sessionRef, cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Cmd id is negative value!");
+ return false;
+ }
+ try {
+ if (validator != null) {
+ validator.run();
+ }
+ } catch (Exception e) {
+ sendError(sessionRef, cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, e.getMessage());
return false;
}
return true;
}
- private void sendWsMsg(WebSocketSessionRef sessionRef, EntityDataUpdate update) {
- sendWsMsg(sessionRef, update.getCmdId(), update);
+ private void sendUpdate(WebSocketSessionRef sessionRef, EntityDataUpdate update) {
+ sendUpdate(sessionRef, update.getCmdId(), update);
}
- private void sendWsMsg(WebSocketSessionRef sessionRef, TelemetrySubscriptionUpdate update) {
- sendWsMsg(sessionRef, update.getSubscriptionId(), update);
+ private void sendUpdate(WebSocketSessionRef sessionRef, TelemetrySubscriptionUpdate update) {
+ sendUpdate(sessionRef, update.getSubscriptionId(), update);
}
- private void sendWsMsg(WebSocketSessionRef sessionRef, int cmdId, Object update) {
+ private void sendUpdate(WebSocketSessionRef sessionRef, int cmdId, Object update) {
try {
String msg = JacksonUtil.OBJECT_MAPPER.writeValueAsString(update);
executor.submit(() -> {
@@ -1055,47 +963,19 @@ public class DefaultWebSocketService implements WebSocketService {
.map(TenantProfile::getDefaultProfileConfiguration).orElse(null);
}
-
- public static WsCmdHandler newCmdHandler(java.util.function.Function cmdExtractor,
- BiConsumer handler) {
- return new WsCmdHandler<>(cmdExtractor, handler);
- }
-
- public static WsCmdListHandler newCmdsHandler(java.util.function.Function> cmdsExtractor,
- BiConsumer handler) {
- return new WsCmdListHandler<>(cmdsExtractor, handler);
+ public static WsCmdHandler newCmdHandler(BiConsumer handler) {
+ return new WsCmdHandler<>(handler);
}
@RequiredArgsConstructor
- public static class WsCmdHandler {
- private final java.util.function.Function cmdExtractor;
- private final BiConsumer handler;
-
- public C extractCmd(W cmdsWrapper) {
- return cmdExtractor.apply(cmdsWrapper);
- }
+ @Getter
+ @SuppressWarnings("unchecked")
+ public static class WsCmdHandler {
+ protected final BiConsumer handler;
- @SuppressWarnings("unchecked")
- public void handle(WebSocketSessionRef sessionRef, Object cmd) {
+ public void handle(WebSocketSessionRef sessionRef, WsCmd cmd) {
handler.accept(sessionRef, (C) cmd);
}
}
- @RequiredArgsConstructor
- public static class WsCmdListHandler {
- private final java.util.function.Function> cmdsExtractor;
- private final BiConsumer handler;
-
- public List extractCmds(W cmdsWrapper) {
- return cmdsExtractor.apply(cmdsWrapper);
- }
-
- @SuppressWarnings("unchecked")
- public void handle(WebSocketSessionRef sessionRef, List> cmds) {
- cmds.forEach(cmd -> {
- handler.accept(sessionRef, (C) cmd);
- });
- }
- }
-
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java
index 1ebd6101f0..19753b91ca 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java
@@ -16,6 +16,7 @@
package org.thingsboard.server.service.ws;
import org.springframework.web.socket.CloseStatus;
+import org.thingsboard.server.service.subscription.SubscriptionErrorCode;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
@@ -24,13 +25,15 @@ import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpda
*/
public interface WebSocketService {
- void handleWebSocketSessionEvent(WebSocketSessionRef sessionRef, SessionEvent sessionEvent);
+ void handleSessionEvent(WebSocketSessionRef sessionRef, SessionEvent sessionEvent);
- void handleWebSocketMsg(WebSocketSessionRef sessionRef, String msg);
+ void handleCommands(WebSocketSessionRef sessionRef, WsCommandsWrapper commandsWrapper);
- void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update);
+ void sendUpdate(String sessionId, TelemetrySubscriptionUpdate update);
- void sendWsMsg(String sessionId, CmdUpdate update);
+ void sendUpdate(String sessionId, CmdUpdate update);
+
+ void sendError(WebSocketSessionRef sessionRef, int subId, SubscriptionErrorCode errorCode, String errorMsg);
void close(String sessionId, CloseStatus status);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java
index e799c8faa0..ba9d27b44c 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java
@@ -16,8 +16,7 @@
package org.thingsboard.server.service.ws;
import lombok.Builder;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
+import lombok.Data;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.net.InetSocketAddress;
@@ -27,15 +26,14 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by ashvayka on 27.03.18.
*/
-@RequiredArgsConstructor
@Builder
-@Getter
+@Data
public class WebSocketSessionRef {
private static final long serialVersionUID = 1L;
private final String sessionId;
- private final SecurityUser securityCtx;
+ private SecurityUser securityCtx;
private final InetSocketAddress localAddress;
private final InetSocketAddress remoteAddress;
private final WebSocketSessionType sessionType;
@@ -56,11 +54,13 @@ public class WebSocketSessionRef {
@Override
public String toString() {
- return "WebSocketSessionRef{" +
- "sessionId='" + sessionId + '\'' +
- ", localAddress=" + localAddress +
- ", remoteAddress=" + remoteAddress +
- ", sessionType=" + sessionType +
- '}';
+ String info = "";
+ if (securityCtx != null) {
+ info += "[" + securityCtx.getTenantId() + "]";
+ info += "[" + securityCtx.getId() + "]";
+ }
+ info += "[" + sessionId + "]";
+ return info;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java
index 5c1a155c4b..af2206686f 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java
@@ -15,23 +15,25 @@
*/
package org.thingsboard.server.service.ws;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.Optional;
-@RequiredArgsConstructor
-@Getter
+@NoArgsConstructor
+@AllArgsConstructor
public enum WebSocketSessionType {
- TELEMETRY("telemetry"),
- NOTIFICATIONS("notifications");
+ GENERAL(),
+ TELEMETRY("telemetry"), // deprecated
+ NOTIFICATIONS("notifications"); // deprecated
- private final String name;
+ private String name;
public static Optional forName(String name) {
return Arrays.stream(values())
- .filter(sessionType -> sessionType.getName().equals(name))
+ .filter(sessionType -> StringUtils.equals(sessionType.name, name))
.findFirst();
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java
similarity index 82%
rename from application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java
rename to application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java
index 97bfeab70c..0f4678cf76 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java
@@ -13,8 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.thingsboard.server.service.ws.notification.cmd;
+package org.thingsboard.server.service.ws;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
public interface WsCmd {
+
int getCmdId();
+
+ @JsonIgnore
+ WsCmdType getType();
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WsCmdType.java b/application/src/main/java/org/thingsboard/server/service/ws/WsCmdType.java
new file mode 100644
index 0000000000..285dd88efe
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/ws/WsCmdType.java
@@ -0,0 +1,39 @@
+/**
+ * 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.ws;
+
+public enum WsCmdType {
+ AUTH,
+
+ ATTRIBUTES,
+ TIMESERIES,
+ TIMESERIES_HISTORY,
+ ENTITY_DATA,
+ ENTITY_COUNT,
+ ALARM_DATA,
+ ALARM_COUNT,
+
+ NOTIFICATIONS,
+ NOTIFICATIONS_COUNT,
+ MARK_NOTIFICATIONS_AS_READ,
+ MARK_ALL_NOTIFICATIONS_AS_READ,
+
+ ALARM_DATA_UNSUBSCRIBE,
+ ALARM_COUNT_UNSUBSCRIBE,
+ ENTITY_DATA_UNSUBSCRIBE,
+ ENTITY_COUNT_UNSUBSCRIBE,
+ NOTIFICATIONS_UNSUBSCRIBE
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WsCommandsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/WsCommandsWrapper.java
new file mode 100644
index 0000000000..5704f3b2dc
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/ws/WsCommandsWrapper.java
@@ -0,0 +1,71 @@
+/**
+ * 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.ws;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd;
+import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd;
+import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd;
+import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd;
+import org.thingsboard.server.service.ws.notification.cmd.NotificationsUnsubCmd;
+import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd;
+import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd;
+import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
+import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd;
+import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUnsubscribeCmd;
+import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd;
+import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUnsubscribeCmd;
+import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
+import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUnsubscribeCmd;
+import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd;
+import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class WsCommandsWrapper {
+
+ private AuthCmd authCmd;
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
+ @JsonSubTypes({
+ @Type(name = "ATTRIBUTES", value = AttributesSubscriptionCmd.class),
+ @Type(name = "TIMESERIES", value = TimeseriesSubscriptionCmd.class),
+ @Type(name = "TIMESERIES_HISTORY", value = GetHistoryCmd.class),
+ @Type(name = "ENTITY_DATA", value = EntityDataCmd.class),
+ @Type(name = "ENTITY_COUNT", value = EntityCountCmd.class),
+ @Type(name = "ALARM_DATA", value = AlarmDataCmd.class),
+ @Type(name = "ALARM_COUNT", value = AlarmCountCmd.class),
+ @Type(name = "NOTIFICATIONS", value = NotificationsSubCmd.class),
+ @Type(name = "NOTIFICATIONS_COUNT", value = NotificationsCountSubCmd.class),
+ @Type(name = "MARK_NOTIFICATIONS_AS_READ", value = MarkNotificationsAsReadCmd.class),
+ @Type(name = "MARK_ALL_NOTIFICATIONS_AS_READ", value = MarkAllNotificationsAsReadCmd.class),
+ @Type(name = "ALARM_DATA_UNSUBSCRIBE", value = AlarmDataUnsubscribeCmd.class),
+ @Type(name = "ALARM_COUNT_UNSUBSCRIBE", value = AlarmCountUnsubscribeCmd.class),
+ @Type(name = "ENTITY_DATA_UNSUBSCRIBE", value = EntityDataUnsubscribeCmd.class),
+ @Type(name = "ENTITY_COUNT_UNSUBSCRIBE", value = EntityCountUnsubscribeCmd.class),
+ @Type(name = "NOTIFICATIONS_UNSUBSCRIBE", value = NotificationsUnsubCmd.class),
+ })
+ private List cmds;
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java
index 0937275a0d..5a02814870 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java
@@ -245,7 +245,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void sendUpdate(String sessionId, CmdUpdate update) {
log.trace("[{}, cmdId: {}] Sending WS update: {}", sessionId, update.getCmdId(), update);
- wsService.sendWsMsg(sessionId, update);
+ wsService.sendUpdate(sessionId, update);
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java
index f096106135..d11992ba4f 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java
@@ -18,10 +18,17 @@ package org.thingsboard.server.service.ws.notification.cmd;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
+import org.thingsboard.server.service.ws.WsCmd;
+import org.thingsboard.server.service.ws.WsCmdType;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MarkAllNotificationsAsReadCmd implements WsCmd {
private int cmdId;
+
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.MARK_ALL_NOTIFICATIONS_AS_READ;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java
index 55de75387f..2452163fd0 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java
@@ -18,6 +18,8 @@ package org.thingsboard.server.service.ws.notification.cmd;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
+import org.thingsboard.server.service.ws.WsCmd;
+import org.thingsboard.server.service.ws.WsCmdType;
import java.util.List;
import java.util.UUID;
@@ -28,4 +30,9 @@ import java.util.UUID;
public class MarkNotificationsAsReadCmd implements WsCmd {
private int cmdId;
private List notifications;
+
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.MARK_NOTIFICATIONS_AS_READ;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java
index 88613eb966..8ba52a9b3c 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java
@@ -15,9 +15,19 @@
*/
package org.thingsboard.server.service.ws.notification.cmd;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
+import org.thingsboard.server.service.ws.WsCommandsWrapper;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @deprecated Use {@link WsCommandsWrapper}. This class is left for backward compatibility
+ * */
@Data
+@Deprecated
public class NotificationCmdsWrapper {
private NotificationsCountSubCmd unreadCountSubCmd;
@@ -30,4 +40,13 @@ public class NotificationCmdsWrapper {
private NotificationsUnsubCmd unsubCmd;
+ @JsonIgnore
+ public WsCommandsWrapper toCommonCmdsWrapper() {
+ return new WsCommandsWrapper(null, Stream.of(
+ unreadCountSubCmd, unreadSubCmd, markAsReadCmd, markAllAsReadCmd, unsubCmd
+ )
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ }
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java
index 0d6757a6e7..7fa0ac9a6a 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java
@@ -18,10 +18,17 @@ package org.thingsboard.server.service.ws.notification.cmd;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
+import org.thingsboard.server.service.ws.WsCmd;
+import org.thingsboard.server.service.ws.WsCmdType;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class NotificationsCountSubCmd implements WsCmd {
private int cmdId;
+
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.NOTIFICATIONS_COUNT;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java
index e022d2c49f..44d6c47ff6 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java
@@ -18,6 +18,8 @@ package org.thingsboard.server.service.ws.notification.cmd;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
+import org.thingsboard.server.service.ws.WsCmd;
+import org.thingsboard.server.service.ws.WsCmdType;
@Data
@NoArgsConstructor
@@ -25,4 +27,9 @@ import lombok.NoArgsConstructor;
public class NotificationsSubCmd implements WsCmd {
private int cmdId;
private int limit;
+
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.NOTIFICATIONS;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java
index c9f0897eeb..a63351d9b9 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java
@@ -18,6 +18,8 @@ package org.thingsboard.server.service.ws.notification.cmd;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
+import org.thingsboard.server.service.ws.WsCmd;
+import org.thingsboard.server.service.ws.WsCmdType;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd;
@Data
@@ -25,4 +27,9 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd;
@AllArgsConstructor
public class NotificationsUnsubCmd implements UnsubscribeCmd, WsCmd {
private int cmdId;
+
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.NOTIFICATIONS_UNSUBSCRIBE;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java
index 93f51a965b..42dc670a21 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java
@@ -28,14 +28,17 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType;
public class UnreadNotificationsCountUpdate extends CmdUpdate {
private final int totalUnreadCount;
+ private final int sequenceNumber;
@Builder
@JsonCreator
public UnreadNotificationsCountUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode,
@JsonProperty("errorMsg") String errorMsg,
- @JsonProperty("totalUnreadCount") int totalUnreadCount) {
+ @JsonProperty("totalUnreadCount") int totalUnreadCount,
+ @JsonProperty("sequenceNumber") int sequenceNumber) {
super(cmdId, errorCode, errorMsg);
this.totalUnreadCount = totalUnreadCount;
+ this.sequenceNumber = sequenceNumber;
}
@Override
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java
index e64624d16e..65492f9579 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java
@@ -33,6 +33,7 @@ public class UnreadNotificationsUpdate extends CmdUpdate {
private final Collection notifications;
private final Notification update;
private final int totalUnreadCount;
+ private final int sequenceNumber;
@Builder
@JsonCreator
@@ -40,11 +41,13 @@ public class UnreadNotificationsUpdate extends CmdUpdate {
@JsonProperty("errorMsg") String errorMsg,
@JsonProperty("notifications") Collection notifications,
@JsonProperty("update") Notification update,
- @JsonProperty("totalUnreadCount") int totalUnreadCount) {
+ @JsonProperty("totalUnreadCount") int totalUnreadCount,
+ @JsonProperty("sequenceNumber") int sequenceNumber) {
super(cmdId, errorCode, errorMsg);
this.notifications = notifications;
this.update = update;
this.totalUnreadCount = totalUnreadCount;
+ this.sequenceNumber = sequenceNumber;
}
@Override
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java
index 5263f9d651..94939682e9 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java
@@ -41,6 +41,7 @@ public class NotificationsCountSubscription extends TbSubscription attrSubCmds;
@@ -58,4 +65,17 @@ public class TelemetryPluginCmdsWrapper {
private List alarmCountUnsubscribeCmds;
+ @JsonIgnore
+ public WsCommandsWrapper toCommonCmdsWrapper() {
+ return new WsCommandsWrapper(null, Stream.of(
+ attrSubCmds, tsSubCmds, historyCmds, entityDataCmds,
+ entityDataUnsubscribeCmds, alarmDataCmds, alarmDataUnsubscribeCmds,
+ entityCountCmds, entityCountUnsubscribeCmds,
+ alarmCountCmds, alarmCountUnsubscribeCmds
+ )
+ .filter(Objects::nonNull)
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList()));
+ }
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java
index 1ccec261b0..d1153b8bdd 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java
@@ -16,7 +16,7 @@
package org.thingsboard.server.service.ws.telemetry.cmd.v1;
import lombok.NoArgsConstructor;
-import org.thingsboard.server.service.ws.telemetry.TelemetryFeature;
+import org.thingsboard.server.service.ws.WsCmdType;
/**
* @author Andrew Shvayka
@@ -25,8 +25,8 @@ import org.thingsboard.server.service.ws.telemetry.TelemetryFeature;
public class AttributesSubscriptionCmd extends SubscriptionCmd {
@Override
- public TelemetryFeature getType() {
- return TelemetryFeature.ATTRIBUTES;
+ public WsCmdType getType() {
+ return WsCmdType.ATTRIBUTES;
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java
index 6791066641..0da067f6da 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java
@@ -18,6 +18,7 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v1;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
+import org.thingsboard.server.service.ws.WsCmdType;
/**
* @author Andrew Shvayka
@@ -37,4 +38,8 @@ public class GetHistoryCmd implements TelemetryPluginCmd {
private int limit;
private String agg;
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.TIMESERIES_HISTORY;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java
index 6a5f9d820d..3b742c3a2f 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java
@@ -32,8 +32,6 @@ public abstract class SubscriptionCmd implements TelemetryPluginCmd {
private String scope;
private boolean unsubscribe;
- public abstract TelemetryFeature getType();
-
@Override
public String toString() {
return "SubscriptionCmd [entityType=" + entityType + ", entityId=" + entityId + ", tags=" + keys + ", unsubscribe=" + unsubscribe + "]";
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java
index dc9d3a48c2..b57b907184 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java
@@ -15,10 +15,12 @@
*/
package org.thingsboard.server.service.ws.telemetry.cmd.v1;
+import org.thingsboard.server.service.ws.WsCmd;
+
/**
* @author Andrew Shvayka
*/
-public interface TelemetryPluginCmd {
+public interface TelemetryPluginCmd extends WsCmd {
int getCmdId();
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java
index f0a7b9d3af..aea228eebc 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java
@@ -17,8 +17,9 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v1;
import lombok.AllArgsConstructor;
import lombok.Data;
+import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
-import org.thingsboard.server.service.ws.telemetry.TelemetryFeature;
+import org.thingsboard.server.service.ws.WsCmdType;
/**
* @author Andrew Shvayka
@@ -26,6 +27,7 @@ import org.thingsboard.server.service.ws.telemetry.TelemetryFeature;
@NoArgsConstructor
@AllArgsConstructor
@Data
+@EqualsAndHashCode(callSuper = true)
public class TimeseriesSubscriptionCmd extends SubscriptionCmd {
private long startTs;
@@ -35,7 +37,7 @@ public class TimeseriesSubscriptionCmd extends SubscriptionCmd {
private String agg;
@Override
- public TelemetryFeature getType() {
- return TelemetryFeature.TIMESERIES;
+ public WsCmdType getType() {
+ return WsCmdType.TIMESERIES;
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java
index 80fd998eb1..b1fa9cf785 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.thingsboard.server.common.data.query.AlarmCountQuery;
+import org.thingsboard.server.service.ws.WsCmdType;
public class AlarmCountCmd extends DataCmd {
@@ -31,4 +32,9 @@ public class AlarmCountCmd extends DataCmd {
super(cmdId);
this.query = query;
}
+
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.ALARM_COUNT;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java
index 79c860f3f6..02cf5d5572 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java
@@ -16,10 +16,15 @@
package org.thingsboard.server.service.ws.telemetry.cmd.v2;
import lombok.Data;
+import org.thingsboard.server.service.ws.WsCmdType;
@Data
public class AlarmCountUnsubscribeCmd implements UnsubscribeCmd {
private final int cmdId;
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.ALARM_COUNT_UNSUBSCRIBE;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java
index 0f83d00d6d..0efa6ae608 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
+import org.thingsboard.server.service.ws.WsCmdType;
public class AlarmDataCmd extends DataCmd {
@@ -30,4 +31,9 @@ public class AlarmDataCmd extends DataCmd {
super(cmdId);
this.query = query;
}
+
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.ALARM_DATA;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java
index 97db558cf9..7dfb1159c5 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java
@@ -16,10 +16,15 @@
package org.thingsboard.server.service.ws.telemetry.cmd.v2;
import lombok.Data;
+import org.thingsboard.server.service.ws.WsCmdType;
@Data
public class AlarmDataUnsubscribeCmd implements UnsubscribeCmd {
private final int cmdId;
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.ALARM_DATA_UNSUBSCRIBE;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java
index c09cbc03d1..74133230cb 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java
@@ -17,9 +17,10 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v2;
import lombok.Data;
import lombok.Getter;
+import org.thingsboard.server.service.ws.WsCmd;
@Data
-public class DataCmd {
+public abstract class DataCmd implements WsCmd {
@Getter
private final int cmdId;
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java
index c39f81f1b7..3431afaf1a 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.thingsboard.server.common.data.query.EntityCountQuery;
+import org.thingsboard.server.service.ws.WsCmdType;
public class EntityCountCmd extends DataCmd {
@@ -31,4 +32,9 @@ public class EntityCountCmd extends DataCmd {
super(cmdId);
this.query = query;
}
+
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.ENTITY_COUNT;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java
index b82fecd8fd..4e0a50c7e1 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java
@@ -16,10 +16,15 @@
package org.thingsboard.server.service.ws.telemetry.cmd.v2;
import lombok.Data;
+import org.thingsboard.server.service.ws.WsCmdType;
@Data
public class EntityCountUnsubscribeCmd implements UnsubscribeCmd {
private final int cmdId;
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.ENTITY_COUNT_UNSUBSCRIBE;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java
index 5fe9179651..ace96f6673 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java
@@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.thingsboard.server.common.data.query.EntityDataQuery;
+import org.thingsboard.server.service.ws.WsCmdType;
public class EntityDataCmd extends DataCmd {
@@ -62,4 +63,8 @@ public class EntityDataCmd extends DataCmd {
return historyCmd != null || latestCmd != null || tsCmd != null || aggHistoryCmd != null || aggTsCmd != null;
}
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.ENTITY_DATA;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java
index 80577edda2..56727faf54 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java
@@ -16,10 +16,15 @@
package org.thingsboard.server.service.ws.telemetry.cmd.v2;
import lombok.Data;
+import org.thingsboard.server.service.ws.WsCmdType;
@Data
public class EntityDataUnsubscribeCmd implements UnsubscribeCmd {
private final int cmdId;
+ @Override
+ public WsCmdType getType() {
+ return WsCmdType.ENTITY_DATA_UNSUBSCRIBE;
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java
index 81288c76da..4efdb279be 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java
@@ -15,7 +15,9 @@
*/
package org.thingsboard.server.service.ws.telemetry.cmd.v2;
-public interface UnsubscribeCmd {
+import org.thingsboard.server.service.ws.WsCmd;
+
+public interface UnsubscribeCmd extends WsCmd {
int getCmdId();
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 93348d02ee..b7829d2b88 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -76,6 +76,8 @@ server:
max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:10000}"
# Maximum queue size of the websocket updates per session. This restriction prevents infinite updates of WS
max_queue_messages_per_session: "${TB_SERVER_WS_DEFAULT_QUEUE_MESSAGES_PER_SESSION:1000}"
+ # Maximum time between WS session opening and sending auth command
+ auth_timeout_ms: "${TB_SERVER_WS_AUTH_TIMEOUT_MS:10000}"
rest:
server_side_rpc:
# Minimum value of the server-side RPC timeout. May override value provided in the REST API call.
@@ -1280,7 +1282,7 @@ swagger:
# Queue configuration parameters
queue:
type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
- prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka) except of js executor topics (please use REMOTE_JS_EVAL_REQUEST_TOPIC and REMOTE_JS_EVAL_RESPONSE_TOPIC to specify custom topic names)
+ prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
in_memory:
stats:
# For debug level
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..1729285034 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
@@ -52,8 +52,8 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest {
@LocalServerPort
protected int wsPort;
- private volatile TbTestWebSocketClient wsClient; // lazy
- private volatile TbTestWebSocketClient anotherWsClient; // lazy
+ protected volatile TbTestWebSocketClient wsClient; // lazy
+ protected volatile TbTestWebSocketClient anotherWsClient; // lazy
public TbTestWebSocketClient getWsClient() {
if (wsClient == null) {
@@ -101,8 +101,15 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest {
}
protected TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException {
- TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws/plugins/telemetry?token=" + token));
+ return buildAndConnectWebSocketClient("/api/ws");
+ }
+
+ protected TbTestWebSocketClient buildAndConnectWebSocketClient(String path) throws URISyntaxException, InterruptedException {
+ TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + path));
assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ if (!path.contains("token=")) {
+ wsClient.authenticate(token);
+ }
return wsClient;
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java
index 38420c3e32..34b1006ffb 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java
@@ -28,6 +28,7 @@ import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
@@ -46,11 +47,13 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE;
@ContextConfiguration(classes = {AssetProfileControllerTest.Config.class})
@DaoSqlTest
@@ -459,6 +462,62 @@ public class AssetProfileControllerTest extends AbstractControllerTest {
testEntityDaoWithRelationsTransactionalException(assetProfileDao, savedTenant.getId(), assetProfileId, "/api/assetProfile/" + assetProfileId);
}
+ @Test
+ public void testGetAssetProfileNames() throws Exception {
+ var pageLink = new PageLink(Integer.MAX_VALUE);
+ var assetProfileInfos = doGetTypedWithPageLink("/api/assetProfileInfos?",
+ new TypeReference>() {
+ }, pageLink);
+ Assert.assertNotNull("Asset Profile Infos page data is null!", assetProfileInfos);
+ Assert.assertEquals("Asset Profile Infos Page data is empty! Expected to have default profile created!", 1, assetProfileInfos.getTotalElements());
+ List expectedAssetProfileNames = assetProfileInfos.getData().stream()
+ .map(info -> new EntityInfo(info.getId(), info.getName()))
+ .sorted(Comparator.comparing(EntityInfo::getName))
+ .collect(Collectors.toList());
+ var assetProfileNames = doGetTyped("/api/assetProfile/names", new TypeReference>() {
+ });
+ Assert.assertNotNull("Asset Profile Names list is null!", assetProfileNames);
+ Assert.assertFalse("Asset Profile Names list is empty!", assetProfileNames.isEmpty());
+ Assert.assertEquals(expectedAssetProfileNames, assetProfileNames);
+ Assert.assertEquals(1, assetProfileNames.size());
+ Assert.assertEquals(DEFAULT_DEVICE_TYPE, assetProfileNames.get(0).getName());
+
+ int count = 3;
+ for (int i = 0; i < count; i++) {
+ Asset asset = new Asset();
+ asset.setName("AssetName" + i);
+ asset.setType("AssetProfileName" + i);
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+ Assert.assertNotNull(savedAsset);
+ }
+ assetProfileInfos = doGetTypedWithPageLink("/api/assetProfileInfos?",
+ new TypeReference<>() {
+ }, pageLink);
+ Assert.assertNotNull("Asset Profile Infos page data is null!", assetProfileInfos);
+ Assert.assertEquals("Asset Profile Infos Page data is empty! Expected to have default profile created + count value!", 1 + count, assetProfileInfos.getTotalElements());
+ expectedAssetProfileNames = assetProfileInfos.getData().stream()
+ .map(info -> new EntityInfo(info.getId(), info.getName()))
+ .sorted(Comparator.comparing(EntityInfo::getName))
+ .collect(Collectors.toList());
+
+ assetProfileNames = doGetTyped("/api/assetProfile/names", new TypeReference<>() {
+ });
+ Assert.assertNotNull("Asset Profile Names list is null!", assetProfileNames);
+ Assert.assertFalse("Asset Profile Names list is empty!", assetProfileNames.isEmpty());
+ Assert.assertEquals(expectedAssetProfileNames, assetProfileNames);
+ Assert.assertEquals(1 + count, assetProfileNames.size());
+
+ assetProfileNames = doGetTyped("/api/assetProfile/names?activeOnly=true", new TypeReference<>() {
+ });
+ Assert.assertNotNull("Asset Profile Names list is null!", assetProfileNames);
+ Assert.assertFalse("Asset Profile Names list is empty!", assetProfileNames.isEmpty());
+ var expectedAssetProfileNamesWithoutDefault = expectedAssetProfileNames.stream()
+ .filter(entityInfo -> !entityInfo.getName().equals(DEFAULT_DEVICE_TYPE))
+ .collect(Collectors.toList());
+ Assert.assertEquals(expectedAssetProfileNamesWithoutDefault, assetProfileNames);
+ Assert.assertEquals(count, assetProfileNames.size());
+ }
+
private AssetProfile savedAssetProfile(String name) {
AssetProfile assetProfile = createAssetProfile(name);
return doPost("/api/assetProfile", assetProfile, AssetProfile.class);
diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java
index 14b052d93c..7cdfa10ea0 100644
--- a/application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java
@@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.DeviceProfileInfo;
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.EntityInfo;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest;
import org.thingsboard.server.common.data.StringUtils;
@@ -55,11 +56,13 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
@@ -1045,6 +1048,62 @@ public class DeviceProfileControllerTest extends AbstractControllerTest {
testEntityDaoWithRelationsTransactionalException(deviceProfileDao, savedTenant.getId(), deviceProfileId, "/api/deviceProfile/" + deviceProfileId);
}
+ @Test
+ public void testGetDeviceProfileNames() throws Exception {
+ var pageLink = new PageLink(Integer.MAX_VALUE);
+ var deviceProfileInfos = doGetTypedWithPageLink("/api/deviceProfileInfos?",
+ new TypeReference>() {
+ }, pageLink);
+ Assert.assertNotNull("Device Profile Infos page data is null!", deviceProfileInfos);
+ Assert.assertEquals("Device Profile Infos Page data is empty! Expected to have default profile created!", 1, deviceProfileInfos.getTotalElements());
+ List expectedDeviceProfileNames = deviceProfileInfos.getData().stream()
+ .map(info -> new EntityInfo(info.getId(), info.getName()))
+ .sorted(Comparator.comparing(EntityInfo::getName))
+ .collect(Collectors.toList());
+ var deviceProfileNames = doGetTyped("/api/deviceProfile/names", new TypeReference>() {
+ });
+ Assert.assertNotNull("Device Profile Names list is null!", deviceProfileNames);
+ Assert.assertFalse("Device Profile Names list is empty!", deviceProfileNames.isEmpty());
+ Assert.assertEquals(expectedDeviceProfileNames, deviceProfileNames);
+ Assert.assertEquals(1, deviceProfileNames.size());
+ Assert.assertEquals(DEFAULT_DEVICE_TYPE, deviceProfileNames.get(0).getName());
+
+ int count = 3;
+ for (int i = 0; i < count; i++) {
+ Device device = new Device();
+ device.setName("DeviceName" + i);
+ device.setType("DeviceProfileName" + i);
+ Device savedDevice = doPost("/api/device", device, Device.class);
+ Assert.assertNotNull(savedDevice);
+ }
+ deviceProfileInfos = doGetTypedWithPageLink("/api/deviceProfileInfos?",
+ new TypeReference<>() {
+ }, pageLink);
+ Assert.assertNotNull("Device Profile Infos page data is null!", deviceProfileInfos);
+ Assert.assertEquals("Device Profile Infos Page data is empty! Expected to have default profile created + count value!", 1 + count, deviceProfileInfos.getTotalElements());
+ expectedDeviceProfileNames = deviceProfileInfos.getData().stream()
+ .map(info -> new EntityInfo(info.getId(), info.getName()))
+ .sorted(Comparator.comparing(EntityInfo::getName))
+ .collect(Collectors.toList());
+
+ deviceProfileNames = doGetTyped("/api/deviceProfile/names", new TypeReference<>() {
+ });
+ Assert.assertNotNull("Device Profile Names list is null!", deviceProfileNames);
+ Assert.assertFalse("Device Profile Names list is empty!", deviceProfileNames.isEmpty());
+ Assert.assertEquals(expectedDeviceProfileNames, deviceProfileNames);
+ Assert.assertEquals(1 + count, deviceProfileNames.size());
+
+ deviceProfileNames = doGetTyped("/api/deviceProfile/names?activeOnly=true", new TypeReference<>() {
+ });
+ Assert.assertNotNull("Device Profile Names list is null!", deviceProfileNames);
+ Assert.assertFalse("Device Profile Names list is empty!", deviceProfileNames.isEmpty());
+ var expectedDeviceProfileNamesWithoutDefault = expectedDeviceProfileNames.stream()
+ .filter(entityInfo -> !entityInfo.getName().equals(DEFAULT_DEVICE_TYPE))
+ .collect(Collectors.toList());
+ Assert.assertEquals(expectedDeviceProfileNamesWithoutDefault, deviceProfileNames);
+ Assert.assertEquals(count, deviceProfileNames.size());
+ }
+
private DeviceProfile savedDeviceProfile(String name) {
DeviceProfile deviceProfile = createDeviceProfile(name);
return doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
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 28d48948d4..d4f4a4a241 100644
--- a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java
@@ -48,6 +48,7 @@ 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.TenantProfile;
+import org.thingsboard.server.common.data.EdgeUpgradeInfo;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
@@ -74,6 +75,7 @@ import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.SyncCompletedMsg;
@@ -82,9 +84,11 @@ import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UserUpdateMsg;
+import org.thingsboard.server.service.edge.instructions.EdgeUpgradeInstructionsService;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -115,6 +119,9 @@ public class EdgeControllerTest extends AbstractControllerTest {
@Autowired
private EdgeDao edgeDao;
+ @Autowired
+ private EdgeUpgradeInstructionsService edgeUpgradeInstructionsService;
+
static class Config {
@Bean
@Primary
@@ -1172,8 +1179,25 @@ public class EdgeControllerTest extends AbstractControllerTest {
public void testGetEdgeInstallInstructions() throws Exception {
Edge edge = constructEdge(tenantId, "Edge for Test Docker Install Instructions", "default", "7390c3a6-69b0-9910-d155-b90aca4b772e", "l7q4zsjplzwhk16geqxy");
Edge savedEdge = doPost("/api/edge", edge, Edge.class);
- String installInstructions = doGet("/api/edge/instructions/" + savedEdge.getId().getId().toString() + "/docker", String.class);
+ String installInstructions = doGet("/api/edge/instructions/install/" + savedEdge.getId().getId().toString() + "/docker", String.class);
Assert.assertTrue(installInstructions.contains("l7q4zsjplzwhk16geqxy"));
Assert.assertTrue(installInstructions.contains("7390c3a6-69b0-9910-d155-b90aca4b772e"));
}
+
+ @Test
+ public void testGetEdgeUpgradeInstructions() throws Exception {
+ // UpdateInfo config is updating from Thingsboard Update server
+ HashMap upgradeInfoHashMap = new HashMap<>();
+ upgradeInfoHashMap.put("3.6.0", new EdgeUpgradeInfo(true, "3.6.1"));
+ upgradeInfoHashMap.put("3.6.1", new EdgeUpgradeInfo(true, "3.6.2"));
+ upgradeInfoHashMap.put("3.6.2", new EdgeUpgradeInfo(true, null));
+ edgeUpgradeInstructionsService.updateInstructionMap(upgradeInfoHashMap);
+ Edge edge = constructEdge("Edge for Test Docker Upgrade Instructions", "default");
+ Edge savedEdge = doPost("/api/edge", edge, Edge.class);
+ String body = "{\"edgeVersion\": \"V_3_6_0\"}";
+ doPostAsync("/api/plugins/telemetry/EDGE/" + savedEdge.getId().getId() + "/attributes/SERVER_SCOPE", body, String.class, status().isOk());
+ String upgradeInstructions = doGet("/api/edge/instructions/upgrade/" + EdgeVersion.V_3_6_0.name() + "/docker", String.class);
+ Assert.assertTrue(upgradeInstructions.contains("Upgrading to 3.6.1EDGE"));
+ Assert.assertTrue(upgradeInstructions.contains("Upgrading to 3.6.2EDGE"));
+ }
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
index ede6c69fd4..f7082782a3 100644
--- a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
+++ b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
@@ -16,7 +16,6 @@
package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
@@ -28,11 +27,11 @@ import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityFilter;
import org.thingsboard.server.common.data.query.EntityKey;
-import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper;
+import org.thingsboard.server.service.ws.AuthCmd;
+import org.thingsboard.server.service.ws.WsCmd;
+import org.thingsboard.server.service.ws.WsCommandsWrapper;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd;
-import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate;
-import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate;
@@ -66,6 +65,12 @@ public class TbTestWebSocketClient extends WebSocketClient {
}
+ public void authenticate(String token) {
+ WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper();
+ cmdsWrapper.setAuthCmd(new AuthCmd(1, token));
+ send(JacksonUtil.toString(cmdsWrapper));
+ }
+
@Override
public void onMessage(String s) {
log.info("RECEIVED: {}", s);
@@ -105,24 +110,6 @@ public class TbTestWebSocketClient extends WebSocketClient {
super.send(text);
}
- public void send(EntityDataCmd cmd) throws NotYetConnectedException {
- TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
- wrapper.setEntityDataCmds(Collections.singletonList(cmd));
- this.send(JacksonUtil.toString(wrapper));
- }
-
- public void send(EntityCountCmd cmd) throws NotYetConnectedException {
- TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
- wrapper.setEntityCountCmds(Collections.singletonList(cmd));
- this.send(JacksonUtil.toString(wrapper));
- }
-
- public void send(AlarmCountCmd cmd) throws NotYetConnectedException {
- TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
- wrapper.setAlarmCountCmds(Collections.singletonList(cmd));
- this.send(JacksonUtil.toString(wrapper));
- }
-
public String waitForUpdate() {
return waitForUpdate(false);
}
@@ -240,11 +227,7 @@ public class TbTestWebSocketClient extends WebSocketClient {
cmd.setEntityId(entityId.getId().toString());
cmd.setScope(scope);
cmd.setKeys(String.join(",", keys));
- TelemetryPluginCmdsWrapper cmdsWrapper = new TelemetryPluginCmdsWrapper();
- cmdsWrapper.setAttrSubCmds(List.of(cmd));
- JsonNode msg = JacksonUtil.valueToTree(cmdsWrapper);
- ((ObjectNode) msg.get("attrSubCmds").get(0)).remove("type");
- send(msg.toString());
+ send(cmd);
return JacksonUtil.toJsonNode(waitForReply());
}
@@ -288,4 +271,10 @@ public class TbTestWebSocketClient extends WebSocketClient {
return sendEntityDataQuery(edq);
}
+ public void send(WsCmd... cmds) {
+ WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper();
+ cmdsWrapper.setCmds(List.of(cmds));
+ send(JacksonUtil.toString(cmdsWrapper));
+ }
+
}
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 73ee8f9252..47ce021dae 100644
--- a/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java
@@ -648,7 +648,7 @@ public class WebsocketApiTest extends AbstractControllerTest {
}
@Test
- public void testEntityCountCmd_filterTypeSingularCompatibilityTest() {
+ public void testEntityCountCmd_filterTypeSingularCompatibilityTest() throws Exception {
ObjectNode oldFormatDeviceTypeFilterSingular = JacksonUtil.newObjectNode();
oldFormatDeviceTypeFilterSingular.put("type", "deviceType");
oldFormatDeviceTypeFilterSingular.put("deviceType", "default");
@@ -667,9 +667,10 @@ public class WebsocketApiTest extends AbstractControllerTest {
ObjectNode wrapperNode = JacksonUtil.newObjectNode();
wrapperNode.set("entityCountCmds", entityCountCmds);
- getWsClient().send(JacksonUtil.toString(wrapperNode));
+ wsClient = buildAndConnectWebSocketClient("/api/ws/plugins/telemetry?token=" + token);
+ wsClient.send(JacksonUtil.toString(wrapperNode));
- EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply());
+ EntityCountUpdate update = wsClient.parseCountReply(wsClient.waitForReply());
Assert.assertEquals(1, update.getCmdId());
Assert.assertEquals(1, update.getCount());
diff --git a/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java b/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java
index 0394e8a505..7d3d68f2d3 100644
--- a/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java
@@ -32,7 +32,10 @@ import javax.websocket.SendResult;
import javax.websocket.Session;
import java.io.IOException;
import java.util.Collection;
+import java.util.Deque;
import java.util.List;
+import java.util.Random;
+import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
@@ -49,6 +52,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.BDDMockito.willDoNothing;
import static org.mockito.BDDMockito.willReturn;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -79,7 +83,9 @@ class TbWebSocketHandlerTest {
asyncRemote = mock(RemoteEndpoint.Async.class);
willReturn(asyncRemote).given(nativeSession).getAsyncRemote();
sessionRef = mock(WebSocketSessionRef.class, Mockito.RETURNS_DEEP_STUBS); //prevent NPE on logs
- sendHandler = spy(wsHandler.new SessionMetaData(session, sessionRef, maxMsgQueuePerSession));
+ TbWebSocketHandler.SessionMetaData sessionMd = wsHandler.new SessionMetaData(session, sessionRef);
+ sessionMd.setMaxMsgQueueSize(maxMsgQueuePerSession);
+ sendHandler = spy(sessionMd);
}
@AfterEach
@@ -157,4 +163,25 @@ class TbWebSocketHandlerTest {
verify(asyncRemote, times(1)).sendText(anyString(), any());
}
+ @Test
+ void sendHandler_onMsg_allProcessed() throws Exception {
+ Deque msgs = new ConcurrentLinkedDeque<>();
+ doAnswer(inv -> msgs.add(inv.getArgument(1))).when(wsHandler).processMsg(any(), any());
+ for (int i = 0; i < 100; i++) {
+ String msg = String.valueOf(i);
+ executor.submit(() -> {
+ try {
+ Thread.sleep(new Random().nextInt(50));
+ sendHandler.onMsg(msg);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+ executor.shutdown();
+ executor.awaitTermination(5, TimeUnit.SECONDS);
+
+ assertThat(msgs).map(Integer::parseInt).doesNotHaveDuplicates().hasSize(100);
+ }
+
}
diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
index 27a0eed1a4..f7fc195146 100644
--- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
@@ -259,8 +259,9 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
@Override
protected NotificationApiWsClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException {
- NotificationApiWsClient wsClient = new NotificationApiWsClient(WS_URL + wsPort, token);
+ NotificationApiWsClient wsClient = new NotificationApiWsClient(WS_URL + wsPort);
assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ wsClient.authenticate(token);
return wsClient;
}
diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java
index 664ff7bd54..d96d04b31a 100644
--- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java
+++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java
@@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.controller.TbTestWebSocketClient;
import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd;
import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd;
-import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper;
import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd;
import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd;
import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCountUpdate;
@@ -49,40 +48,27 @@ public class NotificationApiWsClient extends TbTestWebSocketClient {
private int unreadCount;
private List notifications;
- public NotificationApiWsClient(String wsUrl, String token) throws URISyntaxException {
- super(new URI(wsUrl + "/api/ws/plugins/notifications?token=" + token));
+ public NotificationApiWsClient(String wsUrl) throws URISyntaxException {
+ super(new URI(wsUrl + "/api/ws"));
}
public NotificationApiWsClient subscribeForUnreadNotifications(int limit) {
- NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper();
- cmdsWrapper.setUnreadSubCmd(new NotificationsSubCmd(1, limit));
- sendCmd(cmdsWrapper);
+ send(new NotificationsSubCmd(1, limit));
this.limit = limit;
return this;
}
public NotificationApiWsClient subscribeForUnreadNotificationsCount() {
- NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper();
- cmdsWrapper.setUnreadCountSubCmd(new NotificationsCountSubCmd(2));
- sendCmd(cmdsWrapper);
+ send(new NotificationsCountSubCmd(2));
return this;
}
public void markNotificationAsRead(UUID... notifications) {
- NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper();
- cmdsWrapper.setMarkAsReadCmd(new MarkNotificationsAsReadCmd(newCmdId(), Arrays.asList(notifications)));
- sendCmd(cmdsWrapper);
+ send(new MarkNotificationsAsReadCmd(newCmdId(), Arrays.asList(notifications)));
}
public void markAllNotificationsAsRead() {
- NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper();
- cmdsWrapper.setMarkAllAsReadCmd(new MarkAllNotificationsAsReadCmd(newCmdId()));
- sendCmd(cmdsWrapper);
- }
-
- public void sendCmd(NotificationCmdsWrapper cmdsWrapper) {
- String cmd = JacksonUtil.toString(cmdsWrapper);
- send(cmd);
+ send(new MarkAllNotificationsAsReadCmd(newCmdId()));
}
@Override
diff --git a/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java b/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java
index 7bb4f1e559..48070678f6 100644
--- a/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java
@@ -106,7 +106,7 @@ public class JwtTokenFactoryTest {
AccessJwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
checkExpirationTime(accessToken, jwtSettings.getTokenExpirationTime());
- SecurityUser parsedSecurityUser = tokenFactory.parseAccessJwtToken(new RawAccessJwtToken(accessToken.getToken()));
+ SecurityUser parsedSecurityUser = tokenFactory.parseAccessJwtToken(accessToken.getToken());
assertThat(parsedSecurityUser.getId()).isEqualTo(securityUser.getId());
assertThat(parsedSecurityUser.getEmail()).isEqualTo(securityUser.getEmail());
assertThat(parsedSecurityUser.getUserPrincipal()).matches(userPrincipal -> {
@@ -135,7 +135,7 @@ public class JwtTokenFactoryTest {
JwtToken refreshToken = tokenFactory.createRefreshToken(securityUser);
checkExpirationTime(refreshToken, jwtSettings.getRefreshTokenExpTime());
- SecurityUser parsedSecurityUser = tokenFactory.parseRefreshToken(new RawAccessJwtToken(refreshToken.getToken()));
+ SecurityUser parsedSecurityUser = tokenFactory.parseRefreshToken(refreshToken.getToken());
assertThat(parsedSecurityUser.getId()).isEqualTo(securityUser.getId());
assertThat(parsedSecurityUser.getUserPrincipal()).matches(userPrincipal -> {
return userPrincipal.getType().equals(securityUser.getUserPrincipal().getType())
@@ -159,7 +159,7 @@ public class JwtTokenFactoryTest {
JwtToken preVerificationToken = tokenFactory.createPreVerificationToken(securityUser, tokenLifetime);
checkExpirationTime(preVerificationToken, tokenLifetime);
- SecurityUser parsedSecurityUser = tokenFactory.parseAccessJwtToken(new RawAccessJwtToken(preVerificationToken.getToken()));
+ SecurityUser parsedSecurityUser = tokenFactory.parseAccessJwtToken(preVerificationToken.getToken());
assertThat(parsedSecurityUser.getId()).isEqualTo(securityUser.getId());
assertThat(parsedSecurityUser.getAuthority()).isEqualTo(Authority.PRE_VERIFICATION_TOKEN);
assertThat(parsedSecurityUser.getTenantId()).isEqualTo(securityUser.getTenantId());
@@ -198,7 +198,7 @@ public class JwtTokenFactoryTest {
}
private void checkExpirationTime(JwtToken jwtToken, int tokenLifetime) {
- Claims claims = tokenFactory.parseTokenClaims(jwtToken).getBody();
+ Claims claims = tokenFactory.parseTokenClaims(jwtToken.getToken()).getBody();
assertThat(claims.getExpiration()).matches(actualExpirationTime -> {
Calendar expirationTime = Calendar.getInstance();
expirationTime.setTime(new Date());
diff --git a/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java b/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java
index eeb9fe64a2..90d911833c 100644
--- a/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java
@@ -114,12 +114,12 @@ public class TokenOutdatingTest {
// Token outdatage time is rounded to 1 sec. Need to wait before outdating so that outdatage time is strictly after token issue time
SECONDS.sleep(1);
eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId()));
- assertTrue(tokenOutdatingService.isOutdated(jwtToken, securityUser.getId()));
+ assertTrue(tokenOutdatingService.isOutdated(jwtToken.getToken(), securityUser.getId()));
SECONDS.sleep(1);
JwtToken newJwtToken = tokenFactory.createAccessJwtToken(securityUser);
- assertFalse(tokenOutdatingService.isOutdated(newJwtToken, securityUser.getId()));
+ assertFalse(tokenOutdatingService.isOutdated(newJwtToken.getToken(), securityUser.getId()));
}
@Test
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 00c6ba6a09..8094718383 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
@@ -63,7 +63,7 @@ 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.TelemetryPluginCmdsWrapper;
+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;
@@ -229,10 +229,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
- TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
- wrapper.setEntityDataCmds(Collections.singletonList(cmd));
-
- getWsClient().send(JacksonUtil.toString(wrapper));
+ getWsClient().send(cmd);
getWsClient().waitForReply();
getWsClient().registerWaitForUpdate();
diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/MqttV5ClientSparkplugBAttributesInProfileTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/MqttV5ClientSparkplugBAttributesInProfileTest.java
index 1d5a2c127f..faa29ad35d 100644
--- a/application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/MqttV5ClientSparkplugBAttributesInProfileTest.java
+++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/MqttV5ClientSparkplugBAttributesInProfileTest.java
@@ -52,4 +52,4 @@ public class MqttV5ClientSparkplugBAttributesInProfileTest extends AbstractMqttV
processClientDeviceWithCorrectAccessTokenPublish_AttributesInProfileContainsKeyAttributes();
}
-}
\ No newline at end of file
+}
diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/MqttV5ClientSparkplugBAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/MqttV5ClientSparkplugBAttributesTest.java
index f5d42046b7..abe1810887 100644
--- a/application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/MqttV5ClientSparkplugBAttributesTest.java
+++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/MqttV5ClientSparkplugBAttributesTest.java
@@ -78,4 +78,4 @@ public class MqttV5ClientSparkplugBAttributesTest extends AbstractMqttV5ClientSp
processClientDeviceWithCorrectAccessTokenPublishWithBirth_SharedAttributes_LongType_IfMetricFailedTypeCheck_SendValueOk();
}
-}
\ No newline at end of file
+}
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 477c352b54..7d7769411e 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
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.asset;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
import org.thingsboard.server.common.data.id.AssetProfileId;
@@ -23,6 +24,8 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.EntityDaoService;
+import java.util.List;
+
public interface AssetProfileService extends EntityDaoService {
AssetProfile findAssetProfileById(TenantId tenantId, AssetProfileId assetProfileId);
@@ -57,4 +60,6 @@ public interface AssetProfileService extends EntityDaoService {
void deleteAssetProfilesByTenantId(TenantId tenantId);
+ List findAssetProfileNamesByTenantId(TenantId tenantId, boolean activeOnly);
+
}
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
index 722eeff365..cec8dd9007 100644
--- a/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
+++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
@@ -81,6 +81,7 @@ public interface AssetService extends EntityDaoService {
ListenableFuture> findAssetsByQuery(TenantId tenantId, AssetSearchQuery query);
+ @Deprecated(since = "3.6.2", forRemoval = true)
ListenableFuture> findAssetTypesByTenantId(TenantId tenantId);
Asset assignAssetToEdge(TenantId tenantId, AssetId assetId, EdgeId edgeId);
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 5d325aa1a6..066254d28d 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
@@ -17,12 +17,15 @@ package org.thingsboard.server.dao.device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.id.DeviceProfileId;
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.EntityDaoService;
+import java.util.List;
+
public interface DeviceProfileService extends EntityDaoService {
DeviceProfile findDeviceProfileById(TenantId tenantId, DeviceProfileId deviceProfileId);
@@ -59,4 +62,6 @@ public interface DeviceProfileService extends EntityDaoService {
void deleteDeviceProfilesByTenantId(TenantId tenantId);
+ List findDeviceProfileNamesByTenantId(TenantId tenantId, boolean activeOnly);
+
}
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..0bb0dbf50e 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
@@ -95,6 +95,7 @@ public interface DeviceService extends EntityDaoService {
ListenableFuture> findDevicesByQuery(TenantId tenantId, DeviceSearchQuery query);
+ @Deprecated(since = "3.6.2", forRemoval = true)
ListenableFuture> findDeviceTypesByTenantId(TenantId tenantId);
Device assignDeviceToTenant(TenantId tenantId, Device device);
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUpgradeInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUpgradeInfo.java
new file mode 100644
index 0000000000..cadd589b49
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUpgradeInfo.java
@@ -0,0 +1,26 @@
+/**
+ * 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.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class EdgeUpgradeInfo {
+ private boolean requiresUpdateDb;
+ private String nextEdgeVersion;
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUpgradeMessage.java b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUpgradeMessage.java
new file mode 100644
index 0000000000..bb17479607
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUpgradeMessage.java
@@ -0,0 +1,33 @@
+/**
+ * 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 io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Map;
+
+@Data
+@ApiModel
+public class EdgeUpgradeMessage implements Serializable {
+
+ private static final long serialVersionUID = 2872965507642822989L;
+
+ @ApiModelProperty(position = 1, value = "Mapping for upgrade versions and upgrade strategy (next ver).")
+ private final Map edgeVersions;
+}
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/EdgeInstructions.java
similarity index 90%
rename from common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstallInstructions.java
rename to common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeInstructions.java
index 8343058250..2c3a66c002 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/EdgeInstructions.java
@@ -25,8 +25,8 @@ import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
-public class EdgeInstallInstructions {
+public class EdgeInstructions {
- @ApiModelProperty(position = 1, value = "Markdown with install instructions")
- private String installInstructions;
+ @ApiModelProperty(position = 1, value = "Markdown with install/upgrade instructions")
+ private String instructions;
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
index 76f5a1948b..1c4aed5393 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
@@ -19,6 +19,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.util.CollectionUtils;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
@@ -31,6 +32,7 @@ import org.thingsboard.server.dao.model.ToData;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -152,15 +154,26 @@ public abstract class DaoUtil {
}
}
- public static List convertTenantEntityTypesToDto(UUID tenantId, EntityType entityType, List types) {
+ public static List convertTenantEntityTypesToDto(UUID tenantUUID, EntityType entityType, List types) {
if (CollectionUtils.isEmpty(types)) {
return Collections.emptyList();
}
+ TenantId tenantId = TenantId.fromUUID(tenantUUID);
+ return types.stream()
+ .map(type -> new EntitySubtype(tenantId, entityType, type))
+ .collect(Collectors.toList());
+ }
- List list = new ArrayList<>(types.size());
- for (String type : types) {
- list.add(new EntitySubtype(TenantId.fromUUID(tenantId), entityType, type));
+ @Deprecated // used only in deprecated DAO api
+ public static List convertTenantEntityInfosToDto(UUID tenantUUID, EntityType entityType, List entityInfos) {
+ if (CollectionUtils.isEmpty(entityInfos)) {
+ return Collections.emptyList();
}
- return list;
+ var tenantId = TenantId.fromUUID(tenantUUID);
+ return entityInfos.stream()
+ .map(info -> new EntitySubtype(tenantId, entityType, info.getName()))
+ .sorted(Comparator.comparing(EntitySubtype::getType))
+ .collect(Collectors.toList());
}
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
index 37d5dcea7c..4cdb64348b 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
@@ -24,10 +24,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.common.data.util.TbPair;
-import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
-import org.thingsboard.server.dao.ImageContainerDao;
import org.thingsboard.server.dao.TenantEntityDao;
import java.util.List;
@@ -191,6 +189,7 @@ public interface AssetDao extends Dao, TenantEntityDao, ExportableEntityD
*
* @return the list of tenant asset type objects
*/
+ @Deprecated(since = "3.6.2", forRemoval = true)
ListenableFuture> findTenantAssetTypesAsync(UUID tenantId);
Long countAssetsByAssetProfileId(TenantId tenantId, UUID assetProfileId);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileDao.java
index 87e9177d66..7dc188000c 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileDao.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.asset;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
import org.thingsboard.server.common.data.id.AssetProfileId;
@@ -25,6 +26,7 @@ import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.ImageContainerDao;
+import java.util.List;
import java.util.UUID;
public interface AssetProfileDao extends Dao, ExportableEntityDao, ImageContainerDao {
@@ -47,4 +49,6 @@ public interface AssetProfileDao extends Dao, ExportableEntityDao<
PageData findAllWithImages(PageLink pageLink);
+ List findTenantAssetProfileNames(UUID tenantId, boolean activeOnly);
+
}
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 86cafcd10b..0b9e949c7c 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
@@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.asset.Asset;
@@ -42,9 +43,11 @@ import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.stream.Collectors;
import static org.thingsboard.server.dao.service.Validator.validateId;
@@ -318,7 +321,16 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService tenantAssetProfilesRemover =
+ @Override
+ public List findAssetProfileNamesByTenantId(TenantId tenantId, boolean activeOnly) {
+ log.trace("Executing findAssetProfileNamesByTenantId, tenantId [{}]", tenantId);
+ validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+ return assetProfileDao.findTenantAssetProfileNames(tenantId.getId(), activeOnly)
+ .stream().sorted(Comparator.comparing(EntityInfo::getName))
+ .collect(Collectors.toList());
+ }
+
+ private final PaginatedRemover tenantAssetProfilesRemover =
new PaginatedRemover<>() {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
index 0431b48cea..6de1158cf6 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
@@ -56,7 +56,6 @@ import org.thingsboard.server.dao.service.PaginatedRemover;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -363,7 +362,12 @@ public class BaseAssetService extends AbstractCachedEntityService
- assetList == null ? Collections.emptyList() : assetList.stream().filter(asset -> query.getAssetTypes().contains(asset.getType())).collect(Collectors.toList()), MoreExecutors.directExecutor()
+ assetList == null ?
+ Collections.emptyList() :
+ assetList.stream()
+ .filter(asset -> query.getAssetTypes().contains(asset.getType()))
+ .collect(Collectors.toList()),
+ MoreExecutors.directExecutor()
);
return assets;
}
@@ -372,12 +376,7 @@ public class BaseAssetService extends AbstractCachedEntityService> findAssetTypesByTenantId(TenantId tenantId) {
log.trace("Executing findAssetTypesByTenantId, tenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
- ListenableFuture> tenantAssetTypes = assetDao.findTenantAssetTypesAsync(tenantId.getId());
- return Futures.transform(tenantAssetTypes,
- assetTypes -> {
- assetTypes.sort(Comparator.comparing(EntitySubtype::getType));
- return assetTypes;
- }, MoreExecutors.directExecutor());
+ return assetDao.findTenantAssetTypesAsync(tenantId.getId());
}
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
index e6b19b271d..1638c7d872 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
@@ -162,6 +162,7 @@ public interface DeviceDao extends Dao, TenantEntityDao, ExportableEntit
*
* @return the list of tenant device type objects
*/
+ @Deprecated(since = "3.6.2", forRemoval = true)
ListenableFuture> findTenantDeviceTypesAsync(UUID tenantId);
/**
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java
index 7bca5ebf8f..95336e8f4a 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@@ -25,6 +26,7 @@ import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.ImageContainerDao;
+import java.util.List;
import java.util.UUID;
public interface DeviceProfileDao extends Dao, ExportableEntityDao, ImageContainerDao {
@@ -49,4 +51,6 @@ public interface DeviceProfileDao extends Dao, ExportableEntityDa
PageData findAllWithImages(PageLink pageLink);
+ List findTenantDeviceProfileNames(UUID tenantId, boolean activeOnly);
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
index 847811cd4f..7e3567c888 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
@@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DeviceProfileInfo;
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.EntityInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
@@ -57,11 +58,13 @@ import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateString;
@@ -373,7 +376,16 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService tenantDeviceProfilesRemover =
+ @Override
+ public List findDeviceProfileNamesByTenantId(TenantId tenantId, boolean activeOnly) {
+ log.trace("Executing findDeviceProfileNamesByTenantId, tenantId [{}]", tenantId);
+ validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+ return deviceProfileDao.findTenantDeviceProfileNames(tenantId.getId(), activeOnly)
+ .stream().sorted(Comparator.comparing(EntityInfo::getName))
+ .collect(Collectors.toList());
+ }
+
+ private final PaginatedRemover tenantDeviceProfilesRemover =
new PaginatedRemover<>() {
@Override
@@ -422,7 +434,8 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService 1) {
return EncryptionUtil.certTrimNewLinesForChainInDeviceProfile(certificateValue);
}
- } catch (CertificateException ignored) {}
+ } catch (CertificateException ignored) {
+ }
return EncryptionUtil.certTrimNewLines(certificateValue);
}
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 cfc30ccd8f..fcedc94c9c 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
@@ -486,12 +486,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService> findDeviceTypesByTenantId(TenantId tenantId) {
log.trace("Executing findDeviceTypesByTenantId, tenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
- ListenableFuture> tenantDeviceTypes = deviceDao.findTenantDeviceTypesAsync(tenantId.getId());
- return Futures.transform(tenantDeviceTypes,
- deviceTypes -> {
- deviceTypes.sort(Comparator.comparing(EntitySubtype::getType));
- return deviceTypes;
- }, MoreExecutors.directExecutor());
+ return deviceDao.findTenantDeviceTypesAsync(tenantId.getId());
}
@Transactional
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 29f9ba8ae0..2074b222c4 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
@@ -78,6 +78,7 @@ import java.util.regex.Pattern;
public class BaseImageService extends BaseResourceService implements ImageService {
private static final int MAX_ENTITIES_TO_FIND = 10;
+ private static final String DEFAULT_CONFIG_TAG = "defaultConfig";
public static Map DASHBOARD_BASE64_MAPPING = new HashMap<>();
public static Map WIDGET_TYPE_BASE64_MAPPING = new HashMap<>();
@@ -301,12 +302,12 @@ public class BaseImageService extends BaseResourceService implements ImageServic
boolean updated = result.isUpdated();
if (entity.getDescriptor().isObject()) {
ObjectNode descriptor = (ObjectNode) entity.getDescriptor();
- JsonNode defaultConfig = Optional.ofNullable(descriptor.get("defaultConfig"))
+ JsonNode defaultConfig = Optional.ofNullable(descriptor.get(DEFAULT_CONFIG_TAG))
.filter(JsonNode::isTextual).map(JsonNode::asText)
.map(JacksonUtil::toJsonNode).orElse(null);
if (defaultConfig != null) {
updated |= base64ToImageUrlUsingMapping(entity.getTenantId(), WIDGET_TYPE_BASE64_MAPPING, Collections.singletonMap("prefix", prefix), defaultConfig);
- descriptor.put("defaultConfig", defaultConfig.toString());
+ descriptor.put(DEFAULT_CONFIG_TAG, defaultConfig.toString());
}
}
updated |= base64ToImageUrlRecursively(entity.getTenantId(), prefix, entity.getDescriptor());
@@ -525,7 +526,17 @@ public class BaseImageService extends BaseResourceService implements ImageServic
public void inlineImages(WidgetTypeDetails widgetTypeDetails) {
log.trace("Executing inlineImage [{}] [WidgetTypeDetails] [{}]", widgetTypeDetails.getTenantId(), widgetTypeDetails.getId());
inlineImage(widgetTypeDetails);
- inlineIntoJson(widgetTypeDetails.getTenantId(), widgetTypeDetails.getDescriptor());
+ ObjectNode descriptor = (ObjectNode) widgetTypeDetails.getDescriptor();
+ inlineIntoJson(widgetTypeDetails.getTenantId(), descriptor);
+ if (descriptor.has(DEFAULT_CONFIG_TAG) && descriptor.get(DEFAULT_CONFIG_TAG).isTextual()) {
+ try {
+ var defaultConfig = JacksonUtil.toJsonNode(descriptor.get(DEFAULT_CONFIG_TAG).asText());
+ inlineIntoJson(widgetTypeDetails.getTenantId(), defaultConfig);
+ descriptor.put(DEFAULT_CONFIG_TAG, JacksonUtil.toString(defaultConfig));
+ } catch (Exception e) {
+ log.debug("[{}][{}] Failed to process default config: ", widgetTypeDetails.getTenantId(), widgetTypeDetails.getId(), e);
+ }
+ }
}
private void inlineIntoJson(TenantId tenantId, JsonNode root) {
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..940231aaaa 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
@@ -54,7 +54,7 @@ public class TbSqlBlockingQueue implements TbSqlQueue {
String logName = params.getLogName();
int batchSize = params.getBatchSize();
long maxDelay = params.getMaxDelay();
- List> entities = new ArrayList<>(batchSize);
+ final List> entities = new ArrayList<>(batchSize);
while (!Thread.interrupted()) {
try {
long currentTs = System.currentTimeMillis();
@@ -83,19 +83,23 @@ public class TbSqlBlockingQueue implements TbSqlQueue {
Thread.sleep(remainingDelay);
}
}
- } catch (Exception e) {
- stats.incrementFailed(entities.size());
- entities.forEach(entityFutureWrapper -> entityFutureWrapper.getFuture().setException(e));
- if (e instanceof InterruptedException) {
+ } catch (Throwable t) {
+ log.error("[{}] Failed to save {} entities", logName, entities.size(), t);
+ try {
+ stats.incrementFailed(entities.size());
+ entities.forEach(entityFutureWrapper -> entityFutureWrapper.getFuture().setException(t));
+ } catch (Throwable th) {
+ log.error("[{}] Failed to set future exception", logName, th);
+ }
+ if (t instanceof InterruptedException) {
log.info("[{}] Queue polling was interrupted", logName);
break;
- } else {
- log.error("[{}] Failed to save {} entities", logName, entities.size(), e);
}
} finally {
entities.clear();
}
}
+ log.info("[{}] Queue polling completed", logName);
});
logExecutor.scheduleAtFixedRate(() -> {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetProfileRepository.java
index a9acef8608..27dde2fc51 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetProfileRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetProfileRepository.java
@@ -20,6 +20,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.AssetProfileEntity;
@@ -71,4 +72,13 @@ public interface AssetProfileRepository extends JpaRepository findAllByImageNotNull(Pageable pageable);
+ @Query("SELECT new org.thingsboard.server.common.data.EntityInfo(ap.id, 'ASSET_PROFILE', ap.name) " +
+ "FROM AssetProfileEntity ap WHERE ap.tenantId = :tenantId AND EXISTS " +
+ "(SELECT 1 FROM AssetEntity a WHERE a.tenantId = :tenantId AND a.assetProfileId = ap.id)")
+ List findActiveTenantAssetProfileNames(@Param("tenantId") UUID tenantId);
+
+ @Query("SELECT new org.thingsboard.server.common.data.EntityInfo(a.id, 'ASSET_PROFILE', a.name) " +
+ "FROM AssetProfileEntity a WHERE a.tenantId = :tenantId")
+ List findAllTenantAssetProfileNames(@Param("tenantId") UUID tenantId);
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
index c16afc9983..0c2db8ec66 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
@@ -167,9 +167,6 @@ public interface AssetRepository extends JpaRepository, Expor
@Param("textSearch") String textSearch,
Pageable pageable);
- @Query("SELECT DISTINCT a.type FROM AssetEntity a WHERE a.tenantId = :tenantId")
- List findTenantAssetTypes(@Param("tenantId") UUID tenantId);
-
Long countByAssetProfileId(UUID assetProfileId);
@Query("SELECT a FROM AssetEntity a, RelationEntity re WHERE a.tenantId = :tenantId " +
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
index e2fe60945d..48dd0996de 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
@@ -39,11 +39,10 @@ import org.thingsboard.server.dao.util.SqlDao;
import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
-import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityTypesToDto;
+import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityInfosToDto;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
/**
@@ -57,6 +56,9 @@ public class JpaAssetDao extends JpaAbstractDao implements A
@Autowired
private AssetRepository assetRepository;
+ @Autowired
+ private AssetProfileRepository assetProfileRepository;
+
@Override
protected Class getEntityClass() {
return AssetEntity.class;
@@ -193,7 +195,7 @@ public class JpaAssetDao extends JpaAbstractDao implements A
@Override
public ListenableFuture> findTenantAssetTypesAsync(UUID tenantId) {
- return service.submit(() -> convertTenantEntityTypesToDto(tenantId, EntityType.ASSET, assetRepository.findTenantAssetTypes(tenantId)));
+ return service.submit(() -> convertTenantEntityInfosToDto(tenantId, EntityType.ASSET, assetProfileRepository.findActiveTenantAssetProfileNames(tenantId)));
}
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetProfileDao.java
index b57e5c0c40..d409b2c4d2 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetProfileDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetProfileDao.java
@@ -20,6 +20,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
@@ -103,6 +104,13 @@ public class JpaAssetProfileDao extends JpaAbstractDao findTenantAssetProfileNames(UUID tenantId, boolean activeOnly) {
+ return activeOnly ?
+ assetProfileRepository.findActiveTenantAssetProfileNames(tenantId) :
+ assetProfileRepository.findAllTenantAssetProfileNames(tenantId);
+ }
+
@Override
public AssetProfile findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(assetProfileRepository.findByTenantIdAndExternalId(tenantId, externalId));
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java
index a9baf60e60..3b74de9e98 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java
@@ -22,7 +22,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceTransportType;
-import org.thingsboard.server.common.data.asset.AssetProfileInfo;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
@@ -83,4 +83,13 @@ public interface DeviceProfileRepository extends JpaRepository findAllByImageNotNull(Pageable pageable);
+ @Query("SELECT new org.thingsboard.server.common.data.EntityInfo(dp.id, 'DEVICE_PROFILE', dp.name) " +
+ "FROM DeviceProfileEntity dp WHERE dp.tenantId = :tenantId AND EXISTS " +
+ "(SELECT 1 FROM DeviceEntity dv WHERE dv.tenantId = :tenantId AND dv.deviceProfileId = dp.id)")
+ List findActiveTenantDeviceProfileNames(@Param("tenantId") UUID tenantId);
+
+ @Query("SELECT new org.thingsboard.server.common.data.EntityInfo(d.id, 'DEVICE_PROFILE', d.name) " +
+ "FROM DeviceProfileEntity d WHERE d.tenantId = :tenantId")
+ List findAllTenantDeviceProfileNames(@Param("tenantId") UUID tenantId);
+
}
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 3ca96e7087..97ddb94b88 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
@@ -145,9 +145,6 @@ public interface DeviceRepository extends JpaRepository, Exp
@Param("textSearch") String textSearch,
Pageable pageable);
- @Query("SELECT DISTINCT d.type FROM DeviceEntity d WHERE d.tenantId = :tenantId")
- List findTenantDeviceTypes(@Param("tenantId") UUID tenantId);
-
DeviceEntity findByTenantIdAndName(UUID tenantId, String name);
List findDevicesByTenantIdAndCustomerIdAndIdIn(UUID tenantId, UUID customerId, List deviceIds);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
index bf7d8670b1..d022c42af6 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
@@ -44,11 +44,10 @@ import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
-import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityTypesToDto;
+import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityInfosToDto;
/**
* Created by Valerii Sosliuk on 5/6/2017.
@@ -64,6 +63,9 @@ public class JpaDeviceDao extends JpaAbstractDao implement
@Autowired
private NativeDeviceRepository nativeDeviceRepository;
+ @Autowired
+ private DeviceProfileRepository deviceProfileRepository;
+
@Override
protected Class getEntityClass() {
return DeviceEntity.class;
@@ -217,7 +219,7 @@ public class JpaDeviceDao extends JpaAbstractDao implement
@Override
public ListenableFuture> findTenantDeviceTypesAsync(UUID tenantId) {
- return service.submit(() -> convertTenantEntityTypesToDto(tenantId, EntityType.DEVICE, deviceRepository.findTenantDeviceTypes(tenantId)));
+ return service.submit(() -> convertTenantEntityInfosToDto(tenantId, EntityType.DEVICE, deviceProfileRepository.findActiveTenantDeviceProfileNames(tenantId)));
}
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java
index 12a91e3cf7..9109f223a9 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java
@@ -23,6 +23,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceTransportType;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@@ -121,6 +122,13 @@ public class JpaDeviceProfileDao extends JpaAbstractDao findTenantDeviceProfileNames(UUID tenantId, boolean activeOnly) {
+ return activeOnly ?
+ deviceProfileRepository.findActiveTenantDeviceProfileNames(tenantId) :
+ deviceProfileRepository.findAllTenantDeviceProfileNames(tenantId);
+ }
+
@Override
public DeviceProfile findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndExternalId(tenantId, externalId));
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeInfoRepository.java
index 83797d7baa..221642699e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeInfoRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeInfoRepository.java
@@ -204,7 +204,7 @@ public interface WidgetTypeInfoRepository extends JpaRepository findByImageUrl(@Param("imageLink") String imageLink, @Param("lmt") int lmt);
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java
index c5708ce34e..63ddba1c14 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java
@@ -217,7 +217,7 @@ public class DeviceConnectivityUtil {
return host;
}
- private static boolean isLocalhost(String host) {
+ public static boolean isLocalhost(String host) {
try {
InetAddress inetAddress = InetAddress.getByName(host);
return inetAddress.isLoopbackAddress();
diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java b/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java
index 466e9d4bab..29f7b32fae 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java
@@ -34,6 +34,7 @@ import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.batik.util.XMLResourceDescriptor;
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.thingsboard.server.common.data.StringUtils;
@@ -69,13 +70,17 @@ public class ImageUtils {
public static ProcessedImage processImage(byte[] data, String mediaType, int thumbnailMaxDimension) throws Exception {
if (mediaTypeToFileExtension(mediaType).equals("svg")) {
- return processSvgImage(data, mediaType, thumbnailMaxDimension);
+ try {
+ return processSvgImage(data, mediaType, thumbnailMaxDimension);
+ } catch (Exception e) {
+ if (log.isDebugEnabled()) { // printing stacktrace
+ log.warn("Couldn't process SVG image, leaving preview as original image", e);
+ } else {
+ log.warn("Couldn't process SVG image, leaving preview as original image: {}", ExceptionUtils.getMessage(e));
+ }
+ return previewAsOriginalImage(data, mediaType);
+ }
}
- ProcessedImage image = new ProcessedImage();
- image.setMediaType(mediaType);
- image.setData(data);
- image.setSize(data.length);
-
BufferedImage bufferedImage = null;
try {
bufferedImage = ImageIO.read(new ByteArrayInputStream(data));
@@ -83,6 +88,8 @@ public class ImageUtils {
}
if (bufferedImage == null) { // means that media type is not supported by ImageIO; extracting width and height from metadata and leaving preview as original image
Metadata metadata = ImageMetadataReader.readMetadata(new ByteArrayInputStream(data));
+ ProcessedImage image = previewAsOriginalImage(data, mediaType);
+ String dirName = "Unknown";
for (Directory dir : metadata.getDirectories()) {
Tag widthTag = dir.getTags().stream()
.filter(tag -> tag.getTagName().toLowerCase().contains("width"))
@@ -94,24 +101,22 @@ public class ImageUtils {
continue;
}
int width = Integer.parseInt(dir.getObject(widthTag.getTagType()).toString());
- int height = Integer.parseInt(dir.getObject(widthTag.getTagType()).toString());
+ int height = Integer.parseInt(dir.getObject(heightTag.getTagType()).toString());
image.setWidth(width);
image.setHeight(height);
-
- ProcessedImage preview = new ProcessedImage();
- preview.setWidth(image.getWidth());
- preview.setHeight(image.getHeight());
- preview.setMediaType(mediaType);
- preview.setData(null);
- preview.setSize(data.length);
- image.setPreview(preview);
- log.warn("Couldn't process {} ({}) with ImageIO, leaving preview as original image", mediaType, dir.getName());
- return image;
+ image.getPreview().setWidth(width);
+ image.getPreview().setHeight(height);
+ dirName = dir.getName();
+ break;
}
- log.warn("Image media type {} not supported", mediaType);
- throw new IllegalArgumentException("Media type " + mediaType + " not supported");
+ log.warn("Couldn't process {} ({}) with ImageIO, leaving preview as original image", mediaType, dirName);
+ return image;
}
+ ProcessedImage image = new ProcessedImage();
+ image.setMediaType(mediaType);
+ image.setData(data);
+ image.setSize(data.length);
image.setWidth(bufferedImage.getWidth());
image.setHeight(bufferedImage.getHeight());
@@ -202,6 +207,23 @@ public class ImageUtils {
return image;
}
+ private static ProcessedImage previewAsOriginalImage(byte[] data, String mediaType) {
+ ProcessedImage image = new ProcessedImage();
+ image.setMediaType(mediaType);
+ image.setData(data);
+ image.setSize(data.length);
+ image.setWidth(0);
+ image.setHeight(0);
+ ProcessedImage preview = new ProcessedImage();
+ preview.setMediaType(mediaType);
+ preview.setData(null);
+ preview.setSize(data.length);
+ preview.setWidth(0);
+ preview.setHeight(0);
+ image.setPreview(preview);
+ return image;
+ }
+
private static int[] getThumbnailDimensions(int originalWidth, int originalHeight, int maxDimension) {
if (originalWidth <= maxDimension && originalHeight <= maxDimension) {
return new int[]{originalWidth, originalHeight};
diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql
index 472c0f601e..91f3eff713 100644
--- a/dao/src/main/resources/sql/schema-entities-idx.sql
+++ b/dao/src/main/resources/sql/schema-entities-idx.sql
@@ -57,6 +57,8 @@ CREATE INDEX IF NOT EXISTS idx_asset_customer_id_and_type ON asset(tenant_id, cu
CREATE INDEX IF NOT EXISTS idx_asset_type ON asset(tenant_id, type);
+CREATE INDEX IF NOT EXISTS idx_asset_profile_id ON asset(tenant_id, asset_profile_id);
+
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);
CREATE INDEX IF NOT EXISTS idx_audit_log_tenant_id_and_created_time ON audit_log(tenant_id, created_time DESC);
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AssetProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AssetProfileServiceTest.java
index 391c00fa49..0d6f0d8075 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/AssetProfileServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/AssetProfileServiceTest.java
@@ -24,6 +24,7 @@ import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
@@ -35,11 +36,14 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
+import static org.assertj.core.api.Assertions.assertThat;
+
@DaoSqlTest
public class AssetProfileServiceTest extends AbstractServiceTest {
@@ -272,4 +276,97 @@ public class AssetProfileServiceTest extends AbstractServiceTest {
Assert.assertEquals(1, pageData.getTotalElements());
}
+ @Test
+ public void testFindAllassetProfilesByTenantId() {
+ int assetProfilesCount = 4; // 3 created + default
+ var assetProfiles = new ArrayList(4);
+
+ var profileC = assetProfileService.saveAssetProfile(
+ createAssetProfile(tenantId, "profile C"));
+ assetProfiles.add(assetProfileService.saveAssetProfile(profileC));
+
+
+ var profileA = assetProfileService.saveAssetProfile(
+ createAssetProfile(tenantId, "profile A"));
+ assetProfiles.add(assetProfileService.saveAssetProfile(profileA));
+
+
+ var profileB = assetProfileService.saveAssetProfile(
+ createAssetProfile(tenantId, "profile B"));
+ assetProfiles.add(assetProfileService.saveAssetProfile(profileB));
+
+
+ assetProfiles.add(assetProfileService.findDefaultAssetProfile(tenantId));
+
+ List sortedProfileInfos = assetProfiles.stream()
+ .map(profile -> new EntityInfo(profile.getId(), profile.getName()))
+ .sorted(Comparator.comparing(EntityInfo::getName))
+ .collect(Collectors.toList());
+
+ var assetProfileInfos = assetProfileService
+ .findAssetProfileNamesByTenantId(tenantId, false);
+
+ assertThat(assetProfileInfos).isNotNull();
+ assertThat(assetProfileInfos).hasSize(assetProfilesCount);
+ assertThat(assetProfileInfos).isEqualTo(sortedProfileInfos);
+ }
+
+ @Test
+ public void testFindActiveOnlyassetProfilesByTenantId() {
+
+ String profileCName = "profile C";
+ assetProfileService.saveAssetProfile(
+ createAssetProfile(tenantId, profileCName));
+
+ String profileAName = "profile A";
+ assetProfileService.saveAssetProfile(
+ createAssetProfile(tenantId, profileAName));
+
+ String profileBName = "profile B";
+ assetProfileService.saveAssetProfile(
+ createAssetProfile(tenantId, profileBName));
+
+
+ var assetProfileInfos = assetProfileService
+ .findAssetProfileNamesByTenantId(tenantId, true);
+
+ assertThat(assetProfileInfos).isNotNull();
+ assertThat(assetProfileInfos).isEmpty();
+
+ var assetC = new Asset();
+ assetC.setName("Test asset C");
+ assetC.setType(profileCName);
+ assetC.setTenantId(tenantId);
+
+ assetC = assetService.saveAsset(assetC);
+
+ var assetA = new Asset();
+ assetA.setName("Test asset A");
+ assetA.setType(profileAName);
+ assetA.setTenantId(tenantId);
+
+ assetA = assetService.saveAsset(assetA);
+
+ var assetB = new Asset();
+ assetB.setName("Test asset B");
+ assetB.setType(profileBName);
+ assetB.setTenantId(tenantId);
+
+ assetB = assetService.saveAsset(assetB);
+
+ assetProfileInfos = assetProfileService
+ .findAssetProfileNamesByTenantId(tenantId, true);
+
+ var expected = List.of(
+ new EntityInfo(assetA.getAssetProfileId(), profileAName),
+ new EntityInfo(assetB.getAssetProfileId(), profileBName),
+ new EntityInfo(assetC.getAssetProfileId(), profileCName)
+ );
+
+ assertThat(assetProfileInfos).isNotEmpty();
+ assertThat(assetProfileInfos).hasSize(3);
+ assertThat(assetProfileInfos).isEqualTo(expected);
+ }
+
+
}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceProfileServiceTest.java
index 16a80e438d..ac3b603f13 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceProfileServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceProfileServiceTest.java
@@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceTransportType;
+import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.page.PageData;
@@ -41,6 +42,7 @@ import org.thingsboard.server.dao.ota.OtaPackageService;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
@@ -372,4 +374,96 @@ public class DeviceProfileServiceTest extends AbstractServiceTest {
Assert.assertEquals(1, pageData.getTotalElements());
}
+ @Test
+ public void testFindAllDeviceProfilesByTenantId() {
+ int deviceProfilesCount = 4; // 3 created + default
+ var deviceProfiles = new ArrayList(4);
+
+ var profileC = deviceProfileService.saveDeviceProfile(
+ createDeviceProfile(tenantId, "profile C"));
+ deviceProfiles.add(deviceProfileService.saveDeviceProfile(profileC));
+
+
+ var profileA = deviceProfileService.saveDeviceProfile(
+ createDeviceProfile(tenantId, "profile A"));
+ deviceProfiles.add(deviceProfileService.saveDeviceProfile(profileA));
+
+
+ var profileB = deviceProfileService.saveDeviceProfile(
+ createDeviceProfile(tenantId, "profile B"));
+ deviceProfiles.add(deviceProfileService.saveDeviceProfile(profileB));
+
+
+ deviceProfiles.add(deviceProfileService.findDefaultDeviceProfile(tenantId));
+
+ List sortedProfileInfos = deviceProfiles.stream()
+ .map(profile -> new EntityInfo(profile.getId(), profile.getName()))
+ .sorted(Comparator.comparing(EntityInfo::getName))
+ .collect(Collectors.toList());
+
+ var deviceProfileInfos = deviceProfileService
+ .findDeviceProfileNamesByTenantId(tenantId, false);
+
+ assertThat(deviceProfileInfos).isNotNull();
+ assertThat(deviceProfileInfos).hasSize(deviceProfilesCount);
+ assertThat(deviceProfileInfos).isEqualTo(sortedProfileInfos);
+ }
+
+ @Test
+ public void testFindActiveOnlyDeviceProfilesByTenantId() {
+
+ String profileCName = "profile C";
+ deviceProfileService.saveDeviceProfile(
+ createDeviceProfile(tenantId, profileCName));
+
+ String profileAName = "profile A";
+ deviceProfileService.saveDeviceProfile(
+ createDeviceProfile(tenantId, profileAName));
+
+ String profileBName = "profile B";
+ deviceProfileService.saveDeviceProfile(
+ createDeviceProfile(tenantId, profileBName));
+
+
+ var deviceProfileInfos = deviceProfileService
+ .findDeviceProfileNamesByTenantId(tenantId, true);
+
+ assertThat(deviceProfileInfos).isNotNull();
+ assertThat(deviceProfileInfos).isEmpty();
+
+ var deviceC = new Device();
+ deviceC.setName("Test Device C");
+ deviceC.setType(profileCName);
+ deviceC.setTenantId(tenantId);
+
+ deviceC = deviceService.saveDevice(deviceC);
+
+ var deviceA = new Device();
+ deviceA.setName("Test Device A");
+ deviceA.setType(profileAName);
+ deviceA.setTenantId(tenantId);
+
+ deviceA = deviceService.saveDevice(deviceA);
+
+ var deviceB = new Device();
+ deviceB.setName("Test Device B");
+ deviceB.setType(profileBName);
+ deviceB.setTenantId(tenantId);
+
+ deviceB = deviceService.saveDevice(deviceB);
+
+ deviceProfileInfos = deviceProfileService
+ .findDeviceProfileNamesByTenantId(tenantId, true);
+
+ var expected = List.of(
+ new EntityInfo(deviceA.getDeviceProfileId(), profileAName),
+ new EntityInfo(deviceB.getDeviceProfileId(), profileBName),
+ new EntityInfo(deviceC.getDeviceProfileId(), profileCName)
+ );
+
+ assertThat(deviceProfileInfos).isNotEmpty();
+ assertThat(deviceProfileInfos).hasSize(3);
+ assertThat(deviceProfileInfos).isEqualTo(expected);
+ }
+
}
diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml
index 8c363b019c..645acfb164 100644
--- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml
+++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml
@@ -48,7 +48,7 @@ zk:
# Queue configuration parameters
queue:
type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
- prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka) except of js executor topics (please use REMOTE_JS_EVAL_REQUEST_TOPIC and REMOTE_JS_EVAL_RESPONSE_TOPIC to specify custom topic names)
+ prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
in_memory:
stats:
# For debug lvl
diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
index fb878a33b6..5fe2561f3f 100644
--- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
+++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
@@ -84,7 +84,7 @@ import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeInfo;
-import org.thingsboard.server.common.data.edge.EdgeInstallInstructions;
+import org.thingsboard.server.common.data.edge.EdgeInstructions;
import org.thingsboard.server.common.data.edge.EdgeSearchQuery;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.id.AlarmCommentId;
@@ -505,7 +505,7 @@ public class RestClient implements Closeable {
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference>() {
- }).getBody();
+ }).getBody();
}
public AlarmComment saveAlarmComment(AlarmId alarmId, AlarmComment alarmComment) {
@@ -703,6 +703,7 @@ public class RestClient implements Closeable {
}).getBody();
}
+ @Deprecated(since = "3.6.2")
public List getAssetTypes() {
return restTemplate.exchange(URI.create(
baseURL + "/api/asset/types"),
@@ -712,6 +713,15 @@ public class RestClient implements Closeable {
}).getBody();
}
+ public List getAssetProfileNames(boolean activeOnly) {
+ return restTemplate.exchange(
+ baseURL + "/api/assetProfile/names?activeOnly={activeOnly}",
+ HttpMethod.GET,
+ HttpEntity.EMPTY,
+ new ParameterizedTypeReference>() {
+ }, activeOnly).getBody();
+ }
+
public BulkImportResult processAssetsBulkImport(BulkImportRequest request) {
return restTemplate.exchange(
baseURL + "/api/asset/bulk_import",
@@ -1394,6 +1404,7 @@ public class RestClient implements Closeable {
}).getBody();
}
+ @Deprecated(since = "3.6.2")
public List getDeviceTypes() {
return restTemplate.exchange(
baseURL + "/api/device/types",
@@ -1403,6 +1414,15 @@ public class RestClient implements Closeable {
}).getBody();
}
+ public List getDeviceProfileNames(boolean activeOnly) {
+ return restTemplate.exchange(
+ baseURL + "/api/deviceProfile/names?activeOnly={activeOnly}",
+ HttpMethod.GET,
+ HttpEntity.EMPTY,
+ new ParameterizedTypeReference>() {
+ }, activeOnly).getBody();
+ }
+
public JsonNode claimDevice(String deviceName, ClaimRequest claimRequest) {
return restTemplate.exchange(
baseURL + "/api/customer/device/{deviceName}/claim",
@@ -3241,12 +3261,18 @@ public class RestClient implements Closeable {
}).getBody();
}
- public Optional getEdgeDockerInstallInstructions(EdgeId edgeId) {
- ResponseEntity edgeInstallInstructionsResult =
- restTemplate.getForEntity(baseURL + "/api/edge/instructions/{edgeId}", EdgeInstallInstructions.class, edgeId.getId());
+ public Optional getEdgeInstallInstructions(EdgeId edgeId, String method) {
+ ResponseEntity edgeInstallInstructionsResult =
+ restTemplate.getForEntity(baseURL + "/api/edge/instructions/install/{edgeId}/{method}", EdgeInstructions.class, edgeId.getId(), method);
return Optional.ofNullable(edgeInstallInstructionsResult.getBody());
}
+ public Optional getEdgeUpgradeInstructions(String edgeVersion, String method) {
+ ResponseEntity edgeUpgradeInstructionsResult =
+ restTemplate.getForEntity(baseURL + "/api/edge/instructions/upgrade/{edgeVersion}/{method}", EdgeInstructions.class, edgeVersion, method);
+ return Optional.ofNullable(edgeUpgradeInstructionsResult.getBody());
+ }
+
public UUID saveEntitiesVersion(VersionCreateRequest request) {
return restTemplate.postForEntity(baseURL + "/api/entities/vc/version", request, UUID.class).getBody();
}
diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml
index a45b41803e..ab5deff3f7 100644
--- a/transport/coap/src/main/resources/tb-coap-transport.yml
+++ b/transport/coap/src/main/resources/tb-coap-transport.yml
@@ -198,7 +198,7 @@ transport:
# Queue configuration parameters
queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
- prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka) except of js executor topics (please use REMOTE_JS_EVAL_REQUEST_TOPIC and REMOTE_JS_EVAL_RESPONSE_TOPIC to specify custom topic names)
+ prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
kafka:
# Kafka Bootstrap Servers
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml
index 81fdf414bb..05231a6fb1 100644
--- a/transport/http/src/main/resources/tb-http-transport.yml
+++ b/transport/http/src/main/resources/tb-http-transport.yml
@@ -181,7 +181,7 @@ transport:
# Queue configuration parameters
queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
- prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka) except of js executor topics (please use REMOTE_JS_EVAL_REQUEST_TOPIC and REMOTE_JS_EVAL_RESPONSE_TOPIC to specify custom topic names)
+ prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka) .
kafka:
# Kafka Bootstrap Servers
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
index f68f158dc1..10dbac101c 100644
--- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
+++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
@@ -277,7 +277,7 @@ transport:
# Queue configuration properties
queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
- prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka) except of js executor topics (please use REMOTE_JS_EVAL_REQUEST_TOPIC and REMOTE_JS_EVAL_RESPONSE_TOPIC to specify custom topic names)
+ prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
kafka:
# Kafka Bootstrap Servers
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml
index 16c2c019df..0eb8b4315b 100644
--- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml
+++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml
@@ -214,7 +214,7 @@ transport:
# Queue configuration parameters
queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
- prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka) except of js executor topics (please use REMOTE_JS_EVAL_REQUEST_TOPIC and REMOTE_JS_EVAL_RESPONSE_TOPIC to specify custom topic names)
+ prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
kafka:
# Kafka Bootstrap Servers
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml
index 2b6cc17d9b..d139b641a6 100644
--- a/transport/snmp/src/main/resources/tb-snmp-transport.yml
+++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml
@@ -160,7 +160,7 @@ transport:
# Queue configuration parameters
queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
- prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka) except of js executor topics (please use REMOTE_JS_EVAL_REQUEST_TOPIC and REMOTE_JS_EVAL_RESPONSE_TOPIC to specify custom topic names)
+ prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
kafka:
# Kafka Bootstrap Servers
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
diff --git a/ui-ngx/src/app/core/http/edge.service.ts b/ui-ngx/src/app/core/http/edge.service.ts
index 4ddcc69458..30fbb64c54 100644
--- a/ui-ngx/src/app/core/http/edge.service.ts
+++ b/ui-ngx/src/app/core/http/edge.service.ts
@@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http';
import { PageLink, TimePageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
import { EntitySubtype } from '@app/shared/models/entity-type.models';
-import { Edge, EdgeEvent, EdgeInfo, EdgeInstallInstructions, EdgeSearchQuery } from '@shared/models/edge.models';
+import { Edge, EdgeEvent, EdgeInfo, EdgeInstructions, EdgeSearchQuery } from '@shared/models/edge.models';
import { EntityId } from '@shared/models/id/entity-id';
import { BulkImportRequest, BulkImportResult } from '@shared/import-export/import-export.models';
@@ -114,7 +114,11 @@ export class EdgeService {
return this.http.post('/api/edge/bulk_import', entitiesData, defaultHttpOptionsFromConfig(config));
}
- public getEdgeInstallInstructions(edgeId: string, method: string = 'ubuntu', config?: RequestConfig): Observable {
- return this.http.get(`/api/edge/instructions/${edgeId}/${method}`, defaultHttpOptionsFromConfig(config));
+ public getEdgeInstallInstructions(edgeId: string, method: string = 'ubuntu', config?: RequestConfig): Observable {
+ return this.http.get(`/api/edge/instructions/install/${edgeId}/${method}`, defaultHttpOptionsFromConfig(config));
+ }
+
+ public getEdgeUpgradeInstructions(edgeVersion: string, method: string = 'ubuntu', config?: RequestConfig): Observable {
+ return this.http.get(`/api/edge/instructions/upgrade/${edgeVersion}/${method}`, defaultHttpOptionsFromConfig(config));
}
}
diff --git a/ui-ngx/src/app/core/ws/notification-websocket.service.ts b/ui-ngx/src/app/core/ws/notification-websocket.service.ts
index 8f219c029d..cb33c23635 100644
--- a/ui-ngx/src/app/core/ws/notification-websocket.service.ts
+++ b/ui-ngx/src/app/core/ws/notification-websocket.service.ts
@@ -15,117 +15,45 @@
///
import { Inject, Injectable, NgZone } from '@angular/core';
+import {
+ TelemetryPluginCmdsWrapper,
+ TelemetrySubscriber,
+ WebsocketDataMsg
+} from '@shared/models/telemetry/telemetry.models';
+import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { AuthService } from '@core/auth/auth.service';
import { WINDOW } from '@core/services/window.service';
-import {
- isNotificationCountUpdateMsg,
- isNotificationsUpdateMsg,
- MarkAllAsReadCmd,
- MarkAsReadCmd,
- NotificationCountUpdate,
- NotificationPluginCmdWrapper,
- NotificationSubscriber,
- NotificationsUpdate,
- UnreadCountSubCmd,
- UnreadSubCmd,
- UnsubscribeCmd,
- WebsocketNotificationMsg
-} from '@shared/models/websocket/notification-ws.models';
import { WebsocketService } from '@core/ws/websocket.service';
-
// @dynamic
@Injectable({
providedIn: 'root'
})
-export class NotificationWebsocketService extends WebsocketService {
+export class NotificationWebsocketService extends WebsocketService {
- cmdWrapper: NotificationPluginCmdWrapper;
-
- constructor(protected store: Store,
+ constructor(private telemetryWebsocketService: TelemetryWebsocketService,
+ protected store: Store,
protected authService: AuthService,
protected ngZone: NgZone,
@Inject(WINDOW) protected window: Window) {
- super(store, authService, ngZone, 'api/ws/plugins/notifications', new NotificationPluginCmdWrapper(), window);
- this.errorName = 'WebSocket Notification Error';
+ super(store, authService, ngZone, 'api/ws/plugins/telemetry', new TelemetryPluginCmdsWrapper(), window);
}
- public subscribe(subscriber: NotificationSubscriber) {
- this.isActive = true;
- subscriber.subscriptionCommands.forEach(
- (subscriptionCommand) => {
- const cmdId = this.nextCmdId();
- this.subscribersMap.set(cmdId, subscriber);
- subscriptionCommand.cmdId = cmdId;
- if (subscriptionCommand instanceof UnreadCountSubCmd) {
- this.cmdWrapper.unreadCountSubCmd = subscriptionCommand;
- } else if (subscriptionCommand instanceof UnreadSubCmd) {
- this.cmdWrapper.unreadSubCmd = subscriptionCommand;
- } else if (subscriptionCommand instanceof MarkAsReadCmd) {
- this.cmdWrapper.markAsReadCmd = subscriptionCommand;
- this.subscribersMap.delete(cmdId);
- } else if (subscriptionCommand instanceof MarkAllAsReadCmd) {
- this.cmdWrapper.markAllAsReadCmd = subscriptionCommand;
- this.subscribersMap.delete(cmdId);
- }
- }
- );
- if (this.cmdWrapper.unreadCountSubCmd || this.cmdWrapper.unreadSubCmd) {
- this.subscribersCount++;
- }
- this.publishCommands();
+ public subscribe(subscriber: TelemetrySubscriber) {
+ this.telemetryWebsocketService.subscribe(subscriber);
}
- public update(subscriber: NotificationSubscriber) {
- if (!this.isReconnect) {
- subscriber.subscriptionCommands.forEach(
- (subscriptionCommand) => {
- if (subscriptionCommand.cmdId && subscriptionCommand instanceof UnreadSubCmd) {
- this.cmdWrapper.unreadSubCmd = subscriptionCommand;
- }
- }
- );
- this.publishCommands();
- }
+ public update(subscriber: TelemetrySubscriber) {
+ this.telemetryWebsocketService.update(subscriber);
}
- public unsubscribe(subscriber: NotificationSubscriber) {
- if (this.isActive) {
- subscriber.subscriptionCommands.forEach(
- (subscriptionCommand) => {
- if (subscriptionCommand instanceof UnreadCountSubCmd
- || subscriptionCommand instanceof UnreadSubCmd) {
- const unreadCountUnsubscribeCmd = new UnsubscribeCmd();
- unreadCountUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
- this.cmdWrapper.unsubCmd = unreadCountUnsubscribeCmd;
- }
- const cmdId = subscriptionCommand.cmdId;
- if (cmdId) {
- this.subscribersMap.delete(cmdId);
- }
- }
- );
- this.reconnectSubscribers.delete(subscriber);
- this.subscribersCount--;
- this.publishCommands();
- }
+ public unsubscribe(subscriber: TelemetrySubscriber) {
+ this.telemetryWebsocketService.unsubscribe(subscriber);
}
- processOnMessage(message: WebsocketNotificationMsg) {
- let subscriber: NotificationSubscriber;
- if (isNotificationCountUpdateMsg(message)) {
- subscriber = this.subscribersMap.get(message.cmdId);
- if (subscriber) {
- subscriber.onNotificationCountUpdate(new NotificationCountUpdate(message));
- }
- } else if (isNotificationsUpdateMsg(message)) {
- subscriber = this.subscribersMap.get(message.cmdId);
- if (subscriber) {
- subscriber.onNotificationsUpdate(new NotificationsUpdate(message));
- }
- }
+ processOnMessage(message: WebsocketDataMsg) {
+ this.telemetryWebsocketService.processOnMessage(message);
}
-
}
diff --git a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts
index 56c35328fb..f8fe055136 100644
--- a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts
+++ b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts
@@ -16,28 +16,36 @@
import { Inject, Injectable, NgZone } from '@angular/core';
import {
- AlarmCountCmd, AlarmCountUnsubscribeCmd,
+ AlarmCountCmd,
+ AlarmCountUnsubscribeCmd,
AlarmCountUpdate,
AlarmDataCmd,
AlarmDataUnsubscribeCmd,
AlarmDataUpdate,
- AttributesSubscriptionCmd,
EntityCountCmd,
EntityCountUnsubscribeCmd,
EntityCountUpdate,
EntityDataCmd,
EntityDataUnsubscribeCmd,
EntityDataUpdate,
- GetHistoryCmd, isAlarmCountUpdateMsg,
+ isAlarmCountUpdateMsg,
isAlarmDataUpdateMsg,
isEntityCountUpdateMsg,
isEntityDataUpdateMsg,
+ isNotificationCountUpdateMsg,
+ isNotificationsUpdateMsg,
+ MarkAllAsReadCmd,
+ MarkAsReadCmd,
+ NotificationCountUpdate,
+ NotificationSubscriber,
+ NotificationsUpdate,
SubscriptionCmd,
SubscriptionUpdate,
- TelemetryFeature,
TelemetryPluginCmdsWrapper,
TelemetrySubscriber,
- TimeseriesSubscriptionCmd,
+ UnreadCountSubCmd,
+ UnreadSubCmd,
+ UnsubscribeCmd,
WebsocketDataMsg
} from '@app/shared/models/telemetry/telemetry.models';
import { Store } from '@ngrx/store';
@@ -58,7 +66,7 @@ export class TelemetryWebsocketService extends WebsocketService {
const cmdId = this.nextCmdId();
- this.subscribersMap.set(cmdId, subscriber);
- subscriptionCommand.cmdId = cmdId;
- if (subscriptionCommand instanceof SubscriptionCmd) {
- if (subscriptionCommand.getType() === TelemetryFeature.TIMESERIES) {
- this.cmdWrapper.tsSubCmds.push(subscriptionCommand as TimeseriesSubscriptionCmd);
- } else {
- this.cmdWrapper.attrSubCmds.push(subscriptionCommand as AttributesSubscriptionCmd);
- }
- } else if (subscriptionCommand instanceof GetHistoryCmd) {
- this.cmdWrapper.historyCmds.push(subscriptionCommand);
- } else if (subscriptionCommand instanceof EntityDataCmd) {
- this.cmdWrapper.entityDataCmds.push(subscriptionCommand);
- } else if (subscriptionCommand instanceof AlarmDataCmd) {
- this.cmdWrapper.alarmDataCmds.push(subscriptionCommand);
- } else if (subscriptionCommand instanceof EntityCountCmd) {
- this.cmdWrapper.entityCountCmds.push(subscriptionCommand);
- } else if (subscriptionCommand instanceof AlarmCountCmd) {
- this.cmdWrapper.alarmCountCmds.push(subscriptionCommand);
+ if (!(subscriptionCommand instanceof MarkAsReadCmd) && !(subscriptionCommand instanceof MarkAllAsReadCmd)) {
+ this.subscribersMap.set(cmdId, subscriber);
}
+ subscriptionCommand.cmdId = cmdId;
+ this.cmdWrapper.cmds.push(subscriptionCommand);
}
);
this.subscribersCount++;
@@ -95,8 +89,8 @@ export class TelemetryWebsocketService extends WebsocketService {
- if (subscriptionCommand.cmdId && subscriptionCommand instanceof EntityDataCmd) {
- this.cmdWrapper.entityDataCmds.push(subscriptionCommand);
+ if (subscriptionCommand.cmdId && (subscriptionCommand instanceof EntityDataCmd || subscriptionCommand instanceof UnreadSubCmd)) {
+ this.cmdWrapper.cmds.push(subscriptionCommand);
}
}
);
@@ -110,27 +104,27 @@ export class TelemetryWebsocketService extends WebsocketService {
if (subscriptionCommand instanceof SubscriptionCmd) {
subscriptionCommand.unsubscribe = true;
- if (subscriptionCommand.getType() === TelemetryFeature.TIMESERIES) {
- this.cmdWrapper.tsSubCmds.push(subscriptionCommand as TimeseriesSubscriptionCmd);
- } else {
- this.cmdWrapper.attrSubCmds.push(subscriptionCommand as AttributesSubscriptionCmd);
- }
+ this.cmdWrapper.cmds.push(subscriptionCommand);
} else if (subscriptionCommand instanceof EntityDataCmd) {
const entityDataUnsubscribeCmd = new EntityDataUnsubscribeCmd();
entityDataUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
- this.cmdWrapper.entityDataUnsubscribeCmds.push(entityDataUnsubscribeCmd);
+ this.cmdWrapper.cmds.push(entityDataUnsubscribeCmd);
} else if (subscriptionCommand instanceof AlarmDataCmd) {
const alarmDataUnsubscribeCmd = new AlarmDataUnsubscribeCmd();
alarmDataUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
- this.cmdWrapper.alarmDataUnsubscribeCmds.push(alarmDataUnsubscribeCmd);
+ this.cmdWrapper.cmds.push(alarmDataUnsubscribeCmd);
} else if (subscriptionCommand instanceof EntityCountCmd) {
const entityCountUnsubscribeCmd = new EntityCountUnsubscribeCmd();
entityCountUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
- this.cmdWrapper.entityCountUnsubscribeCmds.push(entityCountUnsubscribeCmd);
+ this.cmdWrapper.cmds.push(entityCountUnsubscribeCmd);
} else if (subscriptionCommand instanceof AlarmCountCmd) {
const alarmCountUnsubscribeCmd = new AlarmCountUnsubscribeCmd();
alarmCountUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
- this.cmdWrapper.alarmCountUnsubscribeCmds.push(alarmCountUnsubscribeCmd);
+ this.cmdWrapper.cmds.push(alarmCountUnsubscribeCmd);
+ } else if (subscriptionCommand instanceof UnreadCountSubCmd || subscriptionCommand instanceof UnreadSubCmd) {
+ const notificationsUnsubCmds = new UnsubscribeCmd();
+ notificationsUnsubCmds.cmdId = subscriptionCommand.cmdId;
+ this.cmdWrapper.cmds.push(notificationsUnsubCmds);
}
const cmdId = subscriptionCommand.cmdId;
if (cmdId) {
@@ -145,29 +139,28 @@ export class TelemetryWebsocketService extends WebsocketService implements WsServ
lastCmdId = 0;
subscribersCount = 0;
- subscribersMap = new Map();
+ subscribersMap = new Map();
- reconnectSubscribers = new Set();
+ reconnectSubscribers = new Set();
- notificationUri: string;
+ wsUri: string;
- dataStream: WebSocketSubject;
+ dataStream: WebSocketSubject;
errorName = 'WebSocket Error';
@@ -69,23 +74,23 @@ export abstract class WebsocketService implements WsServ
if (!port) {
port = '443';
}
- this.notificationUri = 'wss:';
+ this.wsUri = 'wss:';
} else {
if (!port) {
port = '80';
}
- this.notificationUri = 'ws:';
+ this.wsUri = 'ws:';
}
- this.notificationUri += `//${this.window.location.hostname}:${port}/${apiEndpoint}`;
+ this.wsUri += `//${this.window.location.hostname}:${port}/${apiEndpoint}`;
}
- abstract subscribe(subscriber: T);
+ abstract subscribe(subscriber: WsSubscriber);
abstract update(subscriber: T);
abstract unsubscribe(subscriber: T);
- abstract processOnMessage(message: any);
+ abstract processOnMessage(message: WebsocketDataMsg);
protected nextCmdId(): number {
this.lastCmdId++;
@@ -158,13 +163,13 @@ export abstract class WebsocketService implements WsServ
}
private openSocket(token: string) {
- const uri = `${this.notificationUri}?token=${token}`;
- this.dataStream = webSocket(
+ const uri = `${this.wsUri}`;
+ this.dataStream = webSocket(
{
url: uri,
openObserver: {
next: () => {
- this.onOpen();
+ this.onOpen(token);
}
},
closeObserver: {
@@ -176,9 +181,9 @@ export abstract class WebsocketService implements WsServ
);
this.dataStream.subscribe({
- next: (message) => {
+ next: (message: CmdUpdateMsg) => {
this.ngZone.runOutsideAngular(() => {
- this.onMessage(message as WebsocketNotificationMsg);
+ this.onMessage(message);
});
},
error: (error) => {
@@ -187,9 +192,10 @@ export abstract class WebsocketService implements WsServ
});
}
- private onOpen() {
+ private onOpen(token: string) {
this.isOpening = false;
this.isOpened = true;
+ this.cmdWrapper.setAuth(token);
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
@@ -208,11 +214,11 @@ export abstract class WebsocketService implements WsServ
}
}
- private onMessage(message: WebsocketNotificationMsg) {
+ private onMessage(message: CmdUpdateMsg) {
if (message.errorCode) {
this.showWsError(message.errorCode, message.errorMsg);
} else {
- this.processOnMessage(message);
+ this.processOnMessage(message as WebsocketDataMsg);
}
this.checkToClose();
}
@@ -259,5 +265,4 @@ export abstract class WebsocketService implements WsServ
message, type: 'error'
}));
}
-
}
diff --git a/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts b/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts
index 8ee5771fe7..33b126c663 100644
--- a/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts
+++ b/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts
@@ -25,11 +25,11 @@ import {
} from '@angular/core';
import { NotificationWebsocketService } from '@core/ws/notification-websocket.service';
import { BehaviorSubject, ReplaySubject, Subscription } from 'rxjs';
-import { distinctUntilChanged, map, share, tap } from 'rxjs/operators';
+import { distinctUntilChanged, map, share, skip, tap } from 'rxjs/operators';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import { ShowNotificationPopoverComponent } from '@home/components/notification/show-notification-popover.component';
-import { NotificationSubscriber } from '@shared/models/websocket/notification-ws.models';
+import { NotificationSubscriber } from '@shared/models/telemetry/telemetry.models';
import { select, Store } from '@ngrx/store';
import { selectIsAuthenticated } from '@core/auth/auth.selectors';
import { AppState } from '@core/core.state';
@@ -77,11 +77,11 @@ export class NotificationBellComponent implements OnDestroy {
if ($event) {
$event.stopPropagation();
}
- this.unsubscribeSubscription();
const trigger = createVersionButton._elementRef.nativeElement;
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
+ this.unsubscribeSubscription();
const showNotificationPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, ShowNotificationPopoverComponent, 'bottom', true, null,
{
@@ -100,7 +100,9 @@ export class NotificationBellComponent implements OnDestroy {
private initSubscription() {
this.notificationSubscriber = NotificationSubscriber.createNotificationCountSubscription(this.notificationWsService, this.zone);
- this.notificationCountSubscriber = this.notificationSubscriber.notificationCount$.subscribe(value => this.countSubject.next(value));
+ this.notificationCountSubscriber = this.notificationSubscriber.notificationCount$.pipe(
+ skip(1),
+ ).subscribe(value => this.countSubject.next(value));
this.notificationSubscriber.subscribe();
}
@@ -108,5 +110,6 @@ export class NotificationBellComponent implements OnDestroy {
private unsubscribeSubscription() {
this.notificationCountSubscriber.unsubscribe();
this.notificationSubscriber.unsubscribe();
+ this.notificationSubscriber = null;
}
}
diff --git a/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts b/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts
index b2a9009a57..7865da5c29 100644
--- a/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts
+++ b/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts
@@ -22,9 +22,9 @@ import { AppState } from '@core/core.state';
import { Notification, NotificationRequest } from '@shared/models/notification.models';
import { NotificationWebsocketService } from '@core/ws/notification-websocket.service';
import { BehaviorSubject, Observable, ReplaySubject, Subscription } from 'rxjs';
-import { map, share, tap } from 'rxjs/operators';
+import { map, share, skip, tap } from 'rxjs/operators';
import { Router } from '@angular/router';
-import { NotificationSubscriber } from '@shared/models/websocket/notification-ws.models';
+import { NotificationSubscriber } from '@shared/models/telemetry/telemetry.models';
@Component({
selector: 'tb-show-notification-popover',
@@ -71,7 +71,9 @@ export class ShowNotificationPopoverComponent extends PageComponent implements O
}),
tap(() => setTimeout(() => this.cd.markForCheck()))
);
- this.notificationCountSubscriber = this.notificationSubscriber.notificationCount$.subscribe(value => this.counter.next(value));
+ this.notificationCountSubscriber = this.notificationSubscriber.notificationCount$.pipe(
+ skip(1),
+ ).subscribe(value => this.counter.next(value));
this.notificationSubscriber.subscribe();
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts
index bce2f0b506..27899f6e83 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts
@@ -183,7 +183,7 @@ export class ValueCardWidgetComponent implements OnInit, AfterViewInit, OnDestro
const tsValue = getSingleTsValue(this.ctx.data);
let ts;
let value;
- if (tsValue && isDefinedAndNotNull(tsValue[1])) {
+ if (tsValue && isDefinedAndNotNull(tsValue[1]) && tsValue[0] !== 0) {
ts = tsValue[0];
value = tsValue[1];
this.valueText = formatValue(value, this.decimals, this.units, false);
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 ec130600ac..6f1fc90f5e 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
@@ -139,6 +139,21 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
this.cd.detectChanges();
});
+ this.connectorForm.get('type').valueChanges.subscribe(type=> {
+ if(type && !this.initialConnector) {
+ this.attributeService.getEntityAttributes(this.device, AttributeScope.CLIENT_SCOPE,
+ [`${type.toUpperCase()}_DEFAULT_CONFIG`], {ignoreErrors: true}).subscribe(defaultConfig=>{
+ if (defaultConfig && defaultConfig.length) {
+ this.connectorForm.get('configurationJson').setValue(
+ isString(defaultConfig[0].value) ?
+ JSON.parse(defaultConfig[0].value) :
+ defaultConfig[0].value);
+ this.cd.detectChanges();
+ }
+ })
+ }
+ });
+
this.dataSource.sort = this.sort;
this.dataSource.sortingDataAccessor = (data: AttributeData, sortHeaderId: string) => {
if (sortHeaderId === 'syncStatus') {
@@ -210,7 +225,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
value
}];
const attributesToDelete = [];
- const scope = (this.initialConnector && this.activeConnectors.includes(this.initialConnector.name))
+ const scope = (!this.initialConnector || this.activeConnectors.includes(this.initialConnector.name))
? AttributeScope.SHARED_SCOPE
: AttributeScope.SERVER_SCOPE;
let updateActiveConnectors = false;
@@ -307,6 +322,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
}
private clearOutConnectorForm(): void {
+ this.initialConnector = null;
this.connectorForm.setValue({
name: '',
type: 'mqtt',
@@ -316,7 +332,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
configuration: '',
configurationJson: {}
});
- this.initialConnector = null;
this.connectorForm.markAsPristine();
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts
index 6e0f20bdc3..721cea4ae6 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts
@@ -114,10 +114,16 @@ export class GatewayLogsComponent implements AfterViewInit {
const result = {
ts: data[0],
key: this.activeLink.key,
- message: /\[(.*)/.exec(data[1])[0],
+ message: data[1],
status: 'INVALID LOG FORMAT' as GatewayStatus
};
+ try {
+ result.message = /\[(.*)/.exec(data[1])[0];
+ } catch (e) {
+ result.message = data[1];
+ }
+
try {
result.status = data[1].match(/\|(\w+)\|/)[1];
} catch (e) {
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 4aec0fd467..8a96f1849b 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
@@ -36,7 +36,7 @@
{{ 'gateway.statistics.command' | translate }}
-
+
{{ 'widget-config.datasource-parameters' | translate }}
@@ -56,8 +56,10 @@
- {{ 'gateway.rpc-command-result' | 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 345688d451..5dab1d91c1 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
@@ -24,6 +24,7 @@
.command-form {
flex-wrap: nowrap;
padding: 0 5px 5px;
+
& > button {
margin-top: 10px;
}
@@ -34,9 +35,30 @@
display: flex;
flex-direction: column;
flex: 1;
+
& > span {
font-weight: 600;
+ position: relative;
+ font-size: 14px;
+ margin-bottom: 10px;
+
+
+ .result-time {
+ font-weight: 400;
+ font-size: 14px;
+ line-height: 32px;
+ position: absolute;
+ left: 0;
+ top: 25px;
+ z-index: 5;
+ color: rgba(0, 0, 0, 0.54);
+
+ span {
+ padding-left: 10px;
+ }
+ }
}
+
tb-json-content {
flex: 1;
}
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 a8ad61e1ec..d19624e257 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
@@ -37,6 +37,8 @@ export class GatewayServiceRPCComponent implements AfterViewInit {
contentTypes = ContentType;
+ resultTime: number | null;
+
@Input()
dialogRef: MatDialogRef;
@@ -76,12 +78,17 @@ export class GatewayServiceRPCComponent implements AfterViewInit {
}
sendCommand() {
+ this.resultTime = null;
const formValues = this.commandForm.value;
const commandPrefix = this.isConnector ? `${this.connectorType}_` : 'gateway_';
this.ctx.controlApi.sendTwoWayCommand(commandPrefix+formValues.command.toLowerCase(), formValues.params,formValues.time).subscribe({
- next: resp => this.commandForm.get('result').setValue(JSON.stringify(resp)),
+ next: resp => {
+ this.resultTime = new Date().getTime();
+ this.commandForm.get('result').setValue(JSON.stringify(resp))
+ },
error: error => {
- console.log(error);
+ 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/indicator/battery-level-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.models.ts
index 34c00a4b92..2ab87416ac 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
@@ -78,7 +78,7 @@ 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)'},
@@ -88,7 +88,7 @@ export const batteryLevelDefaultSettings: BatteryLevelWidgetSettings = {
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)'},
diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.ts
index 99da1dfb14..6c9511d709 100644
--- a/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.ts
+++ b/ui-ngx/src/app/modules/home/pages/edge/edge-instructions-dialog.component.ts
@@ -21,12 +21,21 @@ import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ActionPreferencesPutUserSettings } from '@core/auth/auth.actions';
-import { EdgeInfo, EdgeInstructionsMethod } from '@shared/models/edge.models';
+import {
+ EdgeInfo,
+ EdgeInstructions,
+ EdgeInstructionsMethod,
+ edgeVersionAttributeKey
+} from '@shared/models/edge.models';
import { EdgeService } from '@core/http/edge.service';
+import { AttributeService } from '@core/http/attribute.service';
+import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
+import { mergeMap, Observable } from 'rxjs';
export interface EdgeInstructionsDialogData {
edge: EdgeInfo;
afterAdd: boolean;
+ upgradeAvailable: boolean;
}
@Component({
@@ -49,12 +58,16 @@ export class EdgeInstructionsDialogComponent extends DialogComponent,
+ private attributeService: AttributeService,
private edgeService: EdgeService) {
super(store, router, dialogRef);
if (this.data.afterAdd) {
this.dialogTitle = 'edge.install-connect-instructions-edge-created';
this.showDontShowAgain = true;
+ } else if (this.data.upgradeAvailable) {
+ this.dialogTitle = 'edge.upgrade-instructions';
+ this.showDontShowAgain = false;
} else {
this.dialogTitle = 'edge.install-connect-instructions';
this.showDontShowAgain = false;
@@ -85,12 +98,22 @@ export class EdgeInstructionsDialogComponent extends DialogComponent {
- this.contentData[method] = res.installInstructions;
- this.loadedInstructions = true;
- }
- );
+ let edgeInstructions$: Observable;
+ if (this.data.upgradeAvailable) {
+ edgeInstructions$ = this.attributeService.getEntityAttributes(this.data.edge.id, AttributeScope.SERVER_SCOPE, [edgeVersionAttributeKey])
+ .pipe(mergeMap(attributes => {
+ if (attributes.length) {
+ const edgeVersion = attributes[0].value;
+ return this.edgeService.getEdgeUpgradeInstructions(edgeVersion, method);
+ }
+ }));
+ } else {
+ edgeInstructions$ = this.edgeService.getEdgeInstallInstructions(this.data.edge.id.id, method);
+ }
+ edgeInstructions$.subscribe(res => {
+ this.contentData[method] = res.instructions;
+ this.loadedInstructions = true;
+ });
}
}
}
diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge.component.html b/ui-ngx/src/app/modules/home/pages/edge/edge.component.html
index 9c9a78f91a..3d0baad650 100644
--- a/ui-ngx/src/app/modules/home/pages/edge/edge.component.html
+++ b/ui-ngx/src/app/modules/home/pages/edge/edge.component.html
@@ -114,13 +114,26 @@
-
- info_outline
- {{ 'edge.install-connect-instructions' | translate }}
-
+
+
+
+ info_outline
+ {{ 'edge.install-connect-instructions' | translate }}
+
+
+
+
+ info_outline
+ {{ 'edge.upgrade-instructions' | translate }}
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts b/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts
index 78c8df6c08..b7cb2b1752 100644
--- a/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts
+++ b/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts
@@ -20,12 +20,15 @@ import { AppState } from '@core/core.state';
import { EntityComponent } from '@home/components/entity/entity.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { EntityType } from '@shared/models/entity-type.models';
-import { EdgeInfo } from '@shared/models/edge.models';
+import { EdgeInfo, edgeVersionAttributeKey } from '@shared/models/edge.models';
import { TranslateService } from '@ngx-translate/core';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { generateSecret, guid } from '@core/utils';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
+import { environment as env } from '@env/environment';
+import { AttributeService } from '@core/http/attribute.service';
+import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
@Component({
selector: 'tb-edge',
@@ -37,9 +40,11 @@ export class EdgeComponent extends EntityComponent
{
entityType = EntityType;
edgeScope: 'tenant' | 'customer' | 'customer_user';
+ upgradeAvailable: boolean = false;
constructor(protected store: Store,
protected translate: TranslateService,
+ private attributeService: AttributeService,
@Inject('entity') protected entityValue: EdgeInfo,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig,
public fb: UntypedFormBuilder,
@@ -95,6 +100,7 @@ export class EdgeComponent extends EntityComponent {
}
});
this.generateRoutingKeyAndSecret(entity, this.entityForm);
+ this.checkEdgeVersion();
}
updateFormState() {
@@ -133,4 +139,25 @@ export class EdgeComponent extends EntityComponent {
form.get('secret').patchValue(generateSecret(20), {emitEvent: false});
}
}
+
+ checkEdgeVersion() {
+ this.attributeService.getEntityAttributes(this.entity.id, AttributeScope.SERVER_SCOPE, [edgeVersionAttributeKey])
+ .subscribe(attributes => {
+ if (attributes?.length) {
+ const edgeVersion = attributes[0].value;
+ const tbVersion = 'V_' + env.tbVersion.replaceAll('.', '_');
+ this.upgradeAvailable = this.versionUpgradeSupported(edgeVersion) && (edgeVersion !== tbVersion);
+ } else {
+ this.upgradeAvailable = false;
+ }
+ }
+ );
+ }
+
+ private versionUpgradeSupported(edgeVersion: string): boolean {
+ const edgeVersionArray = edgeVersion.split('_');
+ const major = parseInt(edgeVersionArray[1]);
+ const minor = parseInt(edgeVersionArray[2]);
+ return major >= 3 && minor >= 6;
+ }
}
diff --git a/ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts
index 6a80388b52..4429515705 100644
--- a/ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts
+++ b/ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts
@@ -558,7 +558,7 @@ export class EdgesTableConfigResolver implements Resolve {
if (afterAdd) {
@@ -610,9 +611,12 @@ export class EdgesTableConfigResolver implements Resolve {
if (image) {
- this.updateData();
+ if (this.selectionMode) {
+ this.imageSelected.next(image);
+ } else {
+ this.updateData();
+ }
}
});
}
diff --git a/ui-ngx/src/app/shared/components/rule-chain/rule-chain-select.component.ts b/ui-ngx/src/app/shared/components/rule-chain/rule-chain-select.component.ts
index 003d85b751..d9f2f90cc8 100644
--- a/ui-ngx/src/app/shared/components/rule-chain/rule-chain-select.component.ts
+++ b/ui-ngx/src/app/shared/components/rule-chain/rule-chain-select.component.ts
@@ -65,7 +65,7 @@ export class RuleChainSelectComponent implements ControlValueAccessor, OnInit {
}
ngOnInit() {
- const pageLink = new PageLink(100, 0, null, {
+ const pageLink = new PageLink(1024, 0, null, {
property: 'name',
direction: Direction.ASC
});
diff --git a/ui-ngx/src/app/shared/models/edge.models.ts b/ui-ngx/src/app/shared/models/edge.models.ts
index 01789636b8..26337b5e35 100644
--- a/ui-ngx/src/app/shared/models/edge.models.ts
+++ b/ui-ngx/src/app/shared/models/edge.models.ts
@@ -179,8 +179,8 @@ export interface EdgeEvent extends BaseData {
body: string;
}
-export interface EdgeInstallInstructions {
- installInstructions: string;
+export interface EdgeInstructions {
+ instructions: string;
}
export enum EdgeInstructionsMethod {
@@ -188,3 +188,5 @@ export enum EdgeInstructionsMethod {
centos,
docker
}
+
+export const edgeVersionAttributeKey = 'edgeVersion';
diff --git a/ui-ngx/src/app/shared/models/public-api.ts b/ui-ngx/src/app/shared/models/public-api.ts
index 517ede9ba3..4ff3ce5778 100644
--- a/ui-ngx/src/app/shared/models/public-api.ts
+++ b/ui-ngx/src/app/shared/models/public-api.ts
@@ -41,7 +41,6 @@ export * from './limited-api.models';
export * from './login.models';
export * from './material.models';
export * from './notification.models';
-export * from './websocket/notification-ws.models';
export * from './websocket/websocket.models';
export * from './oauth2.models';
export * from './ota-package.models';
diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
index 66f39d080a..e56941f688 100644
--- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
+++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
@@ -17,25 +17,29 @@
import { EntityType } from '@shared/models/entity-type.models';
import { AggregationType } from '../time/time.models';
-import { Observable, ReplaySubject } from 'rxjs';
+import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs';
import { EntityId } from '@shared/models/id/entity-id';
import { map } from 'rxjs/operators';
import { NgZone } from '@angular/core';
import {
AlarmCountQuery,
AlarmData,
- AlarmDataQuery, EntityCountQuery,
+ AlarmDataQuery,
+ EntityCountQuery,
EntityData,
- EntityDataQuery, EntityFilter,
+ EntityDataQuery,
+ EntityFilter,
EntityKey,
TsValue
} from '@shared/models/query/query.models';
import { PageData } from '@shared/models/page/page-data';
import { alarmFields } from '@shared/models/alarm.models';
import { entityFields } from '@shared/models/entity.models';
-import { isUndefined } from '@core/utils';
-import { CmdWrapper, WsSubscriber } from '@shared/models/websocket/websocket.models';
+import { isDefinedAndNotNull, isUndefined } from '@core/utils';
+import { CmdWrapper, WsService, WsSubscriber } from '@shared/models/websocket/websocket.models';
import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
+import { Notification } from '@shared/models/notification.models';
+import { WebsocketService } from '@core/ws/websocket.service';
export const NOT_SUPPORTED = 'Not supported!';
@@ -58,11 +62,6 @@ export enum AttributeScope {
SHARED_SCOPE = 'SHARED_SCOPE'
}
-export enum TelemetryFeature {
- ATTRIBUTES = 'ATTRIBUTES',
- TIMESERIES = 'TIMESERIES'
-}
-
export enum TimeseriesDeleteStrategy {
DELETE_ALL_DATA = 'DELETE_ALL_DATA',
DELETE_ALL_DATA_EXCEPT_LATEST_VALUE = 'DELETE_ALL_DATA_EXCEPT_LATEST_VALUE',
@@ -105,7 +104,7 @@ export const timeseriesDeleteStrategyTranslations = new Map;
- tsSubCmds: Array;
- historyCmds: Array;
- entityDataCmds: Array;
- entityDataUnsubscribeCmds: Array;
- alarmDataCmds: Array;
- alarmDataUnsubscribeCmds: Array;
- entityCountCmds: Array;
- entityCountUnsubscribeCmds: Array;
- alarmCountCmds: Array;
- alarmCountUnsubscribeCmds: Array;
+ this.cmds = [];
+ }
+
+ cmds: Array;
+ authCmd: AuthCmd;
private static popCmds(cmds: Array, leftCount: number): Array {
const toPublish = Math.min(cmds.length, leftCount);
@@ -300,58 +359,25 @@ export class TelemetryPluginCmdsWrapper implements CmdWrapper {
}
}
+ public setAuth(token: string) {
+ this.authCmd = new AuthCmd(token);
+ }
+
public hasCommands(): boolean {
- return this.tsSubCmds.length > 0 ||
- this.historyCmds.length > 0 ||
- this.attrSubCmds.length > 0 ||
- this.entityDataCmds.length > 0 ||
- this.entityDataUnsubscribeCmds.length > 0 ||
- this.alarmDataCmds.length > 0 ||
- this.alarmDataUnsubscribeCmds.length > 0 ||
- this.entityCountCmds.length > 0 ||
- this.entityCountUnsubscribeCmds.length > 0 ||
- this.alarmCountCmds.length > 0 ||
- this.alarmCountUnsubscribeCmds.length > 0;
+ return this.cmds.length > 0;
}
public clear() {
- this.attrSubCmds.length = 0;
- this.tsSubCmds.length = 0;
- this.historyCmds.length = 0;
- this.entityDataCmds.length = 0;
- this.entityDataUnsubscribeCmds.length = 0;
- this.alarmDataCmds.length = 0;
- this.alarmDataUnsubscribeCmds.length = 0;
- this.entityCountCmds.length = 0;
- this.entityCountUnsubscribeCmds.length = 0;
- this.alarmCountCmds.length = 0;
- this.alarmCountUnsubscribeCmds.length = 0;
+ this.cmds.length = 0;
}
public preparePublishCommands(maxCommands: number): TelemetryPluginCmdsWrapper {
const preparedWrapper = new TelemetryPluginCmdsWrapper();
- let leftCount = maxCommands;
- preparedWrapper.tsSubCmds = TelemetryPluginCmdsWrapper.popCmds(this.tsSubCmds, leftCount);
- leftCount -= preparedWrapper.tsSubCmds.length;
- preparedWrapper.historyCmds = TelemetryPluginCmdsWrapper.popCmds(this.historyCmds, leftCount);
- leftCount -= preparedWrapper.historyCmds.length;
- preparedWrapper.attrSubCmds = TelemetryPluginCmdsWrapper.popCmds(this.attrSubCmds, leftCount);
- leftCount -= preparedWrapper.attrSubCmds.length;
- preparedWrapper.entityDataCmds = TelemetryPluginCmdsWrapper.popCmds(this.entityDataCmds, leftCount);
- leftCount -= preparedWrapper.entityDataCmds.length;
- preparedWrapper.entityDataUnsubscribeCmds = TelemetryPluginCmdsWrapper.popCmds(this.entityDataUnsubscribeCmds, leftCount);
- leftCount -= preparedWrapper.entityDataUnsubscribeCmds.length;
- preparedWrapper.alarmDataCmds = TelemetryPluginCmdsWrapper.popCmds(this.alarmDataCmds, leftCount);
- leftCount -= preparedWrapper.alarmDataCmds.length;
- preparedWrapper.alarmDataUnsubscribeCmds = TelemetryPluginCmdsWrapper.popCmds(this.alarmDataUnsubscribeCmds, leftCount);
- leftCount -= preparedWrapper.alarmDataUnsubscribeCmds.length;
- preparedWrapper.entityCountCmds = TelemetryPluginCmdsWrapper.popCmds(this.entityCountCmds, leftCount);
- leftCount -= preparedWrapper.entityCountCmds.length;
- preparedWrapper.entityCountUnsubscribeCmds = TelemetryPluginCmdsWrapper.popCmds(this.entityCountUnsubscribeCmds, leftCount);
- leftCount -= preparedWrapper.entityCountUnsubscribeCmds.length;
- preparedWrapper.alarmCountCmds = TelemetryPluginCmdsWrapper.popCmds(this.alarmCountCmds, leftCount);
- leftCount -= preparedWrapper.alarmCountCmds.length;
- preparedWrapper.alarmCountUnsubscribeCmds = TelemetryPluginCmdsWrapper.popCmds(this.alarmCountUnsubscribeCmds, leftCount);
+ if (this.authCmd) {
+ preparedWrapper.authCmd = this.authCmd;
+ this.authCmd = null;
+ }
+ preparedWrapper.cmds = TelemetryPluginCmdsWrapper.popCmds(this.cmds, maxCommands);
return preparedWrapper;
}
}
@@ -415,8 +441,22 @@ export interface AlarmCountUpdateMsg extends CmdUpdateMsg {
count: number;
}
+export interface NotificationCountUpdateMsg extends CmdUpdateMsg {
+ cmdUpdateType: CmdUpdateType.NOTIFICATIONS_COUNT;
+ totalUnreadCount: number;
+ sequenceNumber: number;
+}
+
+export interface NotificationsUpdateMsg extends CmdUpdateMsg {
+ cmdUpdateType: CmdUpdateType.NOTIFICATIONS;
+ update?: Notification;
+ notifications?: Notification[];
+ totalUnreadCount: number;
+ sequenceNumber: number;
+}
+
export type WebsocketDataMsg = AlarmDataUpdateMsg | AlarmCountUpdateMsg |
- EntityDataUpdateMsg | EntityCountUpdateMsg | SubscriptionUpdateMsg;
+ EntityDataUpdateMsg | EntityCountUpdateMsg | SubscriptionUpdateMsg | NotificationCountUpdateMsg | NotificationsUpdateMsg;
export const isEntityDataUpdateMsg = (message: WebsocketDataMsg): message is EntityDataUpdateMsg => {
const updateMsg = (message as CmdUpdateMsg);
@@ -438,6 +478,16 @@ export const isAlarmCountUpdateMsg = (message: WebsocketDataMsg): message is Ala
return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.ALARM_COUNT_DATA;
};
+export const isNotificationCountUpdateMsg = (message: WebsocketDataMsg): message is NotificationCountUpdateMsg => {
+ const updateMsg = (message as CmdUpdateMsg);
+ return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS_COUNT;
+};
+
+export const isNotificationsUpdateMsg = (message: WebsocketDataMsg): message is NotificationsUpdateMsg => {
+ const updateMsg = (message as CmdUpdateMsg);
+ return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS;
+};
+
export class SubscriptionUpdate implements SubscriptionUpdateMsg {
subscriptionId: number;
errorCode: number;
@@ -627,6 +677,32 @@ export class AlarmCountUpdate extends CmdUpdate {
}
}
+export class NotificationCountUpdate extends CmdUpdate {
+ totalUnreadCount: number;
+ sequenceNumber: number;
+
+ constructor(msg: NotificationCountUpdateMsg) {
+ super(msg);
+ this.totalUnreadCount = msg.totalUnreadCount;
+ this.sequenceNumber = msg.sequenceNumber;
+ }
+}
+
+export class NotificationsUpdate extends CmdUpdate {
+ totalUnreadCount: number;
+ sequenceNumber: number;
+ update?: Notification;
+ notifications?: Notification[];
+
+ constructor(msg: NotificationsUpdateMsg) {
+ super(msg);
+ this.totalUnreadCount = msg.totalUnreadCount;
+ this.sequenceNumber = msg.sequenceNumber;
+ this.update = msg.update;
+ this.notifications = msg.notifications;
+ }
+}
+
export class TelemetrySubscriber extends WsSubscriber {
private dataSubject = new ReplaySubject(1);
@@ -789,3 +865,113 @@ export class TelemetrySubscriber extends WsSubscriber {
);
}
}
+
+export class NotificationSubscriber extends WsSubscriber {
+ private notificationCountSubject = new BehaviorSubject({
+ cmdId: 0,
+ cmdUpdateType: undefined,
+ errorCode: 0,
+ errorMsg: '',
+ totalUnreadCount: 0,
+ sequenceNumber: 0
+ });
+ private notificationsSubject = new BehaviorSubject({
+ cmdId: 0,
+ cmdUpdateType: undefined,
+ errorCode: 0,
+ errorMsg: '',
+ notifications: null,
+ totalUnreadCount: 0,
+ sequenceNumber: 0
+ });
+
+ public messageLimit = 10;
+
+ public notificationCount$ = this.notificationCountSubject.asObservable().pipe(map(msg => msg.totalUnreadCount));
+ public notifications$ = this.notificationsSubject.asObservable().pipe(map(msg => msg.notifications ));
+
+ public static createNotificationCountSubscription(websocketService: WebsocketService,
+ zone: NgZone): NotificationSubscriber {
+ const subscriptionCommand = new UnreadCountSubCmd();
+ const subscriber = new NotificationSubscriber(websocketService, zone);
+ subscriber.subscriptionCommands.push(subscriptionCommand);
+ return subscriber;
+ }
+
+ public static createNotificationsSubscription(websocketService: WebsocketService,
+ zone: NgZone, limit = 10): NotificationSubscriber {
+ const subscriptionCommand = new UnreadSubCmd(limit);
+ const subscriber = new NotificationSubscriber(websocketService, zone);
+ subscriber.messageLimit = limit;
+ subscriber.subscriptionCommands.push(subscriptionCommand);
+ return subscriber;
+ }
+
+ public static createMarkAsReadCommand(websocketService: WebsocketService