Browse Source

Merge branch 'master' into dependency-management-transitive-ce-3-3-1

pull/5128/head
Igor Kulikov 5 years ago
committed by GitHub
parent
commit
7b6e8248d3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      application/pom.xml
  2. 20
      application/src/main/data/json/system/widget_bundles/charts.json
  3. 66
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  4. 10
      application/src/main/data/json/system/widget_bundles/maps.json
  5. 1
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  6. 6
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  7. 50
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  8. 29
      application/src/main/java/org/thingsboard/server/controller/QueueController.java
  9. 110
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeLicenseService.java
  10. 26
      application/src/main/java/org/thingsboard/server/service/edge/EdgeLicenseService.java
  11. 3
      application/src/main/resources/thingsboard.yml
  12. 8
      application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java
  13. 9
      application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesIntegrationTest.java
  14. 5
      application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesProtoIntegrationTest.java
  15. 3
      application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java
  16. 9
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java
  17. 5
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java
  18. 3
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java
  19. 13
      common/actor/pom.xml
  20. 5
      common/cache/pom.xml
  21. 28
      common/cluster-api/src/main/java/org/thingsboard/server/queue/QueueService.java
  22. 17
      common/dao-api/pom.xml
  23. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java
  24. 15
      common/data/pom.xml
  25. 2
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java
  26. 16
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
  27. 13
      common/message/pom.xml
  28. 12
      common/queue/pom.xml
  29. 62
      common/queue/src/main/java/org/thingsboard/server/queue/DefaultQueueService.java
  30. 8
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  31. 2
      common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRuleEngineSettings.java
  32. 14
      common/stats/pom.xml
  33. 8
      common/transport/coap/pom.xml
  34. 8
      common/transport/http/pom.xml
  35. 8
      common/transport/lwm2m/pom.xml
  36. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java
  37. 21
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DownlinkRequestCallback.java
  38. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java
  39. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDownlinkRequestCallbackProxy.java
  40. 9
      common/transport/mqtt/pom.xml
  41. 13
      common/transport/transport-api/pom.xml
  42. 12
      common/util/pom.xml
  43. 24
      dao/pom.xml
  44. 13
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  45. 80
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
  46. 3
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  47. 24
      msa/black-box-tests/pom.xml
  48. 95
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java
  49. 106
      pom.xml
  50. 14
      rule-engine/rule-engine-api/pom.xml
  51. 21
      rule-engine/rule-engine-components/pom.xml
  52. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  53. 8
      transport/coap/pom.xml
  54. 8
      transport/http/pom.xml
  55. 8
      transport/lwm2m/pom.xml
  56. 8
      transport/mqtt/pom.xml
  57. 1
      ui-ngx/package.json
  58. 7
      ui-ngx/src/app/app.component.ts
  59. 1
      ui-ngx/src/app/core/api/widget-api.models.ts
  60. 5
      ui-ngx/src/app/core/api/widget-subscription.ts
  61. 66
      ui-ngx/src/app/core/interceptors/global-http-interceptor.ts
  62. 2
      ui-ngx/src/app/core/services/menu.service.ts
  63. 8
      ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html
  64. 20
      ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts
  65. 2
      ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.ts
  66. 3
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.html
  67. 19
      ui-ngx/src/app/modules/home/components/event/event-table-config.ts
  68. 1
      ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts
  69. 2
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html
  70. 7
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts
  71. 2
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html
  72. 6
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts
  73. 6
      ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.html
  74. 5
      ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts
  75. 28
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts
  76. 10
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts
  77. 3
      ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts
  78. 3
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  79. 1
      ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts
  80. 1
      ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts
  81. 6
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  82. 11
      ui-ngx/src/app/modules/home/pages/user/user.component.html
  83. 17
      ui-ngx/src/app/modules/home/pages/user/user.component.ts
  84. 9
      ui-ngx/src/app/shared/components/json-form/json-form.component.ts
  85. 2
      ui-ngx/src/app/shared/components/led-light.component.ts
  86. 7
      ui-ngx/src/app/shared/components/queue/queue-type-list.component.html
  87. 52
      ui-ngx/src/app/shared/components/queue/queue-type-list.component.ts
  88. 6
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss
  89. 3
      ui-ngx/src/app/shared/components/widgets-bundle-select.component.ts
  90. 15
      ui-ngx/src/app/shared/models/time/time.models.ts
  91. 1
      ui-ngx/src/app/shared/models/widget.models.ts
  92. 16
      ui-ngx/src/assets/locale/locale.constant-de_DE.json
  93. 13
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  94. 2
      ui-ngx/src/assets/locale/locale.constant-ko_KR.json
  95. 4
      ui-ngx/src/assets/locale/locale.constant-ru_RU.json
  96. 2
      ui-ngx/src/assets/locale/locale.constant-sl_SI.json
  97. 4
      ui-ngx/src/assets/locale/locale.constant-uk_UA.json
  98. 2
      ui-ngx/src/tsconfig.app.json
  99. 275
      ui-ngx/src/typings/leadflet-editable.d.ts
  100. 3
      ui-ngx/tsconfig.json

24
application/pom.xml

@ -270,17 +270,6 @@
<artifactId>rest-client</artifactId> <artifactId>rest-client</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId> <artifactId>spring-security-test</artifactId>
@ -297,18 +286,18 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>junit</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.awaitility</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>awaitility</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -324,6 +313,7 @@
<dependency> <dependency>
<groupId>org.hsqldb</groupId> <groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId> <artifactId>hsqldb</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.javadelight</groupId> <groupId>org.javadelight</groupId>

20
application/src/main/data/json/system/widget_bundles/charts.json

@ -22,8 +22,8 @@
], ],
"templateHtml": "<canvas id=\"barChart\"></canvas>\n", "templateHtml": "<canvas id=\"barChart\"></canvas>\n",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataKey = datasource.dataKeys[d];\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n var dataset = {\n label: dataKey.label + units,\n data: [0],\n backgroundColor: [dataKey.color],\n borderColor: [dataKey.color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", "controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataKey = datasource.dataKeys[d];\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n var dataset = {\n label: dataKey.label + units,\n data: [0],\n backgroundColor: [dataKey.color],\n borderColor: [dataKey.color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n",
"settingsSchema": "{}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"showTooltip\": {\n \"title\": \"Show Tooltip\",\n \"type\": \"boolean\",\n \"default\": true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTooltip\"\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars - Chart.js\"}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars - Chart.js\"}"
} }
@ -44,8 +44,8 @@
], ],
"templateHtml": "<canvas id=\"pieChart\"></canvas>\n", "templateHtml": "<canvas id=\"pieChart\"></canvas>\n",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = typeof self.ctx.settings.borderWidth !== 'undefined' ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", "controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = typeof self.ctx.settings.borderWidth !== 'undefined' ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"borderWidth\": {\n \"title\": \"Border width\",\n \"type\": \"number\",\n \"default\": 5\n },\n \"borderColor\": {\n \"title\": \"Border color\",\n \"type\": \"string\",\n \"default\": \"#fff\"\n },\n \"legend\": {\n \"title\": \"Legend settings\",\n \"type\": \"object\",\n \"properties\": {\n \"display\": {\n \"title\": \"Display legend\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"labelsFontColor\": {\n \"title\": \"Labels font color\",\n \"type\": \"string\",\n \"default\": \"#666\"\n }\n }\n }\n },\n \"required\": []\n },\n \"form\": [\n \"borderWidth\", \n {\n \"key\": \"borderColor\",\n \"type\": \"color\"\n }, \n {\n \"key\": \"legend\",\n \"items\": [\n \"legend.display\",\n {\n \"key\": \"legend.labelsFontColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"showTooltip\": {\n \"title\": \"Show Tooltip\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"borderWidth\": {\n \"title\": \"Border width\",\n \"type\": \"number\",\n \"default\": 5\n },\n \"borderColor\": {\n \"title\": \"Border color\",\n \"type\": \"string\",\n \"default\": \"#fff\"\n },\n \"legend\": {\n \"title\": \"Legend settings\",\n \"type\": \"object\",\n \"properties\": {\n \"display\": {\n \"title\": \"Display legend\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"labelsFontColor\": {\n \"title\": \"Labels font color\",\n \"type\": \"string\",\n \"default\": \"#666\"\n }\n }\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTooltip\",\n \"borderWidth\", \n {\n \"key\": \"borderColor\",\n \"type\": \"color\"\n }, \n {\n \"key\": \"legend\",\n \"items\": [\n \"legend.display\",\n {\n \"key\": \"legend.labelsFontColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#26a69a\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#afb42b\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#673ab7\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"borderWidth\":5,\"borderColor\":\"#fff\",\"legend\":{\"display\":true,\"labelsFontColor\":\"#666666\"}},\"title\":\"Doughnut - Chart.js\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#26a69a\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#afb42b\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#673ab7\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"borderWidth\":5,\"borderColor\":\"#fff\",\"legend\":{\"display\":true,\"labelsFontColor\":\"#666666\"}},\"title\":\"Doughnut - Chart.js\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
} }
@ -84,8 +84,8 @@
], ],
"templateHtml": "<canvas id=\"pieChart\"></canvas>\n", "templateHtml": "<canvas id=\"pieChart\"></canvas>\n",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n \n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'pie',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n }); \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n", "controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'pie',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n }); \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n",
"settingsSchema": "{}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"showTooltip\": {\n \"title\": \"Show Tooltip\",\n \"type\": \"boolean\",\n \"default\": true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTooltip\"\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Pie - Chart.js\"}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Pie - Chart.js\"}"
} }
@ -106,8 +106,8 @@
], ],
"templateHtml": "<canvas id=\"pieChart\"></canvas>\n", "templateHtml": "<canvas id=\"pieChart\"></canvas>\n",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'polarArea',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n try {\n self.ctx.chart.resize();\n } catch (e) {}\n }\n}\n", "controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n\n pieData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'polarArea',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n try {\n self.ctx.chart.resize();\n } catch (e) {}\n }\n}\n",
"settingsSchema": "{}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"showTooltip\": {\n \"title\": \"Show Tooltip\",\n \"type\": \"boolean\",\n \"default\": true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTooltip\"\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fifth\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.2074391823443591,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Polar Area - Chart.js\"}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fifth\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.2074391823443591,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Polar Area - Chart.js\"}"
} }
@ -128,8 +128,8 @@
], ],
"templateHtml": "<canvas id=\"radarChart\"></canvas>\n", "templateHtml": "<canvas id=\"radarChart\"></canvas>\n",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n\n var backgroundColor = tinycolor(self.ctx.data[0].dataKey.color);\n backgroundColor.setAlpha(0.2);\n var borderColor = tinycolor(self.ctx.data[0].dataKey.color);\n borderColor.setAlpha(1);\n var dataset = {\n label: self.ctx.datasources[0].name,\n data: [],\n backgroundColor: backgroundColor.toRgbString(),\n borderColor: borderColor.toRgbString(),\n pointBackgroundColor: borderColor.toRgbString(),\n pointBorderColor: borderColor.darken().toRgbString(),\n borderWidth: 1\n }\n \n barData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n barData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n }\n\n var ctx = $('#radarChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'radar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n } \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n self.ctx.chart.resize();\n }\n}\n", "controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var barData = {\n labels: [],\n datasets: []\n };\n\n var backgroundColor = tinycolor(self.ctx.data[0].dataKey.color);\n backgroundColor.setAlpha(0.2);\n var borderColor = tinycolor(self.ctx.data[0].dataKey.color);\n borderColor.setAlpha(1);\n var dataset = {\n label: self.ctx.datasources[0].name,\n data: [],\n backgroundColor: backgroundColor.toRgbString(),\n borderColor: borderColor.toRgbString(),\n pointBackgroundColor: borderColor.toRgbString(),\n pointBorderColor: borderColor.darken().toRgbString(),\n borderWidth: 1\n }\n \n barData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n barData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n }\n\n var ctx = $('#radarChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'radar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n } \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n self.ctx.chart.resize();\n }\n}\n",
"settingsSchema": "{}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"showTooltip\": {\n \"title\": \"Show Tooltip\",\n \"type\": \"boolean\",\n \"default\": true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTooltip\"\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Radar - Chart.js\"}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Radar - Chart.js\"}"
} }

66
application/src/main/data/json/system/widget_bundles/input_widgets.json

File diff suppressed because one or more lines are too long

10
application/src/main/data/json/system/widget_bundles/maps.json

File diff suppressed because one or more lines are too long

1
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -486,7 +486,6 @@ public class ActorSystemContext {
return partitionService.resolve(serviceType, queueName, tenantId, entityId); return partitionService.resolve(serviceType, queueName, tenantId, entityId);
} }
public String getServiceId() { public String getServiceId() {
return serviceInfoProvider.getServiceId(); return serviceInfoProvider.getServiceId();
} }

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

@ -27,6 +27,7 @@ import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
@ -122,12 +123,12 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.action.RuleEngineEntityActionService; import org.thingsboard.server.service.action.RuleEngineEntityActionService;
import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.EdgeLicenseService;
import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository; import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository;
import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.resource.TbResourceService; import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService; import org.thingsboard.server.service.security.permission.AccessControlService;
@ -274,6 +275,9 @@ public abstract class BaseController {
@Autowired(required = false) @Autowired(required = false)
protected EdgeRpcService edgeGrpcService; protected EdgeRpcService edgeGrpcService;
@Autowired(required = false)
protected EdgeLicenseService edgeLicenseService;
@Autowired @Autowired
protected RuleEngineEntityActionService ruleEngineEntityActionService; protected RuleEngineEntityActionService ruleEngineEntityActionService;

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

@ -15,8 +15,11 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@ -59,6 +62,7 @@ import java.util.stream.Collectors;
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@Slf4j
@RequestMapping("/api") @RequestMapping("/api")
public class EdgeController extends BaseController { public class EdgeController extends BaseController {
@ -544,27 +548,6 @@ public class EdgeController extends BaseController {
} }
} }
@RequestMapping(value = "/license/checkInstance", method = RequestMethod.POST)
@ResponseBody
public Object checkInstance(@RequestBody Object request) throws ThingsboardException {
try {
return edgeService.checkInstance(request);
} catch (Exception e) {
throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
}
}
@RequestMapping(value = "/license/activateInstance", params = {"licenseSecret", "releaseDate"}, method = RequestMethod.POST)
@ResponseBody
public Object activateInstance(@RequestParam String licenseSecret,
@RequestParam String releaseDate) throws ThingsboardException {
try {
return edgeService.activateInstance(licenseSecret, releaseDate);
} catch (Exception e) {
throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/missingToRelatedRuleChains/{edgeId}", method = RequestMethod.GET) @RequestMapping(value = "/edge/missingToRelatedRuleChains/{edgeId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -583,4 +566,29 @@ public class EdgeController extends BaseController {
private void cleanUpLicenseKey(Edge edge) { private void cleanUpLicenseKey(Edge edge) {
edge.setEdgeLicenseKey(null); edge.setEdgeLicenseKey(null);
} }
@RequestMapping(value = "/license/checkInstance", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<JsonNode> checkInstance(@RequestBody JsonNode request) throws ThingsboardException {
log.debug("Checking instance [{}]", request);
try {
return edgeLicenseService.checkInstance(request);
} catch (Exception e) {
log.error("Error occurred: [{}]", e.getMessage(), e);
throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
}
}
@RequestMapping(value = "/license/activateInstance", params = {"licenseSecret", "releaseDate"}, method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<JsonNode> activateInstance(@RequestParam String licenseSecret,
@RequestParam String releaseDate) throws ThingsboardException {
log.debug("Activating instance [{}], [{}]", licenseSecret, releaseDate);
try {
return edgeLicenseService.activateInstance(licenseSecret, releaseDate);
} catch (Exception e) {
log.error("Error occurred: [{}]", e.getMessage(), e);
throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
}
}
} }

29
application/src/main/java/org/thingsboard/server/controller/QueueController.java

@ -15,7 +15,7 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import org.springframework.beans.factory.annotation.Autowired; import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
@ -24,41 +24,26 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.QueueService;
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.Arrays; import java.util.Set;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@RequestMapping("/api") @RequestMapping("/api")
@RequiredArgsConstructor
public class QueueController extends BaseController { public class QueueController extends BaseController {
@Autowired(required = false) private final QueueService queueService;
private TbQueueRuleEngineSettings ruleEngineSettings;
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/queues", params = {"serviceType"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/queues", params = {"serviceType"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public List<String> getTenantQueuesByServiceType(@RequestParam String serviceType) throws ThingsboardException { public Set<String> getTenantQueuesByServiceType(@RequestParam String serviceType) throws ThingsboardException {
checkParameter("serviceType", serviceType); checkParameter("serviceType", serviceType);
try { try {
ServiceType type = ServiceType.valueOf(serviceType); return queueService.getQueuesByServiceType(ServiceType.valueOf(serviceType));
switch (type) {
case TB_RULE_ENGINE:
if (ruleEngineSettings == null) {
return Arrays.asList("Main", "HighPriority", "SequentialByOriginator");
}
return ruleEngineSettings.getQueues().stream()
.map(TbRuleEngineQueueConfiguration::getName)
.collect(Collectors.toList());
default:
return Collections.emptyList();
}
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

110
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeLicenseService.java

@ -0,0 +1,110 @@
/**
* Copyright © 2016-2021 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;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.HashMap;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@Service
@TbCoreComponent
@Slf4j
public class DefaultEdgeLicenseService implements EdgeLicenseService {
private RestTemplate restTemplate;
private static final String EDGE_LICENSE_SERVER_ENDPOINT = "https://license.thingsboard.io";
@Value("${edges.enabled:false}")
private boolean edgesEnabled;
@PostConstruct
public void init() {
if (edgesEnabled) {
initRestTemplate();
}
}
@Override
public ResponseEntity<JsonNode> checkInstance(JsonNode request) {
return this.restTemplate.postForEntity(EDGE_LICENSE_SERVER_ENDPOINT + "/api/license/checkInstance", request, JsonNode.class);
}
@Override
public ResponseEntity<JsonNode> activateInstance(String edgeLicenseSecret, String releaseDate) {
Map<String, String> params = new HashMap<>();
params.put("licenseSecret", edgeLicenseSecret);
params.put("releaseDate", releaseDate);
return this.restTemplate.postForEntity(EDGE_LICENSE_SERVER_ENDPOINT + "/api/license/activateInstance?licenseSecret={licenseSecret}&releaseDate={releaseDate}", null, JsonNode.class, params);
}
private void initRestTemplate() {
boolean jdkHttpClientEnabled = isNotEmpty(System.getProperty("tb.proxy.jdk")) && System.getProperty("tb.proxy.jdk").equalsIgnoreCase("true");
boolean systemProxyEnabled = isNotEmpty(System.getProperty("tb.proxy.system")) && System.getProperty("tb.proxy.system").equalsIgnoreCase("true");
boolean proxyEnabled = isNotEmpty(System.getProperty("tb.proxy.host")) && isNotEmpty(System.getProperty("tb.proxy.port"));
if (jdkHttpClientEnabled) {
log.warn("Going to use plain JDK Http Client!");
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
if (proxyEnabled) {
log.warn("Going to use Proxy Server: [{}:{}]", System.getProperty("tb.proxy.host"), System.getProperty("tb.proxy.port"));
factory.setProxy(new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(System.getProperty("tb.proxy.host"), Integer.parseInt(System.getProperty("tb.proxy.port")))));
}
this.restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());
} else {
CloseableHttpClient httpClient;
HttpComponentsClientHttpRequestFactory requestFactory;
if (systemProxyEnabled) {
log.warn("Going to use System Proxy Server!");
httpClient = HttpClients.createSystem();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
} else if (proxyEnabled) {
log.warn("Going to use Proxy Server: [{}:{}]", System.getProperty("tb.proxy.host"), System.getProperty("tb.proxy.port"));
httpClient = HttpClients.custom().setSSLHostnameVerifier(new DefaultHostnameVerifier()).setProxy(new HttpHost(System.getProperty("tb.proxy.host"), Integer.parseInt(System.getProperty("tb.proxy.port")), "https")).build();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
} else {
httpClient = HttpClients.custom().setSSLHostnameVerifier(new DefaultHostnameVerifier()).build();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
}
}
}
}

26
application/src/main/java/org/thingsboard/server/service/edge/EdgeLicenseService.java

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2021 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;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.http.ResponseEntity;
public interface EdgeLicenseService {
ResponseEntity<JsonNode> checkInstance(JsonNode request);
ResponseEntity<JsonNode> activateInstance(String licenseSecret, String releaseDate);
}

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

@ -455,6 +455,9 @@ spring.mvc.cors:
#Set whether credentials are supported. When not set, credentials are not supported. #Set whether credentials are supported. When not set, credentials are not supported.
allow-credentials: "true" allow-credentials: "true"
# The default timeout for asynchronous requests in milliseconds
spring.mvc.async.request-timeout: "${SPRING_MVC_ASYNC_REQUEST_TIMEOUT:30000}"
# spring serve gzip compressed static resources # spring serve gzip compressed static resources
spring.resources.chain: spring.resources.chain:
compressed: "true" compressed: "true"

8
application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java

@ -21,11 +21,13 @@ import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.queue.QueueService;
import org.thingsboard.server.queue.discovery.HashPartitionService; import org.thingsboard.server.queue.discovery.HashPartitionService;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
@ -57,6 +59,7 @@ public class HashPartitionServiceTest {
private TenantRoutingInfoService routingInfoService; private TenantRoutingInfoService routingInfoService;
private ApplicationEventPublisher applicationEventPublisher; private ApplicationEventPublisher applicationEventPublisher;
private TbQueueRuleEngineSettings ruleEngineSettings; private TbQueueRuleEngineSettings ruleEngineSettings;
private QueueService queueService;
private String hashFunctionName = "sha256"; private String hashFunctionName = "sha256";
@ -67,10 +70,12 @@ public class HashPartitionServiceTest {
applicationEventPublisher = mock(ApplicationEventPublisher.class); applicationEventPublisher = mock(ApplicationEventPublisher.class);
routingInfoService = mock(TenantRoutingInfoService.class); routingInfoService = mock(TenantRoutingInfoService.class);
ruleEngineSettings = mock(TbQueueRuleEngineSettings.class); ruleEngineSettings = mock(TbQueueRuleEngineSettings.class);
queueService = mock(QueueService.class);
clusterRoutingService = new HashPartitionService(discoveryService, clusterRoutingService = new HashPartitionService(discoveryService,
routingInfoService, routingInfoService,
applicationEventPublisher, applicationEventPublisher,
ruleEngineSettings ruleEngineSettings,
queueService
); );
when(ruleEngineSettings.getQueues()).thenReturn(Collections.emptyList()); when(ruleEngineSettings.getQueues()).thenReturn(Collections.emptyList());
ReflectionTestUtils.setField(clusterRoutingService, "coreTopic", "tb.core"); ReflectionTestUtils.setField(clusterRoutingService, "coreTopic", "tb.core");
@ -82,6 +87,7 @@ public class HashPartitionServiceTest {
.setTenantIdLSB(TenantId.NULL_UUID.getLeastSignificantBits()) .setTenantIdLSB(TenantId.NULL_UUID.getLeastSignificantBits())
.addAllServiceTypes(Collections.singletonList(ServiceType.TB_CORE.name())) .addAllServiceTypes(Collections.singletonList(ServiceType.TB_CORE.name()))
.build(); .build();
// when(queueService.resolve(Mockito.any(), Mockito.anyString())).thenAnswer(i -> i.getArguments()[1]);
// when(discoveryService.getServiceInfo()).thenReturn(currentServer); // when(discoveryService.getServiceInfo()).thenReturn(currentServer);
List<TransportProtos.ServiceInfo> otherServers = new ArrayList<>(); List<TransportProtos.ServiceInfo> otherServers = new ArrayList<>();
for (int i = 1; i < SERVER_COUNT; i++) { for (int i = 1; i < SERVER_COUNT; i++) {

9
application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesIntegrationTest.java

@ -154,15 +154,6 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap
case "key1": case "key1":
assertEquals("", value); assertEquals("", value);
break; break;
case "key2":
assertEquals(false, value);
break;
case "key3":
assertEquals(0.0, value);
break;
case "key4":
assertEquals(0, value);
break;
case "key5": case "key5":
assertNotNull(value); assertNotNull(value);
assertEquals(2, ((LinkedHashMap) value).size()); assertEquals(2, ((LinkedHashMap) value).size());

5
application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesProtoIntegrationTest.java

@ -123,12 +123,9 @@ public abstract class AbstractCoapAttributesProtoIntegrationTest extends Abstrac
assertNotNull(postAttributesMsgDescriptor); assertNotNull(postAttributesMsgDescriptor);
DynamicMessage postAttributesMsg = postAttributesBuilder DynamicMessage postAttributesMsg = postAttributesBuilder
.setField(postAttributesMsgDescriptor.findFieldByName("key1"), "") .setField(postAttributesMsgDescriptor.findFieldByName("key1"), "")
.setField(postAttributesMsgDescriptor.findFieldByName("key2"), false)
.setField(postAttributesMsgDescriptor.findFieldByName("key3"), 0.0)
.setField(postAttributesMsgDescriptor.findFieldByName("key4"), 0)
.setField(postAttributesMsgDescriptor.findFieldByName("key5"), jsonObject) .setField(postAttributesMsgDescriptor.findFieldByName("key5"), jsonObject)
.build(); .build();
processAttributesTest(Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), true); processAttributesTest(Arrays.asList("key1", "key5"), postAttributesMsg.toByteArray(), true);
} }
} }

3
application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java

@ -207,10 +207,9 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac
.setField(postTelemetryMsgDescriptor.findFieldByName("key1"), "") .setField(postTelemetryMsgDescriptor.findFieldByName("key1"), "")
.setField(postTelemetryMsgDescriptor.findFieldByName("key2"), false) .setField(postTelemetryMsgDescriptor.findFieldByName("key2"), false)
.setField(postTelemetryMsgDescriptor.findFieldByName("key3"), 0.0) .setField(postTelemetryMsgDescriptor.findFieldByName("key3"), 0.0)
.setField(postTelemetryMsgDescriptor.findFieldByName("key4"), 0)
.setField(postTelemetryMsgDescriptor.findFieldByName("key5"), jsonObject) .setField(postTelemetryMsgDescriptor.findFieldByName("key5"), jsonObject)
.build(); .build();
processTestPostTelemetry(postTelemetryMsg.toByteArray(), Arrays.asList("key1", "key2", "key3", "key4", "key5"), false, true); processTestPostTelemetry(postTelemetryMsg.toByteArray(), Arrays.asList("key1", "key2", "key3", "key5"), false, true);
} }
@Test @Test

9
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java

@ -192,15 +192,6 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
case "key1": case "key1":
assertEquals("", value); assertEquals("", value);
break; break;
case "key2":
assertEquals(false, value);
break;
case "key3":
assertEquals(0.0, value);
break;
case "key4":
assertEquals(0, value);
break;
case "key5": case "key5":
assertNotNull(value); assertNotNull(value);
assertEquals(2, ((LinkedHashMap) value).size()); assertEquals(2, ((LinkedHashMap) value).size());

5
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java

@ -119,12 +119,9 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac
assertNotNull(postAttributesMsgDescriptor); assertNotNull(postAttributesMsgDescriptor);
DynamicMessage postAttributesMsg = postAttributesBuilder DynamicMessage postAttributesMsg = postAttributesBuilder
.setField(postAttributesMsgDescriptor.findFieldByName("key1"), "") .setField(postAttributesMsgDescriptor.findFieldByName("key1"), "")
.setField(postAttributesMsgDescriptor.findFieldByName("key2"), false)
.setField(postAttributesMsgDescriptor.findFieldByName("key3"), 0.0)
.setField(postAttributesMsgDescriptor.findFieldByName("key4"), 0)
.setField(postAttributesMsgDescriptor.findFieldByName("key5"), jsonObject) .setField(postAttributesMsgDescriptor.findFieldByName("key5"), jsonObject)
.build(); .build();
processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), true); processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, Arrays.asList("key1", "key5"), postAttributesMsg.toByteArray(), true);
} }
@Test @Test

3
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java

@ -204,10 +204,9 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
.setField(postTelemetryMsgDescriptor.findFieldByName("key1"), "") .setField(postTelemetryMsgDescriptor.findFieldByName("key1"), "")
.setField(postTelemetryMsgDescriptor.findFieldByName("key2"), false) .setField(postTelemetryMsgDescriptor.findFieldByName("key2"), false)
.setField(postTelemetryMsgDescriptor.findFieldByName("key3"), 0.0) .setField(postTelemetryMsgDescriptor.findFieldByName("key3"), 0.0)
.setField(postTelemetryMsgDescriptor.findFieldByName("key4"), 0)
.setField(postTelemetryMsgDescriptor.findFieldByName("key5"), jsonObject) .setField(postTelemetryMsgDescriptor.findFieldByName("key5"), jsonObject)
.build(); .build();
processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, true); processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, Arrays.asList("key1", "key2", "key3", "key5"), postTelemetryMsg.toByteArray(), false, true);
} }
@Test @Test

13
common/actor/pom.xml

@ -61,13 +61,18 @@
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>junit</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>mockito-core</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

5
common/cache/pom.xml

@ -81,6 +81,11 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.awaitility</groupId> <groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId> <artifactId>awaitility</artifactId>

28
common/cluster-api/src/main/java/org/thingsboard/server/queue/QueueService.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.queue;
import org.thingsboard.server.common.msg.queue.ServiceType;
import java.util.Set;
public interface QueueService {
Set<String> getQueuesByServiceType(ServiceType serviceType);
String resolve(ServiceType serviceType, String queueName);
}

17
common/dao-api/pom.xml

@ -40,6 +40,10 @@
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<artifactId>data</artifactId> <artifactId>data</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>cluster-api</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<artifactId>message</artifactId> <artifactId>message</artifactId>
@ -97,13 +101,18 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>junit</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

6
common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java

@ -21,10 +21,8 @@ import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeInfo; import org.thingsboard.server.common.data.edge.EdgeInfo;
import org.thingsboard.server.common.data.edge.EdgeSearchQuery; import org.thingsboard.server.common.data.edge.EdgeSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId; 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.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
@ -86,9 +84,5 @@ public interface EdgeService {
PageData<EdgeId> findRelatedEdgeIdsByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink); PageData<EdgeId> findRelatedEdgeIdsByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink);
Object checkInstance(Object request);
Object activateInstance(String licenseSecret, String releaseDate);
String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId); String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId);
} }

15
common/data/pom.xml

@ -63,15 +63,20 @@
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>junit</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>mockito-core</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

2
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java

@ -15,11 +15,13 @@
*/ */
package org.thingsboard.server.common.data.widget; package org.thingsboard.server.common.data.widget;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
@Data @Data
@JsonPropertyOrder({ "alias", "name", "image", "description", "descriptor" })
public class WidgetTypeDetails extends WidgetType { public class WidgetTypeDetails extends WidgetType {
private String image; private String image;

16
common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java

@ -15,16 +15,17 @@
*/ */
package org.thingsboard.edge.rpc; package org.thingsboard.edge.rpc;
import com.google.common.io.Resources;
import io.grpc.ManagedChannel; import io.grpc.ManagedChannel;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import io.grpc.stub.StreamObserver; import io.grpc.stub.StreamObserver;
import io.netty.handler.ssl.SslContextBuilder;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.edge.exception.EdgeConnectionException; import org.thingsboard.edge.exception.EdgeConnectionException;
import org.thingsboard.server.common.data.ResourceUtils; import org.thingsboard.server.common.data.ResourceUtils;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.gen.edge.v1.ConnectRequestMsg; import org.thingsboard.server.gen.edge.v1.ConnectRequestMsg;
import org.thingsboard.server.gen.edge.v1.ConnectResponseCode; import org.thingsboard.server.gen.edge.v1.ConnectResponseCode;
import org.thingsboard.server.gen.edge.v1.ConnectResponseMsg; import org.thingsboard.server.gen.edge.v1.ConnectResponseMsg;
@ -40,9 +41,6 @@ import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg; import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import java.io.File;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -61,7 +59,7 @@ public class EdgeGrpcClient implements EdgeRpcClient {
private int keepAliveTimeSec; private int keepAliveTimeSec;
@Value("${cloud.rpc.ssl.enabled}") @Value("${cloud.rpc.ssl.enabled}")
private boolean sslEnabled; private boolean sslEnabled;
@Value("${cloud.rpc.ssl.cert}") @Value("${cloud.rpc.ssl.cert:}")
private String certResource; private String certResource;
private ManagedChannel channel; private ManagedChannel channel;
@ -81,7 +79,11 @@ public class EdgeGrpcClient implements EdgeRpcClient {
.keepAliveTime(keepAliveTimeSec, TimeUnit.SECONDS); .keepAliveTime(keepAliveTimeSec, TimeUnit.SECONDS);
if (sslEnabled) { if (sslEnabled) {
try { try {
builder.sslContext(GrpcSslContexts.forClient().trustManager(ResourceUtils.getInputStream(this, certResource)).build()); SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient();
if (StringUtils.isNotEmpty(certResource)) {
sslContextBuilder.trustManager(ResourceUtils.getInputStream(this, certResource));
}
builder.sslContext(sslContextBuilder.build());
} catch (SSLException e) { } catch (SSLException e) {
log.error("Failed to initialize channel!", e); log.error("Failed to initialize channel!", e);
throw new RuntimeException(e); throw new RuntimeException(e);
@ -92,7 +94,7 @@ public class EdgeGrpcClient implements EdgeRpcClient {
channel = builder.build(); channel = builder.build();
EdgeRpcServiceGrpc.EdgeRpcServiceStub stub = EdgeRpcServiceGrpc.newStub(channel); EdgeRpcServiceGrpc.EdgeRpcServiceStub stub = EdgeRpcServiceGrpc.newStub(channel);
log.info("[{}] Sending a connect request to the TB!", edgeKey); log.info("[{}] Sending a connect request to the TB!", edgeKey);
this.inputStream = stub.handleMsgs(initOutputStream(edgeKey, onUplinkResponse, onEdgeUpdate, onDownlink, onError)); this.inputStream = stub.withCompression("gzip").handleMsgs(initOutputStream(edgeKey, onUplinkResponse, onEdgeUpdate, onDownlink, onError));
this.inputStream.onNext(RequestMsg.newBuilder() this.inputStream.onNext(RequestMsg.newBuilder()
.setMsgType(RequestMsgType.CONNECT_RPC_MESSAGE) .setMsgType(RequestMsgType.CONNECT_RPC_MESSAGE)
.setConnectRequestMsg(ConnectRequestMsg.newBuilder().setEdgeRoutingKey(edgeKey).setEdgeSecret(edgeSecret).build()) .setConnectRequestMsg(ConnectRequestMsg.newBuilder().setEdgeRoutingKey(edgeKey).setEdgeSecret(edgeSecret).build())

13
common/message/pom.xml

@ -70,13 +70,18 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>junit</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>mockito-core</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

12
common/queue/pom.xml

@ -125,18 +125,18 @@
<artifactId>curator-recipes</artifactId> <artifactId>curator-recipes</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>junit</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hamcrest</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>hamcrest</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

62
common/queue/src/main/java/org/thingsboard/server/queue/DefaultQueueService.java

@ -0,0 +1,62 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.queue;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.msg.queue.ServiceQueue;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class DefaultQueueService implements QueueService {
private final TbQueueRuleEngineSettings ruleEngineSettings;
private Set<String> ruleEngineQueues;
@PostConstruct
public void init() {
ruleEngineQueues = ruleEngineSettings.getQueues().stream()
.map(TbRuleEngineQueueConfiguration::getName).collect(Collectors.toCollection(LinkedHashSet::new));
}
@Override
public Set<String> getQueuesByServiceType(ServiceType type) {
if (type == ServiceType.TB_RULE_ENGINE) {
return ruleEngineQueues;
} else {
return Collections.emptySet();
}
}
@Override
public String resolve(ServiceType serviceType, String queueName) {
if (StringUtils.isEmpty(queueName) || !getQueuesByServiceType(serviceType).contains(queueName)) {
return ServiceQueue.MAIN;
} else {
return queueName;
}
}
}

8
common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java

@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceQueue; import org.thingsboard.server.common.msg.queue.ServiceQueue;
@ -30,6 +31,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
import org.thingsboard.server.queue.QueueService;
import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent; import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent; import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent;
@ -64,6 +66,7 @@ public class HashPartitionService implements PartitionService {
private final TbServiceInfoProvider serviceInfoProvider; private final TbServiceInfoProvider serviceInfoProvider;
private final TenantRoutingInfoService tenantRoutingInfoService; private final TenantRoutingInfoService tenantRoutingInfoService;
private final TbQueueRuleEngineSettings tbQueueRuleEngineSettings; private final TbQueueRuleEngineSettings tbQueueRuleEngineSettings;
private final QueueService queueService;
private final ConcurrentMap<ServiceQueue, String> partitionTopics = new ConcurrentHashMap<>(); private final ConcurrentMap<ServiceQueue, String> partitionTopics = new ConcurrentHashMap<>();
private final ConcurrentMap<ServiceQueue, Integer> partitionSizes = new ConcurrentHashMap<>(); private final ConcurrentMap<ServiceQueue, Integer> partitionSizes = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, TenantRoutingInfo> tenantRoutingInfoMap = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantId, TenantRoutingInfo> tenantRoutingInfoMap = new ConcurrentHashMap<>();
@ -81,11 +84,13 @@ public class HashPartitionService implements PartitionService {
public HashPartitionService(TbServiceInfoProvider serviceInfoProvider, public HashPartitionService(TbServiceInfoProvider serviceInfoProvider,
TenantRoutingInfoService tenantRoutingInfoService, TenantRoutingInfoService tenantRoutingInfoService,
ApplicationEventPublisher applicationEventPublisher, ApplicationEventPublisher applicationEventPublisher,
TbQueueRuleEngineSettings tbQueueRuleEngineSettings) { TbQueueRuleEngineSettings tbQueueRuleEngineSettings,
QueueService queueService) {
this.serviceInfoProvider = serviceInfoProvider; this.serviceInfoProvider = serviceInfoProvider;
this.tenantRoutingInfoService = tenantRoutingInfoService; this.tenantRoutingInfoService = tenantRoutingInfoService;
this.applicationEventPublisher = applicationEventPublisher; this.applicationEventPublisher = applicationEventPublisher;
this.tbQueueRuleEngineSettings = tbQueueRuleEngineSettings; this.tbQueueRuleEngineSettings = tbQueueRuleEngineSettings;
this.queueService = queueService;
} }
@PostConstruct @PostConstruct
@ -106,6 +111,7 @@ public class HashPartitionService implements PartitionService {
@Override @Override
public TopicPartitionInfo resolve(ServiceType serviceType, String queueName, TenantId tenantId, EntityId entityId) { public TopicPartitionInfo resolve(ServiceType serviceType, String queueName, TenantId tenantId, EntityId entityId) {
queueName = queueService.resolve(serviceType, queueName);
return resolve(new ServiceQueue(serviceType, queueName), tenantId, entityId); return resolve(new ServiceQueue(serviceType, queueName), tenantId, entityId);
} }

2
common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRuleEngineSettings.java

@ -36,8 +36,6 @@ public class TbQueueRuleEngineSettings {
private String topic; private String topic;
private List<TbRuleEngineQueueConfiguration> queues; private List<TbRuleEngineQueueConfiguration> queues;
//TODO 2.5 ybondarenko: make sure the queue names are valid to all queue providers.
// See how they are used in TbRuleEngineQueueFactory.createToRuleEngineMsgConsumer and all producers
@PostConstruct @PostConstruct
public void validate() { public void validate() {
queues.stream().filter(queue -> queue.getName().equals("Main")).findFirst().orElseThrow(() -> { queues.stream().filter(queue -> queue.getName().equals("Main")).findFirst().orElseThrow(() -> {

14
common/stats/pom.xml

@ -71,15 +71,19 @@
<groupId>io.micrometer</groupId> <groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId> <artifactId>micrometer-registry-prometheus</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>junit</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

8
common/transport/coap/pom.xml

@ -82,13 +82,13 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>junit</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

8
common/transport/http/pom.xml

@ -67,13 +67,13 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>junit</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

8
common/transport/lwm2m/pom.xml

@ -90,13 +90,13 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>junit</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java

@ -319,7 +319,8 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
clientContext.awake(client); clientContext.awake(client);
} }
}); });
},e -> handleDownlinkError(client, request, callback, e)); }, e -> handleDownlinkError(client, request, callback, e));
callback.onSent(request);
} catch (Exception e) { } catch (Exception e) {
handleDownlinkError(client, request, callback, e); handleDownlinkError(client, request, callback, e);
} }

21
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DownlinkRequestCallback.java

@ -17,29 +17,12 @@ package org.thingsboard.server.transport.lwm2m.server.downlink;
public interface DownlinkRequestCallback<R, T> { public interface DownlinkRequestCallback<R, T> {
default void onSent(R request){};
void onSuccess(R request, T response); void onSuccess(R request, T response);
void onValidationError(String params, String msg); void onValidationError(String params, String msg);
void onError(String params, Exception e); void onError(String params, Exception e);
static <R, T> DownlinkRequestCallback<R, T> doNothing() {
return new DownlinkRequestCallback<>() {
@Override
public void onSuccess(R request, T response) {
}
@Override
public void onValidationError(String params, String msg) {
}
@Override
public void onError(String params, Exception e) {
}
};
}
} }

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java

@ -160,7 +160,6 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); throw new IllegalArgumentException("Unsupported operation: " + operationType.name());
} }
} }
transportService.process(client.getSession(), rpcRequest, RpcStatus.SENT, TransportServiceCallback.EMPTY);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.BAD_REQUEST, e.getMessage()); this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.BAD_REQUEST, e.getMessage());
} }

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDownlinkRequestCallbackProxy.java

@ -43,6 +43,11 @@ public abstract class RpcDownlinkRequestCallbackProxy<R, T> implements DownlinkR
this.callback = callback; this.callback = callback;
} }
@Override
public void onSent(R request) {
transportService.process(client.getSession(), this.request, RpcStatus.SENT, TransportServiceCallback.EMPTY);
}
@Override @Override
public void onSuccess(R request, T response) { public void onSuccess(R request, T response) {
transportService.process(client.getSession(), this.request, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY); transportService.process(client.getSession(), this.request, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY);

9
common/transport/mqtt/pom.xml

@ -88,8 +88,8 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>junit</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -97,11 +97,6 @@
<artifactId>awaitility</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

13
common/transport/transport-api/pom.xml

@ -85,13 +85,18 @@
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>junit</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

12
common/util/pom.xml

@ -70,18 +70,18 @@
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>junit</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hamcrest</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>hamcrest</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

24
dao/pom.xml

@ -80,8 +80,18 @@
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>junit</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -94,11 +104,6 @@
<artifactId>spring-test-dbunit</artifactId> <artifactId>spring-test-dbunit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
@ -206,11 +211,6 @@
<artifactId>hsqldb</artifactId> <artifactId>hsqldb</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId> <artifactId>spring-context-support</artifactId>

13
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -28,7 +28,7 @@ import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import com.squareup.wire.schema.internal.parser.ProtoParser; import com.squareup.wire.schema.internal.parser.ProtoParser;
import com.squareup.wire.schema.internal.parser.TypeElement; import com.squareup.wire.schema.internal.parser.TypeElement;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
@ -63,6 +63,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
@ -72,6 +73,7 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.queue.QueueService;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -82,7 +84,6 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.google.protobuf.FieldType.MESSAGE;
import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
@ -104,6 +105,9 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
return "[Transport Configuration] invalid " + schemaName + " provided!"; return "[Transport Configuration] invalid " + schemaName + " provided!";
} }
@Autowired(required = false)
private QueueService queueService;
@Autowired @Autowired
private DeviceProfileDao deviceProfileDao; private DeviceProfileDao deviceProfileDao;
@ -373,6 +377,11 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
throw new DataValidationException("Another default device profile is present in scope of current tenant!"); throw new DataValidationException("Another default device profile is present in scope of current tenant!");
} }
} }
if (!StringUtils.isEmpty(deviceProfile.getDefaultQueueName()) && queueService != null){
if(!queueService.getQueuesByServiceType(ServiceType.TB_RULE_ENGINE).contains(deviceProfile.getDefaultQueueName())){
throw new DataValidationException("Device profile is referencing to non-existent queue!");
}
}
if (deviceProfile.getProvisionType() == null) { if (deviceProfile.getProvisionType() == null) {
deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
} }

80
dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java

@ -23,22 +23,14 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -72,20 +64,14 @@ import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import static org.thingsboard.server.common.data.CacheConstants.EDGE_CACHE; import static org.thingsboard.server.common.data.CacheConstants.EDGE_CACHE;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs; import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -106,10 +92,6 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
private static final int DEFAULT_PAGE_SIZE = 1000; private static final int DEFAULT_PAGE_SIZE = 1000;
private RestTemplate restTemplate;
private static final String EDGE_LICENSE_SERVER_ENDPOINT = "https://license.thingsboard.io";
@Autowired @Autowired
private EdgeDao edgeDao; private EdgeDao edgeDao;
@ -131,16 +113,6 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
@Autowired @Autowired
private RelationService relationService; private RelationService relationService;
@Value("${edges.enabled:false}")
private boolean edgesEnabled;
@PostConstruct
public void init() {
if (edgesEnabled) {
initRestTemplate();
}
}
@Override @Override
public Edge findEdgeById(TenantId tenantId, EdgeId edgeId) { public Edge findEdgeById(TenantId tenantId, EdgeId edgeId) {
log.trace("Executing findEdgeById [{}]", edgeId); log.trace("Executing findEdgeById [{}]", edgeId);
@ -542,19 +514,6 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
return new PageData<>(edgeIds, pageData.getTotalPages(), pageData.getTotalElements(), pageData.hasNext()); return new PageData<>(edgeIds, pageData.getTotalPages(), pageData.getTotalElements(), pageData.hasNext());
} }
@Override
public Object checkInstance(Object request) {
return this.restTemplate.postForEntity(EDGE_LICENSE_SERVER_ENDPOINT + "/api/license/checkInstance", request, Object.class, new Object[0]);
}
@Override
public Object activateInstance(String edgeLicenseSecret, String releaseDate) {
Map<String, String> params = new HashMap<>();
params.put("licenseSecret", edgeLicenseSecret);
params.put("releaseDate", releaseDate);
return this.restTemplate.postForEntity(EDGE_LICENSE_SERVER_ENDPOINT + "/api/license/activateInstance?licenseSecret={licenseSecret}&releaseDate={releaseDate}", (Object) null, Object.class, params);
}
@Override @Override
public String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId) { public String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId) {
List<RuleChain> edgeRuleChains = findEdgeRuleChains(tenantId, edgeId); List<RuleChain> edgeRuleChains = findEdgeRuleChains(tenantId, edgeId);
@ -600,43 +559,4 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
} while (pageData != null && pageData.hasNext()); } while (pageData != null && pageData.hasNext());
return result; return result;
} }
private void initRestTemplate() {
boolean jdkHttpClientEnabled = isNotEmpty(System.getProperty("tb.proxy.jdk")) && System.getProperty("tb.proxy.jdk").equalsIgnoreCase("true");
boolean systemProxyEnabled = isNotEmpty(System.getProperty("tb.proxy.system")) && System.getProperty("tb.proxy.system").equalsIgnoreCase("true");
boolean proxyEnabled = isNotEmpty(System.getProperty("tb.proxy.host")) && isNotEmpty(System.getProperty("tb.proxy.port"));
if (jdkHttpClientEnabled) {
log.warn("Going to use plain JDK Http Client!");
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
if (proxyEnabled) {
log.warn("Going to use Proxy Server: [{}:{}]", System.getProperty("tb.proxy.host"), System.getProperty("tb.proxy.port"));
factory.setProxy(new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(System.getProperty("tb.proxy.host"), Integer.parseInt(System.getProperty("tb.proxy.port")))));
}
this.restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());
} else {
CloseableHttpClient httpClient;
HttpComponentsClientHttpRequestFactory requestFactory;
if (systemProxyEnabled) {
log.warn("Going to use System Proxy Server!");
httpClient = HttpClients.createSystem();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
} else if (proxyEnabled) {
log.warn("Going to use Proxy Server: [{}:{}]", System.getProperty("tb.proxy.host"), System.getProperty("tb.proxy.port"));
httpClient = HttpClients.custom().setSSLHostnameVerifier(new DefaultHostnameVerifier()).setProxy(new HttpHost(System.getProperty("tb.proxy.host"), Integer.parseInt(System.getProperty("tb.proxy.port")), "https")).build();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
} else {
httpClient = HttpClients.custom().setSSLHostnameVerifier(new DefaultHostnameVerifier()).build();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
}
}
}
} }

3
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -192,9 +192,8 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
@Override @Override
public UserCredentials requestPasswordReset(TenantId tenantId, String email) { public UserCredentials requestPasswordReset(TenantId tenantId, String email) {
log.trace("Executing requestPasswordReset email [{}]", email); log.trace("Executing requestPasswordReset email [{}]", email);
validateString(email, "Incorrect email " + email);
DataValidator.validateEmail(email); DataValidator.validateEmail(email);
User user = userDao.findByEmail(tenantId, email); User user = findUserByEmail(tenantId, email);
if (user == null) { if (user == null) {
throw new UsernameNotFoundException(String.format("Unable to find user by email [%s]", email)); throw new UsernameNotFoundException(String.format("Unable to find user by email [%s]", email));
} }

24
msa/black-box-tests/pom.xml

@ -34,32 +34,48 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../..</main.dir> <main.dir>${basedir}/../..</main.dir>
<testcontainers.version>1.11.4</testcontainers.version>
<zeroturnaround.version>1.10</zeroturnaround.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.testcontainers</groupId> <groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId> <artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.zeroturnaround</groupId> <groupId>org.zeroturnaround</groupId>
<artifactId>zt-exec</artifactId> <artifactId>zt-exec</artifactId>
<version>${zeroturnaround.version}</version> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.java-websocket</groupId> <groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId> <artifactId>Java-WebSocket</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.takari.junit</groupId> <groupId>io.takari.junit</groupId>
<artifactId>takari-cpsuite</artifactId> <artifactId>takari-cpsuite</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>

95
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java

@ -16,26 +16,34 @@
package org.thingsboard.server.msa; package org.thingsboard.server.msa;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite; import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.rules.ExternalResource;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.testcontainers.containers.DockerComposeContainer; import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.Base58;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.util.Arrays; import java.util.UUID;
import java.util.HashMap;
import java.util.List; import static org.hamcrest.CoreMatchers.containsString;
import java.util.Map; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;
@RunWith(ClasspathSuite.class) @RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"}) @ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"})
@Slf4j @Slf4j
public class ContainerTestSuite { public class ContainerTestSuite {
private static final String SOURCE_DIR = "./../../docker/";
private static final String TB_CORE_LOG_REGEXP = ".*Starting polling for events.*";
private static final String TRANSPORTS_LOG_REGEXP = ".*Going to recalculate partitions.*";
private static DockerComposeContainer<?> testContainer; private static DockerComposeContainer<?> testContainer;
@ClassRule @ClassRule
@ -46,31 +54,78 @@ public class ContainerTestSuite {
if (testContainer == null) { if (testContainer == null) {
boolean skipTailChildContainers = Boolean.valueOf(System.getProperty("blackBoxTests.skipTailChildContainers")); boolean skipTailChildContainers = Boolean.valueOf(System.getProperty("blackBoxTests.skipTailChildContainers"));
try { try {
String tbCoreLogRegexp = ".*Starting polling for events.*"; final String targetDir = FileUtils.getTempDirectoryPath() + "/" + "ContainerTestSuite-" + UUID.randomUUID() + "/";
String transportsLogRegexp = ".*Going to recalculate partitions.*"; log.info("targetDir {}", targetDir);
FileUtils.copyDirectory(new File(SOURCE_DIR), new File(targetDir));
replaceInFile(targetDir + "docker-compose.yml", " container_name: \"${LOAD_BALANCER_NAME}\"", "", "container_name");
class DockerComposeContainerImpl<SELF extends DockerComposeContainer<SELF>> extends DockerComposeContainer<SELF> {
public DockerComposeContainerImpl(File... composeFiles) {
super(composeFiles);
}
testContainer = new DockerComposeContainer<>( @Override
new File("./../../docker/docker-compose.yml"), public void stop() {
new File("./../../docker/docker-compose.postgres.yml"), super.stop();
new File("./../../docker/docker-compose.postgres.volumes.yml"), tryDeleteDir(targetDir);
new File("./../../docker/docker-compose.kafka.yml")) }
}
testContainer = new DockerComposeContainerImpl<>(
new File(targetDir + "docker-compose.yml"),
new File(targetDir + "docker-compose.postgres.yml"),
new File(targetDir + "docker-compose.postgres.volumes.yml"),
new File(targetDir + "docker-compose.kafka.yml"))
.withPull(false) .withPull(false)
.withLocalCompose(true) .withLocalCompose(true)
.withTailChildContainers(!skipTailChildContainers) .withTailChildContainers(!skipTailChildContainers)
.withEnv(installTb.getEnv()) .withEnv(installTb.getEnv())
.withEnv("LOAD_BALANCER_NAME", "") .withEnv("LOAD_BALANCER_NAME", "")
.withExposedService("haproxy", 80, Wait.forHttp("/swagger-ui.html").withStartupTimeout(Duration.ofSeconds(400))) .withExposedService("haproxy", 80, Wait.forHttp("/swagger-ui.html").withStartupTimeout(Duration.ofSeconds(400)))
.waitingFor("tb-core1", Wait.forLogMessage(tbCoreLogRegexp, 1).withStartupTimeout(Duration.ofSeconds(400))) .waitingFor("tb-core1", Wait.forLogMessage(TB_CORE_LOG_REGEXP, 1).withStartupTimeout(Duration.ofSeconds(400)))
.waitingFor("tb-core2", Wait.forLogMessage(tbCoreLogRegexp, 1).withStartupTimeout(Duration.ofSeconds(400))) .waitingFor("tb-core2", Wait.forLogMessage(TB_CORE_LOG_REGEXP, 1).withStartupTimeout(Duration.ofSeconds(400)))
.waitingFor("tb-http-transport1", Wait.forLogMessage(transportsLogRegexp, 1).withStartupTimeout(Duration.ofSeconds(400))) .waitingFor("tb-http-transport1", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(Duration.ofSeconds(400)))
.waitingFor("tb-http-transport2", Wait.forLogMessage(transportsLogRegexp, 1).withStartupTimeout(Duration.ofSeconds(400))) .waitingFor("tb-http-transport2", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(Duration.ofSeconds(400)))
.waitingFor("tb-mqtt-transport1", Wait.forLogMessage(transportsLogRegexp, 1).withStartupTimeout(Duration.ofSeconds(400))) .waitingFor("tb-mqtt-transport1", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(Duration.ofSeconds(400)))
.waitingFor("tb-mqtt-transport2", Wait.forLogMessage(transportsLogRegexp, 1).withStartupTimeout(Duration.ofSeconds(400))); .waitingFor("tb-mqtt-transport2", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(Duration.ofSeconds(400)));
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to create test container", e); log.error("Failed to create test container", e);
throw e; fail("Failed to create test container");
} }
} }
return testContainer; return testContainer;
} }
private static void tryDeleteDir(String targetDir) {
try {
log.info("Trying to delete temp dir {}", targetDir);
FileUtils.deleteDirectory(new File(targetDir));
} catch (IOException e) {
log.error("Can't delete temp directory " + targetDir, e);
}
}
/**
* This workaround is actual until issue will be resolved:
* Support container_name in docker-compose file #2472 https://github.com/testcontainers/testcontainers-java/issues/2472
* docker-compose files which contain container_name are not supported and the creation of DockerComposeContainer fails due to IllegalStateException.
* This has been introduced in #1151 as a quick fix for unintuitive feedback. https://github.com/testcontainers/testcontainers-java/issues/1151
* Using the latest testcontainers and waiting for the fix...
* */
private static void replaceInFile(String sourceFilename, String target, String replacement, String verifyPhrase) {
try {
File file = new File(sourceFilename);
String sourceContent = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
String outputContent = sourceContent.replace(target, replacement);
assertThat(outputContent, (not(containsString(target))));
assertThat(outputContent, (not(containsString(verifyPhrase))));
FileUtils.writeStringToFile(file, outputContent, StandardCharsets.UTF_8);
assertThat(FileUtils.readFileToString(file, StandardCharsets.UTF_8), is(outputContent));
} catch (IOException e) {
log.error("failed to update file " + sourceFilename, e);
fail("failed to update file");
}
}
} }

106
pom.xml

@ -46,21 +46,13 @@
<spring-data-redis.version>2.4.3</spring-data-redis.version> <spring-data-redis.version>2.4.3</spring-data-redis.version>
<jedis.version>3.3.0</jedis.version> <jedis.version>3.3.0</jedis.version>
<jjwt.version>0.7.0</jjwt.version> <jjwt.version>0.7.0</jjwt.version>
<json-path.version>2.2.0</json-path.version>
<junit.version>4.12</junit.version>
<jupiter.version>5.7.1</jupiter.version>
<awaitility.version>4.1.0</awaitility.version>
<hamcrest.version>2.2</hamcrest.version>
<slf4j.version>1.7.7</slf4j.version> <slf4j.version>1.7.7</slf4j.version>
<logback.version>1.2.3</logback.version> <logback.version>1.2.3</logback.version>
<mockito.version>3.3.3</mockito.version>
<rat.version>0.10</rat.version> <rat.version>0.10</rat.version>
<cassandra.version>4.10.0</cassandra.version> <cassandra.version>4.10.0</cassandra.version>
<metrics.version>4.0.5</metrics.version> <metrics.version>4.0.5</metrics.version>
<cassandra-unit.version>4.3.1.0</cassandra-unit.version>
<cassandra-all.version>3.11.10</cassandra-all.version> <cassandra-all.version>3.11.10</cassandra-all.version>
<cassandra-driver-core.version>3.11.0</cassandra-driver-core.version> <cassandra-driver-core.version>3.11.0</cassandra-driver-core.version>
<takari-cpsuite.version>1.2.7</takari-cpsuite.version>
<guava.version>28.2-jre</guava.version> <guava.version>28.2-jre</guava.version>
<caffeine.version>2.6.1</caffeine.version> <caffeine.version>2.6.1</caffeine.version>
<commons-lang3.version>3.4</commons-lang3.version> <commons-lang3.version>3.4</commons-lang3.version>
@ -97,9 +89,6 @@
<jts.version>1.15.0</jts.version> <jts.version>1.15.0</jts.version>
<bouncycastle.version>1.64</bouncycastle.version> <bouncycastle.version>1.64</bouncycastle.version>
<winsw.version>2.0.1</winsw.version> <winsw.version>2.0.1</winsw.version>
<hsqldb.version>2.6.0</hsqldb.version>
<dbunit.version>2.5.3</dbunit.version>
<spring-test-dbunit.version>1.2.1</spring-test-dbunit.version>
<postgresql.driver.version>42.2.20</postgresql.driver.version> <postgresql.driver.version>42.2.20</postgresql.driver.version>
<sonar.exclusions>org/thingsboard/server/gen/**/*, <sonar.exclusions>org/thingsboard/server/gen/**/*,
org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/*
@ -119,7 +108,6 @@
<ua-parser.version>1.4.3</ua-parser.version> <ua-parser.version>1.4.3</ua-parser.version>
<commons-beanutils.version>1.9.4</commons-beanutils.version> <commons-beanutils.version>1.9.4</commons-beanutils.version>
<commons-collections.version>3.2.2</commons-collections.version> <commons-collections.version>3.2.2</commons-collections.version>
<java-websocket.version>1.5.0</java-websocket.version>
<micrometer.version>1.5.2</micrometer.version> <micrometer.version>1.5.2</micrometer.version>
<protobuf-dynamic.version>1.0.3TB</protobuf-dynamic.version> <protobuf-dynamic.version>1.0.3TB</protobuf-dynamic.version>
<wire-schema.version>3.4.0</wire-schema.version> <wire-schema.version>3.4.0</wire-schema.version>
@ -129,6 +117,20 @@
<javax.validation-api.version>2.0.1.Final</javax.validation-api.version> <javax.validation-api.version>2.0.1.Final</javax.validation-api.version>
<antisamy.version>1.6.2</antisamy.version> <antisamy.version>1.6.2</antisamy.version>
<snmp4j.version>2.8.5</snmp4j.version> <snmp4j.version>2.8.5</snmp4j.version>
<httpclient.version>4.5.13</httpclient.version>
<!-- TEST SCOPE -->
<awaitility.version>4.1.0</awaitility.version>
<cassandra-unit.version>4.3.1.0</cassandra-unit.version>
<dbunit.version>2.7.2</dbunit.version>
<hsqldb.version>2.6.0</hsqldb.version>
<java-websocket.version>1.5.2</java-websocket.version>
<jupiter.version>5.6.3</jupiter.version> <!-- keep the same version as spring-boot-starter-test depend on jupiter-->
<json-path.version>2.6.0</json-path.version>
<spring-test-dbunit.version>1.3.0</spring-test-dbunit.version> <!-- 2016 -->
<takari-cpsuite.version>1.2.7</takari-cpsuite.version> <!-- 2015 -->
<!-- BLACKBOX TEST SCOPE -->
<testcontainers.version>1.16.0</testcontainers.version>
<zeroturnaround.version>1.12</zeroturnaround.version>
</properties> </properties>
<modules> <modules>
@ -1096,6 +1098,12 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version> <version>${spring-boot.version}</version>
<scope>test</scope> <scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -1541,12 +1549,32 @@
<artifactId>takari-cpsuite</artifactId> <artifactId>takari-cpsuite</artifactId>
<version>${takari-cpsuite.version}</version> <version>${takari-cpsuite.version}</version>
<scope>test</scope> <scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.cassandraunit</groupId> <groupId>org.cassandraunit</groupId>
<artifactId>cassandra-unit</artifactId> <artifactId>cassandra-unit</artifactId>
<version>${cassandra-unit.version}</version> <version>${cassandra-unit.version}</version>
<scope>test</scope> <scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.cassandra</groupId> <groupId>org.apache.cassandra</groupId>
@ -1554,10 +1582,16 @@
<version>${cassandra-all.version}</version> <version>${cassandra-all.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>junit</artifactId> <artifactId>junit-vintage-engine</artifactId>
<version>${junit.version}</version> <version>${jupiter.version}</version>
<scope>test</scope> <scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.awaitility</groupId> <groupId>org.awaitility</groupId>
@ -1565,30 +1599,12 @@
<version>${awaitility.version}</version> <version>${awaitility.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.dbunit</groupId> <groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId> <artifactId>dbunit</artifactId>
<version>${dbunit.version}</version> <version>${dbunit.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
@ -1624,6 +1640,7 @@
<groupId>org.hsqldb</groupId> <groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId> <artifactId>hsqldb</artifactId>
<version>${hsqldb.version}</version> <version>${hsqldb.version}</version>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.data</groupId> <groupId>org.springframework.data</groupId>
@ -1813,6 +1830,29 @@
<artifactId>snmp4j</artifactId> <artifactId>snmp4j</artifactId>
<version>${snmp4j.version}</version> <version>${snmp4j.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>zt-exec</artifactId>
<version>${zeroturnaround.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

14
rule-engine/rule-engine-api/pom.xml

@ -99,14 +99,18 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>junit</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<version>${junit.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>mockito-core</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

21
rule-engine/rule-engine-components/pom.xml

@ -122,9 +122,18 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>junit</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<version>${junit.version}</version> <scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -142,12 +151,6 @@
</exclusions> </exclusions>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

2
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

8
transport/coap/pom.xml

@ -70,13 +70,13 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>junit</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

8
transport/http/pom.xml

@ -70,13 +70,13 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>junit</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

8
transport/lwm2m/pom.xml

@ -104,13 +104,13 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>junit</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

8
transport/mqtt/pom.xml

@ -70,13 +70,13 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>junit</artifactId> <artifactId>junit-vintage-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.awaitility</groupId>
<artifactId>mockito-core</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

1
ui-ngx/package.json

@ -113,6 +113,7 @@
"@types/js-beautify": "^1.13.1", "@types/js-beautify": "^1.13.1",
"@types/jstree": "^3.3.40", "@types/jstree": "^3.3.40",
"@types/leaflet": "1.5.17", "@types/leaflet": "1.5.17",
"@types/leaflet-editable": "^1.2.1",
"@types/leaflet-polylinedecorator": "^1.6.0", "@types/leaflet-polylinedecorator": "^1.6.0",
"@types/leaflet.markercluster": "^1.4.4", "@types/leaflet.markercluster": "^1.4.4",
"@types/lodash": "^4.14.170", "@types/lodash": "^4.14.170",

7
ui-ngx/src/app/app.component.ts

@ -68,6 +68,13 @@ export class AppComponent implements OnInit {
) )
); );
this.matIconRegistry.addSvgIconLiteral(
'filter-variant-remove',
this.domSanitizer.bypassSecurityTrustHtml(
'<svg viewBox="0 0 24 24"><path d="M21 8H3V6h18v2m-7.19 8H10v2h3.09c.12-.72.37-1.39.72-2M18 11H6v2h12v-2m3.12 4.46L19 17.59l-2.12-2.13-1.41 1.42L17.59 19l-2.12 2.12 1.41 1.42L19 20.41l2.12 2.13 1.42-1.42L20.41 19l2.13-2.12-1.42-1.42z"/></svg>'
)
);
this.matIconRegistry.addSvgIconLiteral( this.matIconRegistry.addSvgIconLiteral(
'google-logo', 'google-logo',
this.domSanitizer.bypassSecurityTrustHtml( this.domSanitizer.bypassSecurityTrustHtml(

1
ui-ngx/src/app/core/api/widget-api.models.ts

@ -243,6 +243,7 @@ export interface WidgetSubscriptionOptions {
legendConfig?: LegendConfig; legendConfig?: LegendConfig;
comparisonEnabled?: boolean; comparisonEnabled?: boolean;
timeForComparison?: moment_.unitOfTime.DurationConstructor; timeForComparison?: moment_.unitOfTime.DurationConstructor;
comparisonCustomIntervalValue?: number;
decimals?: number; decimals?: number;
units?: string; units?: string;
callbacks?: WidgetSubscriptionCallbacks; callbacks?: WidgetSubscriptionCallbacks;

5
ui-ngx/src/app/core/api/widget-subscription.ts

@ -110,6 +110,7 @@ export class WidgetSubscription implements IWidgetSubscription {
units: string; units: string;
comparisonEnabled: boolean; comparisonEnabled: boolean;
timeForComparison: ComparisonDuration; timeForComparison: ComparisonDuration;
comparisonCustomIntervalValue: number;
comparisonTimeWindow: WidgetTimewindow; comparisonTimeWindow: WidgetTimewindow;
timewindowForComparison: SubscriptionTimewindow; timewindowForComparison: SubscriptionTimewindow;
@ -234,6 +235,7 @@ export class WidgetSubscription implements IWidgetSubscription {
this.comparisonEnabled = options.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig); this.comparisonEnabled = options.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig);
if (this.comparisonEnabled) { if (this.comparisonEnabled) {
this.timeForComparison = options.timeForComparison; this.timeForComparison = options.timeForComparison;
this.comparisonCustomIntervalValue = options.comparisonCustomIntervalValue;
this.comparisonTimeWindow = {}; this.comparisonTimeWindow = {};
this.timewindowForComparison = null; this.timewindowForComparison = null;
@ -1183,7 +1185,8 @@ export class WidgetSubscription implements IWidgetSubscription {
} }
private updateSubscriptionForComparison(): SubscriptionTimewindow { private updateSubscriptionForComparison(): SubscriptionTimewindow {
this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison); this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison,
this.comparisonCustomIntervalValue);
this.updateComparisonTimewindow(); this.updateComparisonTimewindow();
return this.timewindowForComparison; return this.timewindowForComparison;
} }

66
ui-ngx/src/app/core/interceptors/global-http-interceptor.ts

@ -14,21 +14,14 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
HttpErrorResponse,
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
HttpResponseBase
} from '@angular/common/http';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { Inject, Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { AuthService } from '@core/auth/auth.service'; import { AuthService } from '@core/auth/auth.service';
import { Constants } from '@shared/models/constants'; import { Constants } from '@shared/models/constants';
import { InterceptorHttpParams } from './interceptor-http-params'; import { InterceptorHttpParams } from './interceptor-http-params';
import { catchError, delay, mergeMap, switchMap, tap } from 'rxjs/operators'; import { catchError, delay, finalize, mergeMap, switchMap } from 'rxjs/operators';
import { throwError, of } from 'rxjs'; import { of, throwError } from 'rxjs';
import { InterceptorConfig } from './interceptor-config'; import { InterceptorConfig } from './interceptor-config';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
@ -61,19 +54,26 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.startsWith('/api/')) { if (req.url.startsWith('/api/')) {
const config = this.getInterceptorConfig(req); const config = this.getInterceptorConfig(req);
const isLoading = !this.isInternalUrlPrefix(req.url); this.updateLoadingState(config, true);
this.updateLoadingState(config, isLoading); let observable$: Observable<HttpEvent<any>>;
if (this.isTokenBasedAuthEntryPoint(req.url)) { if (this.isTokenBasedAuthEntryPoint(req.url)) {
if (!AuthService.getJwtToken() && !this.authService.refreshTokenPending()) { if (!AuthService.getJwtToken() && !this.authService.refreshTokenPending()) {
return this.handleResponseError(req, next, new HttpErrorResponse({error: {message: 'Unauthorized!'}, status: 401})); observable$ = this.handleResponseError(req, next, new HttpErrorResponse({error: {message: 'Unauthorized!'}, status: 401}));
} else if (!AuthService.isJwtTokenValid()) { } else if (!AuthService.isJwtTokenValid()) {
return this.handleResponseError(req, next, new HttpErrorResponse({error: {refreshTokenPending: true}})); observable$ = this.handleResponseError(req, next, new HttpErrorResponse({error: {refreshTokenPending: true}}));
} else { } else {
return this.jwtIntercept(req, next); observable$ = this.jwtIntercept(req, next);
} }
} else { } else {
return this.handleRequest(req, next); observable$ = this.handleRequest(req, next);
} }
return observable$.pipe(
finalize(() => {
if (req.url.startsWith('/api/')) {
this.updateLoadingState(config, false);
}
})
);
} else { } else {
return next.handle(req); return next.handle(req);
} }
@ -84,43 +84,20 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
if (newReq) { if (newReq) {
return this.handleRequest(newReq, next); return this.handleRequest(newReq, next);
} else { } else {
return this.handleRequestError(req, new Error('Could not get JWT token from store.')); return throwError(new Error('Could not get JWT token from store.'));
} }
} }
private handleRequest(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { private handleRequest(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe( return next.handle(req).pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponseBase) {
this.handleResponse(req, event as HttpResponseBase);
}
}),
catchError((err) => { catchError((err) => {
const errorResponse = err as HttpErrorResponse; const errorResponse = err as HttpErrorResponse;
return this.handleResponseError(req, next, errorResponse); return this.handleResponseError(req, next, errorResponse);
})); }));
} }
private handleRequestError(req: HttpRequest<any>, err): Observable<HttpEvent<any>> {
const config = this.getInterceptorConfig(req);
if (req.url.startsWith('/api/')) {
this.updateLoadingState(config, false);
}
return throwError(err);
}
private handleResponse(req: HttpRequest<any>, response: HttpResponseBase) {
const config = this.getInterceptorConfig(req);
if (req.url.startsWith('/api/')) {
this.updateLoadingState(config, false);
}
}
private handleResponseError(req: HttpRequest<any>, next: HttpHandler, errorResponse: HttpErrorResponse): Observable<HttpEvent<any>> { private handleResponseError(req: HttpRequest<any>, next: HttpHandler, errorResponse: HttpErrorResponse): Observable<HttpEvent<any>> {
const config = this.getInterceptorConfig(req); const config = this.getInterceptorConfig(req);
if (req.url.startsWith('/api/')) {
this.updateLoadingState(config, false);
}
let unhandled = false; let unhandled = false;
const ignoreErrors = config.ignoreErrors; const ignoreErrors = config.ignoreErrors;
const resendRequest = config.resendRequest; const resendRequest = config.resendRequest;
@ -258,11 +235,16 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
} }
private getInterceptorConfig(req: HttpRequest<any>): InterceptorConfig { private getInterceptorConfig(req: HttpRequest<any>): InterceptorConfig {
let config: InterceptorConfig;
if (req.params && req.params instanceof InterceptorHttpParams) { if (req.params && req.params instanceof InterceptorHttpParams) {
return (req.params as InterceptorHttpParams).interceptorConfig; config = (req.params as InterceptorHttpParams).interceptorConfig;
} else { } else {
return new InterceptorConfig(false, false); config = new InterceptorConfig(false, false);
}
if (this.isInternalUrlPrefix(req.url)) {
config.ignoreLoading = true;
} }
return config;
} }
private showError(error: string, timeout: number = 0) { private showError(error: string, timeout: number = 0) {

2
ui-ngx/src/app/core/services/menu.service.ts

@ -219,7 +219,7 @@ export class MenuService {
{ {
name: 'resource.resources-library', name: 'resource.resources-library',
icon: 'folder', icon: 'folder',
path: '/resources-library' path: '/settings/resources-library'
} }
] ]
} }

8
ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html

@ -112,8 +112,8 @@
fxFlex fxFlex
[selectFirstBundle]="false" [selectFirstBundle]="false"
[selectBundleAlias]="selectedWidgetsBundleAlias" [selectBundleAlias]="selectedWidgetsBundleAlias"
[(ngModel)]="widgetsBundle" [ngModel]="null"
(ngModelChange)="onWidgetsBundleChanged()"> (ngModelChange)="onWidgetsBundleChanged($event)">
</tb-widgets-bundle-select> </tb-widgets-bundle-select>
</div> </div>
<button mat-raised-button [fxShow]="widgetsList.length > 0" <button mat-raised-button [fxShow]="widgetsList.length > 0"
@ -245,11 +245,11 @@
</ngx-hm-carousel> </ngx-hm-carousel>
<span translate *ngIf="mode === 'widget' && widgetsLoaded && <span translate *ngIf="mode === 'widget' && widgetsLoaded &&
widgetsList.length === 0 && widgetsList.length === 0 &&
widgetsBundle" widgetBundleSet"
fxFlex fxLayoutAlign="center center" fxFlex fxLayoutAlign="center center"
style="display: flex;" style="display: flex;"
class="mat-headline">widgets-bundle.empty</span> class="mat-headline">widgets-bundle.empty</span>
<span translate *ngIf="mode === 'widget' && !widgetsBundle" <span translate *ngIf="mode === 'widget' && !widgetBundleSet"
fxFlex fxLayoutAlign="center center" fxFlex fxLayoutAlign="center center"
style="display: flex;" style="display: flex;"
class="mat-headline">widget.select-widgets-bundle</span> class="mat-headline">widget.select-widgets-bundle</span>

20
ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts

@ -17,6 +17,7 @@
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef,
Component, Component,
ElementRef, ElementRef,
Injector, Injector,
@ -118,7 +119,7 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
viewsInited = false; viewsInited = false;
selectedWidgetsBundleAlias: string = null; selectedWidgetsBundleAlias: string = null;
widgetsBundle: WidgetsBundle = null; widgetBundleSet = false;
widgetsLoaded = false; widgetsLoaded = false;
widgetsCarouselIndex = 0; widgetsCarouselIndex = 0;
widgetsList: Array<Array<Widget>> = []; widgetsList: Array<Array<Widget>> = [];
@ -182,7 +183,8 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
private utils: UtilsService, private utils: UtilsService,
private dashboardUtils: DashboardUtilsService, private dashboardUtils: DashboardUtilsService,
private widgetService: WidgetService, private widgetService: WidgetService,
private zone: NgZone) { private zone: NgZone,
private cd: ChangeDetectorRef) {
super(store); super(store);
this.dirtyValue = !this.activeValue; this.dirtyValue = !this.activeValue;
const sortOrder: SortOrder = { property: 'key', direction: Direction.ASC }; const sortOrder: SortOrder = { property: 'key', direction: Direction.ASC };
@ -380,8 +382,8 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
this.widgetsList = []; this.widgetsList = [];
this.widgetsListCache = []; this.widgetsListCache = [];
this.widgetsLoaded = false; this.widgetsLoaded = false;
this.widgetBundleSet = false;
this.widgetsCarouselIndex = 0; this.widgetsCarouselIndex = 0;
this.widgetsBundle = null;
this.selectedWidgetsBundleAlias = 'cards'; this.selectedWidgetsBundleAlias = 'cards';
const entityAlias: EntityAlias = { const entityAlias: EntityAlias = {
@ -440,15 +442,16 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
} }
} }
onWidgetsBundleChanged() { onWidgetsBundleChanged(widgetsBundle: WidgetsBundle) {
this.widgetBundleSet = !!widgetsBundle;
if (this.mode === 'widget') { if (this.mode === 'widget') {
this.widgetsList = []; this.widgetsList = [];
this.widgetsListCache = []; this.widgetsListCache = [];
this.widgetsCarouselIndex = 0; this.widgetsCarouselIndex = 0;
if (this.widgetsBundle) { if (widgetsBundle) {
this.widgetsLoaded = false; this.widgetsLoaded = false;
const bundleAlias = this.widgetsBundle.alias; const bundleAlias = widgetsBundle.alias;
const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID; const isSystem = widgetsBundle.tenantId.id === NULL_UUID;
this.widgetService.getBundleWidgetTypes(bundleAlias, isSystem).subscribe( this.widgetService.getBundleWidgetTypes(bundleAlias, isSystem).subscribe(
(widgetTypes) => { (widgetTypes) => {
widgetTypes = widgetTypes.sort((a, b) => { widgetTypes = widgetTypes.sort((a, b) => {
@ -486,6 +489,7 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
} }
} }
this.widgetsLoaded = true; this.widgetsLoaded = true;
this.cd.markForCheck();
} }
); );
} }

2
ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.ts

@ -59,7 +59,7 @@ export class DeviceCredentialsMqttBasicComponent implements ControlValueAccessor
constructor(public fb: FormBuilder) { constructor(public fb: FormBuilder) {
this.deviceCredentialsMqttFormGroup = this.fb.group({ this.deviceCredentialsMqttFormGroup = this.fb.group({
clientId: [null, [Validators.pattern(/^[A-Za-z0-9]+$/)]], clientId: [null],
userName: [null], userName: [null],
password: [null] password: [null]
}, {validators: this.atLeastOne(Validators.required, ['clientId', 'userName'])}); }, {validators: this.atLeastOne(Validators.required, ['clientId', 'userName'])});

3
ui-ngx/src/app/modules/home/components/entity/entities-table.component.html

@ -85,7 +85,8 @@
matTooltip="{{ actionDescriptor.name }}" matTooltip="{{ actionDescriptor.name }}"
matTooltipPosition="above" matTooltipPosition="above"
(click)="actionDescriptor.onAction($event)"> (click)="actionDescriptor.onAction($event)">
<mat-icon>{{actionDescriptor.icon}}</mat-icon> <mat-icon *ngIf="actionDescriptor.isMdiIcon" [svgIcon]="actionDescriptor.icon"></mat-icon>
<mat-icon *ngIf="!actionDescriptor.isMdiIcon">{{actionDescriptor.icon}}</mat-icon>
</button> </button>
<button mat-icon-button [disabled]="isLoading$ | async" (click)="updateData()" <button mat-icon-button [disabled]="isLoading$ | async" (click)="updateData()"
matTooltip="{{ 'action.refresh' | translate }}" matTooltip="{{ 'action.refresh' | translate }}"

19
ui-ngx/src/app/modules/home/components/event/event-table-config.ts

@ -120,6 +120,15 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
this.updateFilterColumns(); this.updateFilterColumns();
this.headerActionDescriptors.push({ this.headerActionDescriptors.push({
name: this.translate.instant('event.clear-filter'),
icon: 'filter-variant-remove',
isMdiIcon: true,
isEnabled: () => !isEqual(this.filterParams, {}),
onAction: ($event) => {
this.clearFiter($event);
}
},
{
name: this.translate.instant('event.events-filter'), name: this.translate.instant('event.events-filter'),
icon: 'filter_list', icon: 'filter_list',
isEnabled: () => true, isEnabled: () => true,
@ -316,6 +325,16 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
} }
} }
private clearFiter($event) {
if ($event) {
$event.stopPropagation();
}
this.filterParams = {};
this.table.paginator.pageIndex = 0;
this.table.updateData();
}
private editEventFilter($event: MouseEvent) { private editEventFilter($event: MouseEvent) {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();

1
ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts

@ -187,6 +187,7 @@ export class AddDeviceProfileDialogComponent extends
name: this.deviceProfileDetailsFormGroup.get('name').value, name: this.deviceProfileDetailsFormGroup.get('name').value,
type: this.deviceProfileDetailsFormGroup.get('type').value, type: this.deviceProfileDetailsFormGroup.get('type').value,
image: this.deviceProfileDetailsFormGroup.get('image').value, image: this.deviceProfileDetailsFormGroup.get('image').value,
defaultQueueName: this.deviceProfileDetailsFormGroup.get('defaultQueueName').value,
transportType: this.transportConfigFormGroup.get('transportType').value, transportType: this.transportConfigFormGroup.get('transportType').value,
provisionType: deviceProvisionConfiguration.type, provisionType: deviceProvisionConfiguration.type,
provisionDeviceKey, provisionDeviceKey,

2
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html

@ -26,7 +26,7 @@
</span> </span>
<span *ngIf="alarmRuleFormGroup.get('alarmDetails').value" class="tb-alarm-rule-details" <span *ngIf="alarmRuleFormGroup.get('alarmDetails').value" class="tb-alarm-rule-details"
(click)="openEditDetailsDialog($event)" (click)="openEditDetailsDialog($event)"
[innerHTML]="alarmRuleFormGroup.get('alarmDetails').value"></span> [innerHTML]="alarmDetailsText"></span>
<button mat-icon-button color="primary" <button mat-icon-button color="primary"
type="button" type="button"
(click)="openEditDetailsDialog($event)" (click)="openEditDetailsDialog($event)"

7
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts

@ -35,6 +35,7 @@ import {
} from '@home/components/profile/alarm/edit-alarm-details-dialog.component'; } from '@home/components/profile/alarm/edit-alarm-details-dialog.component';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { DashboardId } from '@shared/models/id/dashboard-id'; import { DashboardId } from '@shared/models/id/dashboard-id';
import { UtilsService } from '@core/services/utils.service';
@Component({ @Component({
selector: 'tb-alarm-rule', selector: 'tb-alarm-rule',
@ -79,6 +80,7 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
private propagateChange = (v: any) => { }; private propagateChange = (v: any) => { };
constructor(private dialog: MatDialog, constructor(private dialog: MatDialog,
private utils: UtilsService,
private fb: FormBuilder) { private fb: FormBuilder) {
} }
@ -146,6 +148,11 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
}; };
} }
get alarmDetailsText(): string {
const alarmType = this.alarmRuleFormGroup.get('alarmDetails').value;
return this.utils.customTranslation(alarmType, alarmType);
}
private updateModel() { private updateModel() {
const value = this.alarmRuleFormGroup.value; const value = this.alarmRuleFormGroup.value;
if (this.modelValue) { if (this.modelValue) {

2
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html

@ -20,7 +20,7 @@
<div fxFlex fxLayout="row" fxLayoutAlign="start center"> <div fxFlex fxLayout="row" fxLayoutAlign="start center">
<mat-panel-title> <mat-panel-title>
<div fxLayout="row" fxFlex fxLayoutAlign="start center"> <div fxLayout="row" fxFlex fxLayoutAlign="start center">
{{ alarmFormGroup.get('alarmType').value }} {{ alarmTypeTitle }}
</div> </div>
</mat-panel-title> </mat-panel-title>
<span fxFlex></span> <span fxFlex></span>

6
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts

@ -30,6 +30,7 @@ import { MatDialog } from '@angular/material/dialog';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material/chips'; import { MatChipInputEvent } from '@angular/material/chips';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { UtilsService } from '@core/services/utils.service';
@Component({ @Component({
selector: 'tb-device-profile-alarm', selector: 'tb-device-profile-alarm',
@ -72,6 +73,7 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
private propagateChangePending = false; private propagateChangePending = false;
constructor(private dialog: MatDialog, constructor(private dialog: MatDialog,
private utils: UtilsService,
private fb: FormBuilder) { private fb: FormBuilder) {
} }
@ -185,6 +187,10 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
} }
} }
get alarmTypeTitle(): string {
const alarmType = this.alarmFormGroup.get('alarmType').value;
return this.utils.customTranslation(alarmType, alarmType);
}
private updateModel() { private updateModel() {
const value = this.alarmFormGroup.value; const value = this.alarmFormGroup.value;

6
ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.html

@ -29,14 +29,14 @@
<tb-time-unit-select [labelText]="'device-profile.edrx-cycle'" <tb-time-unit-select [labelText]="'device-profile.edrx-cycle'"
[requiredText]="'device-profile.edrx-cycle-required'" [requiredText]="'device-profile.edrx-cycle-required'"
[patternText]="'device-profile.edrx-cycle-pattern'" [patternText]="'device-profile.edrx-cycle-pattern'"
[minTime]="20480" [minTime]="5120"
[minText]="'device-profile.edrx-cycle-min'" [minText]="'device-profile.edrx-cycle-min'"
formControlName="edrxCycle"> formControlName="edrxCycle">
</tb-time-unit-select> </tb-time-unit-select>
<tb-time-unit-select [labelText]="'device-profile.paging-transmission-window'" <tb-time-unit-select [labelText]="'device-profile.paging-transmission-window'"
[requiredText]="'device-profile.paging-transmission-window-required'" [requiredText]="'device-profile.paging-transmission-window-required'"
[patternText]="'device-profile.paging-transmission-window-pattern'" [patternText]="'device-profile.paging-transmission-window-pattern'"
[minTime]="2000" [minTime]="1280"
[minText]="'device-profile.paging-transmission-window-min'" [minText]="'device-profile.paging-transmission-window-min'"
formControlName="pagingTransmissionWindow"> formControlName="pagingTransmissionWindow">
</tb-time-unit-select> </tb-time-unit-select>
@ -45,7 +45,7 @@
[labelText]="'device-profile.psm-activity-timer'" [labelText]="'device-profile.psm-activity-timer'"
[requiredText]="'device-profile.psm-activity-timer-required'" [requiredText]="'device-profile.psm-activity-timer-required'"
[patternText]="'device-profile.psm-activity-timer-pattern'" [patternText]="'device-profile.psm-activity-timer-pattern'"
[minTime]="2000" [minTime]="1280"
[minText]="'device-profile.psm-activity-timer-min'" [minText]="'device-profile.psm-activity-timer-min'"
formControlName="psmActivityTimer"> formControlName="psmActivityTimer">
</tb-time-unit-select> </tb-time-unit-select>

5
ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts

@ -149,7 +149,8 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
entityType: EntityType.DEVICE, entityType: EntityType.DEVICE,
id: '123' id: '123'
}, },
name: 'TEST DEV1' name: 'TEST DEV1',
label: ''
}, },
data: {}, data: {},
level: 2 level: 2
@ -289,7 +290,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
const descriptors = this.ctx.actionsApi.getActionDescriptors('nodeSelected'); const descriptors = this.ctx.actionsApi.getActionDescriptors('nodeSelected');
if (descriptors.length) { if (descriptors.length) {
const entity = selectedNode.data.nodeCtx.entity; const entity = selectedNode.data.nodeCtx.entity;
this.ctx.actionsApi.handleWidgetAction(event, descriptors[0], entity.id, entity.name, { nodeCtx: selectedNode.data.nodeCtx }); this.ctx.actionsApi.handleWidgetAction(event, descriptors[0], entity.id, entity.name,{ nodeCtx: selectedNode.data.nodeCtx }, entity.label);
} }
} }
} }

28
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts

@ -145,6 +145,7 @@ export interface TbFlotComparisonSettings {
comparisonEnabled: boolean; comparisonEnabled: boolean;
timeForComparison: ComparisonDuration; timeForComparison: ComparisonDuration;
xaxisSecond: TbFlotSecondXAxisSettings; xaxisSecond: TbFlotSecondXAxisSettings;
comparisonCustomIntervalValue?: number;
} }
export interface TbFlotThresholdsSettings { export interface TbFlotThresholdsSettings {
@ -182,6 +183,7 @@ export interface TbFlotPieSettings {
color: string; color: string;
width: number; width: number;
}; };
showTooltip: boolean,
showLabels: boolean; showLabels: boolean;
fontColor: string; fontColor: string;
fontSize: number; fontSize: number;
@ -311,6 +313,12 @@ export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema {
default: false default: false
}; };
properties.showTooltip = {
title: 'Show tooltip',
type: 'boolean',
default: true
};
properties.grid = { properties.grid = {
title: 'Grid settings', title: 'Grid settings',
type: 'object', type: 'object',
@ -460,6 +468,7 @@ export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema {
type: 'javascript' type: 'javascript'
}); });
schema.form.push('hideZeros'); schema.form.push('hideZeros');
schema.form.push('showTooltip');
schema.form.push({ schema.form.push({
key: 'grid', key: 'grid',
items: [ items: [
@ -546,6 +555,11 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = {
type: 'string', type: 'string',
default: 'previousInterval' default: 'previousInterval'
}, },
comparisonCustomIntervalValue: {
title: 'Custom interval value (ms)',
type: 'number',
default: 7200000
},
xaxisSecond: { xaxisSecond: {
title: 'Second X axis', title: 'Second X axis',
type: 'object', type: 'object',
@ -596,9 +610,17 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = {
{ {
value: 'years', value: 'years',
label: 'Year ago' label: 'Year ago'
},
{
value: 'customInterval',
label: 'Custom interval'
} }
] ]
}, },
{
key: 'comparisonCustomIntervalValue',
condition: 'model.timeForComparison === "customInterval"'
},
{ {
key: 'xaxisSecond', key: 'xaxisSecond',
items: [ items: [
@ -726,6 +748,11 @@ export const flotPieSettingsSchema: JsonSettingsSchema = {
} }
} }
}, },
showTooltip: {
title: 'Show Tooltip',
type: 'boolean',
default: true,
},
showLabels: { showLabels: {
title: 'Show labels', title: 'Show labels',
type: 'boolean', type: 'boolean',
@ -759,6 +786,7 @@ export const flotPieSettingsSchema: JsonSettingsSchema = {
'stroke.width' 'stroke.width'
] ]
}, },
'showTooltip',
'showLabels', 'showLabels',
{ {
key: 'fontColor', key: 'fontColor',

10
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts

@ -77,7 +77,7 @@ export class TbFlot {
private settings: TbFlotSettings; private settings: TbFlotSettings;
private comparisonEnabled: boolean; private comparisonEnabled: boolean;
private readonly tooltip: JQuery<any>; private tooltip: JQuery<any>;
private readonly yAxisTickFormatter: TbFlotTicksFormatterFunction; private readonly yAxisTickFormatter: TbFlotTicksFormatterFunction;
private readonly yaxis: TbFlotAxisOptions; private readonly yaxis: TbFlotAxisOptions;
@ -119,6 +119,7 @@ export class TbFlot {
private mouseleaveHandler = this.onFlotMouseLeave.bind(this); private mouseleaveHandler = this.onFlotMouseLeave.bind(this);
private flotClickHandler = this.onFlotClick.bind(this); private flotClickHandler = this.onFlotClick.bind(this);
private readonly showTooltip: boolean;
private readonly animatedPie: boolean; private readonly animatedPie: boolean;
private pieDataAnimationDuration: number; private pieDataAnimationDuration: number;
private pieData: DatasourceData[]; private pieData: DatasourceData[];
@ -148,8 +149,9 @@ export class TbFlot {
this.chartType = this.chartType || 'line'; this.chartType = this.chartType || 'line';
this.settings = ctx.settings as TbFlotSettings; this.settings = ctx.settings as TbFlotSettings;
this.utils = this.ctx.$injector.get(UtilsService); this.utils = this.ctx.$injector.get(UtilsService);
this.tooltip = $('#flot-series-tooltip'); this.showTooltip = isDefined(this.settings.showTooltip) ? this.settings.showTooltip : true;
if (this.tooltip.length === 0) { this.tooltip = this.showTooltip ? $('#flot-series-tooltip') : null;
if (this.tooltip?.length === 0) {
this.tooltip = this.createTooltipElement(); this.tooltip = this.createTooltipElement();
} }
@ -1169,7 +1171,7 @@ export class TbFlot {
} }
private onFlotHover(e: any, pos: JQueryPlotPoint, item: TbFlotPlotItem) { private onFlotHover(e: any, pos: JQueryPlotPoint, item: TbFlotPlotItem) {
if (!this.plot) { if (!this.plot || !this.tooltip) {
return; return;
} }
if ((!this.tooltipIndividual || item) && !this.ctx.isEdit) { if ((!this.tooltipIndividual || item) && !this.ctx.isEdit) {

3
ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts

@ -162,7 +162,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
private initializeConfig() { private initializeConfig() {
if (this.settings.widgetTitle && this.settings.widgetTitle.length) { if (this.settings.widgetTitle && this.settings.widgetTitle.length) {
this.ctx.widgetTitle = this.utils.customTranslation(this.settings.widgetTitle, this.settings.widgetTitle); const titlePatternText = this.utils.customTranslation(this.settings.widgetTitle, this.settings.widgetTitle);
this.ctx.widgetTitle = createLabelFromDatasource(this.datasources[0], titlePatternText);
} else { } else {
this.ctx.widgetTitle = this.ctx.widgetConfig.title; this.ctx.widgetTitle = this.ctx.widgetConfig.title;
} }

3
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -908,7 +908,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
warnOnPageDataOverflow: this.typeParameters.warnOnPageDataOverflow, warnOnPageDataOverflow: this.typeParameters.warnOnPageDataOverflow,
ignoreDataUpdateOnIntervalTick: this.typeParameters.ignoreDataUpdateOnIntervalTick, ignoreDataUpdateOnIntervalTick: this.typeParameters.ignoreDataUpdateOnIntervalTick,
comparisonEnabled: comparisonSettings.comparisonEnabled, comparisonEnabled: comparisonSettings.comparisonEnabled,
timeForComparison: comparisonSettings.timeForComparison timeForComparison: comparisonSettings.timeForComparison,
comparisonCustomIntervalValue: comparisonSettings.comparisonCustomIntervalValue
}; };
if (this.widget.type === widgetType.alarm) { if (this.widget.type === widgetType.alarm) {
options.alarmSource = deepClone(this.widget.config.alarmSource); options.alarmSource = deepClone(this.widget.config.alarmSource);

1
ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts

@ -289,6 +289,7 @@ export class DeviceWizardDialogComponent extends
const deviceProfile: DeviceProfile = { const deviceProfile: DeviceProfile = {
name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value, name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value,
type: DeviceProfileType.DEFAULT, type: DeviceProfileType.DEFAULT,
defaultQueueName: this.deviceWizardFormGroup.get('defaultQueueName').value,
transportType: this.transportConfigFormGroup.get('transportType').value, transportType: this.transportConfigFormGroup.get('transportType').value,
provisionType: deviceProvisionConfiguration.type, provisionType: deviceProvisionConfiguration.type,
provisionDeviceKey, provisionDeviceKey,

1
ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts

@ -73,6 +73,7 @@ export interface GroupActionDescriptor<T extends BaseData<HasId>> {
export interface HeaderActionDescriptor { export interface HeaderActionDescriptor {
name: string; name: string;
icon: string; icon: string;
isMdiIcon?: boolean;
isEnabled: () => boolean; isEnabled: () => boolean;
onAction: ($event: MouseEvent) => void; onAction: ($event: MouseEvent) => void;
} }

6
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -78,7 +78,7 @@ import { PageLink } from '@shared/models/page/page-link';
import { SortOrder } from '@shared/models/page/sort-order'; import { SortOrder } from '@shared/models/page/sort-order';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { map, mergeMap } from 'rxjs/operators'; import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
export interface IWidgetAction { export interface IWidgetAction {
name: string; name: string;
@ -254,7 +254,9 @@ export class WidgetContext {
forkJoin, forkJoin,
of, of,
map, map,
mergeMap mergeMap,
switchMap,
catchError
}; };
showSuccessToast(message: string, duration: number = 1000, showSuccessToast(message: string, duration: number = 1000,

11
ui-ngx/src/app/modules/home/pages/user/user.component.html

@ -53,6 +53,17 @@
[fxShow]="!hideDelete() && !isEdit"> [fxShow]="!hideDelete() && !isEdit">
{{'user.delete' | translate }} {{'user.delete' | translate }}
</button> </button>
<div fxLayout="row" fxLayout.xs="column">
<button mat-raised-button
ngxClipboard
(cbOnSuccess)="onUserIdCopied($event)"
[cbContent]="entity?.id?.id"
[disabled]="(isLoading$ | async)"
[fxShow]="!isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>user.copyId</span>
</button>
</div>
</div> </div>
<div class="mat-padding" fxLayout="column"> <div class="mat-padding" fxLayout="column">
<form [formGroup]="entityForm"> <form [formGroup]="entityForm">

17
ui-ngx/src/app/modules/home/pages/user/user.component.ts

@ -25,6 +25,8 @@ import { map } from 'rxjs/operators';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { isDefinedAndNotNull, isUndefined } from '@core/utils'; import { isDefinedAndNotNull, isUndefined } from '@core/utils';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
import { ActionNotificationShow } from '@app/core/notification/notification.actions';
import { TranslateService } from '@ngx-translate/core';
@Component({ @Component({
selector: 'tb-user', selector: 'tb-user',
@ -44,7 +46,8 @@ export class UserComponent extends EntityComponent<User> {
@Optional() @Inject('entity') protected entityValue: User, @Optional() @Inject('entity') protected entityValue: User,
@Optional() @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<User>, @Optional() @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<User>,
public fb: FormBuilder, public fb: FormBuilder,
protected cd: ChangeDetectorRef) { protected cd: ChangeDetectorRef,
protected translate: TranslateService) {
super(store, fb, entityValue, entitiesTableConfigValue, cd); super(store, fb, entityValue, entitiesTableConfigValue, cd);
} }
@ -100,4 +103,16 @@ export class UserComponent extends EntityComponent<User> {
isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true}}); isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true}});
} }
onUserIdCopied($event) {
this.store.dispatch(new ActionNotificationShow(
{
message: this.translate.instant('user.idCopiedMessage'),
type: 'success',
duration: 750,
verticalPosition: 'bottom',
horizontalPosition: 'right'
}
))
}
} }

9
ui-ngx/src/app/shared/components/json-form/json-form.component.ts

@ -15,6 +15,7 @@
/// ///
import { import {
ChangeDetectorRef,
Component, Component,
ElementRef, ElementRef,
forwardRef, forwardRef,
@ -113,7 +114,8 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato
constructor(public elementRef: ElementRef, constructor(public elementRef: ElementRef,
private translate: TranslateService, private translate: TranslateService,
private dialogs: DialogService, private dialogs: DialogService,
protected store: Store<AppState>) { protected store: Store<AppState>,
private cd: ChangeDetectorRef) {
} }
ngOnInit(): void { ngOnInit(): void {
@ -216,8 +218,8 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato
} }
private onIconClick(key: (string | number)[], private onIconClick(key: (string | number)[],
val: string, val: string,
iconSelectedFn: (icon: string) => void) { iconSelectedFn: (icon: string) => void) {
this.dialogs.materialIconPicker(val).subscribe((icon) => { this.dialogs.materialIconPicker(val).subscribe((icon) => {
if (icon && iconSelectedFn) { if (icon && iconSelectedFn) {
iconSelectedFn(icon); iconSelectedFn(icon);
@ -229,6 +231,7 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato
this.targetFullscreenElement = element; this.targetFullscreenElement = element;
this.isFullscreen = !this.isFullscreen; this.isFullscreen = !this.isFullscreen;
this.fullscreenFinishFn = fullscreenFinishFn; this.fullscreenFinishFn = fullscreenFinishFn;
this.cd.markForCheck();
} }
onFullscreenChanged(fullscreen: boolean) { onFullscreenChanged(fullscreen: boolean) {

2
ui-ngx/src/app/shared/components/led-light.component.ts

@ -73,7 +73,7 @@ export class LedLightComponent implements OnInit, AfterViewInit, OnChanges {
for (const propName of Object.keys(changes)) { for (const propName of Object.keys(changes)) {
const change = changes[propName]; const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) { if (!change.firstChange && change.currentValue !== change.previousValue) {
if (propName === 'enabled') { if (propName === 'enabled' && this.circleElement) {
this.draw(); this.draw();
} else if (propName === 'size') { } else if (propName === 'size') {
this.update(); this.update();

7
ui-ngx/src/app/shared/components/queue/queue-type-list.component.html

@ -34,7 +34,12 @@
#queueAutocomplete="matAutocomplete" #queueAutocomplete="matAutocomplete"
[displayWith]="displayQueueFn"> [displayWith]="displayQueueFn">
<mat-option *ngFor="let queue of filteredQueues | async" [value]="queue"> <mat-option *ngFor="let queue of filteredQueues | async" [value]="queue">
<span [innerHTML]="queue | highlight:searchText"></span> <span [innerHTML]="queue.queueName | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="!(filteredQueues | async)?.length" [value]="null">
<span>
{{ translate.get('queue.no-queues-matching', {queue: searchText}) | async }}
</span>
</mat-option> </mat-option>
</mat-autocomplete> </mat-autocomplete>
<mat-error *ngIf="queueFormGroup.get('queue').hasError('required')"> <mat-error *ngIf="queueFormGroup.get('queue').hasError('required')">

52
ui-ngx/src/app/shared/components/queue/queue-type-list.component.ts

@ -34,6 +34,10 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { QueueService } from '@core/http/queue.service'; import { QueueService } from '@core/http/queue.service';
import { ServiceType } from '@shared/models/queue.models'; import { ServiceType } from '@shared/models/queue.models';
interface Queue {
queueName: string;
}
@Component({ @Component({
selector: 'tb-queue-type-list', selector: 'tb-queue-type-list',
templateUrl: './queue-type-list.component.html', templateUrl: './queue-type-list.component.html',
@ -48,7 +52,7 @@ export class QueueTypeListComponent implements ControlValueAccessor, OnInit, Aft
queueFormGroup: FormGroup; queueFormGroup: FormGroup;
modelValue: string | null; modelValue: Queue | null;
private requiredValue: boolean; private requiredValue: boolean;
get required(): boolean { get required(): boolean {
@ -67,9 +71,9 @@ export class QueueTypeListComponent implements ControlValueAccessor, OnInit, Aft
@ViewChild('queueInput', {static: true}) queueInput: ElementRef<HTMLInputElement>; @ViewChild('queueInput', {static: true}) queueInput: ElementRef<HTMLInputElement>;
filteredQueues: Observable<Array<string>>; filteredQueues: Observable<Array<Queue>>;
queues: Observable<Array<string>>; queues: Observable<Array<Queue>>;
searchText = ''; searchText = '';
@ -99,9 +103,15 @@ export class QueueTypeListComponent implements ControlValueAccessor, OnInit, Aft
debounceTime(150), debounceTime(150),
distinctUntilChanged(), distinctUntilChanged(),
tap(value => { tap(value => {
this.updateView(value); let modelValue;
if (typeof value === 'string' || !value) {
modelValue = null;
} else {
modelValue = value;
}
this.updateView(modelValue);
}), }),
map(value => value ? value : ''), map(value => value ? (typeof value === 'string' ? value : value.queueName) : ''),
switchMap(queue => this.fetchQueues(queue) ) switchMap(queue => this.fetchQueues(queue) )
); );
} }
@ -123,8 +133,8 @@ export class QueueTypeListComponent implements ControlValueAccessor, OnInit, Aft
writeValue(value: string | null): void { writeValue(value: string | null): void {
this.searchText = ''; this.searchText = '';
this.modelValue = value; this.modelValue = value ? { queueName: value } : null;
this.queueFormGroup.get('queue').patchValue(value, {emitEvent: false}); this.queueFormGroup.get('queue').patchValue(this.modelValue, {emitEvent: false});
this.dirty = true; this.dirty = true;
} }
@ -135,42 +145,42 @@ export class QueueTypeListComponent implements ControlValueAccessor, OnInit, Aft
} }
} }
updateView(value: string | null) { updateView(value: Queue | null) {
if (this.modelValue !== value) { if (this.modelValue !== value) {
this.modelValue = value; this.modelValue = value;
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue ? this.modelValue.queueName : null);
} }
} }
displayQueueFn(queue?: string): string | undefined { displayQueueFn(queue?: Queue): string | undefined {
return queue ? queue : undefined; return queue ? queue.queueName : undefined;
} }
fetchQueues(searchText?: string): Observable<Array<string>> { fetchQueues(searchText?: string): Observable<Array<Queue>> {
this.searchText = searchText; this.searchText = searchText;
return this.getQueues().pipe( return this.getQueues().pipe(
catchError(() => of([])), catchError(() => of([] as Array<Queue>)),
map(queues => { map(queues => {
const result = queues.filter( queue => { const result = queues.filter( queue => {
return searchText ? queue.toUpperCase().startsWith(searchText.toUpperCase()) : true; return searchText ? queue.queueName.toUpperCase().startsWith(searchText.toUpperCase()) : true;
}); });
if (result.length) { if (result.length) {
if (searchText && searchText.length && result.indexOf(searchText) === -1) { result.sort((q1, q2) => q1.queueName.localeCompare(q2.queueName));
result.push(searchText);
}
result.sort();
} else if (searchText && searchText.length) {
result.push(searchText);
} }
return result; return result;
}) })
); );
} }
getQueues(): Observable<Array<string>> { getQueues(): Observable<Array<Queue>> {
if (!this.queues) { if (!this.queues) {
this.queues = this.queueService. this.queues = this.queueService.
getTenantQueuesByServiceType(this.queueType, {ignoreLoading: true}).pipe( getTenantQueuesByServiceType(this.queueType, {ignoreLoading: true}).pipe(
map((queues) => {
return queues.map((queueName) => {
return { queueName };
});
}),
publishReplay(1), publishReplay(1),
refCount() refCount()
); );

6
ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss

@ -79,4 +79,10 @@
} }
} }
} }
.mat-slider-horizontal .mat-slider-thumb-label {
width: 38px;
height: 38px;
top: -46px;
right: -19px;
}
} }

3
ui-ngx/src/app/shared/components/widgets-bundle-select.component.ts

@ -140,6 +140,9 @@ export class WidgetsBundleSelectComponent implements ControlValueAccessor, OnIni
this.widgetsBundle = found; this.widgetsBundle = found;
this.updateView(); this.updateView();
} }
} else if (this.widgetsBundle) {
this.widgetsBundle = null;
this.updateView();
} }
} }

15
ui-ngx/src/app/shared/models/time/time.models.ts

@ -15,7 +15,7 @@
/// ///
import { TimeService } from '@core/services/time.service'; import { TimeService } from '@core/services/time.service';
import { deepClone, isDefined, isUndefined } from '@app/core/utils'; import { deepClone, isDefined, isNumeric, isUndefined } from '@app/core/utils';
import * as moment_ from 'moment'; import * as moment_ from 'moment';
import * as momentTz from 'moment-timezone'; import * as momentTz from 'moment-timezone';
@ -28,7 +28,7 @@ export const DAY = 24 * HOUR;
export const WEEK = 7 * DAY; export const WEEK = 7 * DAY;
export const YEAR = DAY * 365; export const YEAR = DAY * 365;
export type ComparisonDuration = moment_.unitOfTime.DurationConstructor | 'previousInterval'; export type ComparisonDuration = moment_.unitOfTime.DurationConstructor | 'previousInterval' | 'customInterval';
export enum TimewindowType { export enum TimewindowType {
REALTIME, REALTIME,
@ -640,7 +640,7 @@ export function calculateIntervalComparisonEndTime(interval: QuickTimeInterval,
} }
export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow, export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow,
timeUnit: ComparisonDuration): SubscriptionTimewindow { timeUnit: ComparisonDuration, customIntervalValue: number): SubscriptionTimewindow {
const timewindowForComparison: SubscriptionTimewindow = { const timewindowForComparison: SubscriptionTimewindow = {
fixedWindow: null, fixedWindow: null,
realtimeWindowMs: null, realtimeWindowMs: null,
@ -667,6 +667,15 @@ export function createTimewindowForComparison(subscriptionTimewindow: Subscripti
endTimeMs = subscriptionTimewindow.fixedWindow.startTimeMs; endTimeMs = subscriptionTimewindow.fixedWindow.startTimeMs;
startTimeMs = endTimeMs - timeInterval; startTimeMs = endTimeMs - timeInterval;
} }
} else if (timeUnit === 'customInterval') {
if (isNumeric(customIntervalValue) && isFinite(customIntervalValue) && customIntervalValue > 0) {
const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
endTimeMs = subscriptionTimewindow.fixedWindow.endTimeMs - Math.round(customIntervalValue);
startTimeMs = endTimeMs - timeInterval;
} else {
endTimeMs = subscriptionTimewindow.fixedWindow.endTimeMs;
startTimeMs = subscriptionTimewindow.fixedWindow.startTimeMs;
}
} else { } else {
const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf(); endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf();

1
ui-ngx/src/app/shared/models/widget.models.ts

@ -467,6 +467,7 @@ export interface WidgetActionDescriptor extends CustomActionDescriptor {
export interface WidgetComparisonSettings { export interface WidgetComparisonSettings {
comparisonEnabled?: boolean; comparisonEnabled?: boolean;
timeForComparison?: moment_.unitOfTime.DurationConstructor; timeForComparison?: moment_.unitOfTime.DurationConstructor;
comparisonCustomIntervalValue?: number;
} }
export interface WidgetConfig { export interface WidgetConfig {

16
ui-ngx/src/assets/locale/locale.constant-de_DE.json

@ -6,7 +6,9 @@
"access-forbidden": "Keine Zugangsberechtigung", "access-forbidden": "Keine Zugangsberechtigung",
"access-forbidden-text": "Sie haben keine Zugangsberechtigung für diesen Bereich!<br/>Versuchen Sie sich mit einem anderen Benutzer anzumelden um Zugriff auf diesen Bereich zu bekommen.", "access-forbidden-text": "Sie haben keine Zugangsberechtigung für diesen Bereich!<br/>Versuchen Sie sich mit einem anderen Benutzer anzumelden um Zugriff auf diesen Bereich zu bekommen.",
"refresh-token-expired": "Sitzung ist abgelaufen", "refresh-token-expired": "Sitzung ist abgelaufen",
"refresh-token-failed": "Sitzung kann nicht aktualisiert werden" "refresh-token-failed": "Sitzung kann nicht aktualisiert werden",
"permission-denied": "Zugriff verweigert",
"permission-denied-text": "Sie haben keine Berechtigung, um diesen Vorgang auszuführen!"
}, },
"action": { "action": {
"activate": "Aktivieren", "activate": "Aktivieren",
@ -21,6 +23,7 @@
"no": "Nein", "no": "Nein",
"update": "Aktualisieren", "update": "Aktualisieren",
"remove": "Löschen", "remove": "Löschen",
"select": "Auswählen",
"search": "Suche", "search": "Suche",
"clear-search": "Suchanfrage löschen", "clear-search": "Suchanfrage löschen",
"assign": "Zuordnen", "assign": "Zuordnen",
@ -48,7 +51,13 @@
"paste-reference": "Zeichen einfügen", "paste-reference": "Zeichen einfügen",
"import": "Importieren", "import": "Importieren",
"export": "Exportieren", "export": "Exportieren",
"share-via": "Teilen mit {{provider}}" "share-via": "Teilen mit {{provider}}",
"continue": "Fortsetzen",
"discard-changes": "Änderungen verwerfen",
"download": "Download",
"next-with-label": "Nächste: {{label}}",
"read-more": "Mehr dazu",
"hide": "Verstecken"
}, },
"aggregation": { "aggregation": {
"aggregation": "Aggregation", "aggregation": "Aggregation",
@ -65,12 +74,15 @@
"admin": { "admin": {
"general": "Allgemein", "general": "Allgemein",
"general-settings": "Allgemeine Einstellungen", "general-settings": "Allgemeine Einstellungen",
"home-settings": "Home Einstellungen",
"outgoing-mail": "E-Mail Versand", "outgoing-mail": "E-Mail Versand",
"outgoing-mail-settings": "Konfiguration des Postausgangsservers", "outgoing-mail-settings": "Konfiguration des Postausgangsservers",
"system-settings": "Systemeinstellungen", "system-settings": "Systemeinstellungen",
"test-mail-sent": "Test E-Mail wurde erfolgreich versendet!", "test-mail-sent": "Test E-Mail wurde erfolgreich versendet!",
"base-url": "Basis-URL", "base-url": "Basis-URL",
"base-url-required": "Basis-URL ist erforderlich.", "base-url-required": "Basis-URL ist erforderlich.",
"prohibit-different-url": "Prohibit to use hostname from the client request headers",
"prohibit-different-url-hint": "This setting should be enabled for production environments. May cause security issues when disabled",
"mail-from": "E-Mail von", "mail-from": "E-Mail von",
"mail-from-required": "E-Mail von ist erforderlich.", "mail-from-required": "E-Mail von ist erforderlich.",
"smtp-protocol": "SMTP Protokoll", "smtp-protocol": "SMTP Protokoll",

13
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -1082,7 +1082,7 @@
"default-rule-chain": "Default rule chain", "default-rule-chain": "Default rule chain",
"mobile-dashboard": "Mobile dashboard", "mobile-dashboard": "Mobile dashboard",
"mobile-dashboard-hint": "Used by mobile application as a device details dashboard", "mobile-dashboard-hint": "Used by mobile application as a device details dashboard",
"select-queue-hint": "Select from a drop-down list or add a custom name.", "select-queue-hint": "Select from a drop-down list.",
"delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?", "delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?",
"delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.", "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.",
"delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?", "delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?",
@ -1822,7 +1822,8 @@
"all-events": "All", "all-events": "All",
"has-error": "Has error", "has-error": "Has error",
"entity-id": "Entity Id", "entity-id": "Entity Id",
"entity-type": "Entity type" "entity-type": "Entity type",
"clear-filter": "Clear Filter"
}, },
"extension": { "extension": {
"extensions": "Extensions", "extensions": "Extensions",
@ -2263,6 +2264,7 @@
"total": "total", "total": "total",
"comparison-time-ago": { "comparison-time-ago": {
"previousInterval": "(previous interval)", "previousInterval": "(previous interval)",
"customInterval": "(custom interval)",
"days": "(day ago)", "days": "(day ago)",
"weeks": "(week ago)", "weeks": "(week ago)",
"months": "(month ago)", "months": "(month ago)",
@ -2569,7 +2571,8 @@
"queue": { "queue": {
"select_name": "Select queue name", "select_name": "Select queue name",
"name": "Queue Name", "name": "Queue Name",
"name_required": "Queue name is required!" "name_required": "Queue name is required!",
"no-queues-matching": "No queues matching '{{queue}}' were found."
}, },
"tenant": { "tenant": {
"tenant": "Tenant", "tenant": "Tenant",
@ -2807,7 +2810,9 @@
"disable-account": "Disable User Account", "disable-account": "Disable User Account",
"enable-account": "Enable User Account", "enable-account": "Enable User Account",
"enable-account-message": "User account was successfully enabled!", "enable-account-message": "User account was successfully enabled!",
"disable-account-message": "User account was successfully disabled!" "disable-account-message": "User account was successfully disabled!",
"copyId": "Copy user Id",
"idCopiedMessage": "User Id has been copied to clipboard"
}, },
"value": { "value": {
"type": "Value type", "type": "Value type",

2
ui-ngx/src/assets/locale/locale.constant-ko_KR.json

@ -959,7 +959,7 @@
"profile-configuration": "프로파일 설정", "profile-configuration": "프로파일 설정",
"transport-configuration": "전송 설정", "transport-configuration": "전송 설정",
"default-rule-chain": "기본 규칙 사슬", "default-rule-chain": "기본 규칙 사슬",
"select-queue-hint": "Select from a drop-down list or add a custom name.", "select-queue-hint": "Select from a drop-down list.",
"delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?", "delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?",
"delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.", "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.",
"delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 개 장치 프로파일} other {# 개 장치 프로파일} }?", "delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 개 장치 프로파일} other {# 개 장치 프로파일} }?",

4
ui-ngx/src/assets/locale/locale.constant-ru_RU.json

@ -1510,7 +1510,9 @@
"disable-account": "Отключить учетную запись пользователя", "disable-account": "Отключить учетную запись пользователя",
"enable-account": "Включить учетную запись пользователя", "enable-account": "Включить учетную запись пользователя",
"enable-account-message": "Учетная запись пользователя была успешно включена!", "enable-account-message": "Учетная запись пользователя была успешно включена!",
"disable-account-message": "Учетная запись пользователя была успешно отключена!" "disable-account-message": "Учетная запись пользователя была успешно отключена!",
"copyId": "Копировать ИД пользователя",
"idCopiedMessage": "ИД пользователя скопирован в буфер обмена"
}, },
"value": { "value": {
"type": "Тип значения", "type": "Тип значения",

2
ui-ngx/src/assets/locale/locale.constant-sl_SI.json

@ -959,7 +959,7 @@
"profile-configuration": "Profile configuration", "profile-configuration": "Profile configuration",
"transport-configuration": "Transport configuration", "transport-configuration": "Transport configuration",
"default-rule-chain": "Default rule chain", "default-rule-chain": "Default rule chain",
"select-queue-hint": "Select from a drop-down list or add a custom name.", "select-queue-hint": "Select from a drop-down list.",
"delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?", "delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?",
"delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.", "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.",
"delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?", "delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?",

4
ui-ngx/src/assets/locale/locale.constant-uk_UA.json

@ -2078,7 +2078,9 @@
"disable-account": "Вимкнути обліковий запис користувача", "disable-account": "Вимкнути обліковий запис користувача",
"enable-account": "Увімкнути обліковий запис користувача", "enable-account": "Увімкнути обліковий запис користувача",
"enable-account-message": "Обліковий запис користувача успішно увімкнено!", "enable-account-message": "Обліковий запис користувача успішно увімкнено!",
"disabled-account-message": "Обліковий запис користувача успішно вимкнено!" "disabled-account-message": "Обліковий запис користувача успішно вимкнено!",
"copyId": "Копіювати Id користувача",
"idCopiedMessage": "Id користувача було скопійовано в буфер обміну"
}, },
"value": { "value": {
"type": "Тип значення", "type": "Тип значення",

2
ui-ngx/src/tsconfig.app.json

@ -3,7 +3,7 @@
"compilerOptions": { "compilerOptions": {
"outDir": "../out-tsc/app", "outDir": "../out-tsc/app",
"types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify", "types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify",
"react", "react-dom", "jstree", "raphael", "canvas-gauges", "leaflet", "leaflet.markercluster"] "react", "react-dom", "jstree", "raphael", "canvas-gauges", "leaflet", "leaflet.markercluster", "leaflet-editable"]
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"fullTemplateTypeCheck": true "fullTemplateTypeCheck": true

275
ui-ngx/src/typings/leadflet-editable.d.ts

@ -1,275 +0,0 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import * as leaflet from 'leaflet';
declare module 'leaflet' {
/**
* Make geometries editable in Leaflet.
*
* This is not a plug and play UI, and will not. This is a minimal, lightweight, and fully extendable API to
* control editing of geometries. So you can easily build your own UI with your own needs and choices.
*/
interface EditableStatic {
new (map: Map, options: EditOptions): Editable;
}
/**
* Options to pass to L.Editable when instanciating.
*/
interface EditOptions extends leaflet.MapOptions{
/**
* Whether to create a L.Editable instance at map init or not.
*/
editable: boolean;
/**
* Class to be used when creating a new Polyline.
*/
polylineClass?: object;
/**
* Class to be used when creating a new Polygon.
*/
polygonClass?: object;
/**
* Class to be used when creating a new Marker.
*/
markerClass?: object;
/**
* CSS class to be added to the map container while drawing.
*/
drawingCSSClass?: string;
/**
* Layer used to store edit tools (vertex, line guide…).
*/
editLayer?: LayerGroup<leaflet.Layer>;
/**
* Default layer used to store drawn features (marker, polyline…).
*/
featuresLayer?: LayerGroup<Polyline|Polygon|Marker>;
/**
* Class to be used as vertex, for path editing.
*/
vertexMarkerClass?: object;
/**
* Class to be used as middle vertex, pulled by the user to create a new point in the middle of a path.
*/
middleMarkerClass?: object;
/**
* Class to be used as Polyline editor.
*/
polylineEditorClass?: object;
/**
* Class to be used as Polygon editor.
*/
polygonEditorClass?: object;
/**
* Class to be used as Marker editor.
*/
markerEditorClass?: object;
/**
* Options to be passed to the line guides.
*/
lineGuideOptions?: object;
/**
* Set this to true if you don't want middle markers.
*/
skipMiddleMarkers?: boolean;
}
/**
* Make geometries editable in Leaflet.
*
* This is not a plug and play UI, and will not. This is a minimal, lightweight, and fully extendable API to
* control editing of geometries. So you can easily build your own UI with your own needs and choices.
*/
interface Editable extends leaflet.Evented {
/**
* Options to pass to L.Editable when instanciating.
*/
options: EditOptions;
currentPolygon: Polyline|Polygon|Marker;
/**
* Start drawing a polyline. If latlng is given, a first point will be added. In any case, continuing on user
* click. If options is given, it will be passed to the polyline class constructor.
*/
startPolyline(latLng?: LatLng, options?: PolylineOptions): Polyline;
/**
* Start drawing a polygon. If latlng is given, a first point will be added. In any case, continuing on user
* click. If options is given, it will be passed to the polygon class constructor.
*/
startPolygon(latLng?: LatLng, options?: PolylineOptions): Polygon;
/**
* Start adding a marker. If latlng is given, the marker will be shown first at this point. In any case, it
* will follow the user mouse, and will have a final latlng on next click (or touch). If options is given,
* it will be passed to the marker class constructor.
*/
startMarker(latLng?: LatLng, options?: MarkerOptions): Marker;
/**
* When you need to stop any ongoing drawing, without needing to know which editor is active.
*/
stopDrawing(): void;
/**
* When you need to commit any ongoing drawing, without needing to know which editor is active.
*/
commitDrawing(): void;
}
let Editable: EditableStatic;
/**
* EditableMixin is included to L.Polyline, L.Polygon and L.Marker. It adds the following methods to them.
*
* When editing is enabled, the editor is accessible on the instance with the editor property.
*/
interface EditableMixin {
/**
* Enable editing, by creating an editor if not existing, and then calling enable on it.
*/
enableEdit(map: L.Map): any;
/**
* Disable editing, also remove the editor property reference.
*/
disableEdit(): void;
/**
* Enable or disable editing, according to current status.
*/
toggleEdit(): void;
/**
* Return true if current instance has an editor attached, and this editor is enabled.
*/
editEnabled(): boolean;
}
interface Map {
/**
* Options to pass to L.Editable when instanciating.
*/
editOptions: MapOptions;
/**
* L.Editable plugin instance.
*/
editTools: Editable;
}
// tslint:disable-next-line:no-empty-interface
interface Polyline extends EditableMixin {}
namespace Map {
interface MapOptions {
/**
* Whether to create a L.Editable instance at map init or not.
*/
editable?: boolean;
/**
* Options to pass to L.Editable when instanciating.
*/
editOptions?: MapOptions;
}
}
/**
* When editing a feature (marker, polyline…), an editor is attached to it. This editor basically knows
* how to handle the edition.
*/
interface BaseEditor {
/**
* Set up the drawing tools for the feature to be editable.
*/
enable(): MarkerEditor|PolylineEditor|PolygonEditor;
/**
* Remove editing tools.
*/
disable(): MarkerEditor|PolylineEditor|PolygonEditor;
}
/**
* Inherit from L.Editable.BaseEditor.
* Inherited by L.Editable.PolylineEditor and L.Editable.PolygonEditor.
*/
interface PathEditor extends BaseEditor {
/**
* Rebuild edit elements (vertex, middlemarker, etc.).
*/
reset(): void;
}
/**
* Inherit from L.Editable.PathEditor.
*/
interface PolylineEditor extends PathEditor {
/**
* Set up drawing tools to continue the line forward.
*/
continueForward(): void;
/**
* Set up drawing tools to continue the line backward.
*/
continueBackward(): void;
}
/**
* Inherit from L.Editable.PathEditor.
*/
interface PolygonEditor extends PathEditor {
/**
* Set up drawing tools for creating a new hole on the polygon. If the latlng param is given, a first
* point is created.
*/
newHole(latlng: LatLng): void;
}
/**
* Inherit from L.Editable.BaseEditor.
*/
// tslint:disable-next-line:no-empty-interface
interface MarkerEditor extends BaseEditor {}
interface Marker extends EditableMixin, MarkerEditor {}
interface Polyline extends EditableMixin, PolylineEditor {}
interface Polygon extends EditableMixin, PolygonEditor {}
function map(element: string | HTMLElement, options?: EditOptions): Map;
}

3
ui-ngx/tsconfig.json

@ -20,8 +20,7 @@
"src/typings/jquery.flot.typings.d.ts", "src/typings/jquery.flot.typings.d.ts",
"src/typings/jquery.jstree.typings.d.ts", "src/typings/jquery.jstree.typings.d.ts",
"src/typings/split.js.typings.d.ts", "src/typings/split.js.typings.d.ts",
"src/typings/add-marker.d.ts", "src/typings/add-marker.d.ts"
"src/typings/leaflet-editable.d.ts"
], ],
"paths": { "paths": {
"@app/*": ["src/app/*"], "@app/*": ["src/app/*"],

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

Loading…
Cancel
Save