Browse Source

Merge remote-tracking branch 'origin/master' into feature/attr_tskv_version

pull/10977/head
ViacheslavKlimov 2 years ago
parent
commit
c15f34d91a
  1. 2
      application/pom.xml
  2. 14
      application/src/main/data/json/edge/instructions/install/centos/instructions.md
  3. 14
      application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md
  4. 2
      application/src/main/data/json/system/widget_bundles/buttons.json
  5. 8
      application/src/main/data/json/system/widget_bundles/cards.json
  6. 2
      application/src/main/data/json/system/widget_bundles/html_widgets.json
  7. 2
      application/src/main/data/json/system/widget_types/bar_chart.json
  8. 2
      application/src/main/data/json/system/widget_types/bar_chart_with_labels.json
  9. 2
      application/src/main/data/json/system/widget_types/bars.json
  10. 2
      application/src/main/data/json/system/widget_types/bars_deprecated.json
  11. 2
      application/src/main/data/json/system/widget_types/command_button.json
  12. 2
      application/src/main/data/json/system/widget_types/compass.json
  13. 2
      application/src/main/data/json/system/widget_types/doughnut.json
  14. 2
      application/src/main/data/json/system/widget_types/doughnut_deprecated.json
  15. 2
      application/src/main/data/json/system/widget_types/horizontal_doughnut.json
  16. 25
      application/src/main/data/json/system/widget_types/label___value_card.json
  17. 25
      application/src/main/data/json/system/widget_types/label_card.json
  18. 2
      application/src/main/data/json/system/widget_types/line_chart.json
  19. 2
      application/src/main/data/json/system/widget_types/markdown_html_card.json
  20. 2
      application/src/main/data/json/system/widget_types/pie.json
  21. 2
      application/src/main/data/json/system/widget_types/point_chart.json
  22. 2
      application/src/main/data/json/system/widget_types/polar_area.json
  23. 2
      application/src/main/data/json/system/widget_types/power_button.json
  24. 2
      application/src/main/data/json/system/widget_types/radar.json
  25. 2
      application/src/main/data/json/system/widget_types/range_chart.json
  26. 2
      application/src/main/data/json/system/widget_types/single_switch.json
  27. 2
      application/src/main/data/json/system/widget_types/slider.json
  28. 2
      application/src/main/data/json/system/widget_types/time_series_chart.json
  29. 2
      application/src/main/data/json/system/widget_types/timeseries_bar_chart.json
  30. 2
      application/src/main/data/json/system/widget_types/timeseries_line_chart.json
  31. 2
      application/src/main/data/json/system/widget_types/toggle_button.json
  32. 23
      application/src/main/data/json/system/widget_types/unread_notifications.json
  33. 2
      application/src/main/data/json/system/widget_types/update_json_attribute.json
  34. 738
      application/src/main/data/json/tenant/dashboards/gateways.json
  35. 14
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  36. 9
      application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
  37. 18
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  38. 13
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  39. 3
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  40. 25
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  41. 3
      application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
  42. 3
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  43. 80
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  44. 10
      application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java
  45. 4
      application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
  46. 4
      application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java
  47. 3
      application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java
  48. 3
      application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
  49. 2
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  50. 10
      application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
  51. 5
      application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
  52. 1
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  53. 3
      application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
  54. 65
      application/src/main/java/org/thingsboard/server/service/edge/instructions/BaseEdgeInstallUpgradeInstructionsService.java
  55. 71
      application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallInstructionsService.java
  56. 56
      application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java
  57. 4
      application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallInstructionsService.java
  58. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
  59. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/telemetry/EntityDataMsgConstructor.java
  60. 17
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV2.java
  61. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java
  62. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProcessor.java
  63. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java
  64. 1
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  65. 1
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  66. 2
      application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java
  67. 2
      application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java
  68. 11
      application/src/main/java/org/thingsboard/server/service/notification/rule/cache/DefaultNotificationRulesCache.java
  69. 59
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  70. 29
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
  71. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java
  72. 12
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
  73. 10
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
  74. 2
      application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java
  75. 11
      application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java
  76. 4
      application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java
  77. 6
      application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java
  78. 2
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java
  79. 1
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java
  80. 12
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java
  81. 2
      application/src/main/resources/thingsboard.yml
  82. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  83. 31
      application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java
  84. 10
      application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
  85. 9
      application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
  86. 1
      application/src/test/java/org/thingsboard/server/edge/NotificationEdgeTest.java
  87. 7
      application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java
  88. 8
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  89. 82
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  90. 23
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java
  91. 478
      application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java
  92. 817
      application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java
  93. 1005
      application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java
  94. 2
      common/actor/pom.xml
  95. 2
      common/cache/pom.xml
  96. 2
      common/cluster-api/pom.xml
  97. 2
      common/coap-server/pom.xml
  98. 2
      common/dao-api/pom.xml
  99. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java
  100. 2
      common/data/pom.xml

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>

14
application/src/main/data/json/edge/instructions/install/centos/instructions.md

@ -8,15 +8,15 @@ sudo yum install -y nano wget
sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
```
#### Install Java 11 (OpenJDK)
ThingsBoard service is running on Java 11. Follow these instructions to install OpenJDK 11:
#### Install Java 17 (OpenJDK)
ThingsBoard service is running on Java 17. Follow these instructions to install OpenJDK 17:
```bash
sudo yum install java-11-openjdk
sudo yum install java-17-openjdk
{:copy-code}
```
Please don't forget to configure your operating system to use OpenJDK 11 by default.
Please don't forget to configure your operating system to use OpenJDK 17 by default.
You can configure which version is the default using the following command:
```bash
@ -34,7 +34,7 @@ java -version
Expected command output is:
```text
openjdk version "11.0.xx"
openjdk version "17.x.xx"
OpenJDK Runtime Environment (...)
OpenJDK 64-Bit Server VM (build ...)
```
@ -144,14 +144,14 @@ CREATE DATABASE tb_edge;
Download installation package:
```bash
wget https://github.com/thingsboard/thingsboard-edge/releases/download/v${TB_EDGE_VERSION}/tb-edge-${TB_EDGE_VERSION}.rpm
wget https://github.com/thingsboard/thingsboard-edge/releases/download/v${TB_EDGE_TAG}/tb-edge-${TB_EDGE_TAG}.rpm
{:copy-code}
```
Go to the download repository and install ThingsBoard Edge service:
```bash
sudo rpm -Uvh tb-edge-${TB_EDGE_VERSION}.rpm
sudo rpm -Uvh tb-edge-${TB_EDGE_TAG}.rpm
{:copy-code}
```

14
application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md

@ -1,15 +1,15 @@
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:
#### Install Java 17 (OpenJDK)
ThingsBoard service is running on Java 17. Follow these instructions to install OpenJDK 17:
```bash
sudo apt update
sudo apt install openjdk-11-jdk
sudo apt install openjdk-17-jdk
{:copy-code}
```
Please don't forget to configure your operating system to use OpenJDK 11 by default.
Please don't forget to configure your operating system to use OpenJDK 17 by default.
You can configure which version is the default using the following command:
```bash
@ -27,7 +27,7 @@ java -version
Expected command output is:
```text
openjdk version "11.0.xx"
openjdk version "17.x.xx"
OpenJDK Runtime Environment (...)
OpenJDK 64-Bit Server VM (build ...)
```
@ -76,14 +76,14 @@ CREATE DATABASE tb_edge;
Download installation package:
```bash
wget https://github.com/thingsboard/thingsboard-edge/releases/download/v${TB_EDGE_VERSION}/tb-edge-${TB_EDGE_VERSION}.deb
wget https://github.com/thingsboard/thingsboard-edge/releases/download/v${TB_EDGE_TAG}/tb-edge-${TB_EDGE_TAG}.deb
{:copy-code}
```
Go to the download repository and install ThingsBoard Edge service:
```bash
sudo dpkg -i tb-edge-${TB_EDGE_VERSION}.deb
sudo dpkg -i tb-edge-${TB_EDGE_TAG}.deb
{:copy-code}
```

2
application/src/main/data/json/system/widget_bundles/buttons.json

File diff suppressed because one or more lines are too long

8
application/src/main/data/json/system/widget_bundles/cards.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_bundles/html_widgets.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/bar_chart.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/bar_chart_with_labels.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/bars.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/bars_deprecated.json

@ -3,7 +3,7 @@
"name": "Bars",
"deprecated": true,
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAA8FBMVEUhlvNMr1Bqamp5eXl7e3t8fHx9fX1+fn5/f3+AgICCgoKDg4OEhISGhoaHh4eKioqMjIyNjY2Ojo6QkJCRkZGSkpKWlpaXl5ebm5udnZ2enp6goKChoaGkpKSnp6epqamsrKyurq6xsbGzs7O1tbW2tra3t7e4uLi7u7u9vb3BwcHCwsLDw8PGxsbKysrNzc3Ozs7R0dHS0tLT09PZ2dna2trc3Nzd3d3e3t7g4ODh4eHj4+Pk5OTm5ubn5+fo6Ojp6enu7u7w8PDz8/P0Qzb09PT29vb39/f5+fn6+vr7+/v8/Pz9/f3+/v7/wQf///+dc+aLAAAAAWJLR0RPbmZBSQAAAcFJREFUeNrt3ds2AgEYhuHsaSOZbAvZi0r2YYjCJOW7/7txZhkcDNbM6h/vdwfPmlX/ybtmEorJErGCeJLadz3rkKPpZamaLTp925DHdFvSpKelU9uQ/cLKhtcdk7YqtiHruevtojch7ZZtQ0o1dcdfRqXNqm1IbVU3OaVamm/YhvQW5zIXOknnC5JUt7qEpE5fUv/5HVePy2UHAgQIECBAgAABAgQIECBxgrwGHBAgQIAAAQIECBAgQIAAAQIECJC/QRIBN0iQ+66voDMLuRp2fQWdVUhvNun6CjqrkJ0Dx/UVdEYhzXzfcX0FnVFIrlSZ2mx/LOiMQuqHh6k972NBZ/fv13G/KeiCQkIu4358EL8UdBafSGwuOxAgQIAAAQIECJDYQB4CDggQIECAAAECBAgQIECAAAECBAgQIECA/BrSufn0DjqjkEZmLXkWaUEXEmThXMeFSAu68H4j5b1IC7rQILfZTqQFXViQ1nRTL1EWdCFBnmYuJUVZ0IUEWR1xHKcXZUEXFDLwBR2XHQgQIECAAAEC5H9ChgIOCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgxiBmv+L6Bl9pkxYph15gAAAAAElFTkSuQmCC",
"description": "Displays latest values of the attributes or time-series data for multiple entities as separate bars.",
"description": "Displays latest values of the attributes or time series data for multiple entities as separate bars.",
"descriptor": {
"type": "latest",
"sizeX": 7,

2
application/src/main/data/json/system/widget_types/command_button.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/compass.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/doughnut.json

@ -3,7 +3,7 @@
"name": "Doughnut",
"deprecated": false,
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMkAAACgCAMAAACR47ilAAAA9lBMVEX////39/f5+fkAAAD////////////9/f36+vr///8Ihyv/X2r8/Pzy8vL39/f5+fnu7u4hISHs7Oz09PTq6urm5ubg8OXo6OgXjjjw9/Ki0q8nlkWrq6uDw5VktHr/6+3/c32Ty6LB4cqenp7/9fZ0dHQnlkbj4+N0vIf/19r/r7RFpWA2nlP/h4/R6dc9PT3b29v/aXNGpWBYWFgvLy//w8eQkJCy2r1VrG3IyMj/pavV1dXOzs7/m6LCwsL/ub7/fYb/4eP/zdG6urqCgoK2trb/kZhmZmY8PDykpKRKSkr/9fW728TO4tSDwpWi0rCFw5ZWrG24QEw9AAAACXRSTlOAgIAAcN+QOHDzcMjxAAALEklEQVR42tyae3PSQBTFq/V122ySbrIJYIBSUB4KgooKpWBRO75n/P5fxt2FugkLIZvNMuqZ0Zb/+M05596b6NHx8b378K/r/r3j46PjB/A/6MHx0UP4P/Tw6Aj+Dz06HIl1KzCiuwch4RTOWkZwDkji2J6HmDybyike5s5BSBgHcjH2Mcau6yLPK5rGNIkAQa7vehQH45CEPsXh5jiFsZgmESAYO7CWY7t4RVMgjFGS4G2lU2sEtCO2hzFAa3E1XXSvgYuykRVMMSymSIJK52XpZKWqxUywYDw6Xak/HXMcy/NJiDmLo81iYnaVK7XHJ3E1qCU2dE/jOr8Z1zlMSJ0pwpfCPSk3Xp5sqmQj34L3p5t6v2AwjstShmxbY5Jpk8huMAxZPxCG1uk29ZkzFgpDjJCGLwWnK6iWTrbru+tC/XSHrpgxHmPxbPW6GPDkImaHTOILT2T1uwCAQp93PyeKPonMIesx7QlA/3S3zscAFiaURQHFBElAOVJUsRFGfHal+HJNWXytiOn3pNw5SdVPy/FovGCcisL74rGI5UTRJ2mU0jBKtQu24xFDqV+dp6GMeixiIVYaYurpUg9W6VmnMijfXvQI+/zLXS97/dHOulBbEMEuTdhhSYQhMkVjsOUWtmGl6/HNDhpmi3rC9NNV/rUjUGVIirtCbSF8PAFTd3vU3tdZwtRQ9D0ZPN3C8awiMJIolMV1se+Ttr+iWd5sS9iSJ0wBRZ+kscWOzgB2yXIoDHv+5TQhtmHHDOipoeiTdGSOahl2y6JiMBzHZUlzLYBvY5ll2gInVEDRIynX1DgEDedZw/AZ0DvfVhYFFC2S8vNNkHcBZJQFaxjX9Ylvs4xtGccWe2xxjJMEm11/fAEq+uMMY7Eoy7mEouyKINEB6YhgKcFwFt6X3hYUqfbaJDKIliFCtywhYrZIKLz25kjkjryTDFFkcXFImC0Sik3SUPR3/AZIA3TEVyaLGAJYjJIoLUDE9WQUBU8U9kgpV7LkiGHals2E9QHYi0vHEEk1WZEA9CRu5ZBIKFMA3095jNQieVs4iLiVQ4qSfJe0ACutKjokwVMTIMBfuNKy2NBKolyDTVxkGyAxAiISRjyGkmy9G7pp+cpJUjUDwrUd5Yo9ee3Ll/oUHhgEEa4kaz9m+fIcBRLlbA2gSAkUB+rxvTKi+fL35ksxXVX1hageMJ9YyVdjN6tbch+JiidBHKQKxUugLOIoXbDD/flS8eR5vCRgRBZ7yRf6ADfJ+eXvN0XBk0r8RlFou/peCV1oxVvfy1J6BU+emi2JCBjCxIZusvT+3knMSZQtqYEBCVcQu1umCVMsZoo2iWyJmWyJ1ot8xSYxsnVJZEuqYFSs9cSGpVpTMpM8NTq35HwRC/pSU/RIZEsqYFIiX90NU9IXPZ/Cf5clK1OwbApOjVdGTy7MWiKz2LThCVOWgNKfHjOS1MxbIi8ViJvSB4ukmpItXWXzlsj5CpOm1HnndT2pmLdEnsQbTemBl/rwmI3kmXFLZFP4+FrG7ki+5zVJAmm9mxe/WaA1Eijd9HjxniiE6x0cSqtFP03GS5fkXbHhIplNSWzHvhQvdZKSIClDRk0+cbVBEmpmH8QWxOLFl2OKKft7cpEnXJiQaEKIc+tDU5nE2ozXmB3EWiSNnOGKhpxhNkSAZ82hA87lrC1IlOM1BTvMSSLXJFAmmUWzeYTIvDlDKJrPoktKorBSxPQSczg/SUl5LQoS1EQAnyY8XZh+HkaMJPP0Si7HFoTYc3I0Xt4mNWWSdgT86/OekMn8kwLJ6mJZxDcKxsjO78lb9ZoIkqEgoVRtdKlG4uN4URaA+IuJvCRVQTJQJsFfHID5nJNM5gAKJLwoIbQEyRU7vfJ4IhcelElgHg1nXxCg5iUafrmcfeEkSkU5F/91glXedpR7Ip+Pz0FJl5j9PZxPEP+B6J8ZmjnOTOF5izix15EjMbxyeVLSP7o07mEPponhpUMiwtUB05KHlwvj+NNWiPOTBHrvUDVJMIblxhhWJ5GvrrdgWjKJD9fJy6sQkgswL/leqcdI+ELJS1Ix8O9xSgsFBEmvKJIAlNT+80sUTWAt9CFiUznzYd/eIAkPTtJ+8/rJ+rdXZ1QvIs7x8TX78ARl9EQi8Q7sCWqyb78meXF29pV9ZLbQn69ffT47e5V9yUPssXF6cBLuwpok+t3e2fYkDgRx/DT3ZhpBil7dglSBQJtrrQnWqjHmHjBivMfv/2muHU4HqZDtTLca4j8xAaOmP3dmOjudHWw7BfCT/Oqn+BpuEUvHuookBzWTnA0bp0iCl53b0si2AYanp35ub7Y9LEFSKK/U7PFLJDM7oYSsDMmre/wTyRVetp+/fVRq277+/UQehYsbrQGDBBdlloWse3++HmmaeVHKuzN+WUOCWb1u102HRdJ4sDMl14A6xFjAzFa+Y52ozgySSNCukts0i7zThTUZcba/WBoWZJAuI6snEgxaPgbmpAEoXJdDXZJnJYlveS7M3zMqKq1wSB7sGWSa5ovyqMQeaRdXnu+0PrX4awIRr9yFJHTVVzlJejb7/70z3d1vE04Wdr+Q736BS+KReXFI0KzQyK7x5j6/M97qdhgsnnw+gd0DSUWiTyQxg2SYZY/DwwzkHnOW0/QwTezE1wzCeMp2sTDcqrtyRySQ2qgHP/eWe3ydTEGL5KXKHYNEWE0dpo+X69/OZqPp41ZldjYaNko8//3MrqbKK9xykZvsLlW414QuxlMHnuR736/ipw79V6kT7RTu8Dfo8BU9nZsAT9yGoibc6GZdRKLlKMqFWkQp/TGRHGGLKqyQ1rNfz0CLBPsptmhNOgLzErgJGpd2Z4EWiWuR6jIvbIGeGxclwvtSEghl3Zy8Zu589sQ5owNHNw4rMC1yk124W+qKEvd3uYpdsJeQUEaf6Sg3Lnn3YMD2eQFJuwl3zyNXU04ysLjPHiS3k72F9PEcC6ny3lQI+YvCj10t6vU4BnwwVwFJR7AoEpc/f8pU9sQ93MUer7ELJkVnanDazMl8BNNOPu+vEpKe6IQAt5G7nU+aOPqRvcM9ViUkrhKfCOKcoG3P16GhMUUKSfQWpV6nd39HSnm/2vvtVv6116zsdJPbFZ9u4sxF+Nto5UMwm80Kz85dsE+ccUFQP5uZMo4qT2aGFikCs6ItUbizm2FUe4I5JhDTPSw9RspaggQCqyZXGVgMEtr9lnR6FYMpOWNGnMQ1Yf2vrLEhr0cQRuWgFAkERlFonAujgFuSBCJjKATCGydQlsRR5lAIhFG+JRKGqxhAQRBugNxmTI4xhILOzr1pyaf5VFmhiBGEkUjwSDBrMbJb+aOsRXVdKCX51CtPamE0fpURtiQk4HSt6p3FiRggfBJCqdrC+ooBIicpooxljh9PLAaIkIRsQe4t5CFyEP5ky0trWT0ei1scRB4x/pJglm2vgDJmsBAHyXOBIeEEWIGNEQc7fMhJyO+LmnRATzRQnTsCUG5dhOK9PClZJ5ANAmW9oJAbOAxNr7Ym/RhWy+lfFn5PWh0Qz0Z3QmuF1CS4iN1lx3A6AVEsK4qBJSIRqdO11khFE8/r5fK8y2j9j/IzBbl1kbfIdcnwEMmamGIJB8AXrYlccfjKHHISYvFq5zBDgjbWZWCoQMBhhATVCcsuR98FmUyQoJwSMCFmnGIZIkEYDTNTHq2GXIZIkOYiCNUqiCjoOFC5kMSQ4kG/54VRV80But3QC/oXDhjS9sZ8+ufWO8mb0zvJ29PmkGx/+Aiboa0N+jTpzfmE74351PV/l9Yow3BWYVkAAAAASUVORK5CYII=",
"description": "Displays the latest values of the attributes or time-series data in a doughnut chart. Supports numeric values only.",
"description": "Displays the latest values of the attributes or time series data in a doughnut chart. Supports numeric values only.",
"descriptor": {
"type": "latest",
"sizeX": 4,

2
application/src/main/data/json/system/widget_types/doughnut_deprecated.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/horizontal_doughnut.json

@ -3,7 +3,7 @@
"name": "Horizontal doughnut",
"deprecated": false,
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMkAAACgCAMAAACR47ilAAABDlBMVEX///8AAAD///////////////////////////8Ihyv/X2r9/f36+vrz8/MhISHt7e339/f19fXn5+fr6+vv7++Dw5Xp6unx8fE9PT3y8vLB4cro6OhFpWDg8OW2traenp7j4+ORkZEnlkXw9/I2nlMYjjj/cnzNzc2qqqrCwsLb29t0vIf/h4//6+3/r7RktHrIyMj/w8eTy6JWVlb/aXMnlkai0q//9fbV1dVJSUnR6de7u7v/19ovLy//4eOwsLD/m6L/TVqi0rD/ub6kpKSEhISx2b1kZGTPz890dHT/pav/fYZUrG3/zdH/zdD/m6GsrKxtbW3/6er/kZn/eYNVrW08PDz/pazf7+MnlUVxCrBzAAAACHRSTlOAAHDfkI9AMGXS8IIAAAzaSURBVHja3NdBb9MwGAbgFgS8rW3ZsR1baVoJrayXLp1abas0VVO1STswBgcO8P9/CXFaGImzAHFTBO8l50fv99lOr99/8RL/el6+6Pd7/Vf4H/Kq33uDA4YQkDz4C3nd6+FQIdXgmHlxMImDUEqFEFTkX0KPjOkdTOIcImIsSRhjUZSD8nTbTSeSvUNzrpTinGubOA7tsJnuJIJppZK8FK5SmRrFtc41rppuMN1ICghXjGAXKqySMs019iiYcMn0+io+n987Sd4IBbLlcnODXSjjUhpeNNPdlIVLpqPVh5PBLidfXQkR1u+HRR4+Lz5mcBE8lcph8mKCKV1IRvF48HPeuUqA7Q6y52ydxmGkUdpGImzGupCM5qeDau4Zj/A4rObszmFolKa5hQVYOpAUDD/nTNGiEh+zdBihUsVtuxnrQjK9Gg/qM7aG4GxYn+0GANF5L0kkwinhkmns6qjPB6sIPg2fy2wJgCipNIuCagmXlB1+rtye3HmCioXl+2JZSC3hEt9RPbs4x3o2/JXFSsNdLQGUMMn95aAR8pVGVlFkJYpvuQEol0q7zW9BCZdMxk2M03G8f3URZF+aLdsMYMZoFrWYsHDJ6tnBOjlf3U+AH+8uAmC9eVycXTSNGFUpT1pMWICksZCT+WiK7yE5JUq40QS73Cy3s+drsVLpFudxmKS+kPFqglLIfsBMqngiCFw272fP1SKM0qwFpb1kOq9bjPiJUaYUv1rGuPc8o0U1tc0sAJqaFpT2ksm7mjpGqA3ZW1iiNc+7kUYXmGWN5SEDUcVxfCTJ9eUvHVUKpUJEOcc6jUwT6ixn/oRlgDa8ppVOJLenv+HwMITuOCxxmLdcAHj0ern4CFjpjrAjSGLvtPIcXshPnB3GSMlqZ+wOsGlBCbb4kmZIPMVv5wljuSos64W/90UrgnYq8SHvrvFHId+PgCjJLYoA2ayGknIWSPElzZA52sRZcozl5q0GsPApiQmmNEtib0PahewszPJUCmAz8yjcaBa0Ks2S28pkTdAilStTSQtkD2XKEsQYHdGuJNdlyDnCsh8xlUoKfClTbkBTZcMp9ZJJ+UKMEZzigcm4SUV1WS6ynBJwrTRLDgbxH5hSo/KPPFsjSd0TrAPJvAS5Rev422ILyrJE2QI8dFXqJasuIE+/MDKpUu5AUmVFIMWXTE7DR6uZwnNKZVWE5EyQIIovuTwwxN97qStr/wDo0PnyJbF3sQfG33subeUwXrj5+sas+TWlDURR/PXMNGtwwRAENkSdkmS0YvEPFKbpAxRqndpOp9//q9TdrNms60PMsrTnweHR39x77jkkWN0vk2SoBSJ2KIXSE1M511JF7NdOSR6qFWWIHUuhHOpP+c6Btq3pdZLb6kiO4UCF7dvUw2f9fnkjS9PrJDdO3G6idIL+Oy0hr+/Q5aa3JzHtfgJXEq7vC6vopuf93prE7FtDuJI8YIe4uDaGsiOSI+e7pVC6bSr3Sw3FJh4liTmSE7hUYRUfODeHYkdiFq5bOJFC4fvVwtJwyk5Ibhxmoul63otfDsUPLIJekdw6jhJzv/o9XGhDEUFvT3KmQAZwLh713PRfFMl7HvSkZU3ySRuJe4mhdLShLNELGlcWRXLpeCRm1D8NZaS9w38Uh9ia5Mb94TKdQgmWmufb7V7LsyM53keWmPHY1s7XFQiPFAsSfbku4VYKhWdKNejPm6+XIrkxGpdz8aEEPu7M9bIhOd6r39VQ2lTz/BIk6NqRHDXzOy3kwRBdAKjn+StFcg+PNupeimTQbLkYY1HO2DOJn+FZWYIaJK1eEOBOC0fqExuS0+aVKyIolTGd5O3rVRjFguS40ZMhReKzKB9hsooSYLyKUq8Oich5fr0+KJIfze6wIjlq0lQUCVllGEW+mMks98DGtUk6QQfLqlFa/ddJKCoihP/ZsqzYhJSUJH8UyWkDkhkDkE7kdhEySeuRgNfIALg2EgUvNYpoicF+hWOATufTMAaicBpO6TPJWRObKJI0BTBOBQlheZrXJeHhqHWvayB41fKLnEFqPRck8ZwiDikNI9AwliSnWsDbzWSRcqj6JB3awr1arwv4ZbMfDsr3giTyV165Z4yTEAqk4Xi8psB8LUg0w39sQuJHPkhCkeWCxEvqksjvW1dVy3eCriQ5UzmdLbj5pAoSIGXTDQAOxCTJ10aGVyTIkmQ1A0iSED9ZrVhek4RX+8DHZ71EdqXlxT8EoSQDzV+SrOecgDtmDUlyZF+6iCccLG4KhOqStPvVbPyAw/IMD0rf0ijLZhHVSLjiwvobebu0IgzXMvtwv3q8HuGJMyx/kzUYgivN0zTNJxoJY0AWMpApv1ySZFD7dLkgGQHf1Rnmzcs4wysKgJaeZ8XtSmkcUmzmbLvdGiQDOJeZ8h5+V99vmYEiTZfMqiRkHYbzBNtQSJKclCQP2K9EHfbwWO2QI79mXSEUpQySSziXeYZbksSMxvqSJI4ebNclOax2SEHSejPIf0ESEI3kH82kGy1QaLIJ55uf8vNTZk3jEWrO5AUJfyO0Z5LuYnpwEEPo24HQN5FXxedw1pBk7zOJ/1Jztk1pA0Ecf7sd4JCHmthaEsSEIB3nxlgbjEGZVqmdseMDZabf/4t0kwM2ydkxzYVz3BeIvpEfm//u3f8eIsbWJPeM+SGfM4ZZ4YxNQu5FzCz4dMk6qSvopMSM0WTm9ZrEZ5ENYERsnqTHFpkJitaun8q1S6kKWxxsIpkAxoT5APw+0U7IGC/WT3akfqKJhIJIbllkAADmhEAxNYWmWtgZP2d6fLc8yWHJ0QqRcBR7YF+nPjyPUP6FSGwQJDTuapXWye+S7jaRQLhkGJEHsKle10Yhkk47PRZ+wrGwnJMG2oNNyMXRD4zGyq2QRvVOCRL67NnffLvI/KSBfv3XjLki20ToPPV6npezOh9MjCE0vAcTQHGmRST40S3DsPAVRBh8yUwDipjcnfSc8duzzvCwhy+9XCn0LQHkz4nkjEhuypGYQhRzIfy1dO4LtZM9uEt73DSPf1zM3EciCXmuC3grg8IikvMyjgSR0M9+LPmAB/S3l6cnHzMm5DRlp7qz2WwhSC7b9tBrZLWDPtckTosgkV2iUiQG6ns1ZjHAFL0lYMwq5Xd11o1xFocg8Uaj3mWOpD86wnl8lgQOShYvO9Xjw6TyLpPE9BFugvl5UfCSB5k26xcI4tLTNQrluZbZJ5KSbqrcT1hk4gtHhuXqfR+KrWpNM6WL2smjO1uQToQ/SDGM/SmJ5KSk4UVasP2k8PLk/W2E700LCskkc/D5F9ld+dqFfmD2b6bVtswRkSiuOlAYPKCqGwQ2FHRWavCUMlNJ8Nnv37K+j3L9xJIVj5LXvYZN69goEwJx/28k3KYaoLI6px5IgtOsdDe5gnq72apqxfQENAQ9XHW4ym8teHur2PVk0AWnWZkormKD80o7C2gRO9VNlEjG2p2iWlyD8/tWWjikr24HjgN6IpZJU2zwogUt9b1E4GjcqEaCN+BCfrgUScZ6NU8k1VQuIsHHS3tSUCdG6ulyQTR4VRLY15cU2oCTVvwV1NrdN7fLdtNP6jA93Rz+78ZnNNV3Pg+cV0lKDabuCqS2261mD/d7zUmhO1rujo8vAEFUjm0IEjkpXwaw/RCnAhEliZ2Oyvlyzac2ZKXEKEjQamFyuripXpWEkkJxDtuPD/t/nP2TvS7CNBUvKZNPN+lcmR+v/tNuoyGuwFQAyZEMDrZ2LJNC/t7G9R3lyy9zJHDzTkf9kgYVA/VbfImEGj3Vry1NVOSzVGc1QIpqT5V/cnRJ5bDis1REQoe1dOywR7VXXCeJhJ4vHV0F1V7t2EjjSf9/gzhV6FHj7QvSdS7V2lK6bsSQQCrvW8+SDP62dy8pEsJAAIbtoZmpOkA2IdmUceUiIJiFB3Dldu5/lWFgoKBjTyeW1Yj4ncCfxMfGSkDVDWYQNf6U5hLmSS0lD/EgxiWZqdNYf/5HT+FDVXnCUs4H1LkLS6dejRZ2YUdU2rjlk8gM7KBHtZCK6XDy78kpKIbUTewDidagZkjdFMUxwWZ2VAgpLOEnGAsWNrEOH3SCp1Z9CUy0Nm20FncwmqCOsAQ8YcYlKMcdgheUpIT1mBujh0LedJjrW9jb9knJqSDGDw5XdAPUk5fwDnsUeHr1itb2hKuchyryEmbwmRAHm12Yn4fIFfIFkZcw/43/CW6J5lcfo6MOmXBB5CW5RLgDmkFAVsKMuKUzLcjJS8AbOnAHl5TwAx22g0sKpYAbuKTawSVVpkiVy9FbUCAoYTbS0TJAdArVS85YeBfZyWBmCfgELYNtQZu8hLV2NtE5or8ACks0aXpvBJecw1VyPFfJ8Vwlx9M0H3AOTfMJ5/DV3O5wBvfbeU5d/wHsiOrJpqVt0wAAAABJRU5ErkJggg==",
"description": "Displays the latest values of the attributes or time-series data in a doughnut chart using horizontal layout. Supports numeric values only.",
"description": "Displays the latest values of the attributes or time series data in a doughnut chart using horizontal layout. Supports numeric values only.",
"descriptor": {
"type": "latest",
"sizeX": 4,

25
application/src/main/data/json/system/widget_types/label___value_card.json

File diff suppressed because one or more lines are too long

25
application/src/main/data/json/system/widget_types/label_card.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/line_chart.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/markdown_html_card.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/pie.json

@ -3,7 +3,7 @@
"name": "Pie",
"deprecated": false,
"image": "tb-image:cGllX2NoYXJ0LnN2Zw==:IlBpZSIgc3lzdGVtIHdpZGdldCBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAxIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMSAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfNDUzOF8zNTMwNikiPgo8cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgcng9IjIuMzg4MDYiIGZpbGw9IndoaXRlIiBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiLz4KPHBhdGggZD0iTTE0OC45NzUgMTI5LjQ5N0MxNTYuNzc0IDEyMS42OTggMTYyLjYyIDExMi4xNjYgMTY2LjAzNiAxMDEuNjc4TDk5LjQ3NzUgODBMMTQ4Ljk3NSAxMjkuNDk3WiIgZmlsbD0iI0ZGREUzMCIgc3Ryb2tlPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNOTkuNDc3NSAxMEM4Ny45NjYgMTAgNzYuNjMyMSAxMi44MzkgNjYuNDc5OCAxOC4yNjU1QzU2LjMyNzUgMjMuNjkyIDQ3LjY3MDIgMzEuNTM4NiA0MS4yNzQ3IDQxLjExMDFDMzQuODc5MiA1MC42ODE2IDMwLjk0MjkgNjEuNjgyNyAyOS44MTQ2IDczLjEzODhDMjguNjg2MyA4NC41OTQ5IDMwLjQwMDcgOTYuMTUyNSAzNC44MDYgMTA2Ljc4OEMzOS4yMTEzIDExNy40MjMgNDYuMTcxNCAxMjYuODA4IDU1LjA3IDEzNC4xMTFDNjMuOTY4NiAxNDEuNDE0IDc0LjUzMDggMTQ2LjQwOSA4NS44MjEyIDE0OC42NTVDOTcuMTExNiAxNTAuOTAxIDEwOC43ODIgMTUwLjMyNyAxMTkuNzk3IDE0Ni45ODZDMTMwLjgxMyAxNDMuNjQ0IDE0MC44MzUgMTM3LjYzNyAxNDguOTc1IDEyOS40OTdMOTkuNDc3NSA4MFYxMFoiIGZpbGw9IiMwODg3MkIiIHN0cm9rZT0id2hpdGUiLz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjFfZF80NTM4XzM1MzA2KSI+CjxwYXRoIGQ9Ik0xNjcuNTYyIDEwMi4wNTdDMTcxLjAxNCA5MS4yNTc1IDE3MS44ODIgNzkuNzg5OCAxNzAuMDk2IDY4LjU4ODVDMTY4LjMxMSA1Ny4zODcxIDE2My45MjIgNDYuNzY5MiAxNTcuMjg4IDM3LjYwMDJDMTUwLjY1MyAyOC40MzEyIDE0MS45NjEgMjAuOTcwNiAxMzEuOTIgMTUuODI2NkMxMjEuODc5IDEwLjY4MjYgMTEwLjc3MiA4LjAwMDc4IDk5LjUwNjQgNy45OTk3Nkw5OS41IDc5Ljk5OThMMTY3LjU2MiAxMDIuMDU3WiIgZmlsbD0iI0ZGNEQ1QSIvPgo8cGF0aCBkPSJNMTY3LjU2MiAxMDIuMDU3QzE3MS4wMTQgOTEuMjU3NSAxNzEuODgyIDc5Ljc4OTggMTcwLjA5NiA2OC41ODg1QzE2OC4zMTEgNTcuMzg3MSAxNjMuOTIyIDQ2Ljc2OTIgMTU3LjI4OCAzNy42MDAyQzE1MC42NTMgMjguNDMxMiAxNDEuOTYxIDIwLjk3MDYgMTMxLjkyIDE1LjgyNjZDMTIxLjg3OSAxMC42ODI2IDExMC43NzIgOC4wMDA3OCA5OS41MDY0IDcuOTk5NzZMOTkuNSA3OS45OTk4TDE2Ny41NjIgMTAyLjA1N1oiIHN0cm9rZT0id2hpdGUiLz4KPC9nPgo8L2c+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfNDUzOF8zNTMwNiIgeD0iLTcuMTY0MTgiIHk9Ii00Ljc3NjEyIiB3aWR0aD0iMjE0LjMyOCIgaGVpZ2h0PSIxNzQuMzI4IiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQgZHk9IjIuMzg4MDYiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMy41ODIwOSIvPgo8ZmVDb21wb3NpdGUgaW4yPSJoYXJkQWxwaGEiIG9wZXJhdG9yPSJvdXQiLz4KPGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDYgMCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvd180NTM4XzM1MzA2Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9kcm9wU2hhZG93XzQ1MzhfMzUzMDYiIHJlc3VsdD0ic2hhcGUiLz4KPC9maWx0ZXI+CjxmaWx0ZXIgaWQ9ImZpbHRlcjFfZF80NTM4XzM1MzA2IiB4PSI4NyIgeT0iLTQuNTAwMjQiIHdpZHRoPSI5Ni41IiBoZWlnaHQ9IjExOS4xODciIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldC8+CjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjYiLz4KPGZlQ29tcG9zaXRlIGluMj0iaGFyZEFscGhhIiBvcGVyYXRvcj0ib3V0Ii8+CjxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjI1IDAiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfNDUzOF8zNTMwNiIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvd180NTM4XzM1MzA2IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8L2RlZnM+Cjwvc3ZnPgo=",
"description": "Displays the latest values of the attributes or time-series data in a pie chart. Supports numeric values only.",
"description": "Displays the latest values of the attributes or time series data in a pie chart. Supports numeric values only.",
"descriptor": {
"type": "latest",
"sizeX": 5,

2
application/src/main/data/json/system/widget_types/point_chart.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/polar_area.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/power_button.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/radar.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/range_chart.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/single_switch.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/slider.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/time_series_chart.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/timeseries_bar_chart.json

@ -3,7 +3,7 @@
"name": "Timeseries Bar Chart",
"deprecated": true,
"image": "tb-image:dGltZXNlcmllc19iYXJfY2hhcnRfc3lzdGVtX3dpZGdldF9pbWFnZS5wbmc=:IlRpbWVzZXJpZXMgQmFyIENoYXJ0IiBzeXN0ZW0gd2lkZ2V0IGltYWdl;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAABOFBMVEUAAAA3oPR3d3d6enp8fHyBgYGDg4OGhoaNjY2RkZGSkpKTk5OUlJSVlZWWlpaXl5eYmJiZmZmampqcnJydnZ2enp6goKChoaGioqKjo6OkpKSlpaWmpqanp6eoqKipqamqqqqrq6usrKytra2urq6wsLCxsbGysrK0tLS1tbW2tra3t7e4uLi5ubm6urq8vLy9vb2+vr6/v7/AwMDBwcHDw8PExMTHx8fIyMjJycnLy8vNzc3Ozs7Pz8/S0tLT09PU1NTV1dXW1tbX19fZ2dna2trb29vc3Nzd3d3e3t7f39/h4eHi4uLj4+Pk5OTl5eXm5ubn5+fo6Ojq6urr6+vs7Ozt7e3u7u7v7+/w8PDx8fH09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz9/f3+/v7/xx////8KXFhiAAAAAWJLR0RnW9PpswAAAvtJREFUeNrt3GtXElEUBuDpZlAqylVHCyMwyi4SpkaWAl4qk8wwUhJlmOn9//+gLzbKbc6AAm5991p82WvPOedZZw6zYM3aGupCQ4tYkZDSsKDrocNVvz8jHQKYY1ZiT/6OAJllRPSpsnzIqIG9SjYmHpKfAzI4CIuHjJeBtyHfZwCatiI1/m+BYV2Dw35NniOEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQUp/6Wx+DgbRbxOAhEb/fXJ98akiH1AIAHtSWlqRDSvdDGcOHnYR0SPV7zVsaQyHeO0jTJapEV5C9bUz9fIjsvHRIefhRxHqpjxzA3ftajXOudHGJOtHV+1rV04/wHenDc2QQkFbLJISQwUDaP90IIYQQQgghpAvIxRO9gFxG4lZ9XBFIx6sihJD+QVRjEEIIIYQQcoMgahkhhBDSe0iLX3ydQdSLIKR9ghCJkIYEIYQQIhPS8d+AhPQboppFw9HMxBrs/lqCIbObpgezP67YjnQ8iwagHDzrryUZUgsUz/prCYZY05vn+msJhiwM6XrR7q/V9OJU4xQuEpcxRsezNPbXEn3Yzz9Hulg3IYT0C3LxxI2CNCQIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQlpA7P5a0iF2fy3hkLP+WsIhVbu/lvRby+6vJR1i99eSG6ffu1XHxlRNcfWq3I0iIK4tJK2nXVyV0lOqEmvjGWAtvnCu+jMzkQWMiOlYdRSd3MAH/UnVPWR/ApFfSkcpiOBvRc3XtAeYW1ZUJbZMD5C8azhWLX4xvZVR692Se0huHm9ySogxkhs3lFVewPM4WlFUlUMoJEYUox1+jAFIbLuHrKaQWlMu8TicDp+4gdw7/qS4t2qBohk4UUF2nieBfLyDM/ItgXhBucT113i14QbixW7M+SRNb6EQ0u8kHavyZQxj2/kgNUCsYDRoqXfEF/Mdu4G8D43uOtakh3R9H1DsyKZvOmneDjt+D/0DTzolrPMHmggAAAAASUVORK5CYII=",
"description": "Displays changes to time-series data over time—for example, daily water consumption for the last month.",
"description": "Displays changes to time series data over time—for example, daily water consumption for the last month.",
"descriptor": {
"type": "timeseries",
"sizeX": 8,

2
application/src/main/data/json/system/widget_types/timeseries_line_chart.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/toggle_button.json

File diff suppressed because one or more lines are too long

23
application/src/main/data/json/system/widget_types/unread_notifications.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/update_json_attribute.json

File diff suppressed because one or more lines are too long

738
application/src/main/data/json/tenant/dashboards/gateways.json

@ -181,12 +181,12 @@
"actions": {
"actionCellButton": [
{
"name": "Docker commands",
"name": "Launch command",
"icon": "terminal",
"useShowWidgetActionFunction": null,
"showWidgetActionFunction": "return true;",
"type": "customPretty",
"customHtml": "<div class=\"container\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Docker commands</h2>\n <span fxFlex></span>\n <div [tb-help]=\"'gatewayInstall'\"></div>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <tb-gateway-command [deviceId]=\"entityId\"></tb-gateway-command>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n (click)=\"cancel()\" cdkFocusInitial>\n {{ 'action.close' | translate }}\n </button>\n </div>\n</div>\n",
"customHtml": "<div class=\"container\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>{{ 'gateway.launch-command' | translate }}</h2>\n <span fxFlex></span>\n <div [tb-help]=\"'gatewayInstall'\"></div>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <tb-gateway-command [deviceId]=\"entityId\"></tb-gateway-command>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n (click)=\"cancel()\" cdkFocusInitial>\n {{ 'action.close' | translate }}\n </button>\n </div>\n</div>\n",
"customCss": ".container {\n display: grid;\n grid-template-rows: min-content minmax(auto, 1fr) min-content;\n height: 100%;\n max-height: 100vh;\n width: 600px;\n max-width: 100%;\n}",
"customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\n\nopenCommands();\n\nfunction openCommands() {\n customDialog.customDialog(htmlTemplate, CommandsDialogController, {panelClass: \"test\"}).subscribe();\n}\n\nfunction CommandsDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId.id;\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n}\n",
"customResources": [],
@ -248,7 +248,7 @@
"type": "customPretty",
"customHtml": "<form #addEntityForm=\"ngForm\" [formGroup]=\"addEntityFormGroup\"\r\n (ngSubmit)=\"save($event)\" class=\"add-entity-form\">\r\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\r\n <h2>Add gateway</h2>\r\n <span fxFlex></span>\r\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\r\n <mat-icon class=\"material-icons\">close</mat-icon>\r\n </button>\r\n </mat-toolbar>\r\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\r\n </mat-progress-bar>\r\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\r\n <div mat-dialog-content fxLayout=\"column\">\r\n <div fxLayout=\"row\" fxLayoutGap=\"8px\" fxLayout.xs=\"column\" fxLayoutGap.xs=\"0\">\r\n <mat-form-field fxFlex class=\"mat-block\">\r\n <mat-label>Name</mat-label>\r\n <input matInput formControlName=\"entityName\" required>\r\n <mat-error *ngIf=\"addEntityFormGroup.get('entityName').hasError('required')\">\r\n Gateway name is required.\r\n </mat-error>\r\n </mat-form-field>\r\n </div>\r\n <div fxLayout=\"row\" fxLayoutGap=\"8px\" fxLayout.xs=\"column\" fxLayoutGap.xs=\"0\">\r\n <tb-entity-subtype-autocomplete\r\n fxFlex\r\n class=\"mat-block\"\r\n formControlName=\"type\"\r\n [required]=\"true\"\r\n [entityType]=\"'DEVICE'\"\r\n ></tb-entity-subtype-autocomplete>\r\n </div>\r\n </div>\r\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\r\n <button mat-button color=\"primary\"\r\n type=\"button\"\r\n [disabled]=\"(isLoading$ | async)\"\r\n (click)=\"cancel()\" cdkFocusInitial>\r\n Cancel\r\n </button>\r\n <button mat-button mat-raised-button color=\"primary\"\r\n type=\"submit\"\r\n [disabled]=\"(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty\">\r\n Create\r\n </button>\r\n </div>\r\n</form>\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($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 goToConfigState();\r\n }\r\n\r\n \r\n function 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",
"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 == \"Launch command\");\r\n widgetContext.actionsApi.handleWidgetAction($event, openCommandAction, device.id, device.name, {newDevice: true});\r\n goToConfigState();\r\n }\r\n\r\n \r\n function 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,
@ -880,7 +880,7 @@
"isConnectorLog": true,
"connectorLogState": "connector_logs"
},
"title": "Gateway Logs",
"title": "${connectorName} logs",
"showTitleIcon": false,
"dropShadow": true,
"enableFullscreen": true,
@ -1085,13 +1085,14 @@
"showLegend": false,
"actions": {},
"datasources": [],
"targetDeviceAliasIds": [
"a2f01c66-96cf-49c5-303f-e6f21c559ee8"
],
"showTitleIcon": false,
"widgetCss": "",
"pageSize": 1024,
"noDataDisplayMessage": ""
"noDataDisplayMessage": "",
"targetDevice": {
"type": "entity",
"entityAliasId": "a2f01c66-96cf-49c5-303f-e6f21c559ee8"
}
},
"row": 0,
"col": 0,
@ -1494,15 +1495,16 @@
"showLegend": false,
"actions": {},
"datasources": [],
"targetDeviceAliasIds": [
"a2f01c66-96cf-49c5-303f-e6f21c559ee8"
],
"showTitleIcon": false,
"titleTooltip": "",
"widgetCss": "",
"pageSize": 1024,
"noDataDisplayMessage": "",
"borderRadius": "4px"
"borderRadius": "4px",
"targetDevice": {
"type": "entity",
"entityAliasId": "a2f01c66-96cf-49c5-303f-e6f21c559ee8"
}
},
"row": 0,
"col": 0,
@ -1557,9 +1559,6 @@
},
"title": "Service RPC",
"datasources": [],
"targetDeviceAliasIds": [
"a2f01c66-96cf-49c5-303f-e6f21c559ee8"
],
"showTitleIcon": false,
"titleTooltip": "",
"dropShadow": true,
@ -1574,7 +1573,11 @@
"noDataDisplayMessage": "",
"enableDataExport": false,
"showLegend": false,
"borderRadius": "4px"
"borderRadius": "4px",
"targetDevice": {
"type": "entity",
"entityAliasId": "a2f01c66-96cf-49c5-303f-e6f21c559ee8"
}
},
"row": 0,
"col": 0,
@ -1596,9 +1599,6 @@
},
"title": "Service RPC",
"datasources": [],
"targetDeviceAliasIds": [
"a2f01c66-96cf-49c5-303f-e6f21c559ee8"
],
"showTitleIcon": false,
"titleTooltip": "",
"dropShadow": true,
@ -1613,7 +1613,11 @@
"noDataDisplayMessage": "",
"enableDataExport": false,
"showLegend": false,
"borderRadius": "4px"
"borderRadius": "4px",
"targetDevice": {
"type": "entity",
"entityAliasId": "a2f01c66-96cf-49c5-303f-e6f21c559ee8"
}
},
"row": 0,
"col": 0,
@ -1802,15 +1806,16 @@
"showLegend": false,
"actions": {},
"datasources": [],
"targetDeviceAliasIds": [
"a2f01c66-96cf-49c5-303f-e6f21c559ee8"
],
"showTitleIcon": false,
"titleTooltip": "",
"widgetCss": "",
"pageSize": 1024,
"noDataDisplayMessage": "",
"borderRadius": "4px"
"borderRadius": "4px",
"targetDevice": {
"type": "entity",
"entityAliasId": "a2f01c66-96cf-49c5-303f-e6f21c559ee8"
}
},
"row": 0,
"col": 0,
@ -1861,7 +1866,7 @@
"padding": "8px",
"settings": {
"useMarkdownTextFunction": false,
"markdownTextPattern": "<div class=\"action-buttons-container\">\r\n <button mat-raised-button color=\"primary\"\r\n (click)=\"ctx.actionsApi.handleWidgetAction($event, ctx.actionsApi.getActionDescriptors('elementClick')[0], ctx.datasources[0].entity.id)\"\r\n >Launch command\r\n </button>\r\n</div>",
"markdownTextPattern": "<div class=\"action-buttons-container\">\r\n <button mat-raised-button color=\"primary\"\r\n (click)=\"ctx.actionsApi.handleWidgetAction($event, ctx.actionsApi.getActionDescriptors('elementClick')[0], ctx.datasources[0].entity.id)\"\r\n >{{ 'gateway.launch-command' | translate }}\r\n </button>\r\n</div>",
"applyDefaultMarkdownStyle": false,
"markdownCss": ".action-buttons-container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex-direction: row;\r\n height: 100%;\r\n width: 100%;\r\n align-content: center;\r\n}\r\n\r\nbutton {\r\n flex-grow: 1;\r\n margin: 10px;\r\n min-width: 150px;\r\n height: auto;\r\n}"
},
@ -1887,7 +1892,7 @@
"useShowWidgetActionFunction": null,
"showWidgetActionFunction": "return true;",
"type": "customPretty",
"customHtml": "<div class=\"container\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>Launch command</h2>\n <span fxFlex></span>\n <div [tb-help]=\"'gatewayInstall'\"></div>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <tb-gateway-command [deviceId]=\"entityId\"></tb-gateway-command>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n (click)=\"cancel()\" cdkFocusInitial>\n {{ 'action.close' | translate }}\n </button>\n </div>\n</div>\n",
"customHtml": "<div class=\"container\">\n <mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2>{{ 'gateway.launch-command' | translate }}</h2>\n <span fxFlex></span>\n <div [tb-help]=\"'gatewayInstall'\"></div>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <tb-gateway-command [deviceId]=\"entityId\"></tb-gateway-command>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button color=\"primary\"\n type=\"button\"\n (click)=\"cancel()\" cdkFocusInitial>\n {{ 'action.close' | translate }}\n </button>\n </div>\n</div>\n",
"customCss": ".container {\n display: grid;\n grid-template-rows: min-content minmax(auto, 1fr) min-content;\n height: 100%;\n max-height: 100vh;\n width: 600px;\n max-width: 100%;\n}",
"customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\n\nopenCommands();\n\nfunction openCommands() {\n customDialog.customDialog(htmlTemplate, CommandsDialogController, {panelClass: \"test\"}).subscribe();\n}\n\nfunction CommandsDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId.id;\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n}\n",
"customResources": [],
@ -2073,30 +2078,472 @@
"openInPopover": false,
"id": "35705df8-69cf-7f76-b52f-d53c5a1931c0"
}
]
},
"widgetCss": "",
"pageSize": 1024,
"noDataDisplayMessage": "",
"enableDataExport": false,
"borderRadius": "4px"
},
"row": 0,
"col": 0,
"id": "61d149e8-b249-5526-e5d7-6ad58413982e",
"typeFullFqn": "system.cards.markdown_card"
},
"3d661190-7463-ba61-6793-503c85af67ec": {
"type": "latest",
"sizeX": 5,
"sizeY": 3.5,
"config": {
"datasources": [
]
},
"widgetCss": "",
"pageSize": 1024,
"noDataDisplayMessage": "",
"enableDataExport": false,
"borderRadius": "4px"
},
"row": 0,
"col": 0,
"id": "61d149e8-b249-5526-e5d7-6ad58413982e",
"typeFullFqn": "system.cards.markdown_card"
},
"3d661190-7463-ba61-6793-503c85af67ec": {
"type": "latest",
"sizeX": 5,
"sizeY": 3.5,
"config": {
"datasources": [
{
"type": "entity",
"name": "function",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"dataKeys": [],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "bd9176e1-9e04-3e9b-d5a5-07b72bb9ae90",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "mqttCount",
"color": "#4caf50",
"settings": {},
"_hash": 0.9590451878027946,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entity",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "44038462-1bae-e075-7b31-283341cb2295",
"dataKeys": [
{
"name": "count",
"type": "entityField",
"label": "modbusCount",
"color": "#4caf50",
"settings": {},
"_hash": 0.9300660062254784,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "884e9c34-7534-a483-99be-81b56cd91185",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "grcpCount",
"color": "#f44336",
"settings": {},
"_hash": 0.16110429492126088,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "e91ca0e9-1653-4fbc-5f3d-3da021b1b415",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "opcuaCount",
"color": "#ffc107",
"settings": {},
"_hash": 0.13973521146913304,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "3f74cbaa-6353-5e88-a7e8-708fc0e18039",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "bleCount",
"color": "#607d8b",
"settings": {},
"_hash": 0.7205723252294653,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "c08eee84-64ee-73c4-8d96-c0df813a92cd",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "requestCount",
"color": "#9c27b0",
"settings": {},
"_hash": 0.6993292961463216,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "2f0af7f5-22ea-c0d5-3aef-7f2bb1b534ec",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "canCount",
"color": "#8bc34a",
"settings": {},
"_hash": 0.4850065031079176,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "92a7d208-c143-ea20-5162-1da584532830",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "bacnetCount",
"color": "#3f51b5",
"settings": {},
"_hash": 0.36987531665233075,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "498f090c-b1e5-df74-35d1-3ecf89d33f1c",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "odbcCount",
"color": "#e91e63",
"settings": {},
"_hash": 0.8279345016611896,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "9175179d-a8db-848b-0762-e78da150e768",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "restCount",
"color": "#ffeb3b",
"settings": {},
"_hash": 0.07245826389852739,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "9175179d-a8db-848b-0762-e78da150e768",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "restCount",
"color": "#03a9f4",
"settings": {},
"_hash": 0.4321492578560704,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "1b70460b-428b-2aed-f23a-65927d3e67bb",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "snmpCount",
"color": "#ff9800",
"settings": {},
"_hash": 0.7395574625078822,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "ae357478-b4c2-eee8-dde6-a6942fe6202f",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "ftpCount",
"color": "#673ab7",
"settings": {},
"_hash": 0.9999791065203374,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "6eef4979-369f-c2cc-4894-96a84b6a668a",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "socketCount",
"color": "#cddc39",
"settings": {},
"_hash": 0.47563823619478685,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "9c8e3a63-01a1-64b5-fe44-4f58f8350340",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "xmppCount",
"color": "#009688",
"settings": {},
"_hash": 0.5679285702280172,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entity",
"name": "function",
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"dataKeys": [],
"filterId": "c6501413-d823-29c4-992f-9ae6e8e25549",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "ocppCount",
"color": "#795548",
"settings": {},
"_hash": 0.38390880166484287,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
},
{
"type": "entityCount",
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "2f04d6e5-8438-857a-ca78-ae78cc8b0c03",
"dataKeys": [
{
"name": "count",
"type": "count",
"label": "customCount",
"color": "#00bcd4",
"settings": {},
"_hash": 0.9282529984749979,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
@ -2134,7 +2581,7 @@
"padding": "0px",
"settings": {
"useMarkdownTextFunction": false,
"markdownTextPattern": "<div style=\"width: 100%; height: 100%; padding: 0;\" fxFlex fxLayout=\"column\">\r\n <mat-tab-group [(selectedIndex)]=\"selectedTabIndex\">\r\n <mat-tab label=\"All\" value=\"gateway_devices_0\"></mat-tab>\r\n <mat-tab label=\"MQTT\" value=\"gateway_devices_1\"></mat-tab>\r\n <mat-tab label=\"MODBUS\" value=\"gateway_devices_2\"></mat-tab>\r\n <mat-tab label=\"GRPC\" value=\"gateway_devices_3\"></mat-tab>\r\n <mat-tab label=\"OPCUA\" value=\"gateway_devices_4\"> </mat-tab>\r\n <mat-tab label=\"OPCUA ASYNCIO\" value=\"gateway_devices_5\"></mat-tab>\r\n <mat-tab label=\"BLE\" value=\"gateway_devices_6\"></mat-tab>\r\n <mat-tab label=\"REQUEST\" value=\"gateway_devices_7\"></mat-tab>\r\n <mat-tab label=\"CAN\" value=\"gateway_devices_8\"></mat-tab>\r\n <mat-tab label=\"BACNET\" value=\"gateway_devices_9\"></mat-tab>\r\n <mat-tab label=\"ODBC\" value=\"gateway_devices_10\"></mat-tab>\r\n <mat-tab label=\"REST\" value=\"gateway_devices_11\"></mat-tab>\r\n <mat-tab label=\"SNMP\" value=\"gateway_devices_12\"></mat-tab>\r\n <mat-tab label=\"FTP\" value=\"gateway_devices_13\"></mat-tab>\r\n <mat-tab label=\"SOCKET\" value=\"gateway_devices_14\"></mat-tab>\r\n <mat-tab label=\"XMPP\" value=\"gateway_devices_15\"></mat-tab>\r\n <mat-tab label=\"OCCP\" value=\"gateway_devices_16\"></mat-tab>\r\n <mat-tab label=\"CUSTOM\" value=\"gateway_devices_17\"></mat-tab>\r\n </mat-tab-group><tb-dashboard-state *ngIf=\"selectedTabIndex == 1\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_1\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 2\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_2\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 3\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_3\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 4\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_4\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 5\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_5\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 6\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_6\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 7\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_7\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 8\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_8\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 9\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_9\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 10\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_10\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 11\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_11\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 12\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_12\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 13\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_13\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 14\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_14\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 15\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_15\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 16\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_16\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 17\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_17\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"!selectedTabIndex\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_0\"></tb-dashboard-state>\r\n</div>\r\n",
"markdownTextPattern": "<div style=\"width: 100%; height: 100%; padding: 0;\" fxFlex fxLayout=\"column\">\r\n <mat-tab-group [(selectedIndex)]=\"selectedTabIndex\">\r\n <mat-tab label=\"All\" value=\"gateway_devices_0\"></mat-tab>\r\n <mat-tab *ngIf=\"${mqttCount}\" label=\"MQTT\" value=\"gateway_devices_1\"></mat-tab>\r\n <mat-tab *ngIf=\"${modbusCount}\" label=\"MODBUS\" value=\"gateway_devices_2\"></mat-tab>\r\n <mat-tab *ngIf=\"${grpcCount}\" label=\"GRPC\" value=\"gateway_devices_3\"></mat-tab>\r\n <mat-tab *ngIf=\"${opcuaCount}\" label=\"OPCUA\" value=\"gateway_devices_4\"> </mat-tab>\r\n <mat-tab *ngIf=\"${bleCount}\" label=\"BLE\" value=\"gateway_devices_6\"></mat-tab>\r\n <mat-tab *ngIf=\"${requestCount}\" label=\"REQUEST\" value=\"gateway_devices_7\"></mat-tab>\r\n <mat-tab *ngIf=\"${canCount}\" label=\"CAN\" value=\"gateway_devices_8\"></mat-tab>\r\n <mat-tab *ngIf=\"${bacnetCount}\" label=\"BACNET\" value=\"gateway_devices_9\"></mat-tab>\r\n <mat-tab *ngIf=\"${odbcCount}\" label=\"ODBC\" value=\"gateway_devices_10\"></mat-tab>\r\n <mat-tab *ngIf=\"${restCount}\" label=\"REST\" value=\"gateway_devices_11\"></mat-tab>\r\n <mat-tab *ngIf=\"${snmpCount}\" label=\"SNMP\" value=\"gateway_devices_12\"></mat-tab>\r\n <mat-tab *ngIf=\"${ftpCount}\" label=\"FTP\" value=\"gateway_devices_13\"></mat-tab>\r\n <mat-tab *ngIf=\"${socketCount}\" label=\"SOCKET\" value=\"gateway_devices_14\"></mat-tab>\r\n <mat-tab *ngIf=\"${xmppCount}\" label=\"XMPP\" value=\"gateway_devices_15\"></mat-tab>\r\n <mat-tab *ngIf=\"${occpCount}\" label=\"OCCP\" value=\"gateway_devices_16\"></mat-tab>\r\n <mat-tab *ngIf=\"${customCount}\" label=\"CUSTOM\" value=\"gateway_devices_17\"></mat-tab>\r\n </mat-tab-group><tb-dashboard-state *ngIf=\"selectedTabIndex == 1\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_1\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 2\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_2\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 3\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_3\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 4\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_4\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 6\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_6\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 7\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_7\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 8\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_8\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 9\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_9\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 10\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_10\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 11\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_11\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 12\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_12\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 13\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_13\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 14\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_14\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 15\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_15\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 16\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_16\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"selectedTabIndex == 17\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_17\"></tb-dashboard-state>\r\n <tb-dashboard-state *ngIf=\"!selectedTabIndex\" [ctx]=\"ctx\" fxFlex syncParentStateParams=\"true\" stateId=\"gateway_devices_0\"></tb-dashboard-state>\r\n</div>\r\n",
"applyDefaultMarkdownStyle": false,
"markdownCss": ".mat-mdc-form-field-subscript-wrapper {\n display: none !important;\n}"
},
@ -2996,172 +3443,6 @@
"id": "bb27723a-989c-2327-5808-b56d490b93ab",
"typeFullFqn": "system.cards.entities_table"
},
"e3a9539d-2ccb-96e0-2afa-36f93e40233d": {
"type": "latest",
"sizeX": 7.5,
"sizeY": 6.5,
"config": {
"timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": {
"realtimeType": 1,
"interval": 1000,
"timewindowMs": 86400000,
"quickInterval": "CURRENT_DAY"
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1684327643501,
"endTimeMs": 1684414043501
},
"quickInterval": "CURRENT_DAY"
},
"aggregation": {
"type": "NONE",
"limit": 200
}
},
"showTitle": true,
"backgroundColor": "rgb(255, 255, 255)",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "4px",
"settings": {
"entitiesTitle": "Devices",
"enableSearch": true,
"enableSelectColumnDisplay": true,
"enableStickyHeader": true,
"enableStickyAction": true,
"reserveSpaceForHiddenAction": "true",
"displayEntityName": true,
"entityNameColumnTitle": "Device Name",
"displayEntityLabel": false,
"displayEntityType": false,
"displayPagination": true,
"defaultPageSize": 10,
"defaultSortOrder": "entityName",
"useRowStyleFunction": false
},
"title": "Devices",
"dropShadow": true,
"enableFullscreen": true,
"titleStyle": {
"fontSize": "16px",
"fontWeight": 400,
"padding": "5px 10px 5px 10px"
},
"useDashboardTimewindow": false,
"showLegend": false,
"datasources": [
{
"type": "entity",
"name": null,
"entityAliasId": "a75d9031-ba51-8da4-81be-de65061b72f4",
"filterId": "3931abd5-2205-9386-6ea9-8e8a8131bb9d",
"dataKeys": [
{
"name": "type",
"type": "entityField",
"label": "Device Type",
"color": "#2196f3",
"settings": {},
"_hash": 0.3129929097366162,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
},
{
"name": "active",
"type": "attribute",
"label": "Status",
"color": "#4caf50",
"settings": {
"columnWidth": "0px",
"useCellStyleFunction": false,
"useCellContentFunction": true,
"cellContentFunction": "let cssClass;\r\nswitch (value) {\r\n case \"Active\":\r\n cssClass = \"status status-active\";\r\n break;\r\n case \"Inactive\":\r\n default:\r\n cssClass = \"status status-inactive\";\r\n break;\r\n }\r\n \r\n return `<span class='${cssClass}'>${value}</span>`;",
"defaultColumnVisibility": "visible",
"columnSelectionToDisplay": "enabled"
},
"_hash": 0.5969880627410065,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": true,
"postFuncBody": "return value == 'true' ? \"Active\": \"Inactive\";"
},
{
"name": "connectorName",
"type": "attribute",
"label": "Connector Name",
"color": "#f44336",
"settings": {},
"_hash": 0.012483045440007778,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
},
{
"name": "connectorType",
"type": "attribute",
"label": "Connector Type",
"color": "#ffc107",
"settings": {},
"_hash": 0.6004192233378134,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
],
"alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
}
}
],
"displayTimewindow": true,
"showTitleIcon": false,
"titleTooltip": "",
"widgetStyle": {},
"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,
"actions": {
"actionCellButton": [
{
"name": "Show Device Info",
"icon": "info",
"useShowWidgetActionFunction": null,
"showWidgetActionFunction": "return true;",
"type": "custom",
"customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');",
"openInSeparateDialog": false,
"openInPopover": false,
"id": "94de7690-f91d-b032-6771-85af99abd749"
}
]
}
},
"row": 0,
"col": 0,
"id": "e3a9539d-2ccb-96e0-2afa-36f93e40233d",
"typeFullFqn": "system.cards.entities_table"
},
"cf2eba6b-44f6-9cc2-6089-35c735f54898": {
"type": "latest",
"sizeX": 7.5,
@ -5632,33 +5913,6 @@
}
}
},
"gateway_devices_5": {
"name": "gateway_devices_opcua_async",
"root": false,
"layouts": {
"main": {
"widgets": {
"e3a9539d-2ccb-96e0-2afa-36f93e40233d": {
"sizeX": 24,
"sizeY": 11,
"row": 0,
"col": 0
}
},
"gridSettings": {
"backgroundColor": "#eeeeee",
"columns": 24,
"margin": 0,
"outerMargin": true,
"backgroundSizeMode": "100%",
"autoFillHeight": false,
"backgroundImageUrl": null,
"mobileAutoFillHeight": false,
"mobileRowHeight": 70
}
}
}
},
"gateway_devices_6": {
"name": "gateway_devices_ble",
"root": false,

14
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -270,14 +270,16 @@ public class TenantActor extends RuleChainManagerActor {
initRuleChains();
}
}
if (msg.getEntityId().getEntityType() == EntityType.EDGE) {
if (msg.getEntityId().getEntityType() == EntityType.EDGE && isCore) {
EdgeId edgeId = new EdgeId(msg.getEntityId().getId());
EdgeRpcService edgeRpcService = systemContext.getEdgeRpcService();
if (msg.getEvent() == ComponentLifecycleEvent.DELETED) {
edgeRpcService.deleteEdge(tenantId, edgeId);
} else if (msg.getEvent() == ComponentLifecycleEvent.UPDATED) {
Edge edge = systemContext.getEdgeService().findEdgeById(tenantId, edgeId);
edgeRpcService.updateEdge(tenantId, edge);
if (edgeRpcService != null) {
if (msg.getEvent() == ComponentLifecycleEvent.DELETED) {
edgeRpcService.deleteEdge(tenantId, edgeId);
} else if (msg.getEvent() == ComponentLifecycleEvent.UPDATED) {
Edge edge = systemContext.getEdgeService().findEdgeById(tenantId, edgeId);
edgeRpcService.updateEdge(tenantId, edge);
}
}
}
if (msg.getEntityId().getEntityType() == EntityType.DEVICE && ComponentLifecycleEvent.DELETED == msg.getEvent() && isMyPartition(msg.getEntityId())) {

9
application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java

@ -48,6 +48,7 @@ import org.springdoc.core.models.GroupedOpenApi;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.properties.SwaggerUiConfigProperties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@ -62,7 +63,6 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.exception.ThingsboardCredentialsExpiredResponse;
import org.thingsboard.server.exception.ThingsboardErrorResponse;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import org.thingsboard.server.service.security.auth.rest.LoginResponse;
@ -79,7 +79,7 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@Slf4j
@Configuration
@TbCoreComponent
@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${springdoc.api-docs.enabled:true}'=='true'")
@Profile("!test")
public class SwaggerConfiguration {
@ -113,7 +113,8 @@ public class SwaggerConfiguration {
private String version;
@Value("${app.version:unknown}")
private String appVersion;
@Value("${swagger.group_name:thingsboard}")
private String groupName;
@Bean
public OpenAPI thingsboardApi() {
@ -212,7 +213,7 @@ public class SwaggerConfiguration {
@Bean
public GroupedOpenApi groupedApi(SpringDocParameterNameDiscoverer localSpringDocParameterNameDiscoverer) {
return GroupedOpenApi.builder()
.group("thingsboard")
.group(groupName)
.pathsToMatch(apiPath)
.addRouterOperationCustomizer(routerOperationCustomizer(localSpringDocParameterNameDiscoverer))
.addOperationCustomizer(operationCustomizer())

18
application/src/main/java/org/thingsboard/server/controller/AdminController.java

@ -29,16 +29,13 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@ -120,6 +117,9 @@ public class AdminController extends BaseController {
private final SystemInfoService systemInfoService;
private final AuditLogService auditLogService;
@Value("${queue.vc.request-timeout:180000}")
private int vcRequestTimeout;
@ApiOperation(value = "Get the Administration Settings object using key (getAdminSettings)",
notes = "Get the Administration Settings object using specified string key. Referencing non-existing key will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@ -301,13 +301,14 @@ public class AdminController extends BaseController {
@PostMapping("/repositorySettings")
public DeferredResult<RepositorySettings> saveRepositorySettings(@RequestBody RepositorySettings settings) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE);
settings.setLocalOnly(false); // only to be used in tests
ListenableFuture<RepositorySettings> future = versionControlService.saveVersionControlSettings(getTenantId(), settings);
return wrapFuture(Futures.transform(future, savedSettings -> {
savedSettings.setPassword(null);
savedSettings.setPrivateKey(null);
savedSettings.setPrivateKeyPassword(null);
return savedSettings;
}, MoreExecutors.directExecutor()));
}, MoreExecutors.directExecutor()), vcRequestTimeout);
}
@ApiOperation(value = "Delete repository settings (deleteRepositorySettings)",
@ -318,7 +319,7 @@ public class AdminController extends BaseController {
@ResponseStatus(value = HttpStatus.OK)
public DeferredResult<Void> deleteRepositorySettings() throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE);
return wrapFuture(versionControlService.deleteVersionControlSettings(getTenantId()));
return wrapFuture(versionControlService.deleteVersionControlSettings(getTenantId()), vcRequestTimeout);
}
@ApiOperation(value = "Check repository access (checkRepositoryAccess)",
@ -329,8 +330,8 @@ public class AdminController extends BaseController {
@Parameter(description = "A JSON value representing the Repository Settings.")
@RequestBody RepositorySettings settings) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
settings = checkNotNull(settings);
return wrapFuture(versionControlService.checkVersionControlAccess(getTenantId(), settings));
settings.setLocalOnly(false); // only to be used in tests
return wrapFuture(versionControlService.checkVersionControlAccess(getTenantId(), settings), vcRequestTimeout);
}
@ApiOperation(value = "Get auto commit settings (getAutoCommitSettings)",
@ -482,4 +483,5 @@ public class AdminController extends BaseController {
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings);
response.sendRedirect(prevUri);
}
}

13
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -16,6 +16,7 @@
package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -350,11 +351,11 @@ public class AlarmController extends BaseController {
@PathVariable(ENTITY_TYPE) String strEntityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(ENTITY_ID) String strEntityId,
@Parameter(description = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = {"ANY", "ACTIVE", "CLEARED", "ACK", "UNACK"}))
@Parameter(description = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, array = @ArraySchema(schema = @Schema(type = "string", allowableValues = {"ANY", "ACTIVE", "CLEARED", "ACK", "UNACK"})))
@RequestParam(required = false) String[] statusList,
@Parameter(description = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = {"CRITICAL", "MAJOR", "MINOR", "WARNING", "INDETERMINATE"}))
@Parameter(description = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, array = @ArraySchema(schema = @Schema(type = "string", allowableValues = {"CRITICAL", "MAJOR", "MINOR", "WARNING", "INDETERMINATE"})))
@RequestParam(required = false) String[] severityList,
@Parameter(description = ALARM_QUERY_TYPE_ARRAY_DESCRIPTION)
@Parameter(description = ALARM_QUERY_TYPE_ARRAY_DESCRIPTION, array = @ArraySchema(schema = @Schema(type = "string")))
@RequestParam(required = false) String[] typeList,
@Parameter(description = ALARM_QUERY_ASSIGNEE_DESCRIPTION)
@RequestParam(required = false) String assigneeId,
@ -412,11 +413,11 @@ public class AlarmController extends BaseController {
@RequestMapping(value = "/v2/alarms", method = RequestMethod.GET)
@ResponseBody
public PageData<AlarmInfo> getAllAlarmsV2(
@Parameter(description = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = {"ANY", "ACTIVE", "CLEARED", "ACK", "UNACK"}))
@Parameter(description = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, array = @ArraySchema(schema = @Schema(type = "string", allowableValues = {"ANY", "ACTIVE", "CLEARED", "ACK", "UNACK"})))
@RequestParam(required = false) String[] statusList,
@Parameter(description = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = {"CRITICAL", "MAJOR", "MINOR", "WARNING", "INDETERMINATE"}))
@Parameter(description = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, array = @ArraySchema(schema = @Schema(type = "string", allowableValues = {"CRITICAL", "MAJOR", "MINOR", "WARNING", "INDETERMINATE"})))
@RequestParam(required = false) String[] severityList,
@Parameter(description = ALARM_QUERY_TYPE_ARRAY_DESCRIPTION)
@Parameter(description = ALARM_QUERY_TYPE_ARRAY_DESCRIPTION, array = @ArraySchema(schema = @Schema(type = "string")))
@RequestParam(required = false) String[] typeList,
@Parameter(description = ALARM_QUERY_ASSIGNEE_DESCRIPTION)
@RequestParam(required = false) String assigneeId,

3
application/src/main/java/org/thingsboard/server/controller/AssetController.java

@ -17,6 +17,7 @@ package org.thingsboard.server.controller;
import com.google.common.util.concurrent.ListenableFuture;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -354,7 +355,7 @@ public class AssetController extends BaseController {
@RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET)
@ResponseBody
public List<Asset> getAssetsByIds(
@Parameter(description = "A list of assets ids, separated by comma ','")
@Parameter(description = "A list of assets ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string")))
@RequestParam("assetIds") String[] strAssetIds) throws ThingsboardException, ExecutionException, InterruptedException {
checkArrayParameter("assetIds", strAssetIds);
SecurityUser user = getCurrentUser();

25
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -22,6 +22,7 @@ import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolation;
import lombok.Getter;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -358,29 +359,33 @@ public abstract class BaseController {
private ThingsboardException handleException(Exception exception, boolean logException) {
if (logException && logControllerErrorStackTrace) {
log.error("Error [{}]", exception.getMessage(), exception);
}
String cause = "";
if (exception.getCause() != null) {
cause = exception.getCause().getClass().getCanonicalName();
try {
SecurityUser user = getCurrentUser();
log.error("[{}][{}] Error", user.getTenantId(), user.getId(), exception);
} catch (Exception e) {
log.error("Error", exception);
}
}
Throwable cause = exception.getCause();
if (exception instanceof ThingsboardException) {
return (ThingsboardException) exception;
} else if (exception instanceof IllegalArgumentException || exception instanceof IncorrectParameterException
|| exception instanceof DataValidationException || cause.contains("IncorrectParameterException")) {
|| exception instanceof DataValidationException || cause instanceof IncorrectParameterException) {
return new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} else if (exception instanceof MessagingException) {
return new ThingsboardException("Unable to send mail: " + exception.getMessage(), ThingsboardErrorCode.GENERAL);
} else if (exception instanceof AsyncRequestTimeoutException) {
return new ThingsboardException("Request timeout", ThingsboardErrorCode.GENERAL);
} else if (exception instanceof DataAccessException) {
String errorType = exception.getClass().getSimpleName();
if (!logControllerErrorStackTrace) { // not to log the error twice
log.warn("Database error: {} - {}", errorType, ExceptionUtils.getRootCauseMessage(exception));
log.warn("Database error: {} - {}", exception.getClass().getSimpleName(), ExceptionUtils.getRootCauseMessage(exception));
}
if (cause instanceof ConstraintViolationException) {
return new ThingsboardException(ExceptionUtils.getRootCause(exception).getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} else {
return new ThingsboardException("Database error", ThingsboardErrorCode.GENERAL);
}
return new ThingsboardException("Database error", ThingsboardErrorCode.GENERAL);
}
return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.GENERAL);
}

3
application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java

@ -16,6 +16,7 @@
package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@ -83,7 +84,7 @@ public class ComponentDescriptorController extends BaseController {
@RequestMapping(value = "/components", params = {"componentTypes"}, method = RequestMethod.GET)
@ResponseBody
public List<ComponentDescriptor> getComponentDescriptorsByTypes(
@Parameter(description = "List of types of the Rule Nodes, (ENRICHMENT, FILTER, TRANSFORMATION, ACTION or EXTERNAL)", required = true)
@Parameter(description = "List of types of the Rule Nodes, (ENRICHMENT, FILTER, TRANSFORMATION, ACTION or EXTERNAL)", array = @ArraySchema(schema = @Schema(type = "string")), required = true)
@RequestParam("componentTypes") String[] strComponentTypes,
@Parameter(description = "Type of the Rule Chain", schema = @Schema(allowableValues = {"CORE", "EDGE"}))
@RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException {

3
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -488,7 +489,7 @@ public class DeviceController extends BaseController {
@RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET)
@ResponseBody
public List<Device> getDevicesByIds(
@Parameter(description = "A list of devices ids, separated by comma ','")
@Parameter(description = "A list of devices ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string")))
@RequestParam("deviceIds") String[] strDeviceIds) throws ThingsboardException, ExecutionException, InterruptedException {
checkArrayParameter("deviceIds", strDeviceIds);
SecurityUser user = getCurrentUser();

80
application/src/main/java/org/thingsboard/server/controller/EdgeController.java

@ -17,24 +17,21 @@ package org.thingsboard.server.controller;
import com.google.common.util.concurrent.ListenableFuture;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
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.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
@ -113,8 +110,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Is edges support enabled (isEdgesSupportEnabled)",
notes = "Returns 'true' if edges support enabled on server, 'false' - otherwise.")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edges/enabled", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/edges/enabled")
public boolean isEdgesSupportEnabled() {
return edgesEnabled;
}
@ -122,8 +118,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Edge (getEdgeById)",
notes = "Get the Edge object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/edge/{edgeId}")
public Edge getEdgeById(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
@ -134,8 +129,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Edge Info (getEdgeInfoById)",
notes = "Get the Edge Info object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/info/{edgeId}", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/edge/info/{edgeId}")
public EdgeInfo getEdgeInfoById(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
@ -152,8 +146,7 @@ public class EdgeController extends BaseController {
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Edge entity. " +
TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge", method = RequestMethod.POST)
@ResponseBody
@PostMapping(value = "/edge")
public Edge saveEdge(@Parameter(description = "A JSON value representing the edge.", required = true)
@RequestBody Edge edge) throws Exception {
TenantId tenantId = getTenantId();
@ -178,8 +171,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Delete edge (deleteEdge)",
notes = "Deletes the edge. Referencing non-existing edge Id will cause an error." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
@DeleteMapping(value = "/edge/{edgeId}")
public void deleteEdge(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
@ -192,8 +184,7 @@ public class EdgeController extends BaseController {
notes = "Returns a page of edges owned by tenant. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/edges", params = {"pageSize", "page"})
public PageData<Edge> getEdges(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true)
@ -212,8 +203,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Assign edge to customer (assignEdgeToCustomer)",
notes = "Creates assignment of the edge to customer. Customer will be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/edge/{edgeId}", method = RequestMethod.POST)
@ResponseBody
@PostMapping(value = "/customer/{customerId}/edge/{edgeId}")
public Edge assignEdgeToCustomer(@Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("customerId") String strCustomerId,
@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
@ -230,8 +220,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Unassign edge from customer (unassignEdgeFromCustomer)",
notes = "Clears assignment of the edge to customer. Customer will not be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/edge/{edgeId}", method = RequestMethod.DELETE)
@ResponseBody
@DeleteMapping(value = "/customer/edge/{edgeId}")
public Edge unassignEdgeFromCustomer(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
@ -250,8 +239,7 @@ public class EdgeController extends BaseController {
"This is useful to create dashboards that you plan to share/embed on a publicly available website. " +
"However, users that are logged-in and belong to different tenant will not be able to access the edge." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/edge/{edgeId}", method = RequestMethod.POST)
@ResponseBody
@PostMapping(value = "/customer/public/edge/{edgeId}")
public Edge assignEdgeToPublicCustomer(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
@ -264,8 +252,7 @@ public class EdgeController extends BaseController {
notes = "Returns a page of edges owned by tenant. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/tenant/edges", params = {"pageSize", "page"})
public PageData<Edge> getTenantEdges(
@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ -292,8 +279,7 @@ public class EdgeController extends BaseController {
notes = "Returns a page of edges info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/tenant/edgeInfos", params = {"pageSize", "page"})
public PageData<EdgeInfo> getTenantEdgeInfos(
@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ -320,8 +306,7 @@ public class EdgeController extends BaseController {
notes = "Requested edge must be owned by tenant or customer that the user belongs to. " +
"Edge name is an unique property of edge. So it can be used to identify the edge." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/edges", params = {"edgeName"}, method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/tenant/edges", params = {"edgeName"})
public Edge getTenantEdge(@Parameter(description = "Unique name of the edge", required = true)
@RequestParam String edgeName) throws ThingsboardException {
TenantId tenantId = getCurrentUser().getTenantId();
@ -332,8 +317,7 @@ public class EdgeController extends BaseController {
notes = "Change root rule chain of the edge to the new provided rule chain. \n" +
"This operation will send a notification to update root rule chain on remote edge service." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/{ruleChainId}/root", method = RequestMethod.POST)
@ResponseBody
@PostMapping(value = "/edge/{edgeId}/{ruleChainId}/root")
public Edge setEdgeRootRuleChain(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId,
@Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION, required = true)
@ -352,8 +336,7 @@ public class EdgeController extends BaseController {
notes = "Returns a page of edges objects assigned to customer. " +
PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/customer/{customerId}/edges", params = {"pageSize", "page"})
public PageData<Edge> getCustomerEdges(
@Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION)
@PathVariable("customerId") String strCustomerId,
@ -388,8 +371,7 @@ public class EdgeController extends BaseController {
notes = "Returns a page of edges info objects assigned to customer. " +
PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/customer/{customerId}/edgeInfos", params = {"pageSize", "page"})
public PageData<EdgeInfo> getCustomerEdgeInfos(
@Parameter(description = CUSTOMER_ID_PARAM_DESCRIPTION)
@PathVariable("customerId") String strCustomerId,
@ -423,10 +405,9 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Edges By Ids (getEdgesByIds)",
notes = "Requested edges must be owned by tenant or assigned to customer which user is performing the request." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edges", params = {"edgeIds"}, method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/edges", params = {"edgeIds"})
public List<Edge> getEdgesByIds(
@Parameter(description = "A list of edges ids, separated by comma ','", required = true)
@Parameter(description = "A list of edges ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string")), required = true)
@RequestParam("edgeIds") String[] strEdgeIds) throws ThingsboardException, ExecutionException, InterruptedException {
checkArrayParameter("edgeIds", strEdgeIds);
SecurityUser user = getCurrentUser();
@ -451,8 +432,7 @@ public class EdgeController extends BaseController {
"The entity id, relation type, edge types, depth of the search, and other query parameters defined using complex 'EdgeSearchQuery' object. " +
"See 'Model' tab of the Parameters for more info." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edges", method = RequestMethod.POST)
@ResponseBody
@PostMapping(value = "/edges")
public List<Edge> findByQuery(@RequestBody EdgeSearchQuery query) throws ThingsboardException, ExecutionException, InterruptedException {
checkNotNull(query);
checkNotNull(query.getParameters());
@ -476,8 +456,7 @@ public class EdgeController extends BaseController {
notes = "Returns a set of unique edge types based on edges that are either owned by the tenant or assigned to the customer which user is performing the request."
+ TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/types", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/edge/types")
public List<EntitySubtype> getEdgeTypes() throws ThingsboardException, ExecutionException, InterruptedException {
SecurityUser user = getCurrentUser();
TenantId tenantId = user.getTenantId();
@ -489,7 +468,7 @@ public class EdgeController extends BaseController {
notes = "Starts synchronization process between edge and cloud. \n" +
"All entities that are assigned to particular edge are going to be send to remote edge service." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/sync/{edgeId}", method = RequestMethod.POST)
@PostMapping(value = "/edge/sync/{edgeId}")
public DeferredResult<ResponseEntity> syncEdge(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("edgeId") String strEdgeId) throws ThingsboardException {
checkParameter("edgeId", strEdgeId);
@ -518,8 +497,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Find missing rule chains (findMissingToRelatedRuleChains)",
notes = "Returns list of rule chains ids that are not assigned to particular edge, but these rule chains are present in the already assigned rule chains to edge." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/missingToRelatedRuleChains/{edgeId}", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/edge/missingToRelatedRuleChains/{edgeId}")
public String findMissingToRelatedRuleChains(@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("edgeId") String strEdgeId) throws ThingsboardException {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
@ -546,8 +524,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Edge Install Instructions (getEdgeInstallInstructions)",
notes = "Get an install instructions for provided edge id." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/instructions/install/{edgeId}/{method}", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/edge/instructions/install/{edgeId}/{method}")
public EdgeInstructions getEdgeInstallInstructions(
@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("edgeId") String strEdgeId,
@ -567,8 +544,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Edge Upgrade Instructions (getEdgeUpgradeInstructions)",
notes = "Get an upgrade instructions for provided edge version." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/instructions/upgrade/{edgeVersion}/{method}", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/edge/instructions/upgrade/{edgeVersion}/{method}")
public EdgeInstructions getEdgeUpgradeInstructions(
@Parameter(description = "Edge version", required = true)
@PathVariable("edgeVersion") String edgeVersion,
@ -584,8 +560,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Is edge upgrade enabled (isEdgeUpgradeAvailable)",
notes = "Returns 'true' if upgrade available for connected edge, 'false' - otherwise.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/upgrade/available", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/edge/{edgeId}/upgrade/available")
public boolean isEdgeUpgradeAvailable(
@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("edgeId") String strEdgeId) throws Exception {
@ -598,4 +573,5 @@ public class EdgeController extends BaseController {
throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL);
}
}
}

10
application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java

@ -16,18 +16,14 @@
package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -62,8 +58,7 @@ public class EdgeEventController extends BaseController {
notes = "Returns a page of edge events for the requested edge. " +
PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/events", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/edge/{edgeId}/events")
public PageData<EdgeEvent> getEdgeEvents(
@Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId,
@ -88,4 +83,5 @@ public class EdgeEventController extends BaseController {
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(edgeEventService.findEdgeEvents(tenantId, edgeId, 0L, null, pageLink));
}
}

4
application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java

@ -16,6 +16,8 @@
package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -156,7 +158,7 @@ public class NotificationTargetController extends BaseController {
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping(value = "/targets", params = {"ids"})
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public List<NotificationTarget> getNotificationTargetsByIds(@Parameter(description = "Comma-separated list of uuids representing targets ids", required = true)
public List<NotificationTarget> getNotificationTargetsByIds(@Parameter(description = "Comma-separated list of uuids representing targets ids", array = @ArraySchema(schema = @Schema(type = "string")), required = true)
@RequestParam("ids") UUID[] ids,
@AuthenticationPrincipal SecurityUser user) {
// PE: generic permission

4
application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java

@ -16,6 +16,8 @@
package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
@ -147,7 +149,7 @@ public class NotificationTemplateController extends BaseController {
@RequestParam(required = false) String sortProperty,
@Parameter(description = SORT_ORDER_DESCRIPTION)
@RequestParam(required = false) String sortOrder,
@Parameter(description = "Comma-separated list of notification types to filter the templates")
@Parameter(description = "Comma-separated list of notification types to filter the templates", array = @ArraySchema(schema = @Schema(type = "string")))
@RequestParam(required = false) NotificationType[] notificationTypes,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
// PE: generic permission

3
application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java

@ -16,6 +16,7 @@
package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
@ -89,7 +90,7 @@ public class QueueStatsController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/queueStats", params = {"queueStatsIds"})
public List<QueueStats> getQueueStatsByIds(
@Parameter(description = "A list of queue stats ids, separated by comma ','", required = true)
@Parameter(description = "A list of queue stats ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string")), required = true)
@RequestParam("queueStatsIds") String[] strQueueStatsIds) throws ThingsboardException {
checkArrayParameter("queueStatsIds", strQueueStatsIds);
List<QueueStatsId> queueStatsIds = new ArrayList<>();

3
application/src/main/java/org/thingsboard/server/controller/TbResourceController.java

@ -16,6 +16,7 @@
package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -272,7 +273,7 @@ public class TbResourceController extends BaseController {
@RequestParam String sortOrder,
@Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"id", "name"}, requiredMode = Schema.RequiredMode.REQUIRED))
@RequestParam String sortProperty,
@Parameter(description = "LwM2M Object ids.", required = true)
@Parameter(description = "LwM2M Object ids.", array = @ArraySchema(schema = @Schema(type = "string")), required = true)
@RequestParam(required = false) String[] objectIds) throws ThingsboardException {
return checkNotNull(tbResourceService.findLwM2mObject(getTenantId(), sortOrder, sortProperty, objectIds));
}

2
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -447,7 +447,7 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveEntityTelemetryWithTTL(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")AttributeScope scope,
@Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope,
@Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true)@PathVariable("ttl")Long ttl,
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);

10
application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java

@ -16,6 +16,7 @@
package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -117,8 +118,8 @@ public class TenantProfileController extends BaseController {
"Let's review the example of tenant profile data below: " +
"\n\n" + MARKDOWN_CODE_BLOCK_START +
"{\n" +
" \"name\": \"Default\",\n" +
" \"description\": \"Default tenant profile\",\n" +
" \"name\": \"Your name\",\n" +
" \"description\": \"Your description\",\n" +
" \"isolatedTbRuleEngine\": false,\n" +
" \"profileData\": {\n" +
" \"configuration\": {\n" +
@ -161,7 +162,7 @@ public class TenantProfileController extends BaseController {
" \"warnThreshold\": 0\n" +
" }\n" +
" },\n" +
" \"default\": true\n" +
" \"default\": false\n" +
"}" +
MARKDOWN_CODE_BLOCK_END +
"Remove 'id', from the request body example (below) to create new Tenant Profile entity." +
@ -251,7 +252,8 @@ public class TenantProfileController extends BaseController {
@GetMapping(value = "/tenantProfiles", params = {"ids"})
@PreAuthorize("hasAuthority('SYS_ADMIN')")
public List<TenantProfile> getTenantProfilesByIds(@RequestParam("ids") UUID[] ids) {
public List<TenantProfile> getTenantProfilesByIds(@Parameter(description = "Comma-separated list of tenant profile ids", array = @ArraySchema(schema = @Schema(type = "string")))
@RequestParam("ids") UUID[] ids) {
return tenantProfileService.findTenantProfilesByIds(TenantId.SYS_TENANT_ID, ids);
}

5
application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java

@ -16,6 +16,7 @@
package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -185,7 +186,7 @@ public class WidgetTypeController extends AutoCommitController {
@RequestParam(required = false) Boolean fullSearch,
@Parameter(description = DEPRECATED_FILTER_PARAM_DESCRIPTION, schema = @Schema(allowableValues = {"ALL", "ACTUAL", "DEPRECATED"}))
@RequestParam(required = false) String deprecatedFilter,
@Parameter(description = WIDGET_TYPE_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = {"timeseries", "latest", "control", "alarm", "static"}))
@Parameter(description = WIDGET_TYPE_ARRAY_DESCRIPTION, array = @ArraySchema(schema = @Schema(type = "string", allowableValues = {"timeseries", "latest", "control", "alarm", "static"})))
@RequestParam(required = false) String[] widgetTypeList) throws ThingsboardException {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
List<String> widgetTypes = widgetTypeList != null ? Arrays.asList(widgetTypeList) : Collections.emptyList();
@ -331,7 +332,7 @@ public class WidgetTypeController extends AutoCommitController {
@RequestParam(required = false) Boolean fullSearch,
@Parameter(description = DEPRECATED_FILTER_PARAM_DESCRIPTION, schema = @Schema(allowableValues = {"ALL", "ACTUAL", "DEPRECATED"}))
@RequestParam(required = false) String deprecatedFilter,
@Parameter(description = WIDGET_TYPE_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = {"timeseries", "latest", "control", "alarm", "static"}))
@Parameter(description = WIDGET_TYPE_ARRAY_DESCRIPTION, array = @ArraySchema(schema = @Schema(allowableValues = {"timeseries", "latest", "control", "alarm", "static"})))
@RequestParam(required = false) String[] widgetTypeList) throws ThingsboardException {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);

1
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -137,7 +137,6 @@ public class ThingsboardInstallService {
entityDatabaseSchemaService.createCustomerTitleUniqueConstraintIfNotExists();
systemDataLoaderService.updateDefaultNotificationConfigs(false);
systemDataLoaderService.updateSecuritySettings();
break;
case "3.7.0":
log.info("Upgrading ThingsBoard from version 3.7.0 to 3.7.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.7.0");

3
application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java

@ -204,6 +204,9 @@ public class EdgeEventSourcingListener {
return false;
}
}
if (entity instanceof OAuth2Info oAuth2Info) {
return oAuth2Info.isEdgeEnabled();
}
// Default: If the entity doesn't match any of the conditions, consider it as valid.
return true;
}

65
application/src/main/java/org/thingsboard/server/service/edge/instructions/BaseEdgeInstallUpgradeInstructionsService.java

@ -0,0 +1,65 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.instructions;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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;
@Slf4j
@RequiredArgsConstructor
public abstract class BaseEdgeInstallUpgradeInstructionsService {
private static final String EDGE_DIR = "edge";
private static final String INSTRUCTIONS_DIR = "instructions";
private final InstallScripts installScripts;
@Value("${app.version:unknown}")
@Setter
protected String appVersion;
protected String readFile(Path file) {
try {
return Files.readString(file);
} catch (IOException e) {
log.warn("Failed to read file: {}", file, e);
throw new RuntimeException(e);
}
}
protected String getTagVersion(String version) {
return version.endsWith(".0") ? version.substring(0, version.length() - 2) : version;
}
protected Path resolveFile(String subDir, String... subDirs) {
return getEdgeInstructionsDir().resolve(Paths.get(subDir, subDirs));
}
protected Path getEdgeInstructionsDir() {
return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, INSTRUCTIONS_DIR, getBaseDirName());
}
protected abstract String getBaseDirName();
}

71
application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallInstructionsService.java

@ -15,8 +15,7 @@
*/
package org.thingsboard.server.service.edge.instructions;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -27,47 +26,32 @@ import org.thingsboard.server.dao.util.DeviceConnectivityUtil;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.install.InstallScripts;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Service
@Slf4j
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true")
@TbCoreComponent
public class DefaultEdgeInstallInstructionsService implements EdgeInstallInstructionsService {
public class DefaultEdgeInstallInstructionsService extends BaseEdgeInstallUpgradeInstructionsService implements EdgeInstallInstructionsService {
private static final String EDGE_DIR = "edge";
private static final String INSTRUCTIONS_DIR = "instructions";
private static final String INSTALL_DIR = "install";
private final InstallScripts installScripts;
@Value("${edges.rpc.port}")
private int rpcPort;
@Value("${edges.rpc.ssl.enabled}")
private boolean sslEnabled;
@Value("${app.version:unknown}")
@Setter
private String appVersion;
public DefaultEdgeInstallInstructionsService(InstallScripts installScripts) {
super(installScripts);
}
@Override
public EdgeInstructions getInstallInstructions(Edge edge, String installationMethod, HttpServletRequest request) {
switch (installationMethod.toLowerCase()) {
case "docker":
return getDockerInstallInstructions(edge, request);
case "ubuntu":
return getUbuntuInstallInstructions(edge, request);
case "centos":
return getCentosInstallInstructions(edge, request);
default:
throw new IllegalArgumentException("Unsupported installation method for Edge: " + installationMethod);
}
return switch (installationMethod.toLowerCase()) {
case "docker" -> getDockerInstallInstructions(edge, request);
case "ubuntu", "centos" -> getLinuxInstallInstructions(edge, request, installationMethod.toLowerCase());
default ->
throw new IllegalArgumentException("Unsupported installation method for Edge: " + installationMethod);
};
}
private EdgeInstructions getDockerInstallInstructions(Edge edge, HttpServletRequest request) {
@ -88,25 +72,16 @@ public class DefaultEdgeInstallInstructionsService implements EdgeInstallInstruc
return new EdgeInstructions(dockerInstallInstructions);
}
private EdgeInstructions getUbuntuInstallInstructions(Edge edge, HttpServletRequest request) {
String ubuntuInstallInstructions = readFile(resolveFile("ubuntu", "instructions.md"));
private EdgeInstructions getLinuxInstallInstructions(Edge edge, HttpServletRequest request, String os) {
String ubuntuInstallInstructions = readFile(resolveFile(os, "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);
ubuntuInstallInstructions = ubuntuInstallInstructions.replace("${TB_EDGE_TAG}", getTagVersion(edgeVersion));
return new EdgeInstructions(ubuntuInstallInstructions);
}
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 EdgeInstructions(centosInstallInstructions);
}
private String replacePlaceholders(String instructions, Edge edge) {
instructions = instructions.replace("${CLOUD_ROUTING_KEY}", edge.getRoutingKey());
instructions = instructions.replace("${CLOUD_ROUTING_SECRET}", edge.getSecret());
@ -115,20 +90,8 @@ public class DefaultEdgeInstallInstructionsService implements EdgeInstallInstruc
return instructions;
}
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, INSTALL_DIR);
@Override
protected String getBaseDirName() {
return INSTALL_DIR;
}
}

56
application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java

@ -15,10 +15,7 @@
*/
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.AttributeScope;
@ -32,47 +29,37 @@ import org.thingsboard.server.dao.attributes.AttributesService;
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;
import java.util.Optional;
@Service
@Slf4j
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true")
@TbCoreComponent
public class DefaultEdgeUpgradeInstructionsService implements EdgeUpgradeInstructionsService {
public class DefaultEdgeUpgradeInstructionsService extends BaseEdgeInstallUpgradeInstructionsService implements EdgeUpgradeInstructionsService {
private static final Map<String, EdgeUpgradeInfo> 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;
private final AttributesService attributesService;
@Value("${app.version:unknown}")
@Setter
private String appVersion;
public DefaultEdgeUpgradeInstructionsService(AttributesService attributesService, InstallScripts installScripts) {
super(installScripts);
this.attributesService = attributesService;
}
@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);
}
return switch (upgradeMethod.toLowerCase()) {
case "docker" -> getDockerUpgradeInstructions(tbVersion, currentEdgeVersion);
case "ubuntu", "centos" ->
getLinuxUpgradeInstructions(tbVersion, currentEdgeVersion, upgradeMethod.toLowerCase());
default -> throw new IllegalArgumentException("Unsupported upgrade method for Edge: " + upgradeMethod);
};
}
@Override
@ -167,28 +154,13 @@ public class DefaultEdgeUpgradeInstructionsService implements EdgeUpgradeInstruc
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));
@Override
protected String getBaseDirName() {
return UPGRADE_DIR;
}
private Path getEdgeInstallInstructionsDir() {
return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, INSTRUCTIONS_DIR, UPGRADE_DIR);
}
}

4
application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeInstallInstructionsService.java

@ -15,14 +15,14 @@
*/
package org.thingsboard.server.service.edge.instructions;
import jakarta.servlet.http.HttpServletRequest;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeInstructions;
import jakarta.servlet.http.HttpServletRequest;
public interface EdgeInstallInstructionsService {
EdgeInstructions getInstallInstructions(Edge edge, String installationMethod, HttpServletRequest request);
void setAppVersion(String version);
}

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java

@ -21,6 +21,9 @@ import com.google.common.util.concurrent.Futures;
import io.grpc.Server;
import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
import io.grpc.stub.StreamObserver;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -55,9 +58,6 @@ import org.thingsboard.server.service.edge.EdgeContextComponent;
import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/telemetry/EntityDataMsgConstructor.java

@ -23,12 +23,12 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg;
import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -106,4 +106,5 @@ public class EntityDataMsgConstructor {
}
return result;
}
}

17
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV2.java

@ -19,7 +19,6 @@ import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg;
@ -37,22 +36,6 @@ public class TenantMsgConstructorV2 implements TenantMsgConstructor {
@Override
public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) {
tenantProfile = JacksonUtil.clone(tenantProfile);
// clear all config
var configuration = tenantProfile.getDefaultProfileConfiguration();
configuration.setRpcTtlDays(0);
configuration.setMaxJSExecutions(0);
configuration.setMaxREExecutions(0);
configuration.setMaxDPStorageDays(0);
configuration.setMaxTbelExecutions(0);
configuration.setQueueStatsTtlDays(0);
configuration.setMaxTransportMessages(0);
configuration.setDefaultStorageTtlDays(0);
configuration.setMaxTransportDataPoints(0);
configuration.setRuleEngineExceptionsTtlDays(0);
configuration.setMaxRuleNodeExecutionsPerMessage(0);
tenantProfile.getProfileData().setConfiguration(configuration);
return TenantProfileUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(tenantProfile)).build();
}

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java

@ -45,8 +45,11 @@ public class OAuth2EdgeEventFetcher implements EdgeEventFetcher {
@Override
public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) {
List<EdgeEvent> result = new ArrayList<>();
OAuth2Info oAuth2Info = oAuth2Service.findOAuth2Info();
if (!oAuth2Info.isEdgeEnabled()) {
return new PageData<>();
}
List<EdgeEvent> result = new ArrayList<>();
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.OAUTH2,
EdgeEventActionType.ADDED, null, JacksonUtil.valueToTree(oAuth2Info)));
// returns PageData object to be in sync with other fetchers

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProcessor.java

@ -21,7 +21,6 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
@ -37,4 +36,5 @@ public interface DeviceProcessor extends EdgeProcessor {
DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion);
ListenableFuture<Void> processDeviceRpcCallFromEdge(TenantId tenantId, Edge edge, DeviceRpcCallMsg deviceRpcCallMsg);
}

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java

@ -40,7 +40,7 @@ public class OAuth2EdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertOAuth2EventToDownlink(EdgeEvent edgeEvent) {
DownlinkMsg downlinkMsg = null;
OAuth2Info oAuth2Info = JacksonUtil.convertValue(edgeEvent.getBody(), OAuth2Info.class);
if (oAuth2Info != null) {
if (oAuth2Info != null && oAuth2Info.isEdgeEnabled()) {
OAuth2UpdateMsg oAuth2UpdateMsg = oAuth2MsgConstructor.constructOAuth2UpdateMsg(oAuth2Info);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())

1
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -192,4 +192,5 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
}
return isOldSchema;
}
}

1
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -350,6 +350,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
NotificationUpdate update = NotificationUpdate.builder()
.updated(true)
.notificationId(notificationId.getId())
.notificationType(notification.getType())
.newStatus(NotificationStatus.READ)
.build();
onNotificationUpdate(tenantId, recipientId, update);

2
application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java

@ -33,7 +33,7 @@ public class EmailNotificationChannel implements NotificationChannel<User, Email
@Override
public void sendNotification(User recipient, EmailDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) throws Exception {
mailService.send(recipient.getTenantId(), null, TbEmail.builder()
mailService.send(ctx.getTenantId(), null, TbEmail.builder()
.to(recipient.getEmail())
.subject(processedTemplate.getSubject())
.body(processedTemplate.getBody())

2
application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java

@ -38,7 +38,7 @@ public class SmsNotificationChannel implements NotificationChannel<User, SmsDeli
throw new RuntimeException("User does not have phone number");
}
smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, processedTemplate.getBody());
smsService.sendSms(ctx.getTenantId(), null, new String[]{phone}, processedTemplate.getBody());
}
@Override

11
application/src/main/java/org/thingsboard/server/service/notification/rule/cache/DefaultNotificationRulesCache.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.notification.rule.cache;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@ -29,7 +30,6 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.dao.notification.NotificationRuleService;
import jakarta.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -71,7 +71,14 @@ public class DefaultNotificationRulesCache implements NotificationRulesCache {
if (event.getEvent() == ComponentLifecycleEvent.DELETED) {
lock.writeLock().lock(); // locking in case rules for tenant are fetched while evicting
try {
evict(event.getTenantId());
for (var triggerType : NotificationRuleTriggerType.values()) {
String key = key(event.getTenantId(), triggerType);
/*
* temporarily putting empty value because right after tenant deletion
* the rules are still in the db, we don't want them to be fetched
* */
cache.put(key, Collections.emptyList());
}
} finally {
lock.writeLock().unlock();
}

59
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -24,12 +24,14 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasRuleEngineProfile;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
@ -165,7 +167,7 @@ public class DefaultTbClusterService implements TbClusterService {
@Override
public void pushMsgToVersionControl(TenantId tenantId, TransportProtos.ToVersionControlServiceMsg msg, TbQueueCallback callback) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_VC_EXECUTOR, tenantId, tenantId);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_VC_EXECUTOR, TenantId.SYS_TENANT_ID, tenantId);
log.trace("PUSHING msg: {} to:{}", msg, tpi);
producerProvider.getTbVersionControlMsgProducer().send(tpi, new TbProtoQueueMsg<>(tenantId.getId(), msg), callback);
//TODO: ashvayka
@ -340,29 +342,33 @@ public class DefaultTbClusterService implements TbClusterService {
@Override
public void onResourceChange(TbResourceInfo resource, TbQueueCallback callback) {
TenantId tenantId = resource.getTenantId();
log.trace("[{}][{}][{}] Processing change resource", tenantId, resource.getResourceType(), resource.getResourceKey());
TransportProtos.ResourceUpdateMsg resourceUpdateMsg = TransportProtos.ResourceUpdateMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setResourceType(resource.getResourceType().name())
.setResourceKey(resource.getResourceKey())
.build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setResourceUpdateMsg(resourceUpdateMsg).build();
broadcast(transportMsg, callback);
if (resource.getResourceType() == ResourceType.LWM2M_MODEL) {
TenantId tenantId = resource.getTenantId();
log.trace("[{}][{}][{}] Processing change resource", tenantId, resource.getResourceType(), resource.getResourceKey());
TransportProtos.ResourceUpdateMsg resourceUpdateMsg = TransportProtos.ResourceUpdateMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setResourceType(resource.getResourceType().name())
.setResourceKey(resource.getResourceKey())
.build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setResourceUpdateMsg(resourceUpdateMsg).build();
broadcast(transportMsg, DataConstants.LWM2M_TRANSPORT_NAME, callback);
}
}
@Override
public void onResourceDeleted(TbResourceInfo resource, TbQueueCallback callback) {
log.trace("[{}] Processing delete resource", resource);
TransportProtos.ResourceDeleteMsg resourceUpdateMsg = TransportProtos.ResourceDeleteMsg.newBuilder()
.setTenantIdMSB(resource.getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(resource.getTenantId().getId().getLeastSignificantBits())
.setResourceType(resource.getResourceType().name())
.setResourceKey(resource.getResourceKey())
.build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setResourceDeleteMsg(resourceUpdateMsg).build();
broadcast(transportMsg, callback);
if (resource.getResourceType() == ResourceType.LWM2M_MODEL) {
log.trace("[{}][{}][{}] Processing delete resource", resource.getTenantId(), resource.getResourceType(), resource.getResourceKey());
TransportProtos.ResourceDeleteMsg resourceDeleteMsg = TransportProtos.ResourceDeleteMsg.newBuilder()
.setTenantIdMSB(resource.getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(resource.getTenantId().getId().getLeastSignificantBits())
.setResourceType(resource.getResourceType().name())
.setResourceKey(resource.getResourceKey())
.build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setResourceDeleteMsg(resourceDeleteMsg).build();
broadcast(transportMsg, DataConstants.LWM2M_TRANSPORT_NAME, callback);
}
}
private <T> void broadcastEntityChangeToTransport(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) {
@ -384,8 +390,19 @@ public class DefaultTbClusterService implements TbClusterService {
}
private void broadcast(ToTransportMsg transportMsg, TbQueueCallback callback) {
TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> toTransportNfProducer = producerProvider.getTransportNotificationsMsgProducer();
Set<String> tbTransportServices = partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT);
broadcast(transportMsg, tbTransportServices, callback);
}
private void broadcast(ToTransportMsg transportMsg, String transportType, TbQueueCallback callback) {
Set<String> tbTransportServices = partitionService.getAllServices(ServiceType.TB_TRANSPORT).stream()
.filter(info -> info.getTransportsList().contains(transportType))
.map(TransportProtos.ServiceInfo::getServiceId).collect(Collectors.toSet());
broadcast(transportMsg, tbTransportServices, callback);
}
private void broadcast(ToTransportMsg transportMsg, Set<String> tbTransportServices, TbQueueCallback callback) {
TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> toTransportNfProducer = producerProvider.getTransportNotificationsMsgProducer();
TbQueueCallback proxyCallback = callback != null ? new MultipleTbQueueCallbackWrapper(tbTransportServices.size(), callback) : null;
for (String transportServiceId : tbTransportServices) {
TopicPartitionInfo tpi = topicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportServiceId);

29
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.service.subscription;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
@ -24,9 +26,9 @@ import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.Aggregation;
@ -51,8 +53,6 @@ import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscript
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@ -143,7 +143,28 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
* Even if we cache locally the list of active subscriptions by entity id, it is still time-consuming operation to get them from cache
* Since number of subscriptions is usually much less than number of devices that are pushing data.
*/
subscriptionsByEntityId.values().forEach(sub -> pushSubEventToManagerService(sub.getTenantId(), sub.getEntityId(), sub.toEvent(ComponentLifecycleEvent.UPDATED)));
Set<UUID> staleSubs = new HashSet<>();
subscriptionsByEntityId.forEach((id, sub) -> {
try {
pushSubEventToManagerService(sub.getTenantId(), sub.getEntityId(), sub.toEvent(ComponentLifecycleEvent.UPDATED));
} catch (TenantNotFoundException e) {
staleSubs.add(id);
log.warn("Cleaning up stale subscription {} for tenant {} due to TenantNotFoundException", id, sub.getTenantId());
} catch (Exception e) {
log.error("Failed to push subscription {} to manager service", sub, e);
}
});
if (!staleSubs.isEmpty()) {
subsLock.lock();
try {
staleSubs.forEach(entityId -> {
subscriptionsByEntityId.remove(entityId);
entityUpdates.remove(entityId);
});
} finally {
subsLock.unlock();
}
}
}
}

2
application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java

@ -21,7 +21,6 @@ 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
@ -52,4 +51,5 @@ public abstract class TbSubscription<T> {
public int hashCode() {
return Objects.hash(sessionId, subscriptionId, tenantId, entityId, type);
}
}

12
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java

@ -164,13 +164,13 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
var cacheEntry = taskCache.get(requestId);
if (cacheEntry == null || cacheEntry.get() == null) {
log.debug("[{}] No cache record: {}", requestId, cacheEntry);
throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
throw new ThingsboardException("Task execution timed-out", ThingsboardErrorCode.ITEM_NOT_FOUND);
} else {
var entry = cacheEntry.get();
log.trace("[{}] Cache get: {}", requestId, entry);
var result = getter.apply(entry);
if (result == null) {
throw new ThingsboardException(ThingsboardErrorCode.BAD_REQUEST_PARAMS);
throw new ThingsboardException("Invalid task", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} else {
return result;
}
@ -526,12 +526,8 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
}
@Override
public ListenableFuture<Void> deleteVersionControlSettings(TenantId tenantId) throws Exception {
if (repositorySettingsService.delete(tenantId)) {
return gitServiceQueue.clearRepository(tenantId);
} else {
return Futures.immediateFuture(null);
}
public ListenableFuture<Void> deleteVersionControlSettings(TenantId tenantId) {
return gitServiceQueue.clearRepository(tenantId);
}
@Override

10
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java

@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@ -311,7 +312,13 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
}
return submitFuture;
} else {
throw new RuntimeException("Future is already done!");
try {
request.getFuture().get();
throw new RuntimeException("Failed to process the request");
} catch (Exception e) {
Throwable cause = ExceptionUtils.getRootCause(e);
throw new RuntimeException(cause.getMessage(), cause);
}
}
}
@ -562,5 +569,6 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
private CommitRequestMsg.Builder buildCommitRequest(CommitGitRequest commit) {
return CommitRequestMsg.newBuilder().setTxId(commit.getTxId().toString());
}
}

2
application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java

@ -65,7 +65,7 @@ public interface EntitiesVersionControlService {
ListenableFuture<RepositorySettings> saveVersionControlSettings(TenantId tenantId, RepositorySettings versionControlSettings);
ListenableFuture<Void> deleteVersionControlSettings(TenantId tenantId) throws Exception;
ListenableFuture<Void> deleteVersionControlSettings(TenantId tenantId);
ListenableFuture<Void> checkVersionControlAccess(TenantId tenantId, RepositorySettings settings) throws Exception;

11
application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
@ -78,6 +79,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
.entityId(securityCtx.getId())
.updateProcessor(this::handleNotificationsSubscriptionUpdate)
.limit(cmd.getLimit())
.notificationTypes(cmd.getTypes())
.build();
localSubscriptionService.addSubscription(subscription);
@ -105,8 +107,8 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void fetchUnreadNotifications(NotificationsSubscription subscription) {
log.trace("[{}, subId: {}] Fetching unread notifications from DB", subscription.getSessionId(), subscription.getSubscriptionId());
PageData<Notification> notifications = notificationService.findLatestUnreadNotificationsByRecipientId(subscription.getTenantId(),
WEB, (UserId) subscription.getEntityId(), subscription.getLimit());
PageData<Notification> notifications = notificationService.findLatestUnreadNotificationsByRecipientIdAndNotificationTypes(subscription.getTenantId(),
WEB, (UserId) subscription.getEntityId(), subscription.getNotificationTypes(), subscription.getLimit());
subscription.getLatestUnreadNotifications().clear();
notifications.getData().forEach(notification -> {
subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification);
@ -139,6 +141,11 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
Notification notification = update.getNotification();
UUID notificationId = notification != null ? notification.getUuidId() : update.getNotificationId();
NotificationType notificationType = notification != null ? notification.getType() : update.getNotificationType();
if (notificationType != null && !subscription.checkNotificationType(notificationType)) {
return;
}
if (update.isCreated()) {
subscription.getLatestUnreadNotifications().put(notificationId, notification);
subscription.getTotalUnreadCounter().incrementAndGet();

4
application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java

@ -18,15 +18,19 @@ package org.thingsboard.server.service.ws.notification.cmd;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.service.ws.WsCmd;
import org.thingsboard.server.service.ws.WsCmdType;
import java.util.Set;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class NotificationsSubCmd implements WsCmd {
private int cmdId;
private int limit;
private Set<NotificationType> types;
@Override
public WsCmdType getType() {

6
application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java

@ -24,13 +24,13 @@ import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType;
import java.util.Collection;
import java.util.List;
@Getter
@ToString(exclude = "notifications")
public class UnreadNotificationsUpdate extends CmdUpdate {
private final Collection<Notification> notifications;
private final List<Notification> notifications;
private final Notification update;
private final int totalUnreadCount;
private final int sequenceNumber;
@ -39,7 +39,7 @@ public class UnreadNotificationsUpdate extends CmdUpdate {
@JsonCreator
public UnreadNotificationsUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode,
@JsonProperty("errorMsg") String errorMsg,
@JsonProperty("notifications") Collection<Notification> notifications,
@JsonProperty("notifications") List<Notification> notifications,
@JsonProperty("update") Notification update,
@JsonProperty("totalUnreadCount") int totalUnreadCount,
@JsonProperty("sequenceNumber") int sequenceNumber) {

2
application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java

@ -21,6 +21,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.notification.NotificationType;
import java.util.UUID;
@ -31,6 +32,7 @@ import java.util.UUID;
public class NotificationUpdate {
private UUID notificationId;
private NotificationType notificationType;
private boolean created;
private Notification notification;

1
application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java

@ -23,7 +23,6 @@ import org.thingsboard.server.service.subscription.TbSubscription;
import org.thingsboard.server.service.subscription.TbSubscriptionType;
import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCountUpdate;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
@Getter

12
application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java

@ -17,10 +17,12 @@ package org.thingsboard.server.service.ws.notification.sub;
import lombok.Builder;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.service.subscription.TbSubscription;
import org.thingsboard.server.service.subscription.TbSubscriptionType;
import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate;
@ -29,8 +31,8 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
@ -39,13 +41,19 @@ public class NotificationsSubscription extends AbstractNotificationSubscription<
private final Map<UUID, Notification> latestUnreadNotifications = new HashMap<>();
private final int limit;
private final Set<NotificationType> notificationTypes;
@Builder
public NotificationsSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId,
BiConsumer<TbSubscription<NotificationsSubscriptionUpdate>, NotificationsSubscriptionUpdate> updateProcessor,
int limit) {
int limit, Set<NotificationType> notificationTypes) {
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.NOTIFICATIONS, updateProcessor);
this.limit = limit;
this.notificationTypes = notificationTypes;
}
public boolean checkNotificationType(NotificationType type) {
return CollectionUtils.isEmpty(notificationTypes) || notificationTypes.contains(type);
}
public UnreadNotificationsUpdate createFullUpdate() {

2
application/src/main/resources/thingsboard.yml

@ -1392,6 +1392,8 @@ swagger:
url: "${SWAGGER_LICENSE_URL:https://github.com/thingsboard/thingsboard/blob/master/LICENSE}"
# The version of the API doc to display. Default to the package version.
version: "${SWAGGER_VERSION:}"
# The group name (definition) on the API doc UI page.
group_name: "${SWAGGER_GROUP_NAME:thingsboard}"
# Queue configuration parameters
queue:

2
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -803,7 +803,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
return readResponse(doPost(urlTemplate, content, params).andExpect(resultMatcher), responseType);
}
protected <T> T doPostAsync(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception {
protected <T, R> R doPostAsync(String urlTemplate, T content, Class<R> responseClass, ResultMatcher resultMatcher, String... params) throws Exception {
return readResponse(doPostAsync(urlTemplate, content, DEFAULT_TIMEOUT, params).andExpect(resultMatcher), responseClass);
}

31
application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java

@ -27,13 +27,16 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
@ -48,6 +51,7 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -547,6 +551,33 @@ public class DashboardControllerTest extends AbstractControllerTest {
testEntityDaoWithRelationsTransactionalException(dashboardDao, savedTenant.getId(), dashboardId, "/api/dashboard/" + dashboardId);
}
@Test
public void whenDeletingDashboard_ifReferencedByDeviceProfile_thenReturnError() throws Exception {
Dashboard dashboard = createDashboard("test");
DeviceProfile deviceProfile = createDeviceProfile("test");
deviceProfile.setDefaultDashboardId(dashboard.getId());
doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
String response = doDelete("/api/dashboard/" + dashboard.getUuidId()).andExpect(status().isBadRequest())
.andReturn().getResponse().getContentAsString();
String errorMessage = JacksonUtil.toJsonNode(response).get("message").asText();
assertThat(errorMessage).containsIgnoringCase("referenced by a device profile");
}
@Test
public void whenDeletingDashboard_ifReferencedByAssetProfile_thenReturnError() throws Exception {
Dashboard dashboard = createDashboard("test");
AssetProfile assetProfile = createAssetProfile("test");
assetProfile.setDefaultDashboardId(dashboard.getId());
doPost("/api/assetProfile", assetProfile, AssetProfile.class);
String response = doDelete("/api/dashboard/" + dashboard.getUuidId()).andExpect(status().isBadRequest())
.andReturn().getResponse().getContentAsString();
String errorMessage = JacksonUtil.toJsonNode(response).get("message").asText();
assertThat(errorMessage).containsIgnoringCase("referenced by an asset profile");
}
private Dashboard createDashboard(String title) {
Dashboard dashboard = new Dashboard();
dashboard.setTitle(title);

10
application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java

@ -691,7 +691,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
new EntityKey(EntityKeyType.ENTITY_FIELD, "queueName"), EntityDataSortOrder.Direction.ASC
);
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
List<EntityKey> entityFields = Arrays.asList(new EntityKey(EntityKeyType.ENTITY_FIELD, "queueName"),
List<EntityKey> entityFields = Arrays.asList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), new EntityKey(EntityKeyType.ENTITY_FIELD, "queueName"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "serviceId"));
EntityDataQuery query = new EntityDataQuery(entityTypeFilter, pageLink, entityFields, null, null);
@ -705,8 +705,12 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
Assert.assertTrue(data.hasNext());
Assert.assertEquals(10, data.getData().size());
data.getData().forEach(entityData -> {
assertThat(entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("queueName")).asString().isNotBlank();
assertThat(entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("serviceId")).asString().isNotBlank();
String queueName = entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("queueName").getValue();
String serviceId = entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("serviceId").getValue();
assertThat(queueName).isNotBlank();
assertThat(serviceId).isNotBlank();
assertThat(entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue()).isEqualTo(queueName + "_" + serviceId);
});
EntityCountQuery countQuery = new EntityCountQuery(entityTypeFilter);

9
application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java

@ -616,6 +616,14 @@ public class TenantControllerTest extends AbstractControllerTest {
assertThat(usedTpi.getTopic()).isEqualTo(DataConstants.HP_QUEUE_TOPIC);
assertThat(usedTpi.getTenantId()).get().isEqualTo(TenantId.SYS_TENANT_ID);
});
assertThat(partitionService.resolve(ServiceType.TB_RULE_ENGINE, null, tenantId, tenantId)).satisfies(tpi -> {
assertThat(tpi.getTopic()).isEqualTo(MAIN_QUEUE_TOPIC);
assertThat(tpi.getTenantId()).get().isEqualTo(tenantId);
});
assertThat(partitionService.resolve(ServiceType.TB_RULE_ENGINE, "", tenantId, tenantId)).satisfies(tpi -> {
assertThat(tpi.getTopic()).isEqualTo(MAIN_QUEUE_TOPIC);
assertThat(tpi.getTenantId()).get().isEqualTo(tenantId);
});
loginSysAdmin();
tenantProfile.setIsolatedTbRuleEngine(true);
@ -850,4 +858,5 @@ public class TenantControllerTest extends AbstractControllerTest {
testBroadcastEntityStateChangeEventNever(createEntityId_NULL_UUID(new Tenant()));
Mockito.reset(tbClusterService);
}
}

1
application/src/test/java/org/thingsboard/server/edge/NotificationEdgeTest.java

@ -18,7 +18,6 @@ package org.thingsboard.server.edge;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;

7
application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java

@ -59,12 +59,7 @@ public class OAuth2EdgeTest extends AbstractEdgeTest {
oAuth2Info.setEnabled(false);
oAuth2Info.setEdgeEnabled(false);
doPost("/api/oauth2/config", oAuth2Info, OAuth2Info.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof OAuth2UpdateMsg);
oAuth2UpdateMsg = (OAuth2UpdateMsg) latestMessage;
result = JacksonUtil.fromString(oAuth2UpdateMsg.getEntity(), OAuth2Info.class, true);
Assert.assertEquals(oAuth2Info, result);
Assert.assertFalse(edgeImitator.waitForMessages(5));
edgeImitator.ignoreType(OAuth2UpdateMsg.class);
loginTenantAdmin();

8
application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java

@ -143,6 +143,14 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
return submitNotificationRequest(targetId, text, 0, deliveryMethods);
}
protected NotificationRequest submitNotificationRequest(NotificationType type, NotificationTargetId targetId, String text, NotificationDeliveryMethod... deliveryMethods) {
if (deliveryMethods.length == 0) {
deliveryMethods = new NotificationDeliveryMethod[]{NotificationDeliveryMethod.WEB};
}
NotificationTemplate notificationTemplate = createNotificationTemplate(type, DEFAULT_NOTIFICATION_SUBJECT, text, deliveryMethods);
return submitNotificationRequest(List.of(targetId), notificationTemplate.getId(), 0);
}
protected NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) {
return submitNotificationRequest(List.of(targetId), text, delayInSec, deliveryMethods);
}

82
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

@ -210,6 +210,88 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
checkPartialNotificationsUpdate(otherWsClient.getLastDataUpdate(), notificationText, 1);
}
@Test
public void testNotificationUpdates_typesFilter_multipleSubs() {
int generalSub = wsClient.subscribeForUnreadNotificationsAndWait(10, NotificationType.GENERAL);
int alarmSub = wsClient.subscribeForUnreadNotificationsAndWait(10, NotificationType.ALARM, NotificationType.GENERAL);
int entityActionSub = wsClient.subscribeForUnreadNotificationsAndWait(10, NotificationType.ENTITY_ACTION, NotificationType.GENERAL);
NotificationTarget notificationTarget = createNotificationTarget(customerUserId);
String generalNotificationText1 = "General notification 1";
submitNotificationRequest(NotificationType.GENERAL, notificationTarget.getId(), generalNotificationText1);
// expecting all 3 subs to received update
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(wsClient.getLastUpdates()).extractingByKeys(generalSub, alarmSub, entityActionSub)
.allMatch(update -> update.getUpdate().getText().equals(generalNotificationText1)
&& update.getTotalUnreadCount() == 1);
});
Notification generalNotification1 = wsClient.getLastDataUpdate().getUpdate();
String generalNotificationText2 = "General notification 2";
submitNotificationRequest(NotificationType.GENERAL, notificationTarget.getId(), generalNotificationText2);
// expecting all 3 subs to received update
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(wsClient.getLastUpdates()).extractingByKeys(generalSub, alarmSub, entityActionSub)
.allMatch(update -> update.getUpdate().getText().equals(generalNotificationText2)
&& update.getTotalUnreadCount() == 2);
});
Notification generalNotification2 = wsClient.getLastDataUpdate().getUpdate();
// marking as read, expecting all 3 subs to received update
wsClient.markNotificationAsRead(generalNotification1.getUuidId());
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(wsClient.getLastUpdates()).extractingByKeys(generalSub, alarmSub, entityActionSub)
.allMatch(update -> update.getTotalUnreadCount() == 1 && update.getNotifications().size() == 1
&& update.getNotifications().get(0).getText().equals(generalNotificationText2));
});
wsClient.getLastUpdates().clear();
String alarmNotificationText1 = "Alarm notification 1";
submitNotificationRequest(NotificationType.ALARM, notificationTarget.getId(), alarmNotificationText1);
// expecting only 1 sub to received update
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(wsClient.getLastUpdates()).extractingByKey(alarmSub)
.matches(update -> update.getUpdate().getText().equals(alarmNotificationText1)
&& update.getTotalUnreadCount() == 2);
});
Notification alarmNotification1 = wsClient.getLastDataUpdate().getUpdate();
String alarmNotificationText2 = "Alarm notification 2";
submitNotificationRequest(NotificationType.ALARM, notificationTarget.getId(), alarmNotificationText2);
// expecting only 1 sub to received update
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(wsClient.getLastUpdates()).extractingByKey(alarmSub)
.matches(update -> update.getUpdate().getText().equals(alarmNotificationText2)
&& update.getTotalUnreadCount() == 3);
});
await().during(3, TimeUnit.SECONDS)
.untilAsserted(() -> {
assertThat(wsClient.getLastUpdates()).extractingByKeys(generalSub, entityActionSub)
.containsOnlyNulls();
});
// marking as read, expecting only 1 sub to receive update
wsClient.markNotificationAsRead(alarmNotification1.getUuidId());
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(wsClient.getLastUpdates()).extractingByKey(alarmSub)
.matches(update -> update.getTotalUnreadCount() == 2 && update.getNotifications().size() == 2);
});
await().during(3, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(wsClient.getLastUpdates()).extractingByKeys(generalSub, entityActionSub)
.containsOnlyNulls();
});
// marking as read, expecting general and entity action subs with 0 unread, and alarm with 1 unread
wsClient.markNotificationAsRead(generalNotification2.getUuidId());
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(wsClient.getLastUpdates()).extractingByKeys(generalSub, entityActionSub)
.allMatch(update -> update.getTotalUnreadCount() == 0 && update.getNotifications().isEmpty());
assertThat(wsClient.getLastUpdates()).extractingByKey(alarmSub)
.matches(update -> update.getTotalUnreadCount() == 1 && update.getNotifications().size() == 1
&& update.getNotifications().get(0).getText().equals(alarmNotificationText2));
});
}
@Test
public void testMarkingAsRead_multipleSessions() throws Exception {
connectOtherWsClient();

23
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java

@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.controller.TbTestWebSocketClient;
import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd;
import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd;
@ -35,7 +36,10 @@ import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Slf4j
@Getter
@ -48,18 +52,28 @@ public class NotificationApiWsClient extends TbTestWebSocketClient {
private int unreadCount;
private List<Notification> notifications;
private final Map<Integer, UnreadNotificationsUpdate> lastUpdates = new ConcurrentHashMap<>();
public NotificationApiWsClient(String wsUrl) throws URISyntaxException {
super(new URI(wsUrl + "/api/ws"));
}
public NotificationApiWsClient subscribeForUnreadNotifications(int limit) {
send(new NotificationsSubCmd(1, limit));
public NotificationApiWsClient subscribeForUnreadNotifications(int limit, NotificationType... types) {
send(new NotificationsSubCmd(newCmdId(), limit, Arrays.stream(types).collect(Collectors.toSet())));
this.limit = limit;
return this;
}
public int subscribeForUnreadNotificationsAndWait(int limit, NotificationType... types) {
int subId = newCmdId();
send(new NotificationsSubCmd(subId, limit, Arrays.stream(types).collect(Collectors.toSet())));
waitForReply();
this.limit = limit;
return subId;
}
public NotificationApiWsClient subscribeForUnreadNotificationsCount() {
send(new NotificationsCountSubCmd(2));
send(new NotificationsCountSubCmd(newCmdId()));
return this;
}
@ -84,6 +98,7 @@ public class NotificationApiWsClient extends TbTestWebSocketClient {
CmdUpdateType updateType = CmdUpdateType.valueOf(update.get("cmdUpdateType").asText());
if (updateType == CmdUpdateType.NOTIFICATIONS) {
lastDataUpdate = JacksonUtil.treeToValue(update, UnreadNotificationsUpdate.class);
lastUpdates.put(lastDataUpdate.getCmdId(), lastDataUpdate);
unreadCount = lastDataUpdate.getTotalUnreadCount();
if (lastDataUpdate.getNotifications() != null) {
notifications = new ArrayList<>(lastDataUpdate.getNotifications());
@ -115,7 +130,7 @@ public class NotificationApiWsClient extends TbTestWebSocketClient {
super.onMessage(s);
}
private static int newCmdId() {
private int newCmdId() {
return RandomUtils.nextInt(1, 1000);
}

478
application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java

@ -1,478 +0,0 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.sync.ie;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import org.junit.After;
import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.debug.TbMsgGeneratorNode;
import org.thingsboard.rule.engine.debug.TbMsgGeneratorNodeConfiguration;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNode;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.common.data.sync.ie.EntityExportSettings;
import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.common.data.util.ThrowingRunnable;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
import org.thingsboard.server.service.sync.vc.data.SimpleEntitiesExportCtx;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat;
public abstract class BaseExportImportServiceTest extends AbstractControllerTest {
@Autowired
protected EntitiesExportImportService exportImportService;
@Autowired
protected DeviceService deviceService;
@Autowired
protected OtaPackageService otaPackageService;
@Autowired
protected DeviceProfileService deviceProfileService;
@Autowired
protected AssetProfileService assetProfileService;
@Autowired
protected AssetService assetService;
@Autowired
protected CustomerService customerService;
@Autowired
protected RuleChainService ruleChainService;
@Autowired
protected DashboardService dashboardService;
@Autowired
protected RelationService relationService;
@Autowired
protected TenantService tenantService;
@Autowired
protected EntityViewService entityViewService;
protected TenantId tenantId1;
protected User tenantAdmin1;
protected TenantId tenantId2;
protected User tenantAdmin2;
@Before
public void beforeEach() throws Exception {
loginSysAdmin();
Tenant tenant1 = new Tenant();
tenant1.setTitle("Tenant 1");
tenant1.setEmail("tenant1@thingsboard.org");
this.tenantId1 = tenantService.saveTenant(tenant1).getId();
User tenantAdmin1 = new User();
tenantAdmin1.setTenantId(tenantId1);
tenantAdmin1.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin1.setEmail("tenant1-admin@thingsboard.org");
this.tenantAdmin1 = createUser(tenantAdmin1, "12345678");
Tenant tenant2 = new Tenant();
tenant2.setTitle("Tenant 2");
tenant2.setEmail("tenant2@thingsboard.org");
this.tenantId2 = tenantService.saveTenant(tenant2).getId();
User tenantAdmin2 = new User();
tenantAdmin2.setTenantId(tenantId2);
tenantAdmin2.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin2.setEmail("tenant2-admin@thingsboard.org");
this.tenantAdmin2 = createUser(tenantAdmin2, "12345678");
}
@After
public void afterEach() {
tenantService.deleteTenant(tenantId1);
tenantService.deleteTenant(tenantId2);
}
protected Device createDevice(TenantId tenantId, CustomerId customerId, DeviceProfileId deviceProfileId, String name) {
Device device = new Device();
device.setTenantId(tenantId);
device.setCustomerId(customerId);
device.setName(name);
device.setLabel("lbl");
device.setDeviceProfileId(deviceProfileId);
DeviceData deviceData = new DeviceData();
deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration());
device.setDeviceData(deviceData);
return deviceService.saveDevice(device);
}
protected OtaPackage createOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType type) {
OtaPackage otaPackage = new OtaPackage();
otaPackage.setTenantId(tenantId);
otaPackage.setDeviceProfileId(deviceProfileId);
otaPackage.setType(type);
otaPackage.setTitle("My " + type);
otaPackage.setVersion("v1.0");
otaPackage.setFileName("filename.txt");
otaPackage.setContentType("text/plain");
otaPackage.setChecksumAlgorithm(ChecksumAlgorithm.SHA256);
otaPackage.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a");
otaPackage.setDataSize(1L);
otaPackage.setData(ByteBuffer.wrap(new byte[]{(int) 1}));
return otaPackageService.saveOtaPackage(otaPackage);
}
protected void checkImportedDeviceData(Device initialDevice, Device importedDevice) {
assertThat(importedDevice.getName()).isEqualTo(initialDevice.getName());
assertThat(importedDevice.getType()).isEqualTo(initialDevice.getType());
assertThat(importedDevice.getDeviceData()).isEqualTo(initialDevice.getDeviceData());
assertThat(importedDevice.getLabel()).isEqualTo(initialDevice.getLabel());
}
protected DeviceProfile createDeviceProfile(TenantId tenantId, RuleChainId defaultRuleChainId, DashboardId defaultDashboardId, String name) {
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setTenantId(tenantId);
deviceProfile.setName(name);
deviceProfile.setDescription("dscrptn");
deviceProfile.setType(DeviceProfileType.DEFAULT);
deviceProfile.setTransportType(DeviceTransportType.DEFAULT);
deviceProfile.setDefaultRuleChainId(defaultRuleChainId);
deviceProfile.setDefaultDashboardId(defaultDashboardId);
DeviceProfileData profileData = new DeviceProfileData();
profileData.setConfiguration(new DefaultDeviceProfileConfiguration());
profileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration());
deviceProfile.setProfileData(profileData);
return deviceProfileService.saveDeviceProfile(deviceProfile);
}
protected void checkImportedDeviceProfileData(DeviceProfile initialProfile, DeviceProfile importedProfile) {
assertThat(initialProfile.getName()).isEqualTo(importedProfile.getName());
assertThat(initialProfile.getType()).isEqualTo(importedProfile.getType());
assertThat(initialProfile.getTransportType()).isEqualTo(importedProfile.getTransportType());
assertThat(initialProfile.getProfileData()).isEqualTo(importedProfile.getProfileData());
assertThat(initialProfile.getDescription()).isEqualTo(importedProfile.getDescription());
}
protected AssetProfile createAssetProfile(TenantId tenantId, RuleChainId defaultRuleChainId, DashboardId defaultDashboardId, String name) {
AssetProfile assetProfile = new AssetProfile();
assetProfile.setTenantId(tenantId);
assetProfile.setName(name);
assetProfile.setDescription("dscrptn");
assetProfile.setDefaultRuleChainId(defaultRuleChainId);
assetProfile.setDefaultDashboardId(defaultDashboardId);
return assetProfileService.saveAssetProfile(assetProfile);
}
protected void checkImportedAssetProfileData(AssetProfile initialProfile, AssetProfile importedProfile) {
assertThat(initialProfile.getName()).isEqualTo(importedProfile.getName());
assertThat(initialProfile.getDescription()).isEqualTo(importedProfile.getDescription());
}
protected Asset createAsset(TenantId tenantId, CustomerId customerId, AssetProfileId assetProfileId, String name) {
Asset asset = new Asset();
asset.setTenantId(tenantId);
asset.setCustomerId(customerId);
asset.setAssetProfileId(assetProfileId);
asset.setName(name);
asset.setLabel("lbl");
asset.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
return assetService.saveAsset(asset);
}
protected void checkImportedAssetData(Asset initialAsset, Asset importedAsset) {
assertThat(importedAsset.getName()).isEqualTo(initialAsset.getName());
assertThat(importedAsset.getType()).isEqualTo(initialAsset.getType());
assertThat(importedAsset.getLabel()).isEqualTo(initialAsset.getLabel());
assertThat(importedAsset.getAdditionalInfo()).isEqualTo(initialAsset.getAdditionalInfo());
}
protected Customer createCustomer(TenantId tenantId, String name) {
Customer customer = new Customer();
customer.setTenantId(tenantId);
customer.setTitle(name);
customer.setCountry("ua");
customer.setAddress("abb");
customer.setEmail("ccc@aa.org");
customer.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
return customerService.saveCustomer(customer);
}
protected void checkImportedCustomerData(Customer initialCustomer, Customer importedCustomer) {
assertThat(importedCustomer.getTitle()).isEqualTo(initialCustomer.getTitle());
assertThat(importedCustomer.getCountry()).isEqualTo(initialCustomer.getCountry());
assertThat(importedCustomer.getAddress()).isEqualTo(initialCustomer.getAddress());
assertThat(importedCustomer.getEmail()).isEqualTo(initialCustomer.getEmail());
}
protected Dashboard createDashboard(TenantId tenantId, CustomerId customerId, String name) {
Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenantId);
dashboard.setTitle(name);
dashboard.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
dashboard.setImage("abvregewrg");
dashboard.setMobileHide(true);
dashboard = dashboardService.saveDashboard(dashboard);
if (customerId != null) {
dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerId);
return dashboardService.findDashboardById(tenantId, dashboard.getId());
}
return dashboard;
}
protected Dashboard createDashboard(TenantId tenantId, CustomerId customerId, String name, AssetId assetForEntityAlias) {
Dashboard dashboard = createDashboard(tenantId, customerId, name);
String entityAliases = "{\n" +
"\t\"23c4185d-1497-9457-30b2-6d91e69a5b2c\": {\n" +
"\t\t\"alias\": \"assets\",\n" +
"\t\t\"filter\": {\n" +
"\t\t\t\"entityList\": [\n" +
"\t\t\t\t\"" + assetForEntityAlias.getId().toString() + "\"\n" +
"\t\t\t],\n" +
"\t\t\t\"entityType\": \"ASSET\",\n" +
"\t\t\t\"resolveMultiple\": true,\n" +
"\t\t\t\"type\": \"entityList\"\n" +
"\t\t},\n" +
"\t\t\"id\": \"23c4185d-1497-9457-30b2-6d91e69a5b2c\"\n" +
"\t}\n" +
"}";
ObjectNode dashboardConfiguration = JacksonUtil.newObjectNode();
dashboardConfiguration.set("entityAliases", JacksonUtil.toJsonNode(entityAliases));
dashboardConfiguration.set("description", new TextNode("hallo"));
dashboard.setConfiguration(dashboardConfiguration);
return dashboardService.saveDashboard(dashboard);
}
protected void checkImportedDashboardData(Dashboard initialDashboard, Dashboard importedDashboard) {
assertThat(importedDashboard.getTitle()).isEqualTo(initialDashboard.getTitle());
assertThat(importedDashboard.getConfiguration()).isEqualTo(initialDashboard.getConfiguration());
assertThat(importedDashboard.getImage()).isEqualTo(initialDashboard.getImage());
assertThat(importedDashboard.isMobileHide()).isEqualTo(initialDashboard.isMobileHide());
if (initialDashboard.getAssignedCustomers() != null) {
assertThat(importedDashboard.getAssignedCustomers()).containsAll(initialDashboard.getAssignedCustomers());
}
}
protected RuleChain createRuleChain(TenantId tenantId, String name, EntityId originatorId) {
RuleChain ruleChain = new RuleChain();
ruleChain.setTenantId(tenantId);
ruleChain.setName(name);
ruleChain.setType(RuleChainType.CORE);
ruleChain.setDebugMode(true);
ruleChain.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
ruleChain = ruleChainService.saveRuleChain(ruleChain);
RuleChainMetaData metaData = new RuleChainMetaData();
metaData.setRuleChainId(ruleChain.getId());
RuleNode ruleNode1 = new RuleNode();
ruleNode1.setName("Generator 1");
ruleNode1.setType(TbMsgGeneratorNode.class.getName());
ruleNode1.setDebugMode(true);
TbMsgGeneratorNodeConfiguration configuration1 = new TbMsgGeneratorNodeConfiguration();
configuration1.setOriginatorType(originatorId.getEntityType());
configuration1.setOriginatorId(originatorId.getId().toString());
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1));
RuleNode ruleNode2 = new RuleNode();
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));
metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2));
metaData.setFirstNodeIndex(0);
metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS);
ruleChainService.saveRuleChainMetaData(tenantId, metaData, Function.identity());
return ruleChainService.findRuleChainById(tenantId, ruleChain.getId());
}
protected RuleChain createRuleChain(TenantId tenantId, String name) {
RuleChain ruleChain = new RuleChain();
ruleChain.setTenantId(tenantId);
ruleChain.setName(name);
ruleChain.setType(RuleChainType.CORE);
ruleChain.setDebugMode(true);
ruleChain.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
ruleChain = ruleChainService.saveRuleChain(ruleChain);
RuleChainMetaData metaData = new RuleChainMetaData();
metaData.setRuleChainId(ruleChain.getId());
RuleNode ruleNode1 = new RuleNode();
ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode1.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration();
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1));
RuleNode ruleNode2 = new RuleNode();
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));
metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2));
metaData.setFirstNodeIndex(0);
metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS);
ruleChainService.saveRuleChainMetaData(tenantId, metaData, Function.identity());
return ruleChainService.findRuleChainById(tenantId, ruleChain.getId());
}
protected void checkImportedRuleChainData(RuleChain initialRuleChain, RuleChainMetaData initialMetaData, RuleChain importedRuleChain, RuleChainMetaData importedMetaData) {
assertThat(importedRuleChain.getType()).isEqualTo(initialRuleChain.getType());
assertThat(importedRuleChain.getName()).isEqualTo(initialRuleChain.getName());
assertThat(importedRuleChain.isDebugMode()).isEqualTo(initialRuleChain.isDebugMode());
assertThat(importedRuleChain.getConfiguration()).isEqualTo(initialRuleChain.getConfiguration());
assertThat(importedMetaData.getConnections()).isEqualTo(initialMetaData.getConnections());
assertThat(importedMetaData.getFirstNodeIndex()).isEqualTo(initialMetaData.getFirstNodeIndex());
for (int i = 0; i < initialMetaData.getNodes().size(); i++) {
RuleNode initialNode = initialMetaData.getNodes().get(i);
RuleNode importedNode = importedMetaData.getNodes().get(i);
assertThat(importedNode.getRuleChainId()).isEqualTo(importedRuleChain.getId());
assertThat(importedNode.getName()).isEqualTo(initialNode.getName());
assertThat(importedNode.getType()).isEqualTo(initialNode.getType());
assertThat(importedNode.getConfiguration()).isEqualTo(initialNode.getConfiguration());
assertThat(importedNode.getAdditionalInfo()).isEqualTo(initialNode.getAdditionalInfo());
}
}
protected EntityView createEntityView(TenantId tenantId, CustomerId customerId, EntityId entityId, String name) {
EntityView entityView = new EntityView();
entityView.setTenantId(tenantId);
entityView.setEntityId(entityId);
entityView.setCustomerId(customerId);
entityView.setName(name);
entityView.setType("A");
return entityViewService.saveEntityView(entityView);
}
protected EntityRelation createRelation(EntityId from, EntityId to) {
EntityRelation relation = new EntityRelation();
relation.setFrom(from);
relation.setTo(to);
relation.setType(EntityRelation.MANAGES_TYPE);
relation.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
relation.setTypeGroup(RelationTypeGroup.COMMON);
relationService.saveRelation(TenantId.SYS_TENANT_ID, relation);
return relation;
}
protected <E extends ExportableEntity<?> & HasTenantId> void checkImportedEntity(TenantId tenantId1, E initialEntity, TenantId tenantId2, E importedEntity) {
assertThat(initialEntity.getTenantId()).isEqualTo(tenantId1);
assertThat(importedEntity.getTenantId()).isEqualTo(tenantId2);
assertThat(importedEntity.getExternalId()).isEqualTo(initialEntity.getId());
boolean sameTenant = tenantId1.equals(tenantId2);
if (!sameTenant) {
assertThat(importedEntity.getId()).isNotEqualTo(initialEntity.getId());
} else {
assertThat(importedEntity.getId()).isEqualTo(initialEntity.getId());
}
}
protected <E extends ExportableEntity<I>, I extends EntityId> EntityExportData<E> exportEntity(User user, I entityId) throws Exception {
return exportEntity(user, entityId, EntityExportSettings.builder()
.exportCredentials(true)
.build());
}
protected <E extends ExportableEntity<I>, I extends EntityId> EntityExportData<E> exportEntity(User user, I entityId, EntityExportSettings exportSettings) throws Exception {
return exportImportService.exportEntity(new SimpleEntitiesExportCtx(getSecurityUser(user), null, null, exportSettings), entityId);
}
protected <E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> importEntity(User user, EntityExportData<E> exportData) throws Exception {
return importEntity(user, exportData, EntityImportSettings.builder()
.saveCredentials(true)
.build());
}
protected <E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> importEntity(User user, EntityExportData<E> exportData, EntityImportSettings importSettings) throws Exception {
EntitiesImportCtx ctx = new EntitiesImportCtx(UUID.randomUUID(), getSecurityUser(user), null, importSettings);
ctx.setFinalImportAttempt(true);
exportData = JacksonUtil.treeToValue(JacksonUtil.valueToTree(exportData), EntityExportData.class);
EntityImportResult<E> importResult = exportImportService.importEntity(ctx, exportData);
exportImportService.saveReferencesAndRelations(ctx);
for (ThrowingRunnable throwingRunnable : ctx.getEventCallbacks()) {
throwingRunnable.run();
}
return importResult;
}
protected SecurityUser getSecurityUser(User user) {
return new SecurityUser(user, true, new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()));
}
}

817
application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java

@ -15,10 +15,10 @@
*/
package org.thingsboard.server.service.sync.ie;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.collect.Streams;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
@ -26,19 +26,32 @@ import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.debug.TbMsgGeneratorNode;
import org.thingsboard.rule.engine.debug.TbMsgGeneratorNodeConfiguration;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNode;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -46,27 +59,46 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.sync.ie.DeviceExportData;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.common.data.sync.ie.EntityExportSettings;
import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.common.data.sync.ie.RuleChainExportData;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.common.data.util.ThrowingRunnable;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import java.util.ArrayList;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
import org.thingsboard.server.service.sync.vc.data.SimpleEntitiesExportCtx;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -76,481 +108,77 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.verify;
@DaoSqlTest
public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
public class ExportImportServiceSqlTest extends AbstractControllerTest {
@Autowired
private DeviceCredentialsService deviceCredentialsService;
@SpyBean
private EntityActionService entityActionService;
@SpyBean
private OtaPackageStateService otaPackageStateService;
@Test
public void testExportImportAssetWithProfile_betweenTenants() throws Exception {
AssetProfile assetProfile = createAssetProfile(tenantId1, null, null, "Asset profile of tenant 1");
Asset asset = createAsset(tenantId1, null, assetProfile.getId(), "Asset of tenant 1");
EntityExportData<AssetProfile> profileExportData = exportEntity(tenantAdmin1, assetProfile.getId());
EntityExportData<Asset> assetExportData = exportEntity(tenantAdmin1, asset.getId());
EntityImportResult<AssetProfile> profileImportResult = importEntity(tenantAdmin2, profileExportData);
checkImportedEntity(tenantId1, assetProfile, tenantId2, profileImportResult.getSavedEntity());
checkImportedAssetProfileData(assetProfile, profileImportResult.getSavedEntity());
EntityImportResult<Asset> assetImportResult = importEntity(tenantAdmin2, assetExportData);
Asset importedAsset = assetImportResult.getSavedEntity();
checkImportedEntity(tenantId1, asset, tenantId2, importedAsset);
checkImportedAssetData(asset, importedAsset);
assertThat(importedAsset.getAssetProfileId()).isEqualTo(profileImportResult.getSavedEntity().getId());
}
@Test
public void testExportImportAsset_sameTenant() throws Exception {
AssetProfile assetProfile = createAssetProfile(tenantId1, null, null, "Asset profile v1.0");
Asset asset = createAsset(tenantId1, null, assetProfile.getId(), "Asset v1.0");
EntityExportData<Asset> exportData = exportEntity(tenantAdmin1, asset.getId());
EntityImportResult<Asset> importResult = importEntity(tenantAdmin1, exportData);
checkImportedEntity(tenantId1, asset, tenantId1, importResult.getSavedEntity());
checkImportedAssetData(asset, importResult.getSavedEntity());
}
@Test
public void testExportImportAsset_sameTenant_withCustomer() throws Exception {
AssetProfile assetProfile = createAssetProfile(tenantId1, null, null, "Asset profile v1.0");
Customer customer = createCustomer(tenantId1, "My customer");
Asset asset = createAsset(tenantId1, customer.getId(), assetProfile.getId(), "My asset");
Asset importedAsset = importEntity(tenantAdmin1, this.<Asset, AssetId>exportEntity(tenantAdmin1, asset.getId())).getSavedEntity();
assertThat(importedAsset.getCustomerId()).isEqualTo(asset.getCustomerId());
}
@Test
public void testExportImportCustomer_betweenTenants() throws Exception {
Customer customer = createCustomer(tenantAdmin1.getTenantId(), "Customer of tenant 1");
EntityExportData<Customer> exportData = exportEntity(tenantAdmin1, customer.getId());
EntityImportResult<Customer> importResult = importEntity(tenantAdmin2, exportData);
checkImportedEntity(tenantId1, customer, tenantId2, importResult.getSavedEntity());
checkImportedCustomerData(customer, importResult.getSavedEntity());
}
@Test
public void testExportImportCustomer_sameTenant() throws Exception {
Customer customer = createCustomer(tenantAdmin1.getTenantId(), "Customer v1.0");
EntityExportData<Customer> exportData = exportEntity(tenantAdmin1, customer.getId());
EntityImportResult<Customer> importResult = importEntity(tenantAdmin1, exportData);
checkImportedEntity(tenantId1, customer, tenantId1, importResult.getSavedEntity());
checkImportedCustomerData(customer, importResult.getSavedEntity());
}
@Test
public void testExportImportDeviceWithProfile_betweenTenants() throws Exception {
DeviceProfile deviceProfile = createDeviceProfile(tenantId1, null, null, "Device profile of tenant 1");
Device device = createDevice(tenantId1, null, deviceProfile.getId(), "Device of tenant 1");
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId1, device.getId());
EntityExportData<DeviceProfile> profileExportData = exportEntity(tenantAdmin1, deviceProfile.getId());
EntityExportData<Device> deviceExportData = exportEntity(tenantAdmin1, device.getId());
DeviceCredentials exportedCredentials = ((DeviceExportData) deviceExportData).getCredentials();
exportedCredentials.setCredentialsId(credentials.getCredentialsId() + "a");
EntityImportResult<DeviceProfile> profileImportResult = importEntity(tenantAdmin2, profileExportData);
checkImportedEntity(tenantId1, deviceProfile, tenantId2, profileImportResult.getSavedEntity());
checkImportedDeviceProfileData(deviceProfile, profileImportResult.getSavedEntity());
EntityImportResult<Device> deviceImportResult = importEntity(tenantAdmin2, deviceExportData);
Device importedDevice = deviceImportResult.getSavedEntity();
checkImportedEntity(tenantId1, device, tenantId2, deviceImportResult.getSavedEntity());
checkImportedDeviceData(device, importedDevice);
assertThat(importedDevice.getDeviceProfileId()).isEqualTo(profileImportResult.getSavedEntity().getId());
DeviceCredentials importedCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId2, importedDevice.getId());
assertThat(importedCredentials.getId()).isNotEqualTo(credentials.getId());
assertThat(importedCredentials.getCredentialsId()).isEqualTo(exportedCredentials.getCredentialsId());
assertThat(importedCredentials.getCredentialsValue()).isEqualTo(credentials.getCredentialsValue());
assertThat(importedCredentials.getCredentialsType()).isEqualTo(credentials.getCredentialsType());
}
@Test
public void testExportImportDevice_sameTenant() throws Exception {
DeviceProfile deviceProfile = createDeviceProfile(tenantId1, null, null, "Device profile v1.0");
OtaPackage firmware = createOtaPackage(tenantId1, deviceProfile.getId(), OtaPackageType.FIRMWARE);
OtaPackage software = createOtaPackage(tenantId1, deviceProfile.getId(), OtaPackageType.SOFTWARE);
Device device = createDevice(tenantId1, null, deviceProfile.getId(), "Device v1.0");
device.setFirmwareId(firmware.getId());
device.setSoftwareId(software.getId());
device = deviceService.saveDevice(device);
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId1, device.getId());
EntityExportData<Device> deviceExportData = exportEntity(tenantAdmin1, device.getId());
EntityImportResult<Device> importResult = importEntity(tenantAdmin1, deviceExportData);
Device importedDevice = importResult.getSavedEntity();
checkImportedEntity(tenantId1, device, tenantId1, importResult.getSavedEntity());
assertThat(importedDevice.getDeviceProfileId()).isEqualTo(device.getDeviceProfileId());
assertThat(deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId1, device.getId())).isEqualTo(credentials);
assertThat(importedDevice.getFirmwareId()).isEqualTo(firmware.getId());
assertThat(importedDevice.getSoftwareId()).isEqualTo(software.getId());
}
@Test
public void testExportImportDashboard_betweenTenants() throws Exception {
Dashboard dashboard = createDashboard(tenantAdmin1.getTenantId(), null, "Dashboard of tenant 1");
EntityExportData<Dashboard> exportData = exportEntity(tenantAdmin1, dashboard.getId());
EntityImportResult<Dashboard> importResult = importEntity(tenantAdmin2, exportData);
checkImportedEntity(tenantId1, dashboard, tenantId2, importResult.getSavedEntity());
checkImportedDashboardData(dashboard, importResult.getSavedEntity());
}
@Test
public void testExportImportDashboard_sameTenant() throws Exception {
Dashboard dashboard = createDashboard(tenantAdmin1.getTenantId(), null, "Dashboard v1.0");
EntityExportData<Dashboard> exportData = exportEntity(tenantAdmin1, dashboard.getId());
EntityImportResult<Dashboard> importResult = importEntity(tenantAdmin1, exportData);
checkImportedEntity(tenantId1, dashboard, tenantId1, importResult.getSavedEntity());
checkImportedDashboardData(dashboard, importResult.getSavedEntity());
}
@Test
public void testExportImportDashboard_betweenTenants_withCustomer_updated() throws Exception {
Dashboard dashboard = createDashboard(tenantAdmin1.getTenantId(), null, "Dashboard of tenant 1");
EntityExportData<Dashboard> exportData = exportEntity(tenantAdmin1, dashboard.getId());
Dashboard importedDashboard = importEntity(tenantAdmin2, exportData).getSavedEntity();
checkImportedEntity(tenantId1, dashboard, tenantId2, importedDashboard);
Customer customer = createCustomer(tenantId1, "Customer 1");
EntityExportData<Customer> customerExportData = exportEntity(tenantAdmin1, customer.getId());
dashboardService.assignDashboardToCustomer(tenantId1, dashboard.getId(), customer.getId());
exportData = exportEntity(tenantAdmin1, dashboard.getId());
Customer importedCustomer = importEntity(tenantAdmin2, customerExportData).getSavedEntity();
importedDashboard = importEntity(tenantAdmin2, exportData).getSavedEntity();
assertThat(importedDashboard.getAssignedCustomers()).hasOnlyOneElementSatisfying(customerInfo -> {
assertThat(customerInfo.getCustomerId()).isEqualTo(importedCustomer.getId());
});
}
@Test
public void testExportImportDashboard_betweenTenants_withEntityAliases() throws Exception {
AssetProfile assetProfile = createAssetProfile(tenantId1, null, null, "A");
Asset asset1 = createAsset(tenantId1, null, assetProfile.getId(), "Asset 1");
Asset asset2 = createAsset(tenantId1, null, assetProfile.getId(), "Asset 2");
Dashboard dashboard = createDashboard(tenantId1, null, "Dashboard 1");
Dashboard otherDashboard = createDashboard(tenantId1, null, "Dashboard 2");
DeviceProfile existingDeviceProfile = createDeviceProfile(tenantId2, null, null, "Existing");
String aliasId = "23c4185d-1497-9457-30b2-6d91e69a5b2c";
String unknownUuid = "ea0dc8b0-3d85-11ed-9200-77fc04fa14fa";
String entityAliases = "{\n" +
"\"" + aliasId + "\": {\n" +
"\"alias\": \"assets\",\n" +
"\"filter\": {\n" +
" \"entityList\": [\n" +
" \"" + asset1.getId() + "\",\n" +
" \"" + asset2.getId() + "\",\n" +
" \"" + tenantId1.getId() + "\",\n" +
" \"" + existingDeviceProfile.getId() + "\",\n" +
" \"" + unknownUuid + "\"\n" +
" ],\n" +
" \"id\":\"" + asset1.getId() + "\",\n" +
" \"resolveMultiple\": true\n" +
"},\n" +
"\"id\": \"" + aliasId + "\"\n" +
"}\n" +
"}";
String widgetId = "ea8f34a0-264a-f11f-cde3-05201bb4ff4b";
String actionId = "4a8e6efa-3e68-fa59-7feb-d83366130cae";
String widgets = "{\n" +
" \"" + widgetId + "\": {\n" +
" \"config\": {\n" +
" \"actions\": {\n" +
" \"rowClick\": [\n" +
" {\n" +
" \"name\": \"go to dashboard\",\n" +
" \"targetDashboardId\": \"" + otherDashboard.getId() + "\",\n" +
" \"id\": \"" + actionId + "\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" },\n" +
" \"row\": 0,\n" +
" \"col\": 0,\n" +
" \"id\": \"" + widgetId + "\"\n" +
" }\n" +
"}";
ObjectNode dashboardConfiguration = JacksonUtil.newObjectNode();
dashboardConfiguration.set("entityAliases", JacksonUtil.toJsonNode(entityAliases));
dashboardConfiguration.set("widgets", JacksonUtil.toJsonNode(widgets));
dashboardConfiguration.set("description", new TextNode("hallo"));
dashboard.setConfiguration(dashboardConfiguration);
dashboard = dashboardService.saveDashboard(dashboard);
EntityExportData<AssetProfile> profileExportData = exportEntity(tenantAdmin1, assetProfile.getId());
EntityExportData<Asset> asset1ExportData = exportEntity(tenantAdmin1, asset1.getId());
EntityExportData<Asset> asset2ExportData = exportEntity(tenantAdmin1, asset2.getId());
EntityExportData<Dashboard> dashboardExportData = exportEntity(tenantAdmin1, dashboard.getId());
EntityExportData<Dashboard> otherDashboardExportData = exportEntity(tenantAdmin1, otherDashboard.getId());
AssetProfile importedProfile = importEntity(tenantAdmin2, profileExportData).getSavedEntity();
Asset importedAsset1 = importEntity(tenantAdmin2, asset1ExportData).getSavedEntity();
Asset importedAsset2 = importEntity(tenantAdmin2, asset2ExportData).getSavedEntity();
Dashboard importedOtherDashboard = importEntity(tenantAdmin2, otherDashboardExportData).getSavedEntity();
Dashboard importedDashboard = importEntity(tenantAdmin2, dashboardExportData).getSavedEntity();
Map.Entry<String, JsonNode> entityAlias = importedDashboard.getConfiguration().get("entityAliases").fields().next();
assertThat(entityAlias.getKey()).isEqualTo(aliasId);
assertThat(entityAlias.getValue().get("id").asText()).isEqualTo(aliasId);
List<String> aliasEntitiesIds = Streams.stream(entityAlias.getValue().get("filter").get("entityList").elements())
.map(JsonNode::asText).collect(Collectors.toList());
assertThat(aliasEntitiesIds).size().isEqualTo(5);
assertThat(aliasEntitiesIds).element(0).as("external asset 1 was replaced with imported one")
.isEqualTo(importedAsset1.getId().toString());
assertThat(aliasEntitiesIds).element(1).as("external asset 2 was replaced with imported one")
.isEqualTo(importedAsset2.getId().toString());
assertThat(aliasEntitiesIds).element(2).as("external tenant id was replaced with new tenant id")
.isEqualTo(tenantId2.toString());
assertThat(aliasEntitiesIds).element(3).as("existing device profile id was left as is")
.isEqualTo(existingDeviceProfile.getId().toString());
assertThat(aliasEntitiesIds).element(4).as("unresolved uuid was replaced with tenant id")
.isEqualTo(tenantId2.toString());
assertThat(entityAlias.getValue().get("filter").get("id").asText()).as("external asset 1 was replaced with imported one")
.isEqualTo(importedAsset1.getId().toString());
ObjectNode widgetConfig = importedDashboard.getWidgetsConfig().get(0);
assertThat(widgetConfig.get("id").asText()).as("widget id is not replaced")
.isEqualTo(widgetId);
JsonNode actionConfig = widgetConfig.get("config").get("actions").get("rowClick").get(0);
assertThat(actionConfig.get("id").asText()).as("action id is not replaced")
.isEqualTo(actionId);
assertThat(actionConfig.get("targetDashboardId").asText()).as("dashboard id is replaced with imported one")
.isEqualTo(importedOtherDashboard.getId().toString());
}
@Test
public void testExportImportRuleChain_betweenTenants() throws Exception {
RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain of tenant 1");
RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(tenantId1, ruleChain.getId());
EntityExportData<RuleChain> exportData = exportEntity(tenantAdmin1, ruleChain.getId());
EntityImportResult<RuleChain> importResult = importEntity(tenantAdmin2, exportData);
RuleChain importedRuleChain = importResult.getSavedEntity();
RuleChainMetaData importedMetaData = ruleChainService.loadRuleChainMetaData(tenantId2, importedRuleChain.getId());
checkImportedEntity(tenantId1, ruleChain, tenantId2, importResult.getSavedEntity());
checkImportedRuleChainData(ruleChain, metaData, importedRuleChain, importedMetaData);
}
@Test
public void testExportImportRuleChain_sameTenant() throws Exception {
RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain v1.0");
RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(tenantId1, ruleChain.getId());
EntityExportData<RuleChain> exportData = exportEntity(tenantAdmin1, ruleChain.getId());
EntityImportResult<RuleChain> importResult = importEntity(tenantAdmin1, exportData);
RuleChain importedRuleChain = importResult.getSavedEntity();
RuleChainMetaData importedMetaData = ruleChainService.loadRuleChainMetaData(tenantId1, importedRuleChain.getId());
checkImportedEntity(tenantId1, ruleChain, tenantId1, importResult.getSavedEntity());
checkImportedRuleChainData(ruleChain, metaData, importedRuleChain, importedMetaData);
}
@Test
public void testImportRuleChain_ruleNodesConfigs() throws Exception {
Customer customer = createCustomer(tenantId1, "Customer 1");
RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain 1");
RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(tenantId1, ruleChain.getId());
List<RuleNode> nodes = new ArrayList<>(metaData.getNodes());
RuleNode generatorNode = new RuleNode();
generatorNode.setName("Generator");
generatorNode.setType(TbMsgGeneratorNode.class.getName());
TbMsgGeneratorNodeConfiguration generatorNodeConfig = new TbMsgGeneratorNodeConfiguration();
generatorNodeConfig.setOriginatorType(EntityType.ASSET_PROFILE);
generatorNodeConfig.setOriginatorId(customer.getId().toString());
generatorNodeConfig.setPeriodInSeconds(5);
generatorNodeConfig.setMsgCount(1);
generatorNodeConfig.setScriptLang(ScriptLanguage.JS);
UUID someUuid = UUID.randomUUID();
generatorNodeConfig.setJsScript("var msg = { temp: 42, humidity: 77 };\n" +
"var metadata = { data: 40 };\n" +
"var msgType = \"POST_TELEMETRY_REQUEST\";\n" +
"var someUuid = \"" + someUuid + "\";\n" +
"return { msg: msg, metadata: metadata, msgType: msgType };");
generatorNode.setConfiguration(JacksonUtil.valueToTree(generatorNodeConfig));
nodes.add(generatorNode);
metaData.setNodes(nodes);
ruleChainService.saveRuleChainMetaData(tenantId1, metaData, Function.identity());
EntityExportData<RuleChain> ruleChainExportData = exportEntity(tenantAdmin1, ruleChain.getId());
EntityExportData<Customer> customerExportData = exportEntity(tenantAdmin1, customer.getId());
Customer importedCustomer = importEntity(tenantAdmin2, customerExportData).getSavedEntity();
RuleChain importedRuleChain = importEntity(tenantAdmin2, ruleChainExportData).getSavedEntity();
RuleChainMetaData importedMetaData = ruleChainService.loadRuleChainMetaData(tenantId2, importedRuleChain.getId());
TbMsgGeneratorNodeConfiguration importedGeneratorNodeConfig = JacksonUtil.treeToValue(importedMetaData.getNodes().stream()
.filter(node -> node.getName().equals(generatorNode.getName()))
.findFirst().get().getConfiguration(), TbMsgGeneratorNodeConfiguration.class);
assertThat(importedGeneratorNodeConfig.getOriginatorId()).isEqualTo(importedCustomer.getId().toString());
assertThat(importedGeneratorNodeConfig.getJsScript()).contains("var someUuid = \"" + someUuid + "\";");
}
@Test
public void testExportImportWithInboundRelations_betweenTenants() throws Exception {
Asset asset = createAsset(tenantId1, null, null, "Asset 1");
Device device = createDevice(tenantId1, null, null, "Device 1");
EntityRelation relation = createRelation(asset.getId(), device.getId());
EntityExportData<Asset> assetExportData = exportEntity(tenantAdmin1, asset.getId());
EntityExportData<Device> deviceExportData = exportEntity(tenantAdmin1, device.getId(), EntityExportSettings.builder()
.exportRelations(true)
.exportCredentials(false)
.build());
assertThat(deviceExportData.getRelations()).size().isOne();
assertThat(deviceExportData.getRelations().get(0)).matches(entityRelation -> {
return entityRelation.getFrom().equals(asset.getId()) && entityRelation.getTo().equals(device.getId());
});
((Asset) assetExportData.getEntity()).setAssetProfileId(null);
((Device) deviceExportData.getEntity()).setDeviceProfileId(null);
Asset importedAsset = importEntity(tenantAdmin2, assetExportData).getSavedEntity();
Device importedDevice = importEntity(tenantAdmin2, deviceExportData, EntityImportSettings.builder()
.updateRelations(true)
.build()).getSavedEntity();
checkImportedEntity(tenantId1, device, tenantId2, importedDevice);
checkImportedEntity(tenantId1, asset, tenantId2, importedAsset);
List<EntityRelation> importedRelations = relationService.findByTo(TenantId.SYS_TENANT_ID, importedDevice.getId(), RelationTypeGroup.COMMON);
assertThat(importedRelations).size().isOne();
assertThat(importedRelations.get(0)).satisfies(importedRelation -> {
assertThat(importedRelation.getFrom()).isEqualTo(importedAsset.getId());
assertThat(importedRelation.getType()).isEqualTo(relation.getType());
assertThat(importedRelation.getAdditionalInfo()).isEqualTo(relation.getAdditionalInfo());
});
}
@Test
public void testExportImportWithRelations_betweenTenants() throws Exception {
Asset asset = createAsset(tenantId1, null, null, "Asset 1");
Device device = createDevice(tenantId1, null, null, "Device 1");
EntityRelation relation = createRelation(asset.getId(), device.getId());
EntityExportData<Asset> assetExportData = exportEntity(tenantAdmin1, asset.getId());
EntityExportData<Device> deviceExportData = exportEntity(tenantAdmin1, device.getId(), EntityExportSettings.builder()
.exportRelations(true)
.exportCredentials(false)
.build());
assetExportData.getEntity().setAssetProfileId(null);
deviceExportData.getEntity().setDeviceProfileId(null);
Asset importedAsset = importEntity(tenantAdmin2, assetExportData).getSavedEntity();
Device importedDevice = importEntity(tenantAdmin2, deviceExportData, EntityImportSettings.builder()
.updateRelations(true)
.build()).getSavedEntity();
List<EntityRelation> importedRelations = relationService.findByTo(TenantId.SYS_TENANT_ID, importedDevice.getId(), RelationTypeGroup.COMMON);
assertThat(importedRelations).size().isOne();
assertThat(importedRelations.get(0)).satisfies(importedRelation -> {
assertThat(importedRelation.getFrom()).isEqualTo(importedAsset.getId());
assertThat(importedRelation.getType()).isEqualTo(relation.getType());
assertThat(importedRelation.getAdditionalInfo()).isEqualTo(relation.getAdditionalInfo());
});
}
@Test
public void testExportImportWithRelations_sameTenant() throws Exception {
Asset asset = createAsset(tenantId1, null, null, "Asset 1");
Device device1 = createDevice(tenantId1, null, null, "Device 1");
EntityRelation relation1 = createRelation(asset.getId(), device1.getId());
EntityExportData<Asset> assetExportData = exportEntity(tenantAdmin1, asset.getId(), EntityExportSettings.builder()
.exportRelations(true)
.build());
assertThat(assetExportData.getRelations()).size().isOne();
Device device2 = createDevice(tenantId1, null, null, "Device 2");
EntityRelation relation2 = createRelation(asset.getId(), device2.getId());
importEntity(tenantAdmin1, assetExportData, EntityImportSettings.builder()
.updateRelations(true)
.build());
List<EntityRelation> relations = relationService.findByFrom(TenantId.SYS_TENANT_ID, asset.getId(), RelationTypeGroup.COMMON);
assertThat(relations).contains(relation1);
assertThat(relations).doesNotContain(relation2);
}
@Test
public void textExportImportWithRelations_sameTenant_removeExisting() throws Exception {
Asset asset1 = createAsset(tenantId1, null, null, "Asset 1");
Device device = createDevice(tenantId1, null, null, "Device 1");
EntityRelation relation1 = createRelation(asset1.getId(), device.getId());
EntityExportData<Device> deviceExportData = exportEntity(tenantAdmin1, device.getId(), EntityExportSettings.builder()
.exportRelations(true)
.build());
assertThat(deviceExportData.getRelations()).size().isOne();
Asset asset2 = createAsset(tenantId1, null, null, "Asset 2");
EntityRelation relation2 = createRelation(asset2.getId(), device.getId());
importEntity(tenantAdmin1, deviceExportData, EntityImportSettings.builder()
.updateRelations(true)
.build());
List<EntityRelation> relations = relationService.findByTo(TenantId.SYS_TENANT_ID, device.getId(), RelationTypeGroup.COMMON);
assertThat(relations).contains(relation1);
assertThat(relations).doesNotContain(relation2);
}
@Test
public void testExportImportDefaultDeviceProfile_betweenTenants_findExisting() throws Exception {
DeviceProfile defaultDeviceProfile = deviceProfileService.findDefaultDeviceProfile(tenantId1);
defaultDeviceProfile.setName("non-default-name");
deviceProfileService.saveDeviceProfile(defaultDeviceProfile);
EntityExportData<DeviceProfile> deviceProfileExportData = exportEntity(tenantAdmin1, defaultDeviceProfile.getId());
importEntity(tenantAdmin2, deviceProfileExportData, EntityImportSettings.builder()
.findExistingByName(false)
.build());
DeviceProfile importedDeviceProfile = deviceProfileService.findDefaultDeviceProfile(tenantId2);
assertThat(importedDeviceProfile.isDefault()).isTrue();
assertThat(importedDeviceProfile.getName()).isEqualTo(defaultDeviceProfile.getName());
checkImportedEntity(tenantId1, defaultDeviceProfile, tenantId2, importedDeviceProfile);
@Autowired
protected EntitiesExportImportService exportImportService;
@Autowired
protected DeviceService deviceService;
@Autowired
protected OtaPackageService otaPackageService;
@Autowired
protected DeviceProfileService deviceProfileService;
@Autowired
protected AssetProfileService assetProfileService;
@Autowired
protected AssetService assetService;
@Autowired
protected CustomerService customerService;
@Autowired
protected RuleChainService ruleChainService;
@Autowired
protected DashboardService dashboardService;
@Autowired
protected RelationService relationService;
@Autowired
protected TenantService tenantService;
@Autowired
protected EntityViewService entityViewService;
protected TenantId tenantId1;
protected User tenantAdmin1;
protected TenantId tenantId2;
protected User tenantAdmin2;
@Before
public void beforeEach() throws Exception {
loginSysAdmin();
Tenant tenant1 = new Tenant();
tenant1.setTitle("Tenant 1");
tenant1.setEmail("tenant1@thingsboard.org");
this.tenantId1 = tenantService.saveTenant(tenant1).getId();
User tenantAdmin1 = new User();
tenantAdmin1.setTenantId(tenantId1);
tenantAdmin1.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin1.setEmail("tenant1-admin@thingsboard.org");
this.tenantAdmin1 = createUser(tenantAdmin1, "12345678");
Tenant tenant2 = new Tenant();
tenant2.setTitle("Tenant 2");
tenant2.setEmail("tenant2@thingsboard.org");
this.tenantId2 = tenantService.saveTenant(tenant2).getId();
User tenantAdmin2 = new User();
tenantAdmin2.setTenantId(tenantId2);
tenantAdmin2.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin2.setEmail("tenant2-admin@thingsboard.org");
this.tenantAdmin2 = createUser(tenantAdmin2, "12345678");
}
@SuppressWarnings("rawTypes")
private static EntityExportData getAndClone(Map<EntityType, EntityExportData> map, EntityType entityType) {
return JacksonUtil.clone(map.get(entityType));
@After
public void afterEach() {
tenantService.deleteTenant(tenantId1);
tenantService.deleteTenant(tenantId2);
}
@SuppressWarnings({"rawTypes", "unchecked"})
@ -712,4 +340,255 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
deviceProfileService.saveDeviceProfile(importedDeviceProfile);
}
protected Device createDevice(TenantId tenantId, CustomerId customerId, DeviceProfileId deviceProfileId, String name) {
Device device = new Device();
device.setTenantId(tenantId);
device.setCustomerId(customerId);
device.setName(name);
device.setLabel("lbl");
device.setDeviceProfileId(deviceProfileId);
DeviceData deviceData = new DeviceData();
deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration());
device.setDeviceData(deviceData);
return deviceService.saveDevice(device);
}
protected OtaPackage createOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType type) {
OtaPackage otaPackage = new OtaPackage();
otaPackage.setTenantId(tenantId);
otaPackage.setDeviceProfileId(deviceProfileId);
otaPackage.setType(type);
otaPackage.setTitle("My " + type);
otaPackage.setVersion("v1.0");
otaPackage.setFileName("filename.txt");
otaPackage.setContentType("text/plain");
otaPackage.setChecksumAlgorithm(ChecksumAlgorithm.SHA256);
otaPackage.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a");
otaPackage.setDataSize(1L);
otaPackage.setData(ByteBuffer.wrap(new byte[]{(int) 1}));
return otaPackageService.saveOtaPackage(otaPackage);
}
protected DeviceProfile createDeviceProfile(TenantId tenantId, RuleChainId defaultRuleChainId, DashboardId defaultDashboardId, String name) {
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setTenantId(tenantId);
deviceProfile.setName(name);
deviceProfile.setDescription("dscrptn");
deviceProfile.setType(DeviceProfileType.DEFAULT);
deviceProfile.setTransportType(DeviceTransportType.DEFAULT);
deviceProfile.setDefaultRuleChainId(defaultRuleChainId);
deviceProfile.setDefaultDashboardId(defaultDashboardId);
DeviceProfileData profileData = new DeviceProfileData();
profileData.setConfiguration(new DefaultDeviceProfileConfiguration());
profileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration());
deviceProfile.setProfileData(profileData);
return deviceProfileService.saveDeviceProfile(deviceProfile);
}
protected AssetProfile createAssetProfile(TenantId tenantId, RuleChainId defaultRuleChainId, DashboardId defaultDashboardId, String name) {
AssetProfile assetProfile = new AssetProfile();
assetProfile.setTenantId(tenantId);
assetProfile.setName(name);
assetProfile.setDescription("dscrptn");
assetProfile.setDefaultRuleChainId(defaultRuleChainId);
assetProfile.setDefaultDashboardId(defaultDashboardId);
return assetProfileService.saveAssetProfile(assetProfile);
}
protected Asset createAsset(TenantId tenantId, CustomerId customerId, AssetProfileId assetProfileId, String name) {
Asset asset = new Asset();
asset.setTenantId(tenantId);
asset.setCustomerId(customerId);
asset.setAssetProfileId(assetProfileId);
asset.setName(name);
asset.setLabel("lbl");
asset.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
return assetService.saveAsset(asset);
}
protected Customer createCustomer(TenantId tenantId, String name) {
Customer customer = new Customer();
customer.setTenantId(tenantId);
customer.setTitle(name);
customer.setCountry("ua");
customer.setAddress("abb");
customer.setEmail("ccc@aa.org");
customer.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
return customerService.saveCustomer(customer);
}
protected Dashboard createDashboard(TenantId tenantId, CustomerId customerId, String name) {
Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenantId);
dashboard.setTitle(name);
dashboard.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
dashboard.setImage("abvregewrg");
dashboard.setMobileHide(true);
dashboard = dashboardService.saveDashboard(dashboard);
if (customerId != null) {
dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerId);
return dashboardService.findDashboardById(tenantId, dashboard.getId());
}
return dashboard;
}
protected Dashboard createDashboard(TenantId tenantId, CustomerId customerId, String name, AssetId assetForEntityAlias) {
Dashboard dashboard = createDashboard(tenantId, customerId, name);
String entityAliases = "{\n" +
"\t\"23c4185d-1497-9457-30b2-6d91e69a5b2c\": {\n" +
"\t\t\"alias\": \"assets\",\n" +
"\t\t\"filter\": {\n" +
"\t\t\t\"entityList\": [\n" +
"\t\t\t\t\"" + assetForEntityAlias.getId().toString() + "\"\n" +
"\t\t\t],\n" +
"\t\t\t\"entityType\": \"ASSET\",\n" +
"\t\t\t\"resolveMultiple\": true,\n" +
"\t\t\t\"type\": \"entityList\"\n" +
"\t\t},\n" +
"\t\t\"id\": \"23c4185d-1497-9457-30b2-6d91e69a5b2c\"\n" +
"\t}\n" +
"}";
ObjectNode dashboardConfiguration = JacksonUtil.newObjectNode();
dashboardConfiguration.set("entityAliases", JacksonUtil.toJsonNode(entityAliases));
dashboardConfiguration.set("description", new TextNode("hallo"));
dashboard.setConfiguration(dashboardConfiguration);
return dashboardService.saveDashboard(dashboard);
}
protected RuleChain createRuleChain(TenantId tenantId, String name, EntityId originatorId) {
RuleChain ruleChain = new RuleChain();
ruleChain.setTenantId(tenantId);
ruleChain.setName(name);
ruleChain.setType(RuleChainType.CORE);
ruleChain.setDebugMode(true);
ruleChain.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
ruleChain = ruleChainService.saveRuleChain(ruleChain);
RuleChainMetaData metaData = new RuleChainMetaData();
metaData.setRuleChainId(ruleChain.getId());
RuleNode ruleNode1 = new RuleNode();
ruleNode1.setName("Generator 1");
ruleNode1.setType(TbMsgGeneratorNode.class.getName());
ruleNode1.setDebugMode(true);
TbMsgGeneratorNodeConfiguration configuration1 = new TbMsgGeneratorNodeConfiguration();
configuration1.setOriginatorType(originatorId.getEntityType());
configuration1.setOriginatorId(originatorId.getId().toString());
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1));
RuleNode ruleNode2 = new RuleNode();
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));
metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2));
metaData.setFirstNodeIndex(0);
metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS);
ruleChainService.saveRuleChainMetaData(tenantId, metaData, Function.identity());
return ruleChainService.findRuleChainById(tenantId, ruleChain.getId());
}
protected RuleChain createRuleChain(TenantId tenantId, String name) {
RuleChain ruleChain = new RuleChain();
ruleChain.setTenantId(tenantId);
ruleChain.setName(name);
ruleChain.setType(RuleChainType.CORE);
ruleChain.setDebugMode(true);
ruleChain.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
ruleChain = ruleChainService.saveRuleChain(ruleChain);
RuleChainMetaData metaData = new RuleChainMetaData();
metaData.setRuleChainId(ruleChain.getId());
RuleNode ruleNode1 = new RuleNode();
ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode1.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration();
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1));
RuleNode ruleNode2 = new RuleNode();
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));
metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2));
metaData.setFirstNodeIndex(0);
metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS);
ruleChainService.saveRuleChainMetaData(tenantId, metaData, Function.identity());
return ruleChainService.findRuleChainById(tenantId, ruleChain.getId());
}
protected EntityView createEntityView(TenantId tenantId, CustomerId customerId, EntityId entityId, String name) {
EntityView entityView = new EntityView();
entityView.setTenantId(tenantId);
entityView.setEntityId(entityId);
entityView.setCustomerId(customerId);
entityView.setName(name);
entityView.setType("A");
return entityViewService.saveEntityView(entityView);
}
protected EntityRelation createRelation(EntityId from, EntityId to) {
EntityRelation relation = new EntityRelation();
relation.setFrom(from);
relation.setTo(to);
relation.setType(EntityRelation.MANAGES_TYPE);
relation.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
relation.setTypeGroup(RelationTypeGroup.COMMON);
relationService.saveRelation(TenantId.SYS_TENANT_ID, relation);
return relation;
}
protected <E extends ExportableEntity<I>, I extends EntityId> EntityExportData<E> exportEntity(User user, I entityId) throws Exception {
return exportEntity(user, entityId, EntityExportSettings.builder()
.exportCredentials(true)
.build());
}
protected <E extends ExportableEntity<I>, I extends EntityId> EntityExportData<E> exportEntity(User user, I entityId, EntityExportSettings exportSettings) throws Exception {
return exportImportService.exportEntity(new SimpleEntitiesExportCtx(getSecurityUser(user), null, null, exportSettings), entityId);
}
protected <E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> importEntity(User user, EntityExportData<E> exportData) throws Exception {
return importEntity(user, exportData, EntityImportSettings.builder()
.saveCredentials(true)
.build());
}
protected <E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> importEntity(User user, EntityExportData<E> exportData, EntityImportSettings importSettings) throws Exception {
EntitiesImportCtx ctx = new EntitiesImportCtx(UUID.randomUUID(), getSecurityUser(user), null, importSettings);
ctx.setFinalImportAttempt(true);
exportData = JacksonUtil.treeToValue(JacksonUtil.valueToTree(exportData), EntityExportData.class);
EntityImportResult<E> importResult = exportImportService.importEntity(ctx, exportData);
exportImportService.saveReferencesAndRelations(ctx);
for (ThrowingRunnable throwingRunnable : ctx.getEventCallbacks()) {
throwingRunnable.run();
}
return importResult;
}
@SuppressWarnings("rawTypes")
private static EntityExportData getAndClone(Map<EntityType, EntityExportData> map, EntityType entityType) {
return JacksonUtil.clone(map.get(entityType));
}
protected SecurityUser getSecurityUser(User user) {
return new SecurityUser(user, true, new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()));
}
}

1005
application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java

File diff suppressed because it is too large

2
common/actor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cache/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cluster-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/coap-server/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

5
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java

@ -19,10 +19,13 @@ import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import java.util.Set;
public interface NotificationService {
Notification saveNotification(TenantId tenantId, Notification notification);
@ -35,7 +38,7 @@ public interface NotificationService {
PageData<Notification> findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, boolean unreadOnly, PageLink pageLink);
PageData<Notification> findLatestUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, int limit);
PageData<Notification> findLatestUnreadNotificationsByRecipientIdAndNotificationTypes(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, Set<NotificationType> types, int limit);
int countUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId);

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.7.0-SNAPSHOT</version>
<version>3.7.1-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save