Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard

pull/3865/head
ShvaykaD 6 years ago
parent
commit
a583f9fa8e
  1. 2
      application/pom.xml
  2. 2
      application/src/main/data/json/demo/dashboards/gateways.json
  3. 700
      application/src/main/data/json/demo/dashboards/raspberry_pi_gpio_demo_dashboard.json
  4. 264
      application/src/main/data/json/demo/dashboards/temperature___humidity_demo_dashboard.json
  5. 20
      application/src/main/data/json/demo/dashboards/thermostats.json
  6. 189
      application/src/main/data/json/demo/rule_chains/root_rule_chain.json
  7. 138
      application/src/main/data/json/demo/rule_chains/thermostat_alarms.json
  8. 2
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  9. 14
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  10. 2
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  11. 7
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  12. 15
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  13. 15
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  14. 9
      application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java
  15. 64
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  16. 21
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  17. 2
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  18. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  19. 6
      application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java
  20. 6
      application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java
  21. 2
      application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java
  22. 2
      application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java
  23. 16
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  24. 11
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  25. 2
      application/src/main/resources/i18n/messages.properties
  26. 48
      application/src/main/resources/templates/state.disabled.ftl
  27. 43
      application/src/main/resources/templates/state.enabled.ftl
  28. 49
      application/src/main/resources/templates/state.warning.ftl
  29. 2
      common/actor/pom.xml
  30. 2
      common/dao-api/pom.xml
  31. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java
  32. 7
      common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java
  33. 2
      common/data/pom.xml
  34. 5
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java
  35. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java
  36. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java
  37. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java
  38. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java
  39. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java
  40. 2
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  41. 2
      common/message/pom.xml
  42. 2
      common/pom.xml
  43. 2
      common/queue/pom.xml
  44. 8
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  45. 2
      common/stats/pom.xml
  46. 2
      common/transport/coap/pom.xml
  47. 3
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java
  48. 2
      common/transport/http/pom.xml
  49. 2
      common/transport/mqtt/pom.xml
  50. 1
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  51. 2
      common/transport/pom.xml
  52. 2
      common/transport/transport-api/pom.xml
  53. 2
      common/util/pom.xml
  54. 2
      dao/pom.xml
  55. 8
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  56. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
  57. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
  58. 45
      dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java
  59. 2
      msa/black-box-tests/pom.xml
  60. 2
      msa/js-executor/package.json
  61. 2
      msa/js-executor/pom.xml
  62. 2
      msa/pom.xml
  63. 2
      msa/tb-node/pom.xml
  64. 6
      msa/tb/docker-cassandra/start-db.sh
  65. 8
      msa/tb/docker-postgres/start-db.sh
  66. 2
      msa/tb/pom.xml
  67. 2
      msa/transport/coap/pom.xml
  68. 2
      msa/transport/http/pom.xml
  69. 2
      msa/transport/mqtt/pom.xml
  70. 2
      msa/transport/pom.xml
  71. 2
      msa/web-ui/package.json
  72. 2
      msa/web-ui/pom.xml
  73. 4
      netty-mqtt/pom.xml
  74. 2
      pom.xml
  75. 2
      rest-client/pom.xml
  76. 11
      rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
  77. 2
      rule-engine/pom.xml
  78. 2
      rule-engine/rule-engine-api/pom.xml
  79. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java
  80. 7
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  81. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java
  82. 2
      rule-engine/rule-engine-components/pom.xml
  83. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java
  84. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java
  85. 17
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java
  86. 9
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java
  87. 2
      tools/pom.xml
  88. 2
      transport/coap/pom.xml
  89. 2
      transport/http/pom.xml
  90. 2
      transport/mqtt/pom.xml
  91. 2
      transport/pom.xml
  92. 2
      ui-ngx/package.json
  93. 2
      ui-ngx/pom.xml
  94. 24
      ui-ngx/src/app/core/auth/auth.service.ts
  95. 2
      ui-ngx/src/app/core/http/dashboard.service.ts
  96. 1
      ui-ngx/src/app/core/services/menu.models.ts
  97. 13
      ui-ngx/src/app/core/services/menu.service.ts
  98. 44
      ui-ngx/src/app/core/services/utils.service.ts
  99. 9
      ui-ngx/src/app/core/utils.ts
  100. 28
      ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts

2
application/pom.xml

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

2
application/src/main/data/json/demo/dashboards/gateways.json

@ -955,7 +955,7 @@
}, },
"methodName": "gateway_restart", "methodName": "gateway_restart",
"methodParams": "{}", "methodParams": "{}",
"buttonText": "gateway restart" "buttonText": "GATEWAY RESTART"
}, },
"title": "New RPC Button", "title": "New RPC Button",
"dropShadow": true, "dropShadow": true,

700
application/src/main/data/json/demo/dashboards/raspberry_pi_gpio_demo_dashboard.json

@ -1,700 +0,0 @@
{
"title": "Raspberry PI GPIO Demo Dashboard",
"configuration": {
"description": "Demo dashboard for Raspberry PI GPIO Demo",
"widgets": {
"602177f6-267b-cb87-4e8f-e23d7fb2f61c": {
"isSystemType": true,
"bundleAlias": "gpio_widgets",
"typeAlias": "raspberry_pi_gpio_control",
"type": "rpc",
"title": "New widget",
"sizeX": 6,
"sizeY": 10,
"config": {
"targetDeviceAliases": [],
"showTitle": true,
"backgroundColor": "#fff",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "0px",
"settings": {
"parseGpioStatusFunction": "return body[pin] === true;",
"gpioStatusChangeRequest": {
"method": "setGpioStatus",
"paramsBody": "{\n \"pin\": \"{$pin}\",\n \"enabled\": \"{$enabled}\"\n}"
},
"requestTimeout": 500,
"switchPanelBackgroundColor": "#008a00",
"gpioStatusRequest": {
"method": "getGpioStatus",
"paramsBody": "{}"
},
"gpioList": [
{
"pin": 7,
"label": "GPIO 4 (GPCLK0)",
"row": 3,
"col": 0,
"_uniqueKey": 0
},
{
"pin": 11,
"label": "GPIO 17",
"row": 5,
"col": 0,
"_uniqueKey": 1
},
{
"pin": 12,
"label": "GPIO 18",
"row": 5,
"col": 1,
"_uniqueKey": 2
},
{
"_uniqueKey": 3,
"pin": 13,
"label": "GPIO 27",
"row": 6,
"col": 0
},
{
"_uniqueKey": 4,
"pin": 15,
"label": "GPIO 22",
"row": 7,
"col": 0
},
{
"_uniqueKey": 5,
"pin": 16,
"label": "GPIO 23",
"row": 7,
"col": 1
},
{
"_uniqueKey": 6,
"pin": 18,
"label": "GPIO 24",
"row": 8,
"col": 1
},
{
"_uniqueKey": 7,
"pin": 22,
"label": "GPIO 25",
"row": 10,
"col": 1
},
{
"_uniqueKey": 8,
"pin": 29,
"label": "GPIO 5",
"row": 14,
"col": 0
},
{
"_uniqueKey": 9,
"pin": 31,
"label": "GPIO 6",
"row": 15,
"col": 0
},
{
"_uniqueKey": 10,
"pin": 32,
"label": "GPIO 12",
"row": 15,
"col": 1
},
{
"_uniqueKey": 11,
"pin": 33,
"label": "GPIO 13",
"row": 16,
"col": 0
},
{
"_uniqueKey": 12,
"pin": 35,
"label": "GPIO 19",
"row": 17,
"col": 0
},
{
"_uniqueKey": 13,
"pin": 36,
"label": "GPIO 16",
"row": 17,
"col": 1
},
{
"_uniqueKey": 14,
"pin": 37,
"label": "GPIO 26",
"row": 18,
"col": 0
},
{
"_uniqueKey": 15,
"pin": 38,
"label": "GPIO 20",
"row": 18,
"col": 1
},
{
"_uniqueKey": 16,
"pin": 40,
"label": "GPIO 21",
"row": 19,
"col": 1
}
]
},
"title": "Raspberry Pi GPIO Control Panel",
"datasources": [],
"targetDeviceAliasIds": [
"f26b12b6-6938-e1a0-85ec-d88a1f23e382"
]
},
"row": 0,
"col": 0,
"id": "602177f6-267b-cb87-4e8f-e23d7fb2f61c"
},
"3cca52a5-e874-eb43-b444-8efa01e663c8": {
"isSystemType": true,
"bundleAlias": "gpio_widgets",
"typeAlias": "raspberry_pi_gpio_panel",
"type": "latest",
"title": "New widget",
"sizeX": 7,
"sizeY": 10,
"config": {
"showTitle": true,
"backgroundColor": "#fff",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "0px",
"settings": {
"gpioList": [
{
"pin": 1,
"label": "3.3V",
"row": 0,
"col": 0,
"color": "#fc9700",
"_uniqueKey": 0
},
{
"pin": 2,
"label": "5V",
"row": 0,
"col": 1,
"color": "#fb0000",
"_uniqueKey": 1
},
{
"pin": 3,
"label": "GPIO 2 (I2C1_SDA)",
"row": 1,
"col": 0,
"color": "#02fefb",
"_uniqueKey": 2
},
{
"color": "#fb0000",
"pin": 4,
"label": "5V",
"row": 1,
"col": 1
},
{
"color": "#02fefb",
"pin": 5,
"label": "GPIO 3 (I2C1_SCL)",
"row": 2,
"col": 0
},
{
"color": "#000000",
"pin": 6,
"label": "GND",
"row": 2,
"col": 1
},
{
"color": "#00fd00",
"pin": 7,
"label": "GPIO 4 (GPCLK0)",
"row": 3,
"col": 0
},
{
"color": "#fdfb00",
"pin": 8,
"label": "GPIO 14 (UART_TXD)",
"row": 3,
"col": 1
},
{
"color": "#000000",
"pin": 9,
"label": "GND",
"row": 4,
"col": 0
},
{
"color": "#fdfb00",
"pin": 10,
"label": "GPIO 15 (UART_RXD)",
"row": 4,
"col": 1
},
{
"color": "#00fd00",
"pin": 11,
"label": "GPIO 17",
"row": 5,
"col": 0
},
{
"color": "#00fd00",
"pin": 12,
"label": "GPIO 18",
"row": 5,
"col": 1
},
{
"color": "#00fd00",
"pin": 13,
"label": "GPIO 27",
"row": 6,
"col": 0
},
{
"color": "#000000",
"pin": 14,
"label": "GND",
"row": 6,
"col": 1
},
{
"color": "#00fd00",
"pin": 15,
"label": "GPIO 22",
"row": 7,
"col": 0
},
{
"color": "#00fd00",
"pin": 16,
"label": "GPIO 23",
"row": 7,
"col": 1
},
{
"color": "#fc9700",
"pin": 17,
"label": "3.3V",
"row": 8,
"col": 0
},
{
"color": "#00fd00",
"pin": 18,
"label": "GPIO 24",
"row": 8,
"col": 1
},
{
"color": "#fd01fd",
"pin": 19,
"label": "GPIO 10 (SPI_MOSI)",
"row": 9,
"col": 0
},
{
"color": "#000000",
"pin": 20,
"label": "GND",
"row": 9,
"col": 1
},
{
"color": "#fd01fd",
"pin": 21,
"label": "GPIO 9 (SPI_MISO)",
"row": 10,
"col": 0
},
{
"color": "#00fd00",
"pin": 22,
"label": "GPIO 25",
"row": 10,
"col": 1
},
{
"color": "#fd01fd",
"pin": 23,
"label": "GPIO 11 (SPI_SCLK)",
"row": 11,
"col": 0
},
{
"color": "#fd01fd",
"pin": 24,
"label": "GPIO 8 (SPI_CE0)",
"row": 11,
"col": 1
},
{
"color": "#000000",
"pin": 25,
"label": "GND",
"row": 12,
"col": 0
},
{
"color": "#fd01fd",
"pin": 26,
"label": "GPIO 7 (SPI_CE1)",
"row": 12,
"col": 1
},
{
"color": "#ffffff",
"pin": 27,
"label": "ID_SD",
"row": 13,
"col": 0
},
{
"color": "#ffffff",
"pin": 28,
"label": "ID_SC",
"row": 13,
"col": 1
},
{
"color": "#00fd00",
"pin": 29,
"label": "GPIO 5",
"row": 14,
"col": 0
},
{
"color": "#000000",
"pin": 30,
"label": "GND",
"row": 14,
"col": 1
},
{
"color": "#00fd00",
"pin": 31,
"label": "GPIO 6",
"row": 15,
"col": 0
},
{
"color": "#00fd00",
"pin": 32,
"label": "GPIO 12",
"row": 15,
"col": 1
},
{
"color": "#00fd00",
"pin": 33,
"label": "GPIO 13",
"row": 16,
"col": 0
},
{
"color": "#000000",
"pin": 34,
"label": "GND",
"row": 16,
"col": 1
},
{
"color": "#00fd00",
"pin": 35,
"label": "GPIO 19",
"row": 17,
"col": 0
},
{
"color": "#00fd00",
"pin": 36,
"label": "GPIO 16",
"row": 17,
"col": 1
},
{
"color": "#00fd00",
"pin": 37,
"label": "GPIO 26",
"row": 18,
"col": 0
},
{
"color": "#00fd00",
"pin": 38,
"label": "GPIO 20",
"row": 18,
"col": 1
},
{
"color": "#000000",
"pin": 39,
"label": "GND",
"row": 19,
"col": 0
},
{
"color": "#00fd00",
"pin": 40,
"label": "GPIO 21",
"row": 19,
"col": 1
}
],
"ledPanelBackgroundColor": "#008a00"
},
"title": "Raspberry Pi GPIO Status Panel",
"datasources": [
{
"type": "entity",
"dataKeys": [
{
"name": "7",
"type": "attribute",
"label": "7",
"color": "#2196f3",
"settings": {},
"_hash": 0.20925966435886978
},
{
"name": "11",
"type": "attribute",
"label": "11",
"color": "#4caf50",
"settings": {},
"_hash": 0.330267349594344
},
{
"name": "12",
"type": "attribute",
"label": "12",
"color": "#f44336",
"settings": {},
"_hash": 0.5040578704481748
},
{
"name": "13",
"type": "attribute",
"label": "13",
"color": "#ffc107",
"settings": {},
"_hash": 0.588956328191639
},
{
"name": "15",
"type": "attribute",
"label": "15",
"color": "#607d8b",
"settings": {},
"_hash": 0.9229040530336119
},
{
"name": "16",
"type": "attribute",
"label": "16",
"color": "#9c27b0",
"settings": {},
"_hash": 0.8692315253041654
},
{
"name": "18",
"type": "attribute",
"label": "18",
"color": "#8bc34a",
"settings": {},
"_hash": 0.41465562857521543
},
{
"name": "22",
"type": "attribute",
"label": "22",
"color": "#3f51b5",
"settings": {},
"_hash": 0.36135260043112827
},
{
"name": "29",
"type": "attribute",
"label": "29",
"color": "#e91e63",
"settings": {},
"_hash": 0.9904592276182183
},
{
"name": "31",
"type": "attribute",
"label": "31",
"color": "#ffeb3b",
"settings": {},
"_hash": 0.038330985429919195
},
{
"name": "32",
"type": "attribute",
"label": "32",
"color": "#03a9f4",
"settings": {},
"_hash": 0.4334683890135089
},
{
"name": "33",
"type": "attribute",
"label": "33",
"color": "#ff9800",
"settings": {},
"_hash": 0.6487255992492305
},
{
"name": "35",
"type": "attribute",
"label": "35",
"color": "#673ab7",
"settings": {},
"_hash": 0.971555321150732
},
{
"name": "36",
"type": "attribute",
"label": "36",
"color": "#cddc39",
"settings": {},
"_hash": 0.7826129728424382
},
{
"name": "37",
"type": "attribute",
"label": "37",
"color": "#009688",
"settings": {},
"_hash": 0.44925676517537627
},
{
"name": "38",
"type": "attribute",
"label": "38",
"color": "#795548",
"settings": {},
"_hash": 0.051518155759787465
},
{
"name": "40",
"type": "attribute",
"label": "40",
"color": "#00bcd4",
"settings": {},
"_hash": 0.8733296686871144
}
],
"name": "RPi",
"entityAliasId": "f26b12b6-6938-e1a0-85ec-d88a1f23e382"
}
],
"timewindow": {
"realtime": {
"timewindowMs": 60000
}
}
},
"row": 0,
"col": 6,
"id": "3cca52a5-e874-eb43-b444-8efa01e663c8"
}
},
"states": {
"default": {
"name": "Default",
"root": true,
"layouts": {
"main": {
"widgets": {
"602177f6-267b-cb87-4e8f-e23d7fb2f61c": {
"sizeX": 6,
"sizeY": 10,
"row": 0,
"col": 0
},
"3cca52a5-e874-eb43-b444-8efa01e663c8": {
"sizeX": 7,
"sizeY": 10,
"row": 0,
"col": 6
}
},
"gridSettings": {
"backgroundColor": "#eeeeee",
"color": "rgba(0,0,0,0.870588)",
"columns": 24,
"margins": [
10,
10
],
"backgroundSizeMode": "100%"
}
}
}
}
},
"entityAliases": {
"f26b12b6-6938-e1a0-85ec-d88a1f23e382": {
"id": "f26b12b6-6938-e1a0-85ec-d88a1f23e382",
"alias": "RPi",
"filter": {
"type": "entityName",
"resolveMultiple": false,
"entityType": "DEVICE",
"entityNameFilter": "Raspberry Pi Demo Device"
}
}
},
"timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": {
"interval": 1000,
"timewindowMs": 60000
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1498653734150,
"endTimeMs": 1498740134150
}
},
"aggregation": {
"type": "AVG",
"limit": 200
}
},
"settings": {
"stateControllerId": "default",
"showTitle": true,
"showDashboardsSelect": true,
"showEntitiesSelect": true,
"showDashboardTimewindow": true,
"showDashboardExport": true,
"toolbarAlwaysOpen": false
}
},
"name": "Raspberry PI GPIO Demo Dashboard"
}

264
application/src/main/data/json/demo/dashboards/temperature___humidity_demo_dashboard.json

@ -1,264 +0,0 @@
{
"title": "Temperature & Humidity Demo Dashboard",
"configuration": {
"description": "Demo dashboard for sample applications that upload temperature and humidity received from DHT11 or DHT22 sensors",
"widgets": {
"03e06986-1c50-e9e4-267c-2bae930ad9a2": {
"isSystemType": true,
"bundleAlias": "digital_gauges",
"typeAlias": "digital_thermometer",
"type": "latest",
"title": "New widget",
"sizeX": 5,
"sizeY": 5,
"config": {
"datasources": [
{
"type": "entity",
"dataKeys": [
{
"name": "temperature",
"type": "timeseries",
"label": "temperature",
"color": "#2196f3",
"settings": {},
"_hash": 0.3720839051412099
}
],
"name": "DHT11",
"entityAliasId": "63a93238-c13f-4403-4bcc-9ccc86bd6a62"
}
],
"timewindow": {
"realtime": {
"timewindowMs": 60000
}
},
"showTitle": false,
"backgroundColor": "#000000",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "0px",
"settings": {
"maxValue": 50,
"donutStartAngle": 90,
"showValue": true,
"showMinMax": true,
"gaugeWidthScale": 1,
"levelColors": [
"#304ffe",
"#7e57c2",
"#ff4081",
"#d32f2f"
],
"refreshAnimationType": "<>",
"refreshAnimationTime": 700,
"startAnimationType": "<>",
"startAnimationTime": 700,
"titleFont": {
"family": "RobotoDraft",
"size": 12,
"style": "normal",
"weight": "500"
},
"labelFont": {
"family": "RobotoDraft",
"size": 8,
"style": "normal",
"weight": "500"
},
"valueFont": {
"family": "Segment7Standard",
"style": "normal",
"weight": "500",
"size": 18
},
"minMaxFont": {
"family": "Segment7Standard",
"size": 12,
"style": "normal",
"weight": "500"
},
"dashThickness": 1.5,
"decimals": 0,
"minValue": 0,
"units": "°C",
"gaugeColor": "#333333",
"neonGlowBrightness": 35,
"gaugeType": "donut",
"showTitle": false
},
"title": "Temperature"
},
"row": 0,
"col": 0,
"id": "03e06986-1c50-e9e4-267c-2bae930ad9a2"
},
"88808eb1-d381-9970-c852-e3499df68bd8": {
"isSystemType": true,
"bundleAlias": "digital_gauges",
"typeAlias": "digital_vertical_bar",
"type": "latest",
"title": "New widget",
"sizeX": 3,
"sizeY": 5,
"config": {
"datasources": [
{
"type": "entity",
"dataKeys": [
{
"name": "humidity",
"type": "timeseries",
"label": "humidity",
"color": "#2196f3",
"settings": {},
"_hash": 0.9492802776509441
}
],
"name": "DHT11",
"entityAliasId": "63a93238-c13f-4403-4bcc-9ccc86bd6a62"
}
],
"timewindow": {
"realtime": {
"timewindowMs": 60000
}
},
"showTitle": false,
"backgroundColor": "#000000",
"color": "rgba(0, 0, 0, 0.87)",
"padding": "0px",
"settings": {
"maxValue": 100,
"donutStartAngle": 90,
"showValue": true,
"showMinMax": true,
"gaugeWidthScale": 0.75,
"levelColors": [
"#3d5afe",
"#f44336"
],
"refreshAnimationType": "<>",
"refreshAnimationTime": 700,
"startAnimationType": "<>",
"startAnimationTime": 700,
"titleFont": {
"family": "RobotoDraft",
"size": 12,
"style": "normal",
"weight": "500"
},
"labelFont": {
"family": "RobotoDraft",
"size": 8,
"style": "normal",
"weight": "500"
},
"valueFont": {
"family": "Segment7Standard",
"style": "normal",
"weight": "500",
"size": 14
},
"minMaxFont": {
"family": "Segment7Standard",
"size": 8,
"style": "normal",
"weight": "normal",
"color": "#cccccc"
},
"neonGlowBrightness": 20,
"decimals": 0,
"showUnitTitle": true,
"gaugeColor": "#171a1c",
"gaugeType": "verticalBar",
"showTitle": false,
"minValue": 0,
"dashThickness": 1.2
},
"title": "Humidity"
},
"row": 0,
"col": 5,
"id": "88808eb1-d381-9970-c852-e3499df68bd8"
}
},
"states": {
"default": {
"name": "Default",
"root": true,
"layouts": {
"main": {
"widgets": {
"03e06986-1c50-e9e4-267c-2bae930ad9a2": {
"sizeX": 5,
"sizeY": 5,
"row": 0,
"col": 0
},
"88808eb1-d381-9970-c852-e3499df68bd8": {
"sizeX": 3,
"sizeY": 5,
"row": 0,
"col": 5
}
},
"gridSettings": {
"backgroundColor": "#eeeeee",
"color": "rgba(0,0,0,0.870588)",
"columns": 24,
"margins": [
10,
10
],
"backgroundSizeMode": "100%"
}
}
}
}
},
"entityAliases": {
"63a93238-c13f-4403-4bcc-9ccc86bd6a62": {
"id": "63a93238-c13f-4403-4bcc-9ccc86bd6a62",
"alias": "DHT11",
"filter": {
"type": "entityName",
"resolveMultiple": false,
"entityType": "DEVICE",
"entityNameFilter": "DHT11 Demo Device"
}
}
},
"timewindow": {
"displayValue": "",
"selectedTab": 0,
"realtime": {
"interval": 1000,
"timewindowMs": 60000
},
"history": {
"historyType": 0,
"interval": 1000,
"timewindowMs": 60000,
"fixedTimewindow": {
"startTimeMs": 1498653790019,
"endTimeMs": 1498740190019
}
},
"aggregation": {
"type": "AVG",
"limit": 200
}
},
"settings": {
"stateControllerId": "default",
"showTitle": true,
"showDashboardsSelect": true,
"showEntitiesSelect": true,
"showDashboardTimewindow": true,
"showDashboardExport": true,
"toolbarAlwaysOpen": false
}
},
"name": "Temperature & Humidity Demo Dashboard"
}

20
application/src/main/data/json/demo/dashboards/thermostats.json

@ -147,9 +147,9 @@
"name": "Add", "name": "Add",
"icon": "add", "icon": "add",
"type": "customPretty", "type": "customPretty",
"customHtml": "<form #addEntityForm=\"ngForm\" [formGroup]=\"addEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"add-entity-form\">\n <mat-toolbar color=\"primary\">\n <h2>Add thermostat</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Thermostat name</mat-label>\n <input matInput formControlName=\"entityName\" required>\n <mat-error *ngIf=\"addEntityFormGroup.get('entityName').hasError('required')\">\n Thermostat name is required.\n </mat-error>\n </mat-form-field>\n <div formGroupName=\"attributes\" fxLayout=\"column\">\n <mat-slide-toggle formControlName=\"alarmTemperature\">\n High temperature alarm\n </mat-slide-toggle>\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>High temperature threshold, °C</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"addEntityFormGroup.get('attributes').get('alarmTemperature').value\"\n formControlName=\"thresholdTemperature\">\n <mat-error *ngIf=\"addEntityFormGroup.get('attributes').get('thresholdTemperature').hasError('required')\">\n High temperature threshold is required.\n </mat-error>\n </mat-form-field>\n \n <mat-slide-toggle formControlName=\"alarmHumidity\">\n Low humidity alarm\n </mat-slide-toggle>\n \n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Low humidity threshold, %</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"addEntityFormGroup.get('attributes').get('alarmHumidity').value\"\n formControlName=\"thresholdHumidity\">\n <mat-error *ngIf=\"addEntityFormGroup.get('attributes').get('thresholdHumidity').hasError('required')\">\n Low humidity threshold is required.\n </mat-error>\n </mat-form-field>\n </div>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty\">\n Create\n </button>\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n </div>\n</form>", "customHtml": "<form #addEntityForm=\"ngForm\" [formGroup]=\"addEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"add-entity-form\">\n <mat-toolbar color=\"primary\">\n <h2>Add thermostat</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Thermostat name</mat-label>\n <input matInput formControlName=\"entityName\" required>\n <mat-error *ngIf=\"addEntityFormGroup.get('entityName').hasError('required')\">\n Thermostat name is required.\n </mat-error>\n </mat-form-field>\n <div formGroupName=\"attributes\" fxLayout=\"column\">\n <mat-slide-toggle formControlName=\"temperatureAlarmFlag\">\n High temperature alarm\n </mat-slide-toggle>\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>High temperature threshold, °C</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"addEntityFormGroup.get('attributes').get('temperatureAlarmFlag').value\"\n formControlName=\"temperatureAlarmThreshold\">\n <mat-error *ngIf=\"addEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').hasError('required')\">\n High temperature threshold is required.\n </mat-error>\n </mat-form-field>\n \n <mat-slide-toggle formControlName=\"humidityAlarmFlag\">\n Low humidity alarm\n </mat-slide-toggle>\n \n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Low humidity threshold, %</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"addEntityFormGroup.get('attributes').get('humidityAlarmFlag').value\"\n formControlName=\"humidityAlarmThreshold\">\n <mat-error *ngIf=\"addEntityFormGroup.get('attributes').get('humidityAlarmThreshold').hasError('required')\">\n Low humidity threshold is required.\n </mat-error>\n </mat-form-field>\n </div>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty\">\n Create\n </button>\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n </div>\n</form>",
"customCss": ".add-entity-form{\n width: 300px;\n}\n", "customCss": ".add-entity-form{\n width: 300px;\n}\n",
"customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\n}\n\nfunction AddEntityDialogController(instance) {\n let vm = instance;\n \n vm.addEntityFormGroup = vm.fb.group({\n entityName: ['', [vm.validators.required]],\n attributes: vm.fb.group({\n alarmTemperature: [false],\n thresholdTemperature: [{value: null, disabled: true}],\n alarmHumidity: [false],\n thresholdHumidity: [{value: null, disabled: true}]\n })\n });\n \n vm.addEntityFormGroup.get('attributes').get('alarmTemperature').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('thresholdTemperature').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('thresholdTemperature').disable();\n }\n });\n \n vm.addEntityFormGroup.get('attributes').get('alarmHumidity').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('thresholdHumidity').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('thresholdHumidity').disable();\n }\n });\n\n vm.save = function() {\n vm.addEntityFormGroup.markAsPristine();\n saveEntityObservable().subscribe(\n function (entity) {\n saveAttributes(entity.id).subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function saveEntityObservable() {\n const formValues = vm.addEntityFormGroup.value;\n let entity = {\n name: formValues.entityName,\n type: \"thermostat\"\n };\n return deviceService.saveDevice(entity);\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.addEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if(attributes[key] !== null) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\n}\n\nfunction AddEntityDialogController(instance) {\n let vm = instance;\n \n vm.addEntityFormGroup = vm.fb.group({\n entityName: ['', [vm.validators.required]],\n attributes: vm.fb.group({\n temperatureAlarmFlag: [false],\n temperatureAlarmThreshold: [{value: null, disabled: true}],\n humidityAlarmFlag: [false],\n humidityAlarmThreshold: [{value: null, disabled: true}]\n })\n });\n \n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').disable();\n }\n });\n \n vm.addEntityFormGroup.get('attributes').get('humidityAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('humidityAlarmThreshold').disable();\n }\n });\n\n vm.save = function() {\n vm.addEntityFormGroup.markAsPristine();\n saveEntityObservable().subscribe(\n function (entity) {\n saveAttributes(entity.id).subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function saveEntityObservable() {\n const formValues = vm.addEntityFormGroup.value;\n let entity = {\n name: formValues.entityName,\n type: \"thermostat\"\n };\n return deviceService.saveDevice(entity);\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.addEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if(attributes[key] !== null) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}",
"customResources": [], "customResources": [],
"id": "8ab5a518-67d2-b6a2-956d-81fd512294b2" "id": "8ab5a518-67d2-b6a2-956d-81fd512294b2"
} }
@ -167,9 +167,9 @@
"name": "Edit", "name": "Edit",
"icon": "edit", "icon": "edit",
"type": "customPretty", "type": "customPretty",
"customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar color=\"primary\">\n <h2>Edit thermostat {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Thermostat name</mat-label>\n <input matInput formControlName=\"entityName\" readonly>\n </mat-form-field>\n <div formGroupName=\"attributes\" fxLayout=\"column\">\n <mat-slide-toggle formControlName=\"alarmTemperature\">\n High temperature alarm\n </mat-slide-toggle>\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>High temperature threshold, °C</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"editEntityFormGroup.get('attributes').get('alarmTemperature').value\"\n formControlName=\"thresholdTemperature\">\n <mat-error *ngIf=\"editEntityFormGroup.get('attributes').get('thresholdTemperature').hasError('required')\">\n High temperature threshold is required.\n </mat-error>\n </mat-form-field>\n\n <mat-slide-toggle formControlName=\"alarmHumidity\">\n Low humidity alarm\n </mat-slide-toggle>\n\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Low humidity threshold, %</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"editEntityFormGroup.get('attributes').get('alarmHumidity').value\"\n formControlName=\"thresholdHumidity\">\n <mat-error *ngIf=\"editEntityFormGroup.get('attributes').get('thresholdHumidity').hasError('required')\">\n Low humidity threshold is required.\n </mat-error>\n </mat-form-field>\n </div>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n </div>\n</form>", "customHtml": "<form #editEntityForm=\"ngForm\" [formGroup]=\"editEntityFormGroup\"\n (ngSubmit)=\"save()\" class=\"edit-entity-form\">\n <mat-toolbar color=\"primary\">\n <h2>Edit thermostat {{entityName}}</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n </mat-toolbar>\n <mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n </mat-progress-bar>\n <div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n <div mat-dialog-content fxLayout=\"column\">\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Thermostat name</mat-label>\n <input matInput formControlName=\"entityName\" readonly>\n </mat-form-field>\n <div formGroupName=\"attributes\" fxLayout=\"column\">\n <mat-slide-toggle formControlName=\"temperatureAlarmFlag\">\n High temperature alarm\n </mat-slide-toggle>\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>High temperature threshold, °C</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"editEntityFormGroup.get('attributes').get('temperatureAlarmFlag').value\"\n formControlName=\"temperatureAlarmThreshold\">\n <mat-error *ngIf=\"editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').hasError('required')\">\n High temperature threshold is required.\n </mat-error>\n </mat-form-field>\n\n <mat-slide-toggle formControlName=\"humidityAlarmFlag\">\n Low humidity alarm\n </mat-slide-toggle>\n\n <mat-form-field fxFlex class=\"mat-block\">\n <mat-label>Low humidity threshold, %</mat-label>\n <input type=\"number\" step=\"any\" matInput\n [required] = \"editEntityFormGroup.get('attributes').get('humidityAlarmFlag').value\"\n formControlName=\"humidityAlarmThreshold\">\n <mat-error *ngIf=\"editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').hasError('required')\">\n Low humidity threshold is required.\n </mat-error>\n </mat-form-field>\n </div>\n </div>\n <div mat-dialog-actions fxLayout=\"row\" fxLayoutAlign=\"end center\">\n <button mat-raised-button color=\"primary\"\n type=\"submit\"\n [disabled]=\"(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty\">\n Save\n </button>\n <button mat-button color=\"primary\"\n type=\"button\"\n [disabled]=\"(isLoading$ | async)\"\n (click)=\"cancel()\" cdkFocusInitial>\n Cancel\n </button>\n </div>\n</form>",
"customCss": ".edit-entity-form{\n width: 300px;\n}", "customCss": ".edit-entity-form{\n width: 300px;\n}",
"customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId;\n vm.entityName = entityName;\n vm.attributes = {};\n \n vm.editEntityFormGroup = vm.fb.group({\n entityName: [''],\n attributes: vm.fb.group({\n alarmTemperature: [false],\n thresholdTemperature: [{value: null, disabled: true}],\n alarmHumidity: [false],\n thresholdHumidity: [{value: null, disabled: true}]\n })\n });\n \n vm.editEntityFormGroup.get('attributes').get('alarmTemperature').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('thresholdTemperature').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('thresholdTemperature').disable();\n }\n });\n \n vm.editEntityFormGroup.get('attributes').get('alarmHumidity').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('thresholdHumidity').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('thresholdHumidity').disable();\n }\n });\n \n \n getEntityInfo();\n \n \n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveAttributes(entityId).subscribe(\n function () {\n vm.dialogRef.close(null);\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function getEntityAttributes(attributes) {\n for (var i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value;\n }\n }\n \n function getEntityInfo() {\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe(\n function (attributes) {\n getEntityAttributes(attributes);\n vm.editEntityFormGroup.patchValue({\n entityName: vm.entityName,\n attributes: vm.attributes\n });\n // if(vm.attributes.alarmTemperature) {\n // vm.editEntityFormGroup.get('attributes').get('thresholdTemperature').enable();\n // }\n // if(vm.attributes.alarmHumidity) {\n // vm.editEntityFormGroup.get('attributes').get('thresholdHumidity').enable();\n // }\n }\n );\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.editEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if (attributes[key] !== vm.attributes[key]) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId;\n vm.entityName = entityName;\n vm.attributes = {};\n \n vm.editEntityFormGroup = vm.fb.group({\n entityName: [''],\n attributes: vm.fb.group({\n temperatureAlarmFlag: [false],\n temperatureAlarmThreshold: [{value: null, disabled: true}],\n humidityAlarmFlag: [false],\n humidityAlarmThreshold: [{value: null, disabled: true}]\n })\n });\n \n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').disable();\n }\n });\n \n vm.editEntityFormGroup.get('attributes').get('humidityAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').disable();\n }\n });\n \n \n getEntityInfo();\n \n \n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveAttributes(entityId).subscribe(\n function () {\n vm.dialogRef.close(null);\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function getEntityAttributes(attributes) {\n for (var i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value;\n }\n }\n \n function getEntityInfo() {\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe(\n function (attributes) {\n getEntityAttributes(attributes);\n vm.editEntityFormGroup.patchValue({\n entityName: vm.entityName,\n attributes: vm.attributes\n });\n // if(vm.attributes.temperatureAlarmFlag) {\n // vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n // }\n // if(vm.attributes.humidityAlarmFlag) {\n // vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n // }\n }\n );\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.editEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if (attributes[key] !== vm.attributes[key]) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}",
"customResources": [], "customResources": [],
"id": "7506576f-87ba-d3a0-88fb-e304d451776d" "id": "7506576f-87ba-d3a0-88fb-e304d451776d"
}, },
@ -550,7 +550,7 @@
"type": "entity", "type": "entity",
"dataKeys": [ "dataKeys": [
{ {
"name": "alarmTemperature", "name": "temperatureAlarmFlag",
"type": "attribute", "type": "attribute",
"label": "High temperature alarm", "label": "High temperature alarm",
"color": "#4caf50", "color": "#4caf50",
@ -565,7 +565,7 @@
"_hash": 0.8725278440159361 "_hash": 0.8725278440159361
}, },
{ {
"name": "thresholdTemperature", "name": "temperatureAlarmThreshold",
"type": "attribute", "type": "attribute",
"label": "High temperature threshold, °C", "label": "High temperature threshold, °C",
"color": "#f44336", "color": "#f44336",
@ -576,12 +576,12 @@
"isEditable": "editable", "isEditable": "editable",
"dataKeyHidden": false, "dataKeyHidden": false,
"step": 1, "step": 1,
"disabledOnDataKey": "alarmTemperature" "disabledOnDataKey": "temperatureAlarmFlag"
}, },
"_hash": 0.7316078472857874 "_hash": 0.7316078472857874
}, },
{ {
"name": "alarmHumidity", "name": "humidityAlarmFlag",
"type": "attribute", "type": "attribute",
"label": "Low humidity alarm", "label": "Low humidity alarm",
"color": "#ffc107", "color": "#ffc107",
@ -596,7 +596,7 @@
"_hash": 0.5339673667431057 "_hash": 0.5339673667431057
}, },
{ {
"name": "thresholdHumidity", "name": "humidityAlarmThreshold",
"type": "attribute", "type": "attribute",
"label": "Low humidity threshold, %", "label": "Low humidity threshold, %",
"color": "#607d8b", "color": "#607d8b",
@ -607,7 +607,7 @@
"isEditable": "editable", "isEditable": "editable",
"dataKeyHidden": false, "dataKeyHidden": false,
"step": 1, "step": 1,
"disabledOnDataKey": "alarmHumidity" "disabledOnDataKey": "humidityAlarmFlag"
}, },
"_hash": 0.2687091190358901 "_hash": 0.2687091190358901
} }

189
application/src/main/data/json/demo/rule_chains/root_rule_chain.json

@ -1,189 +0,0 @@
{
"ruleChain": {
"additionalInfo": null,
"name": "Root Rule Chain",
"firstRuleNodeId": null,
"root": true,
"debugMode": false,
"configuration": null
},
"metadata": {
"firstNodeIndex": 3,
"nodes": [
{
"additionalInfo": {
"layoutX": 1069,
"layoutY": 267
},
"type": "org.thingsboard.rule.engine.filter.TbJsFilterNode",
"name": "Is Thermostat?",
"debugMode": false,
"configuration": {
"jsScript": "return msg.id.entityType === \"DEVICE\" && msg.type === \"thermostat\";"
}
},
{
"additionalInfo": {
"layoutX": 824,
"layoutY": 156
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries",
"debugMode": false,
"configuration": {
"defaultTTL": 0
}
},
{
"additionalInfo": {
"layoutX": 825,
"layoutY": 52
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
"debugMode": false,
"configuration": {
"scope": "CLIENT_SCOPE",
"notifyDevice": "false"
}
},
{
"additionalInfo": {
"layoutX": 347,
"layoutY": 149
},
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch",
"debugMode": false,
"configuration": {
"version": 0
}
},
{
"additionalInfo": {
"layoutX": 839,
"layoutY": 345
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device",
"debugMode": false,
"configuration": {
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
"additionalInfo": {
"layoutX": 832,
"layoutY": 407
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other",
"debugMode": false,
"configuration": {
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
"additionalInfo": {
"layoutX": 825,
"layoutY": 468
},
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "RPC Call Request",
"debugMode": false,
"configuration": {
"timeoutInSeconds": 60
}
},
{
"additionalInfo": {
"layoutX": 1069,
"layoutY": 90
},
"type": "org.thingsboard.rule.engine.filter.TbJsFilterNode",
"name": "Is Thermostat?",
"debugMode": false,
"configuration": {
"jsScript": "return metadata[\"deviceType\"] === \"thermostat\";"
}
},
{
"additionalInfo": {
"layoutX": 1090,
"layoutY": 360
},
"type": "org.thingsboard.rule.engine.action.TbCreateRelationNode",
"name": "Relate to Asset",
"debugMode": false,
"configuration": {
"direction": "FROM",
"relationType": "ToAlarmPropagationAsset",
"entityType": "ASSET",
"entityNamePattern": "Thermostat Alarms",
"entityTypePattern": "AlarmPropagationAsset",
"entityCacheExpiration": 300,
"createEntityIfNotExists": true,
"changeOriginatorToRelatedEntity": false,
"removeCurrentRelations": false
}
}
],
"connections": [
{
"fromIndex": 0,
"toIndex": 8,
"type": "True"
},
{
"fromIndex": 1,
"toIndex": 7,
"type": "Success"
},
{
"fromIndex": 3,
"toIndex": 5,
"type": "Other"
},
{
"fromIndex": 3,
"toIndex": 2,
"type": "Post attributes"
},
{
"fromIndex": 3,
"toIndex": 1,
"type": "Post telemetry"
},
{
"fromIndex": 3,
"toIndex": 4,
"type": "RPC Request from Device"
},
{
"fromIndex": 3,
"toIndex": 6,
"type": "RPC Request to Device"
},
{
"fromIndex": 3,
"toIndex": 0,
"type": "Entity Created"
}
],
"ruleChainConnections": [
{
"fromIndex": 7,
"targetRuleChainId": {
"entityType": "RULE_CHAIN",
"id": "25e26570-89ed-11ea-a650-cd6e14e633bd"
},
"additionalInfo": {
"layoutX": 1109,
"layoutY": 182,
"ruleChainNodeId": "rule-chain-node-10"
},
"type": "True"
}
]
}
}

138
application/src/main/data/json/demo/rule_chains/thermostat_alarms.json

@ -1,138 +0,0 @@
{
"ruleChain": {
"additionalInfo": {
"description": ""
},
"name": "Thermostat Alarms",
"firstRuleNodeId": null,
"root": false,
"debugMode": false,
"configuration": null
},
"metadata": {
"firstNodeIndex": 6,
"nodes": [
{
"additionalInfo": {
"layoutX": 822,
"layoutY": 294
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "Save Timeseries",
"debugMode": false,
"configuration": {
"defaultTTL": 0
}
},
{
"additionalInfo": {
"description": null,
"layoutX": 824,
"layoutY": 221
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
"debugMode": false,
"configuration": {
"scope": "SERVER_SCOPE",
"notifyDevice": null
}
},
{
"additionalInfo": {
"layoutX": 494,
"layoutY": 309
},
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch",
"debugMode": false,
"configuration": {
"version": 0
}
},
{
"additionalInfo": {
"layoutX": 824,
"layoutY": 383
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC from Device",
"debugMode": false,
"configuration": {
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
"additionalInfo": {
"layoutX": 823,
"layoutY": 444
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other",
"debugMode": false,
"configuration": {
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
"additionalInfo": {
"layoutX": 822,
"layoutY": 507
},
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
"name": "RPC Call Request",
"debugMode": false,
"configuration": {
"timeoutInSeconds": 60
}
},
{
"additionalInfo": {
"description": "",
"layoutX": 209,
"layoutY": 307
},
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node",
"debugMode": false,
"configuration": {
"persistAlarmRulesState": false,
"fetchAlarmRulesStateOnStart": false
}
}
],
"connections": [
{
"fromIndex": 2,
"toIndex": 4,
"type": "Other"
},
{
"fromIndex": 2,
"toIndex": 1,
"type": "Post attributes"
},
{
"fromIndex": 2,
"toIndex": 0,
"type": "Post telemetry"
},
{
"fromIndex": 2,
"toIndex": 3,
"type": "RPC Request from Device"
},
{
"fromIndex": 2,
"toIndex": 5,
"type": "RPC Request to Device"
},
{
"fromIndex": 6,
"toIndex": 2,
"type": "Success"
}
],
"ruleChainConnections": null
}
}

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

File diff suppressed because one or more lines are too long

14
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -510,14 +511,25 @@ class DefaultTbContext implements TbContext {
mainCtx.getRuleNodeStateService().removeByRuleNodeIdAndEntityId(getTenantId(), getSelfId(), entityId); mainCtx.getRuleNodeStateService().removeByRuleNodeIdAndEntityId(getTenantId(), getSelfId(), entityId);
} }
@Override
public void addTenantProfileListener(Consumer<TenantProfile> listener) {
mainCtx.getTenantProfileCache().addListener(getTenantId(), getSelfId(), listener);
}
@Override @Override
public void addDeviceProfileListeners(Consumer<DeviceProfile> profileListener, BiConsumer<DeviceId, DeviceProfile> deviceListener) { public void addDeviceProfileListeners(Consumer<DeviceProfile> profileListener, BiConsumer<DeviceId, DeviceProfile> deviceListener) {
mainCtx.getDeviceProfileCache().addListener(getTenantId(), getSelfId(), profileListener, deviceListener); mainCtx.getDeviceProfileCache().addListener(getTenantId(), getSelfId(), profileListener, deviceListener);
} }
@Override @Override
public void removeProfileListener() { public void removeListeners() {
mainCtx.getDeviceProfileCache().removeListener(getTenantId(), getSelfId()); mainCtx.getDeviceProfileCache().removeListener(getTenantId(), getSelfId());
mainCtx.getTenantProfileCache().removeListener(getTenantId(), getSelfId());
}
@Override
public TenantProfile getTenantProfile() {
return mainCtx.getTenantProfileCache().get(getTenantId());
} }
private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) { private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {

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

@ -26,7 +26,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;

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

@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.asset.AssetSearchQuery;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
@ -51,6 +52,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@RequestMapping("/api") @RequestMapping("/api")
@ -89,6 +92,10 @@ public class AssetController extends BaseController {
@ResponseBody @ResponseBody
public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException { public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException {
try { try {
if (TB_SERVICE_QUEUE.equals(asset.getType())) {
throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
asset.setTenantId(getCurrentUser().getTenantId()); asset.setTenantId(getCurrentUser().getTenantId());
checkEntity(asset.getId(), asset, Resource.ASSET); checkEntity(asset.getId(), asset, Resource.ASSET);

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

@ -45,6 +45,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -69,6 +70,7 @@ import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -93,6 +95,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -205,7 +208,7 @@ public class TelemetryController extends BaseController {
@RequestParam(name = "interval", defaultValue = "0") Long interval, @RequestParam(name = "interval", defaultValue = "0") Long interval,
@RequestParam(name = "limit", defaultValue = "100") Integer limit, @RequestParam(name = "limit", defaultValue = "100") Integer limit,
@RequestParam(name = "agg", defaultValue = "NONE") String aggStr, @RequestParam(name = "agg", defaultValue = "NONE") String aggStr,
@RequestParam(name= "orderBy", defaultValue = "DESC") String orderBy, @RequestParam(name = "orderBy", defaultValue = "DESC") String orderBy,
@RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
(result, tenantId, entityId) -> { (result, tenantId, entityId) -> {
@ -392,7 +395,7 @@ public class TelemetryController extends BaseController {
if (attributes.isEmpty()) { if (attributes.isEmpty()) {
return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST); return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST);
} }
for (AttributeKvEntry attributeKvEntry: attributes) { for (AttributeKvEntry attributeKvEntry : attributes) {
if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) { if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) {
return getImmediateDeferredResult("Key cannot be empty or contains only spaces", HttpStatus.BAD_REQUEST); return getImmediateDeferredResult("Key cannot be empty or contains only spaces", HttpStatus.BAD_REQUEST);
} }
@ -440,9 +443,13 @@ public class TelemetryController extends BaseController {
if (entries.isEmpty()) { if (entries.isEmpty()) {
return getImmediateDeferredResult("No timeseries data found in request body!", HttpStatus.BAD_REQUEST); return getImmediateDeferredResult("No timeseries data found in request body!", HttpStatus.BAD_REQUEST);
} }
SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_TELEMETRY, entityIdSrc, (result, tenantId, entityId) -> { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_TELEMETRY, entityIdSrc, (result, tenantId, entityId) -> {
tsSubService.saveAndNotify(tenantId, entityId, entries, ttl, new FutureCallback<Void>() { long tenantTtl = ttl;
if (!TenantId.SYS_TENANT_ID.equals(tenantId) && tenantTtl == 0) {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays());
}
tsSubService.saveAndNotify(tenantId, entityId, entries, tenantTtl, new FutureCallback<Void>() {
@Override @Override
public void onSuccess(@Nullable Void tmp) { public void onSuccess(@Nullable Void tmp) {
result.setResult(new ResponseEntity(HttpStatus.OK)); result.setResult(new ResponseEntity(HttpStatus.OK));

15
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java

@ -147,6 +147,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) { public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) {
ToUsageStatsServiceMsg statsMsg = msg.getValue(); ToUsageStatsServiceMsg statsMsg = msg.getValue();
TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB()));
if (tenantProfileCache.get(tenantId) == null) {
return;
}
TenantApiUsageState tenantState; TenantApiUsageState tenantState;
List<TsKvEntry> updatedEntries; List<TsKvEntry> updatedEntries;
Map<ApiFeature, ApiUsageStateValue> result; Map<ApiFeature, ApiUsageStateValue> result;
@ -166,7 +171,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
long newValue = tenantState.add(recordKey, kvProto.getValue()); long newValue = tenantState.add(recordKey, kvProto.getValue());
updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue))); updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue)));
long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue());
updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue))); updatedEntries.add(new BasicTsKvEntry(newHourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue)));
apiFeatures.add(recordKey.getApiFeature()); apiFeatures.add(recordKey.getApiFeature());
} }
result = tenantState.checkStateUpdatedDueToThreshold(apiFeatures); result = tenantState.checkStateUpdatedDueToThreshold(apiFeatures);
@ -343,7 +348,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
myTenantStates.values().forEach(state -> { myTenantStates.values().forEach(state -> {
if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) { if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) {
TenantId tenantId = state.getTenantId();
state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth());
ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder();
msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
msg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {
msg.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(0).build());
}
process(new TbProtoQueueMsg<>(UUID.randomUUID(), msg.build()), TbCallback.EMPTY);
} }
}); });
} finally { } finally {

9
application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java

@ -18,9 +18,7 @@ package org.thingsboard.server.service.device;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -114,6 +112,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) {
String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey();
String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret();
if (!StringUtils.isEmpty(provisionRequest.getDeviceName())) {
provisionRequest.setDeviceName(provisionRequest.getDeviceName().trim());
if (StringUtils.isEmpty(provisionRequest.getDeviceName())) {
log.warn("Provision request contains empty device name!");
throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
}
}
if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) { if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) {
throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name());

64
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -82,7 +82,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.TreeMap;
@Service @Service
@Profile("install") @Profile("install")
@ -272,7 +272,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
thermostatDeviceProfile.setDescription("Thermostat device profile"); thermostatDeviceProfile.setDescription("Thermostat device profile");
thermostatDeviceProfile.setDefaultRuleChainId(ruleChainService.findTenantRuleChains( thermostatDeviceProfile.setDefaultRuleChainId(ruleChainService.findTenantRuleChains(
demoTenant.getId(), new PageLink(1, 0, "Thermostat Alarms")).getData().get(0).getId()); demoTenant.getId(), new PageLink(1, 0, "Thermostat")).getData().get(0).getId());
DeviceProfileData deviceProfileData = new DeviceProfileData(); DeviceProfileData deviceProfileData = new DeviceProfileData();
DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
@ -290,13 +290,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
AlarmCondition temperatureCondition = new AlarmCondition(); AlarmCondition temperatureCondition = new AlarmCondition();
temperatureCondition.setSpec(new SimpleAlarmConditionSpec()); temperatureCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter alarmTemperatureAttributeFilter = new KeyFilter(); KeyFilter temperatureAlarmFlagAttributeFilter = new KeyFilter();
alarmTemperatureAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmTemperature")); temperatureAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperatureAlarmFlag"));
alarmTemperatureAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); temperatureAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate alarmTemperatureAttributePredicate = new BooleanFilterPredicate(); BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate();
alarmTemperatureAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
alarmTemperatureAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); temperatureAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
alarmTemperatureAttributeFilter.setPredicate(alarmTemperatureAttributePredicate); temperatureAlarmFlagAttributeFilter.setPredicate(temperatureAlarmFlagAttributePredicate);
KeyFilter temperatureTimeseriesFilter = new KeyFilter(); KeyFilter temperatureTimeseriesFilter = new KeyFilter();
temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
@ -305,13 +305,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
FilterPredicateValue<Double> temperatureTimeseriesPredicateValue = FilterPredicateValue<Double> temperatureTimeseriesPredicateValue =
new FilterPredicateValue<>(25.0, null, new FilterPredicateValue<>(25.0, null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature")); new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold"));
temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue); temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue);
temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate); temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate);
temperatureCondition.setCondition(Arrays.asList(alarmTemperatureAttributeFilter, temperatureTimeseriesFilter)); temperatureCondition.setCondition(Arrays.asList(temperatureAlarmFlagAttributeFilter, temperatureTimeseriesFilter));
temperatureRule.setAlarmDetails("Current temperature = ${temperature}"); temperatureRule.setAlarmDetails("Current temperature = ${temperature}");
temperatureRule.setCondition(temperatureCondition); temperatureRule.setCondition(temperatureCondition);
highTemperature.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule))); highTemperature.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule)));
AlarmRule clearTemperatureRule = new AlarmRule(); AlarmRule clearTemperatureRule = new AlarmRule();
AlarmCondition clearTemperatureCondition = new AlarmCondition(); AlarmCondition clearTemperatureCondition = new AlarmCondition();
@ -324,7 +324,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL); clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL);
FilterPredicateValue<Double> clearTemperatureTimeseriesPredicateValue = FilterPredicateValue<Double> clearTemperatureTimeseriesPredicateValue =
new FilterPredicateValue<>(25.0, null, new FilterPredicateValue<>(25.0, null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature")); new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold"));
clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue); clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue);
clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate); clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate);
@ -340,13 +340,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
AlarmCondition humidityCondition = new AlarmCondition(); AlarmCondition humidityCondition = new AlarmCondition();
humidityCondition.setSpec(new SimpleAlarmConditionSpec()); humidityCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter alarmHumidityAttributeFilter = new KeyFilter(); KeyFilter humidityAlarmFlagAttributeFilter = new KeyFilter();
alarmHumidityAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmHumidity")); humidityAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "humidityAlarmFlag"));
alarmHumidityAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); humidityAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate alarmHumidityAttributePredicate = new BooleanFilterPredicate(); BooleanFilterPredicate humidityAlarmFlagAttributePredicate = new BooleanFilterPredicate();
alarmHumidityAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); humidityAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
alarmHumidityAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); humidityAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
alarmHumidityAttributeFilter.setPredicate(alarmHumidityAttributePredicate); humidityAlarmFlagAttributeFilter.setPredicate(humidityAlarmFlagAttributePredicate);
KeyFilter humidityTimeseriesFilter = new KeyFilter(); KeyFilter humidityTimeseriesFilter = new KeyFilter();
humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity"));
@ -355,14 +355,14 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
FilterPredicateValue<Double> humidityTimeseriesPredicateValue = FilterPredicateValue<Double> humidityTimeseriesPredicateValue =
new FilterPredicateValue<>(60.0, null, new FilterPredicateValue<>(60.0, null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity")); new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold"));
humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue); humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue);
humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate); humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate);
humidityCondition.setCondition(Arrays.asList(alarmHumidityAttributeFilter, humidityTimeseriesFilter)); humidityCondition.setCondition(Arrays.asList(humidityAlarmFlagAttributeFilter, humidityTimeseriesFilter));
humidityRule.setCondition(humidityCondition); humidityRule.setCondition(humidityCondition);
humidityRule.setAlarmDetails("Current humidity = ${humidity}"); humidityRule.setAlarmDetails("Current humidity = ${humidity}");
lowHumidity.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule))); lowHumidity.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule)));
AlarmRule clearHumidityRule = new AlarmRule(); AlarmRule clearHumidityRule = new AlarmRule();
AlarmCondition clearHumidityCondition = new AlarmCondition(); AlarmCondition clearHumidityCondition = new AlarmCondition();
@ -375,7 +375,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL); clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL);
FilterPredicateValue<Double> clearHumidityTimeseriesPredicateValue = FilterPredicateValue<Double> clearHumidityTimeseriesPredicateValue =
new FilterPredicateValue<>(60.0, null, new FilterPredicateValue<>(60.0, null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity")); new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold"));
clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue); clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue);
clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate); clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate);
@ -394,18 +394,18 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE, attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE,
Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)), Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)),
new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -122.1503)), new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -122.1503)),
new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)), new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("temperatureAlarmFlag", true)),
new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)), new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("humidityAlarmFlag", true)),
new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 20)), new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperatureAlarmThreshold", (long) 20)),
new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 50)))); new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("humidityAlarmThreshold", (long) 50))));
attributesService.save(demoTenant.getId(), t2Id, DataConstants.SERVER_SCOPE, attributesService.save(demoTenant.getId(), t2Id, DataConstants.SERVER_SCOPE,
Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.493801)), Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.493801)),
new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -121.948769)), new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -121.948769)),
new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)), new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("temperatureAlarmFlag", true)),
new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)), new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("humidityAlarmFlag", true)),
new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 25)), new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperatureAlarmThreshold", (long) 25)),
new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 30)))); new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("humidityAlarmThreshold", (long) 30))));
installScripts.loadDashboards(demoTenant.getId(), null); installScripts.loadDashboards(demoTenant.getId(), null);
} }

21
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -210,26 +210,9 @@ public class InstallScripts {
public void loadDemoRuleChains(TenantId tenantId) throws Exception { public void loadDemoRuleChains(TenantId tenantId) throws Exception {
Path ruleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, RULE_CHAINS_DIR);
try { try {
JsonNode ruleChainJson = objectMapper.readTree(ruleChainsDir.resolve("thermostat_alarms.json").toFile()); createDefaultRuleChains(tenantId);
RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class); createDefaultRuleChain(tenantId, "Thermostat");
RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
ruleChain.setTenantId(tenantId);
ruleChain = ruleChainService.saveRuleChain(ruleChain);
ruleChainMetaData.setRuleChainId(ruleChain.getId());
ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);
JsonNode rootChainJson = objectMapper.readTree(ruleChainsDir.resolve("root_rule_chain.json").toFile());
RuleChain rootChain = objectMapper.treeToValue(rootChainJson.get("ruleChain"), RuleChain.class);
RuleChainMetaData rootChainMetaData = objectMapper.treeToValue(rootChainJson.get("metadata"), RuleChainMetaData.class);
RuleChainId thermostatsRuleChainId = ruleChain.getId();
rootChainMetaData.getRuleChainConnections().forEach(connection -> connection.setTargetRuleChainId(thermostatsRuleChainId));
rootChain.setTenantId(tenantId);
rootChain = ruleChainService.saveRuleChain(rootChain);
rootChainMetaData.setRuleChainId(rootChain.getId());
ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), rootChainMetaData);
} catch (Exception e) { } catch (Exception e) {
log.error("Unable to load dashboard from json", e); log.error("Unable to load dashboard from json", e);
throw new RuntimeException("Unable to load dashboard from json", e); throw new RuntimeException("Unable to load dashboard from json", e);

2
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -250,6 +250,8 @@ public class DefaultMailService implements MailService {
helper.setText(body); helper.setText(body);
mailSender.send(helper.getMimeMessage()); mailSender.send(helper.getMimeMessage());
apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1);
} else {
throw new RuntimeException("Email sending is disabled due to API limits!");
} }
} }

2
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -297,7 +297,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
try { try {
handleUsageStats(msg, callback); handleUsageStats(msg, callback);
} catch (Throwable e) { } catch (Throwable e) {
log.warn("[{}] Failed to process usge stats: {}", id, msg, e); log.warn("[{}] Failed to process usage stats: {}", id, msg, e);
callback.onFailure(e); callback.onFailure(e);
} }
}); });

6
application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java

@ -18,9 +18,9 @@ package org.thingsboard.server.service.sms;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.sms.SmsSender; import org.thingsboard.rule.engine.api.sms.SmsSender;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; import org.thingsboard.server.common.data.sms.config.AwsSnsSmsProviderConfiguration;
import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration;
import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration;
import org.thingsboard.server.service.sms.aws.AwsSmsSender; import org.thingsboard.server.service.sms.aws.AwsSmsSender;
import org.thingsboard.server.service.sms.twilio.TwilioSmsSender; import org.thingsboard.server.service.sms.twilio.TwilioSmsSender;

6
application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java

@ -22,8 +22,8 @@ import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.sms.SmsSender; import org.thingsboard.rule.engine.api.sms.SmsSender;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration;
import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@ -106,6 +106,8 @@ public class DefaultSmsService implements SmsService {
apiUsageClient.report(tenantId, ApiUsageRecordKey.SMS_EXEC_COUNT, smsCount); apiUsageClient.report(tenantId, ApiUsageRecordKey.SMS_EXEC_COUNT, smsCount);
} }
} }
} else {
throw new RuntimeException("SMS sending is disabled due to API limits!");
} }
} }

2
application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java

@ -23,7 +23,7 @@ import com.amazonaws.services.sns.AmazonSNSClient;
import com.amazonaws.services.sns.model.PublishRequest; import com.amazonaws.services.sns.model.PublishRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; import org.thingsboard.server.common.data.sms.config.AwsSnsSmsProviderConfiguration;
import org.thingsboard.rule.engine.api.sms.exception.SmsException; import org.thingsboard.rule.engine.api.sms.exception.SmsException;
import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
import org.thingsboard.server.service.sms.AbstractSmsSender; import org.thingsboard.server.service.sms.AbstractSmsSender;

2
application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java

@ -19,7 +19,7 @@ import com.twilio.http.TwilioRestClient;
import com.twilio.rest.api.v2010.account.Message; import com.twilio.rest.api.v2010.account.Message;
import com.twilio.type.PhoneNumber; import com.twilio.type.PhoneNumber;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration;
import org.thingsboard.rule.engine.api.sms.exception.SmsException; import org.thingsboard.rule.engine.api.sms.exception.SmsException;
import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
import org.thingsboard.server.service.sms.AbstractSmsSender; import org.thingsboard.server.service.sms.AbstractSmsSender;

16
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -521,27 +521,27 @@ public class DefaultDeviceStateService implements DeviceStateService {
private void save(DeviceId deviceId, String key, long value) { private void save(DeviceId deviceId, String key, long value) {
if (persistToTelemetry) { if (persistToTelemetry) {
tsSubService.saveAndNotify( tsSubService.saveAndNotifyInternal(
TenantId.SYS_TENANT_ID, deviceId, TenantId.SYS_TENANT_ID, deviceId,
Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))), Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))),
new AttributeSaveCallback(deviceId, key, value)); new AttributeSaveCallback<>(deviceId, key, value));
} else { } else {
tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback<>(deviceId, key, value));
} }
} }
private void save(DeviceId deviceId, String key, boolean value) { private void save(DeviceId deviceId, String key, boolean value) {
if (persistToTelemetry) { if (persistToTelemetry) {
tsSubService.saveAndNotify( tsSubService.saveAndNotifyInternal(
TenantId.SYS_TENANT_ID, deviceId, TenantId.SYS_TENANT_ID, deviceId,
Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))), Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))),
new AttributeSaveCallback(deviceId, key, value)); new AttributeSaveCallback<>(deviceId, key, value));
} else { } else {
tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback<>(deviceId, key, value));
} }
} }
private static class AttributeSaveCallback implements FutureCallback<Void> { private static class AttributeSaveCallback<T> implements FutureCallback<T> {
private final DeviceId deviceId; private final DeviceId deviceId;
private final String key; private final String key;
private final Object value; private final Object value;
@ -553,7 +553,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
} }
@Override @Override
public void onSuccess(@Nullable Void result) { public void onSuccess(@Nullable T result) {
log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value); log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value);
} }

11
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -25,6 +25,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.TenantProfile;
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.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@ -34,13 +35,14 @@ import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
@ -119,11 +121,12 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override @Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) { public void saveAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) {
checkInternalEntity(entityId); checkInternalEntity(entityId);
if (apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null;
if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) {
saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback<Integer>() { saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback<Integer>() {
@Override @Override
public void onSuccess(Integer result) { public void onSuccess(Integer result) {
if (result != null && result > 0) { if (!sysTenant && result != null && result > 0) {
apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result); apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result);
} }
callback.onSuccess(null); callback.onSuccess(null);
@ -134,7 +137,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
callback.onFailure(t); callback.onFailure(t);
} }
}); });
} else{ } else {
callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!")); callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!"));
} }
} }

2
application/src/main/resources/i18n/messages.properties

@ -4,4 +4,4 @@ account.activated.subject=Thingsboard - your account has been activated
reset.password.subject=Thingsboard - Password reset has been requested reset.password.subject=Thingsboard - Password reset has been requested
password.was.reset.subject=Thingsboard - your account password has been reset password.was.reset.subject=Thingsboard - your account password has been reset
account.lockout.subject=Thingsboard - User account has been lockout account.lockout.subject=Thingsboard - User account has been lockout
api.usage.state=Thingsboard - Api Usage State for tenant has been updated api.usage.state=Thingsboard - Account limits

48
application/src/main/resources/templates/state.disabled.ftl

@ -103,40 +103,30 @@
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
bgcolor="#f6f6f6"> bgcolor="#f6f6f6">
<table class="main" <table class="main" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;" cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;"
cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
<tbody> <tbody>
<tr style="box-sizing: border-box; margin: 0px;"> <tr style="box-sizing: border-box; margin: 0px;">
<td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" <td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" align="center" valign="top">
align="center" valign="top"> <table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 367px; background-color: #ffffff; width: 600px; max-width: 600px !important;" width="600" cellspacing="0" cellpadding="0">
<table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 223px; padding: 20px; background-color: #ffffff; width: 600px; max-width: 600px !important;"
width="600" cellspacing="0" cellpadding="0">
<tbody> <tbody>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px; height: 110px;" valign="top"><img src="https://media.thingsboard.io/email/head.png" alt="" width="598" height="91" /></td>
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 84px;"
valign="top">
<h2>Your ThingsBoard account feature was disabled</h2>
</td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; margin: 0;">
<td class="content-block" <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #000000; box-sizing: border-box; font-size: 16px; margin: 0px; padding: 0px 32px; height: 66px; vertical-align: middle;" valign="middle">Your ThingsBoard account feature was <strong>disabled</strong></td>
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;" </tr>
valign="top">We have disabled the ${apiFeature} for your account because ThingsBoard has already ${apiLimitValueLabel}. <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; line-height: 24px; margin: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0px; padding: 0px 32px; height: 93px; vertical-align: top;" valign="top">
<div style="padding: 16px; margin-bottom: 24px; border: solid 2px #EB5757; border-radius: 6px; background: rgba(235, 87, 87, 0.05);"><img style="vertical-align: middle; padding-right: 6px;" src="https://media.thingsboard.io/email/alarm.png" alt="" width="20" height="20" />
<div style="display: inline; vertical-align: middle;">We have <strong>disabled</strong> the ${apiFeature} for your account because ThingsBoard has already <strong>${apiLimitValueLabel}.</strong></div>
</div>
</td> </td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 32px; height: 59px;" valign="top">Please contact your system administrator to resolve the issue.</td>
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 59px;"
valign="top">Please contact your system administrator to resolve the issue.
</td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 32px; height: 40px;" valign="top">&mdash; The ThingsBoard</td>
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"
valign="top">&mdash; The ThingsBoard
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -144,16 +134,10 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" <table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" cellpadding="0px 0px 20px">
cellpadding="0px 0px 20px">
<tbody> <tbody>
<tr style="box-sizing: border-box; margin: 0px;"> <tr style="box-sizing: border-box; margin: 0px;">
<td class="aligncenter content-block" <td class="aligncenter content-block" style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;" align="center" valign="top">This email was sent to&nbsp;<a style="box-sizing: border-box; color: #999999; margin: 0px;" href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.</td>
style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;"
align="center" valign="top">This email was sent to&nbsp;<a
style="box-sizing: border-box; color: #999999; margin: 0px;"
href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

43
application/src/main/resources/templates/state.enabled.ftl

@ -103,34 +103,27 @@
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
bgcolor="#f6f6f6"> bgcolor="#f6f6f6">
<table class="main" <table class="main" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;" cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;"
cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
<tbody> <tbody>
<tr style="box-sizing: border-box; margin: 0px;"> <tr style="box-sizing: border-box; margin: 0px;">
<td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" <td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" align="center" valign="top">
align="center" valign="top"> <table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 310px; background-color: #ffffff; width: 600px; max-width: 600px !important;" width="600" cellspacing="0" cellpadding="0">
<table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 223px; padding: 20px; background-color: #ffffff; width: 600px; max-width: 600px !important;"
width="600" cellspacing="0" cellpadding="0">
<tbody> <tbody>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px; height: 110px;" valign="top"><img src="https://media.thingsboard.io/email/head.png" alt="" width="598" height="91" /></td>
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 84px;"
valign="top">
<h2>Your ThingsBoard account feature was enabled</h2>
</td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; margin: 0;">
<td class="content-block" <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #000000; box-sizing: border-box; font-size: 16px; margin: 0px; padding: 0px 32px; height: 66px; vertical-align: middle;" valign="middle">Your ThingsBoard account feature was <strong>enabled</strong></td>
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;" </tr>
valign="top">We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages. <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; line-height: 24px; margin: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0px; padding: 0px 32px; height: 93px; vertical-align: top;" valign="top">
<div style="padding: 16px; margin-bottom: 24px; border: solid 2px #27AE60; border-radius: 6px; background: rgba(39, 174, 96, 0.05);"><img style="vertical-align: middle; padding-right: 6px;" src="https://media.thingsboard.io/email/confirm.png" alt="" width="20" height="20" />
<div style="display: inline; vertical-align: middle;">We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages.</div>
</div>
</td> </td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 32px; height: 40px;" valign="top">&mdash; The ThingsBoard</td>
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"
valign="top">&mdash; The ThingsBoard
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -138,16 +131,10 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" <table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" cellpadding="0px 0px 20px">
cellpadding="0px 0px 20px">
<tbody> <tbody>
<tr style="box-sizing: border-box; margin: 0px;"> <tr style="box-sizing: border-box; margin: 0px;">
<td class="aligncenter content-block" <td class="aligncenter content-block" style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;" align="center" valign="top">This email was sent to&nbsp;<a style="box-sizing: border-box; color: #999999; margin: 0px;" href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.</td>
style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;"
align="center" valign="top">This email was sent to&nbsp;<a
style="box-sizing: border-box; color: #999999; margin: 0px;"
href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

49
application/src/main/resources/templates/state.warning.ftl

@ -103,41 +103,30 @@
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
bgcolor="#f6f6f6"> bgcolor="#f6f6f6">
<table class="main" <table class="main" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;" cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;"
cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
<tbody> <tbody>
<tr style="box-sizing: border-box; margin: 0px;"> <tr style="box-sizing: border-box; margin: 0px;">
<td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" <td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" align="center" valign="top">
align="center" valign="top"> <table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 367px; background-color: #ffffff; width: 600px; max-width: 600px !important;" width="600" cellspacing="0" cellpadding="0">
<table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 223px; padding: 20px; background-color: #ffffff; width: 600px; max-width: 600px !important;"
width="600" cellspacing="0" cellpadding="0">
<tbody> <tbody>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px; height: 110px;" valign="top"><img src="https://media.thingsboard.io/email/head.png" alt="" width="598" height="91" /></td>
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 84px;"
valign="top">
<h2>Your ThingsBoard account feature may be disabled soon</h2>
</td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; margin: 0;">
<td class="content-block" <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #000000; box-sizing: border-box; font-size: 16px; margin: 0px; padding: 0px 32px; height: 66px; vertical-align: middle;" valign="middle"><strong>Warning:</strong> your ThingsBoard account feature may be disabled soon</td>
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;" </tr>
valign="top"> <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; line-height: 24px; margin: 0;">
ThingsBoard has already&nbsp;${apiValueLabel}.<br> ${apiFeature} will be disabled for your account once the limit will be reached. <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0px; padding: 0px 32px; height: 93px; vertical-align: top;" valign="top">
<div style="padding: 16px; margin-bottom: 24px; border: solid 2px #F2994A; border-radius: 6px; background: rgba(242, 153, 74, 0.05);"><img style="vertical-align: middle; padding-right: 6px;" src="https://media.thingsboard.io/email/warning.png" alt="" width="20" height="20" />
<div style="display: inline; vertical-align: middle;">ThingsBoard has already&nbsp;${apiValueLabel}.<br />${apiFeature} will be <strong>disabled</strong> for your account once the limit will be reached.</div>
</div>
</td> </td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 32px; height: 59px;" valign="top">Please contact your system administrator to resolve the issue.</td>
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 59px;"
valign="top">Please contact your system administrator to resolve the issue.
</td>
</tr> </tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" <td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 32px; height: 40px;" valign="top">&mdash; The ThingsBoard</td>
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;"
valign="top">&mdash; The ThingsBoard
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -145,16 +134,10 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" <table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" cellpadding="0px 0px 20px">
cellpadding="0px 0px 20px">
<tbody> <tbody>
<tr style="box-sizing: border-box; margin: 0px;"> <tr style="box-sizing: border-box; margin: 0px;">
<td class="aligncenter content-block" <td class="aligncenter content-block" style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;" align="center" valign="top">This email was sent to&nbsp;<a style="box-sizing: border-box; color: #999999; margin: 0px;" href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.</td>
style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;"
align="center" valign="top">This email was sent to&nbsp;<a
style="box-sizing: border-box; color: #999999; margin: 0px;"
href="mailto:${targetEmail}">${targetEmail}</a>&nbsp;by ThingsBoard.
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

2
common/actor/pom.xml

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

2
common/dao-api/pom.xml

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

3
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java

@ -15,9 +15,6 @@
*/ */
package org.thingsboard.server.dao.device; package org.thingsboard.server.dao.device;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.device.provision.ProvisionResponse;

7
common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java

@ -16,9 +16,12 @@
package org.thingsboard.server.dao.tenant; package org.thingsboard.server.dao.tenant;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
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.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;
import java.util.function.Consumer;
public interface TbTenantProfileCache { public interface TbTenantProfileCache {
TenantProfile get(TenantId tenantId); TenantProfile get(TenantId tenantId);
@ -31,4 +34,8 @@ public interface TbTenantProfileCache {
void evict(TenantId id); void evict(TenantId id);
void addListener(TenantId tenantId, EntityId listenerId, Consumer<TenantProfile> profileListener);
void removeListener(TenantId tenantId, EntityId listenerId);
} }

2
common/data/pom.xml

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

5
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java

@ -18,9 +18,8 @@ package org.thingsboard.server.common.data.device.profile;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.TreeMap;
@Data @Data
public class DeviceProfileAlarm { public class DeviceProfileAlarm {
@ -28,7 +27,7 @@ public class DeviceProfileAlarm {
private String id; private String id;
private String alarmType; private String alarmType;
private LinkedHashMap<AlarmSeverity, AlarmRule> createRules; private TreeMap<AlarmSeverity, AlarmRule> createRules;
private AlarmRule clearRule; private AlarmRule clearRule;
// Hidden in advanced settings // Hidden in advanced settings

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java → common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.rule.engine.api.sms.config; package org.thingsboard.server.common.data.sms.config;
import lombok.Data; import lombok.Data;

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java → common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.rule.engine.api.sms.config; package org.thingsboard.server.common.data.sms.config;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java → common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.rule.engine.api.sms.config; package org.thingsboard.server.common.data.sms.config;
public enum SmsProviderType { public enum SmsProviderType {
AWS_SNS, AWS_SNS,

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java → common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.rule.engine.api.sms.config; package org.thingsboard.server.common.data.sms.config;
import lombok.Data; import lombok.Data;

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java → common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.rule.engine.api.sms.config; package org.thingsboard.server.common.data.sms.config;
import lombok.Data; import lombok.Data;

2
common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java

@ -45,6 +45,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
private long maxEmails; private long maxEmails;
private long maxSms; private long maxSms;
private int defaultStorageTtlDays;
private double warnThreshold; private double warnThreshold;
@Override @Override

2
common/message/pom.xml

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

2
common/pom.xml

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

2
common/queue/pom.xml

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

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

@ -195,9 +195,11 @@ public class HashPartitionService implements PartitionService {
if (current.getServiceTypesList().contains(serviceType.name())) { if (current.getServiceTypesList().contains(serviceType.name())) {
result.add(current.getServiceId()); result.add(current.getServiceId());
} }
for (ServiceInfo serviceInfo : currentOtherServices) { if (currentOtherServices != null) {
if (serviceInfo.getServiceTypesList().contains(serviceType.name())) { for (ServiceInfo serviceInfo : currentOtherServices) {
result.add(serviceInfo.getServiceId()); if (serviceInfo.getServiceTypesList().contains(serviceType.name())) {
result.add(serviceInfo.getServiceId());
}
} }
} }
return result; return result;

2
common/stats/pom.xml

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

2
common/transport/coap/pom.xml

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

3
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java

@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.CoapServer; import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -46,7 +47,7 @@ public class CoapTransportService {
public void init() throws UnknownHostException { public void init() throws UnknownHostException {
log.info("Starting CoAP transport..."); log.info("Starting CoAP transport...");
log.info("Starting CoAP transport server"); log.info("Starting CoAP transport server");
this.server = new CoapServer(); this.server = new CoapServer(NetworkConfig.createStandardWithoutFile());
createResources(); createResources();
InetAddress addr = InetAddress.getByName(coapTransportContext.getHost()); InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());
InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort()); InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());

2
common/transport/http/pom.xml

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

2
common/transport/mqtt/pom.xml

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

1
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -179,6 +179,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
} }
} }
} else { } else {
ctx.close();
throw new RuntimeException("Unsupported topic for provisioning requests!"); throw new RuntimeException("Unsupported topic for provisioning requests!");
} }
} catch (RuntimeException | AdaptorException e) { } catch (RuntimeException | AdaptorException e) {

2
common/transport/pom.xml

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

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

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

2
common/util/pom.xml

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

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>dao</artifactId> <artifactId>dao</artifactId>

8
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -78,6 +78,8 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
public static final String INCORRECT_PAGE_LINK = "Incorrect page link "; public static final String INCORRECT_PAGE_LINK = "Incorrect page link ";
public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId "; public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
public static final String INCORRECT_ASSET_ID = "Incorrect assetId "; public static final String INCORRECT_ASSET_ID = "Incorrect assetId ";
public static final String TB_SERVICE_QUEUE = "TbServiceQueue";
@Autowired @Autowired
private AssetDao assetDao; private AssetDao assetDao;
@ -329,8 +331,10 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
protected void validateCreate(TenantId tenantId, Asset asset) { protected void validateCreate(TenantId tenantId, Asset asset) {
DefaultTenantProfileConfiguration profileConfiguration = DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxAssets = profileConfiguration.getMaxAssets(); if (!TB_SERVICE_QUEUE.equals(asset.getType())) {
validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET); long maxAssets = profileConfiguration.getMaxAssets();
validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET);
}
} }
@Override @Override

2
dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java

@ -122,5 +122,5 @@ public interface AssetRepository extends PagingAndSortingRepository<AssetEntity,
@Query("SELECT DISTINCT a.type FROM AssetEntity a WHERE a.tenantId = :tenantId") @Query("SELECT DISTINCT a.type FROM AssetEntity a WHERE a.tenantId = :tenantId")
List<String> findTenantAssetTypes(@Param("tenantId") UUID tenantId); List<String> findTenantAssetTypes(@Param("tenantId") UUID tenantId);
Long countByTenantId(UUID tenantId); Long countByTenantIdAndTypeIsNot(UUID tenantId, String type);
} }

4
dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java

@ -39,6 +39,8 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
/** /**
* Created by Valerii Sosliuk on 5/19/2017. * Created by Valerii Sosliuk on 5/19/2017.
*/ */
@ -179,6 +181,6 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
@Override @Override
public Long countByTenantId(TenantId tenantId) { public Long countByTenantId(TenantId tenantId) {
return assetRepository.countByTenantId(tenantId.getId()); return assetRepository.countByTenantIdAndTypeIsNot(tenantId.getId(), TB_SERVICE_QUEUE);
} }
} }

45
dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java

@ -19,16 +19,15 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
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.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
@Service @Service
@Slf4j @Slf4j
@ -40,6 +39,7 @@ public class DefaultTbTenantProfileCache implements TbTenantProfileCache {
private final ConcurrentMap<TenantProfileId, TenantProfile> tenantProfilesMap = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantProfileId, TenantProfile> tenantProfilesMap = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, TenantProfileId> tenantsMap = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantId, TenantProfileId> tenantsMap = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, ConcurrentMap<EntityId, Consumer<TenantProfile>>> profileListeners = new ConcurrentHashMap<>();
public DefaultTbTenantProfileCache(TenantProfileService tenantProfileService, TenantService tenantService) { public DefaultTbTenantProfileCache(TenantProfileService tenantProfileService, TenantService tenantService) {
this.tenantProfileService = tenantProfileService; this.tenantProfileService = tenantProfileService;
@ -85,17 +85,56 @@ public class DefaultTbTenantProfileCache implements TbTenantProfileCache {
public void put(TenantProfile profile) { public void put(TenantProfile profile) {
if (profile.getId() != null) { if (profile.getId() != null) {
tenantProfilesMap.put(profile.getId(), profile); tenantProfilesMap.put(profile.getId(), profile);
notifyTenantListeners(profile);
} }
} }
@Override @Override
public void evict(TenantProfileId profileId) { public void evict(TenantProfileId profileId) {
tenantProfilesMap.remove(profileId); tenantProfilesMap.remove(profileId);
notifyTenantListeners(get(profileId));
}
public void notifyTenantListeners(TenantProfile tenantProfile) {
if (tenantProfile != null) {
tenantsMap.forEach(((tenantId, tenantProfileId) -> {
if (tenantProfileId.equals(tenantProfile.getId())) {
ConcurrentMap<EntityId, Consumer<TenantProfile>> tenantListeners = profileListeners.get(tenantId);
if (tenantListeners != null) {
tenantListeners.forEach((id, listener) -> listener.accept(tenantProfile));
}
}
}));
}
} }
@Override @Override
public void evict(TenantId tenantId) { public void evict(TenantId tenantId) {
tenantsMap.remove(tenantId); tenantsMap.remove(tenantId);
TenantProfile tenantProfile = get(tenantId);
if (tenantProfile != null) {
ConcurrentMap<EntityId, Consumer<TenantProfile>> tenantListeners = profileListeners.get(tenantId);
if (tenantListeners != null) {
tenantListeners.forEach((id, listener) -> listener.accept(tenantProfile));
}
}
}
@Override
public void addListener(TenantId tenantId, EntityId listenerId, Consumer<TenantProfile> profileListener) {
//Force cache of the tenant id.
get(tenantId);
if (profileListener != null) {
profileListeners.computeIfAbsent(tenantId, id -> new ConcurrentHashMap<>()).put(listenerId, profileListener);
}
}
@Override
public void removeListener(TenantId tenantId, EntityId listenerId) {
ConcurrentMap<EntityId, Consumer<TenantProfile>> tenantListeners = profileListeners.get(tenantId);
if (tenantListeners != null) {
tenantListeners.remove(listenerId);
}
} }
} }

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

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/js-executor/package.json

@ -1,7 +1,7 @@
{ {
"name": "thingsboard-js-executor", "name": "thingsboard-js-executor",
"private": true, "private": true,
"version": "3.2.0", "version": "3.2.1",
"description": "ThingsBoard JavaScript Executor Microservice", "description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.js", "main": "server.js",
"bin": "server.js", "bin": "server.js",

2
msa/js-executor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>

2
msa/tb-node/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

6
msa/tb/docker-cassandra/start-db.sh

@ -27,7 +27,11 @@ fi
exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 & exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 &
if [ ! -f ${firstlaunch} ]; then if [ ! -f ${firstlaunch} ]; then
psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" sleep 2
while ! psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard"
do
sleep 1
done
fi fi
cassandra_data_dir=${CASSANDRA_DATA} cassandra_data_dir=${CASSANDRA_DATA}

8
msa/tb/docker-postgres/start-db.sh

@ -27,5 +27,9 @@ fi
exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 & exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 &
if [ ! -f ${firstlaunch} ]; then if [ ! -f ${firstlaunch} ]; then
psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" sleep 2
fi while ! psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard"
do
sleep 1
done
fi

2
msa/tb/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/web-ui/package.json

@ -1,7 +1,7 @@
{ {
"name": "thingsboard-web-ui", "name": "thingsboard-web-ui",
"private": true, "private": true,
"version": "3.2.0", "version": "3.2.1",
"description": "ThingsBoard Web UI Microservice", "description": "ThingsBoard Web UI Microservice",
"main": "server.js", "main": "server.js",
"bin": "server.js", "bin": "server.js",

2
msa/web-ui/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

4
netty-mqtt/pom.xml

@ -19,11 +19,11 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>netty-mqtt</artifactId> <artifactId>netty-mqtt</artifactId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Netty MQTT Client</name> <name>Netty MQTT Client</name>

2
pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Thingsboard</name> <name>Thingsboard</name>

2
rest-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>rest-client</artifactId> <artifactId>rest-client</artifactId>

11
rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

@ -109,6 +109,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
@ -218,7 +219,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
} }
public void sendTestMail(AdminSettings adminSettings) { public void sendTestMail(AdminSettings adminSettings) {
restTemplate.postForEntity(baseURL + "/api/admin/settings/testMail", adminSettings, AdminSettings.class); restTemplate.postForLocation(baseURL + "/api/admin/settings/testMail", adminSettings);
}
public void sendTestSms(TestSmsRequest testSmsRequest) {
restTemplate.postForLocation(baseURL + "/api/admin/settings/testSms", testSmsRequest);
} }
public Optional<SecuritySettings> getSecuritySettings() { public Optional<SecuritySettings> getSecuritySettings() {
@ -1714,6 +1719,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
return restTemplate.postForEntity(baseURL + "/api/oauth2/config", oauth2Params, OAuth2ClientsParams.class).getBody(); return restTemplate.postForEntity(baseURL + "/api/oauth2/config", oauth2Params, OAuth2ClientsParams.class).getBody();
} }
public String getLoginProcessingUrl() {
return restTemplate.getForEntity(baseURL + "/api/oauth2/loginProcessingUrl", String.class).getBody();
}
public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) {
restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId.getId()); restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId.getId());
} }

2
rule-engine/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>

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

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>
</parent> </parent>
<groupId>org.thingsboard.rule-engine</groupId> <groupId>org.thingsboard.rule-engine</groupId>

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java

@ -15,7 +15,7 @@
*/ */
package org.thingsboard.rule.engine.api; package org.thingsboard.rule.engine.api;
import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;

7
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -237,7 +238,11 @@ public interface TbContext {
void clearRuleNodeStates(); void clearRuleNodeStates();
void addTenantProfileListener(Consumer<TenantProfile> listener);
void addDeviceProfileListeners(Consumer<DeviceProfile> listener, BiConsumer<DeviceId, DeviceProfile> deviceListener); void addDeviceProfileListeners(Consumer<DeviceProfile> listener, BiConsumer<DeviceId, DeviceProfile> deviceListener);
void removeProfileListener(); void removeListeners();
TenantProfile getTenantProfile();
} }

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java

@ -15,7 +15,7 @@
*/ */
package org.thingsboard.rule.engine.api.sms; package org.thingsboard.rule.engine.api.sms;
import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration;
public interface SmsSenderFactory { public interface SmsSenderFactory {

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

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>
</parent> </parent>
<groupId>org.thingsboard.rule-engine</groupId> <groupId>org.thingsboard.rule-engine</groupId>

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java

@ -54,7 +54,8 @@ import java.util.concurrent.TimeUnit;
relationTypes = {"Alarm Created", "Alarm Updated", "Alarm Severity Updated", "Alarm Cleared", "Success", "Failure"}, relationTypes = {"Alarm Created", "Alarm Updated", "Alarm Severity Updated", "Alarm Cleared", "Success", "Failure"},
configClazz = TbDeviceProfileNodeConfiguration.class, configClazz = TbDeviceProfileNodeConfiguration.class,
nodeDescription = "Process device messages based on device profile settings", nodeDescription = "Process device messages based on device profile settings",
nodeDetails = "Create and clear alarms based on alarm rules defined in device profile. Generates ", nodeDetails = "Create and clear alarms based on alarm rules defined in device profile. The output relation type is either " +
"'Alarm Created', 'Alarm Updated', 'Alarm Severity Updated' and 'Alarm Cleared' or simply 'Success' if no alarms were affected.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbDeviceProfileConfig" configDirective = "tbDeviceProfileConfig"
) )
@ -119,7 +120,6 @@ public class TbDeviceProfileNode implements TbNode {
} else { } else {
removeDeviceState(deviceId); removeDeviceState(deviceId);
} }
} else { } else {
if (EntityType.DEVICE.equals(originatorType)) { if (EntityType.DEVICE.equals(originatorType)) {
DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); DeviceId deviceId = new DeviceId(msg.getOriginator().getId());
@ -149,7 +149,7 @@ public class TbDeviceProfileNode implements TbNode {
@Override @Override
public void destroy() { public void destroy() {
ctx.removeProfileListener(); ctx.removeListeners();
deviceStates.clear(); deviceStates.clear();
} }

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java

@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.sms;
import lombok.Data; import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration;
@Data @Data
public class TbSendSmsNodeConfiguration implements NodeConfiguration { public class TbSendSmsNodeConfiguration implements NodeConfiguration {

17
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java

@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
@ -31,10 +32,12 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
@ -50,10 +53,20 @@ import java.util.Map;
public class TbMsgTimeseriesNode implements TbNode { public class TbMsgTimeseriesNode implements TbNode {
private TbMsgTimeseriesNodeConfiguration config; private TbMsgTimeseriesNodeConfiguration config;
private TbContext ctx;
private long tenantProfileDefaultStorageTtl;
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbMsgTimeseriesNodeConfiguration.class); this.config = TbNodeUtils.convert(configuration, TbMsgTimeseriesNodeConfiguration.class);
this.ctx = ctx;
ctx.addTenantProfileListener(this::onTenantProfileUpdate);
onTenantProfileUpdate(ctx.getTenantProfile());
}
void onTenantProfileUpdate(TenantProfile tenantProfile) {
DefaultTenantProfileConfiguration configuration = (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration();
tenantProfileDefaultStorageTtl = TimeUnit.DAYS.toSeconds(configuration.getDefaultStorageTtlDays());
} }
@Override @Override
@ -77,6 +90,9 @@ public class TbMsgTimeseriesNode implements TbNode {
} }
String ttlValue = msg.getMetaData().getValue("TTL"); String ttlValue = msg.getMetaData().getValue("TTL");
long ttl = !StringUtils.isEmpty(ttlValue) ? Long.parseLong(ttlValue) : config.getDefaultTTL(); long ttl = !StringUtils.isEmpty(ttlValue) ? Long.parseLong(ttlValue) : config.getDefaultTTL();
if (ttl == 0L) {
ttl = tenantProfileDefaultStorageTtl;
}
ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), msg.getOriginator(), tsKvEntryList, ttl, new TelemetryNodeCallback(ctx, msg)); ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), msg.getOriginator(), tsKvEntryList, ttl, new TelemetryNodeCallback(ctx, msg));
} }
@ -96,6 +112,7 @@ public class TbMsgTimeseriesNode implements TbNode {
@Override @Override
public void destroy() { public void destroy() {
ctx.removeListeners();
} }
} }

9
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java

@ -23,17 +23,13 @@ import org.junit.runner.RunWith;
import org.mockito.AdditionalAnswers; import org.mockito.AdditionalAnswers;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.util.StringUtils;
import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.device.profile.AlarmCondition; import org.thingsboard.server.common.data.device.profile.AlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.AlarmRule;
@ -52,11 +48,10 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -140,7 +135,7 @@ public class TbDeviceProfileNodeTest {
DeviceProfileAlarm dpa = new DeviceProfileAlarm(); DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID"); dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm"); dpa.setAlarmType("highTemperatureAlarm");
dpa.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
KeyFilter lowTempFilter = new KeyFilter(); KeyFilter lowTempFilter = new KeyFilter();
lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));

2
tools/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>tools</artifactId> <artifactId>tools</artifactId>

2
transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.transport</groupId> <groupId>org.thingsboard.transport</groupId>

2
transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.transport</groupId> <groupId>org.thingsboard.transport</groupId>

2
transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.transport</groupId> <groupId>org.thingsboard.transport</groupId>

2
transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>

2
ui-ngx/package.json

@ -1,6 +1,6 @@
{ {
"name": "thingsboard", "name": "thingsboard",
"version": "3.2.0", "version": "3.2.1",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --host 0.0.0.0 --open", "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --host 0.0.0.0 --open",

2
ui-ngx/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.2.0-SNAPSHOT</version> <version>3.2.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>

24
ui-ngx/src/app/core/auth/auth.service.ts

@ -282,8 +282,7 @@ export class AuthService {
if (publicId) { if (publicId) {
return this.publicLogin(publicId).pipe( return this.publicLogin(publicId).pipe(
mergeMap((response) => { mergeMap((response) => {
this.updateAndValidateToken(response.token, 'jwt_token', false); this.updateAndValidateTokens(response.token, response.refreshToken, false);
this.updateAndValidateToken(response.refreshToken, 'refresh_token', false);
return this.procceedJwtTokenValidate(); return this.procceedJwtTokenValidate();
}), }),
catchError((err) => { catchError((err) => {
@ -317,8 +316,7 @@ export class AuthService {
}; };
return this.http.post<LoginResponse>('/api/auth/login', loginRequest, defaultHttpOptions()).pipe( return this.http.post<LoginResponse>('/api/auth/login', loginRequest, defaultHttpOptions()).pipe(
mergeMap((loginResponse: LoginResponse) => { mergeMap((loginResponse: LoginResponse) => {
this.updateAndValidateToken(loginResponse.token, 'jwt_token', false); this.updateAndValidateTokens(loginResponse.token, loginResponse.refreshToken, false);
this.updateAndValidateToken(loginResponse.refreshToken, 'refresh_token', false);
return this.procceedJwtTokenValidate(); return this.procceedJwtTokenValidate();
} }
) )
@ -439,7 +437,7 @@ export class AuthService {
})); }));
} }
public refreshJwtToken(): Observable<LoginResponse> { public refreshJwtToken(loadUserElseStoreJwtToken = true): Observable<LoginResponse> {
let response: Observable<LoginResponse> = this.refreshTokenSubject; let response: Observable<LoginResponse> = this.refreshTokenSubject;
if (this.refreshTokenSubject === null) { if (this.refreshTokenSubject === null) {
this.refreshTokenSubject = new ReplaySubject<LoginResponse>(1); this.refreshTokenSubject = new ReplaySubject<LoginResponse>(1);
@ -456,7 +454,11 @@ export class AuthService {
}; };
const refreshObservable = this.http.post<LoginResponse>('/api/auth/token', refreshTokenRequest, defaultHttpOptions()); const refreshObservable = this.http.post<LoginResponse>('/api/auth/token', refreshTokenRequest, defaultHttpOptions());
refreshObservable.subscribe((loginResponse: LoginResponse) => { refreshObservable.subscribe((loginResponse: LoginResponse) => {
this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false); if (loadUserElseStoreJwtToken) {
this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false);
} else {
this.updateAndValidateTokens(loginResponse.token, loginResponse.refreshToken, true);
}
this.refreshTokenSubject.next(loginResponse); this.refreshTokenSubject.next(loginResponse);
this.refreshTokenSubject.complete(); this.refreshTokenSubject.complete();
this.refreshTokenSubject = null; this.refreshTokenSubject = null;
@ -474,7 +476,7 @@ export class AuthService {
const subject = new ReplaySubject<void>(); const subject = new ReplaySubject<void>();
if (!AuthService.isTokenValid('jwt_token')) { if (!AuthService.isTokenValid('jwt_token')) {
if (doRefresh) { if (doRefresh) {
this.refreshJwtToken().subscribe( this.refreshJwtToken(!doRefresh).subscribe(
() => { () => {
subject.next(); subject.next();
subject.complete(); subject.complete();
@ -505,8 +507,7 @@ export class AuthService {
this.notifyUnauthenticated(); this.notifyUnauthenticated();
} }
} else { } else {
this.updateAndValidateToken(jwtToken, 'jwt_token', true); this.updateAndValidateTokens(jwtToken, refreshToken, true);
this.updateAndValidateToken(refreshToken, 'refresh_token', true);
if (notify) { if (notify) {
this.notifyUserLoaded(false); this.notifyUserLoaded(false);
this.loadUser(false).subscribe( this.loadUser(false).subscribe(
@ -525,6 +526,11 @@ export class AuthService {
} }
} }
private updateAndValidateTokens(jwtToken, refreshToken, notify: boolean) {
this.updateAndValidateToken(jwtToken, 'jwt_token', notify);
this.updateAndValidateToken(refreshToken, 'refresh_token', notify);
}
public parsePublicId(): string { public parsePublicId(): string {
const token = AuthService.getJwtToken(); const token = AuthService.getJwtToken();
if (token) { if (token) {

2
ui-ngx/src/app/core/http/dashboard.service.ts

@ -130,7 +130,7 @@ export class DashboardService {
const publicCustomerId = publicCustomers[0].customerId.id; const publicCustomerId = publicCustomers[0].customerId.id;
let url = this.window.location.protocol + '//' + this.window.location.hostname; let url = this.window.location.protocol + '//' + this.window.location.hostname;
const port = this.window.location.port; const port = this.window.location.port;
if (port !== '80' && port !== '443') { if (port && port.length > 0 && port !== '80' && port !== '443') {
url += ':' + port; url += ':' + port;
} }
url += `/dashboard/${dashboard.id.id}?publicId=${publicCustomerId}`; url += `/dashboard/${dashboard.id.id}?publicId=${publicCustomerId}`;

1
ui-ngx/src/app/core/services/menu.models.ts

@ -23,6 +23,7 @@ export interface MenuSection extends HasUUID{
type: MenuSectionType; type: MenuSectionType;
path: string; path: string;
icon: string; icon: string;
notExact?: boolean;
isMdiIcon?: boolean; isMdiIcon?: boolean;
height?: string; height?: string;
pages?: Array<MenuSection>; pages?: Array<MenuSection>;

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

@ -288,6 +288,14 @@ export class MenuService {
type: 'link', type: 'link',
path: '/auditLogs', path: '/auditLogs',
icon: 'track_changes' icon: 'track_changes'
},
{
id: guid(),
name: 'api-usage.api-usage',
type: 'link',
path: '/usage',
icon: 'insert_chart',
notExact: true
} }
); );
return sections; return sections;
@ -374,6 +382,11 @@ export class MenuService {
name: 'audit-log.audit-logs', name: 'audit-log.audit-logs',
icon: 'track_changes', icon: 'track_changes',
path: '/auditLogs' path: '/auditLogs'
},
{
name: 'api-usage.api-usage',
icon: 'insert_chart',
path: '/usage'
} }
] ]
} }

44
ui-ngx/src/app/core/services/utils.service.ts

@ -21,17 +21,18 @@ import { Inject, Injectable, NgZone } from '@angular/core';
import { WINDOW } from '@core/services/window.service'; import { WINDOW } from '@core/services/window.service';
import { ExceptionData } from '@app/shared/models/error.models'; import { ExceptionData } from '@app/shared/models/error.models';
import { import {
baseUrl,
createLabelFromDatasource, createLabelFromDatasource,
deepClone, deepClone,
deleteNullProperties, deleteNullProperties,
guid, guid,
isDefined, isDefined,
isDefinedAndNotNull, isDefinedAndNotNull, isString,
isUndefined isUndefined
} from '@core/utils'; } from '@core/utils';
import { WindowMessage } from '@shared/models/window-message.model'; import { WindowMessage } from '@shared/models/window-message.model';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { customTranslationsPrefix } from '@app/shared/models/constants'; import { customTranslationsPrefix, i18nPrefix } from '@app/shared/models/constants';
import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models'; import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models';
@ -42,6 +43,8 @@ import jsonSchemaDefaults from 'json-schema-defaults';
import materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints'; import materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints';
import { Observable, of, ReplaySubject } from 'rxjs'; import { Observable, of, ReplaySubject } from 'rxjs';
const i18nRegExp = new RegExp(`{${i18nPrefix}:[^{}]+}`, 'g');
const predefinedFunctions: { [func: string]: string } = { const predefinedFunctions: { [func: string]: string } = {
Sin: 'return Math.round(1000*Math.sin(time/5000));', Sin: 'return Math.round(1000*Math.sin(time/5000));',
Cos: 'return Math.round(1000*Math.cos(time/5000));', Cos: 'return Math.round(1000*Math.cos(time/5000));',
@ -107,8 +110,8 @@ export class UtilsService {
materialIcons: Array<string> = []; materialIcons: Array<string> = [];
constructor(@Inject(WINDOW) private window: Window, constructor(@Inject(WINDOW) private window: Window,
private zone: NgZone, private zone: NgZone,
private translate: TranslateService) { private translate: TranslateService) {
let frame: Element = null; let frame: Element = null;
try { try {
frame = window.frameElement; frame = window.frameElement;
@ -221,8 +224,31 @@ export class UtilsService {
} }
public customTranslation(translationValue: string, defaultValue: string): string { public customTranslation(translationValue: string, defaultValue: string): string {
if (translationValue && isString(translationValue)) {
if (translationValue.includes(`{${i18nPrefix}`)) {
const matches = translationValue.match(i18nRegExp);
let result = translationValue;
for (const match of matches) {
const translationId = match.substring(6, match.length - 1);
result = result.replace(match, this.doTranslate(translationId, match));
}
return result;
} else {
return this.doTranslate(translationValue, defaultValue, customTranslationsPrefix);
}
} else {
return translationValue;
}
}
private doTranslate(translationValue: string, defaultValue: string, prefix?: string): string {
let result: string; let result: string;
const translationId = customTranslationsPrefix + translationValue; let translationId;
if (prefix) {
translationId = prefix + translationValue;
} else {
translationId = translationValue;
}
const translation = this.translate.instant(translationId); const translation = this.translate.instant(translationId);
if (translation !== translationId) { if (translation !== translationId) {
result = translation + ''; result = translation + '';
@ -384,7 +410,7 @@ export class UtilsService {
} }
public updateQueryParam(name: string, value: string | null) { public updateQueryParam(name: string, value: string | null) {
const baseUrl = [this.window.location.protocol, '//', this.window.location.host, this.window.location.pathname].join(''); const baseUrlPart = [baseUrl(), this.window.location.pathname].join('');
const urlQueryString = this.window.location.search; const urlQueryString = this.window.location.search;
let newParam = ''; let newParam = '';
let params = ''; let params = '';
@ -404,7 +430,11 @@ export class UtilsService {
} else if (newParam) { } else if (newParam) {
params = '?' + newParam; params = '?' + newParam;
} }
this.window.history.replaceState({}, '', baseUrl + params); this.window.history.replaceState({}, '', baseUrlPart + params);
}
public baseUrl(): string {
return baseUrl();
} }
public deepClone<T>(target: T, ignoreFields?: string[]): T { public deepClone<T>(target: T, ignoreFields?: string[]): T {

9
ui-ngx/src/app/core/utils.ts

@ -385,6 +385,15 @@ export function padValue(val: any, dec: number): string {
return strVal; return strVal;
} }
export function baseUrl(): string {
let url = window.location.protocol + '//' + window.location.hostname;
const port = window.location.port;
if (port && port.length > 0 && port !== '80' && port !== '443') {
url += ':' + port;
}
return url;
}
export function sortObjectKeys<T>(obj: T): T { export function sortObjectKeys<T>(obj: T): T {
return Object.keys(obj).sort().reduce((acc, key) => { return Object.keys(obj).sort().reduce((acc, key) => {
acc[key] = obj[key]; acc[key] = obj[key];

28
ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts

@ -14,12 +14,12 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { DeviceService } from '@core/http/device.service'; import { DeviceService } from '@core/http/device.service';
import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models';
import { isDefinedAndNotNull, isEqual } from '@core/utils'; import { isDefinedAndNotNull, isEqual } from '@core/utils';
import { BehaviorSubject, Subject } from 'rxjs'; import { BehaviorSubject, Subject, Subscription } from 'rxjs';
import { distinctUntilChanged, filter, mergeMap, tap } from 'rxjs/operators'; import { distinctUntilChanged, filter, mergeMap, tap } from 'rxjs/operators';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
@ -32,12 +32,10 @@ import { TranslateService } from '@ngx-translate/core';
templateUrl: './copy-device-credentials.component.html', templateUrl: './copy-device-credentials.component.html',
styleUrls: [] styleUrls: []
}) })
export class CopyDeviceCredentialsComponent implements OnDestroy { export class CopyDeviceCredentialsComponent implements OnInit, OnDestroy {
private deviceId$ = new BehaviorSubject<EntityId>(null); private deviceId$ = new BehaviorSubject<EntityId>(null);
private credentials$ = new Subject<DeviceCredentials>();
private tooltipMessage: string; private tooltipMessage: string;
public hideButton = true; public hideButton = true;
@ -56,13 +54,14 @@ export class CopyDeviceCredentialsComponent implements OnDestroy {
@Input() disabled: boolean; @Input() disabled: boolean;
@Input() @Input()
set credentials(credential: DeviceCredentials) { credentials$: Subject<DeviceCredentials>;
this.credentials$.next(credential);
} private credentialsSubscription: Subscription = null;
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
private translate: TranslateService, private translate: TranslateService,
private deviceService: DeviceService private deviceService: DeviceService,
private cd: ChangeDetectorRef
) { ) {
this.deviceId$.pipe( this.deviceId$.pipe(
filter(device => isDefinedAndNotNull(device) && device.entityType === EntityType.DEVICE), filter(device => isDefinedAndNotNull(device) && device.entityType === EntityType.DEVICE),
@ -73,19 +72,23 @@ export class CopyDeviceCredentialsComponent implements OnDestroy {
this.processingValue(deviceCredentials); this.processingValue(deviceCredentials);
this.loading = false; this.loading = false;
}); });
}
this.credentials$.pipe( ngOnInit(): void {
this.credentialsSubscription = this.credentials$.pipe(
filter(credential => isDefinedAndNotNull(credential)), filter(credential => isDefinedAndNotNull(credential)),
distinctUntilChanged((prev, curr) => isEqual(prev, curr)) distinctUntilChanged((prev, curr) => isEqual(prev, curr))
).subscribe(deviceCredentials => { ).subscribe(deviceCredentials => {
console.warn(deviceCredentials);
this.processingValue(deviceCredentials); this.processingValue(deviceCredentials);
this.cd.detectChanges();
}); });
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.deviceId$.unsubscribe(); this.deviceId$.unsubscribe();
this.credentials$.unsubscribe(); if (this.credentialsSubscription !== null) {
this.credentialsSubscription.unsubscribe();
}
} }
private processingValue(credential: DeviceCredentials): void { private processingValue(credential: DeviceCredentials): void {
@ -125,4 +128,5 @@ export class CopyDeviceCredentialsComponent implements OnDestroy {
horizontalPosition: 'right' horizontalPosition: 'right'
})); }));
} }
} }

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

Loading…
Cancel
Save