Browse Source

Merge pull request #194 from thingsboard/feature/TB-65

TB-65: ThingsBoard Install application/service.
pull/196/head
Igor Kulikov 9 years ago
committed by GitHub
parent
commit
e972ec65dc
  1. 3
      application/pom.xml
  2. 700
      application/src/main/data/json/demo/dashboards/raspberry_pi_gpio_demo_dashboard.json
  3. 264
      application/src/main/data/json/demo/dashboards/temperature___humidity_demo_dashboard.json
  4. 13
      application/src/main/data/json/demo/plugins/demo_device_messaging_rpc_plugin.json
  5. 28
      application/src/main/data/json/demo/plugins/demo_email_plugin.json
  6. 11
      application/src/main/data/json/demo/plugins/demo_time_rpc_plugin.json
  7. 46
      application/src/main/data/json/demo/rules/demo_alarm_rule.json
  8. 35
      application/src/main/data/json/demo/rules/demo_gettime_rpc_rule.json
  9. 38
      application/src/main/data/json/demo/rules/demo_messaging_rpc_rule.json
  10. 11
      application/src/main/data/json/system/plugins/system_rpc_plugin.json
  11. 9
      application/src/main/data/json/system/plugins/system_telemetry_plugin.json
  12. 29
      application/src/main/data/json/system/rules/system_telemetry_rule.json
  13. 25
      application/src/main/data/json/system/widget_bundles/alarm_widgets.json
  14. 73
      application/src/main/data/json/system/widget_bundles/analogue_gauges.json
  15. 121
      application/src/main/data/json/system/widget_bundles/cards.json
  16. 177
      application/src/main/data/json/system/widget_bundles/charts.json
  17. 217
      application/src/main/data/json/system/widget_bundles/digital_gauges.json
  18. 73
      application/src/main/data/json/system/widget_bundles/gpio_widgets.json
  19. 73
      application/src/main/data/json/system/widget_bundles/maps.json
  20. 55
      application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java
  21. 31
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallException.java
  22. 95
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  23. 162
      application/src/main/java/org/thingsboard/server/install/cql/CQLStatementsParser.java
  24. 33
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  25. 2
      application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java
  26. 59
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java
  27. 22
      application/src/main/java/org/thingsboard/server/service/install/DatabaseSchemaService.java
  28. 336
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  29. 40
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java
  30. 32
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  31. 2
      application/src/main/resources/thingsboard.yml
  32. 13
      application/src/test/java/org/thingsboard/server/controller/AdminControllerTest.java
  33. 14
      application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
  34. 187
      dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java
  35. 129
      dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java
  36. 35
      dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraInstallCluster.java
  37. 13
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
  38. 24
      dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java
  39. 30
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
  40. 3
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  41. 8
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAdminSettingsServiceTest.java
  42. 16
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsServiceTest.java
  43. 2
      dao/src/test/resources/cassandra-test.properties

3
application/pom.xml

@ -470,7 +470,7 @@
<configuration>
<archive>
<manifestEntries>
<Implementation-Title>Thingsboard</Implementation-Title>
<Implementation-Title>ThingsBoard</Implementation-Title>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
@ -480,6 +480,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>org.thingsboard.server.ThingsboardServerApplication</mainClass>
<classifier>boot</classifier>
<layout>ZIP</layout>
<executable>true</executable>

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

@ -0,0 +1,700 @@
{
"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

@ -0,0 +1,264 @@
{
"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"
}

13
application/src/main/data/json/demo/plugins/demo_device_messaging_rpc_plugin.json

@ -0,0 +1,13 @@
{
"apiToken": "messaging",
"name": "Demo Device Messaging RPC Plugin",
"clazz": "org.thingsboard.server.extensions.core.plugin.messaging.DeviceMessagingPlugin",
"publicAccess": false,
"state": "ACTIVE",
"configuration": {
"maxDeviceCountPerCustomer": 1024,
"defaultTimeout": 20000,
"maxTimeout": 60000
},
"additionalInfo": null
}

28
application/src/main/data/json/demo/plugins/demo_email_plugin.json

@ -0,0 +1,28 @@
{
"apiToken": "mail",
"name": "Demo Email Plugin",
"clazz": "org.thingsboard.server.extensions.core.plugin.mail.MailPlugin",
"publicAccess": true,
"state": "ACTIVE",
"configuration": {
"host": "smtp.sendgrid.net",
"port": 2525,
"username": "apikey",
"password": "your_api_key",
"otherProperties": [
{
"key": "mail.smtp.auth",
"value": "true"
},
{
"key": "mail.smtp.timeout",
"value": "10000"
},
{
"key": "mail.smtp.starttls.enable",
"value": "true"
}
]
},
"additionalInfo": null
}

11
application/src/main/data/json/demo/plugins/demo_time_rpc_plugin.json

@ -0,0 +1,11 @@
{
"apiToken": "time",
"name": "Demo Time RPC Plugin",
"clazz": "org.thingsboard.server.extensions.core.plugin.time.TimePlugin",
"publicAccess": false,
"state": "ACTIVE",
"configuration": {
"timeFormat": "yyyy MM dd HH:mm:ss.SSS"
},
"additionalInfo": null
}

46
application/src/main/data/json/demo/rules/demo_alarm_rule.json

@ -0,0 +1,46 @@
{
"name": "Demo Alarm Rule",
"state": "ACTIVE",
"weight": 0,
"pluginToken": "mail",
"filters": [
{
"clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter",
"name": "MsgTypeFilter",
"configuration": {
"messageTypes": [
"POST_TELEMETRY",
"POST_ATTRIBUTES",
"GET_ATTRIBUTES"
]
}
},
{
"clazz": "org.thingsboard.server.extensions.core.filter.DeviceTelemetryFilter",
"name": "Temperature filter",
"configuration": {
"filter": "typeof temperature !== 'undefined' && temperature >= 100"
}
}
],
"processor": {
"clazz": "org.thingsboard.server.extensions.core.processor.AlarmDeduplicationProcessor",
"name": "AlarmDeduplicationProcessor",
"configuration": {
"alarmIdTemplate": "[$date.get('yyyy-MM-dd HH:mm')] Device $cs.get('serialNumber')($cs.get('model')) temperature is high!",
"alarmBodyTemplate": "[$date.get('yyyy-MM-dd HH:mm:ss')] Device $cs.get('serialNumber')($cs.get('model')) temperature is $temp.valueAsString!"
}
},
"action": {
"clazz": "org.thingsboard.server.extensions.core.action.mail.SendMailAction",
"name": "Send Mail Action",
"configuration": {
"sendFlag": "isNewAlarm",
"fromTemplate": "thingsboard@gmail.com",
"toTemplate": "thingsboard@gmail.com",
"subjectTemplate": "$alarmId",
"bodyTemplate": "$alarmBody"
}
},
"additionalInfo": null
}

35
application/src/main/data/json/demo/rules/demo_gettime_rpc_rule.json

@ -0,0 +1,35 @@
{
"name": "Demo getTime RPC Rule",
"state": "ACTIVE",
"weight": 0,
"pluginToken": "time",
"filters": [
{
"configuration": {
"messageTypes": [
"RPC_REQUEST"
]
},
"name": "RPC Request Filter",
"clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter"
},
{
"configuration": {
"methodNames": [
{
"name": "getTime"
}
]
},
"name": "getTime method filter",
"clazz": "org.thingsboard.server.extensions.core.filter.MethodNameFilter"
}
],
"processor": null,
"action": {
"configuration": {},
"clazz": "org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction",
"name": "getTimeAction"
},
"additionalInfo": null
}

38
application/src/main/data/json/demo/rules/demo_messaging_rpc_rule.json

@ -0,0 +1,38 @@
{
"name": "Demo Messaging RPC Rule",
"state": "ACTIVE",
"weight": 0,
"pluginToken": "messaging",
"filters": [
{
"configuration": {
"messageTypes": [
"RPC_REQUEST"
]
},
"name": "RPC Request Filter",
"clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter"
},
{
"configuration": {
"methodNames": [
{
"name": "getDevices"
},
{
"name": "sendMsg"
}
]
},
"name": "Messaging methods filter",
"clazz": "org.thingsboard.server.extensions.core.filter.MethodNameFilter"
}
],
"processor": null,
"action": {
"configuration": {},
"clazz": "org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction",
"name": "Messaging RPC Action"
},
"additionalInfo": null
}

11
application/src/main/data/json/system/plugins/system_rpc_plugin.json

@ -0,0 +1,11 @@
{
"apiToken": "rpc",
"name": "System RPC Plugin",
"clazz": "org.thingsboard.server.extensions.core.plugin.rpc.RpcPlugin",
"publicAccess": true,
"state": "ACTIVE",
"configuration": {
"defaultTimeout": 20000
},
"additionalInfo": null
}

9
application/src/main/data/json/system/plugins/system_telemetry_plugin.json

@ -0,0 +1,9 @@
{
"apiToken": "telemetry",
"name": "System Telemetry Plugin",
"clazz": "org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin",
"publicAccess": true,
"state": "ACTIVE",
"configuration": {},
"additionalInfo": null
}

29
application/src/main/data/json/system/rules/system_telemetry_rule.json

@ -0,0 +1,29 @@
{
"name": "System Telemetry Rule",
"state": "ACTIVE",
"weight": 0,
"pluginToken": "telemetry",
"filters": [
{
"clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter",
"name": "TelemetryFilter",
"configuration": {
"messageTypes": [
"POST_TELEMETRY",
"POST_ATTRIBUTES",
"GET_ATTRIBUTES"
]
}
}
],
"processor": null,
"action": {
"clazz": "org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction",
"name": "TelemetryMsgConverterAction",
"configuration": {
"timeUnit": "DAYS",
"ttlValue": 365
}
},
"additionalInfo": null
}

25
application/src/main/data/json/system/widget_bundles/alarm_widgets.json

@ -0,0 +1,25 @@
{
"widgetsBundle": {
"alias": "alarm_widgets",
"title": "Alarm widgets",
"image": null
},
"widgetTypes": [
{
"alias": "alarms_table",
"name": "Alarms table",
"descriptor": {
"type": "alarm",
"sizeX": 10.5,
"sizeY": 6.5,
"resources": [],
"templateHtml": "<tb-alarms-table-widget \n table-id=\"tableId\"\n ctx=\"ctx\">\n</tb-alarms-table-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('alarms-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}",
"dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
"defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\"},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"18px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5}"
}
}
]
}

73
application/src/main/data/json/system/widget_bundles/analogue_gauges.json

@ -0,0 +1,73 @@
{
"widgetsBundle": {
"alias": "analogue_gauges",
"title": "Analogue gauges",
"image": null
},
"widgetTypes": [
{
"alias": "radial_gauge_canvas_gauges",
"name": "Radial gauge - Canvas Gauges",
"descriptor": {
"type": "latest",
"sizeX": 6,
"sizeY": 5,
"resources": [],
"templateHtml": "<canvas id=\"radialGauge\"></canvas>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueRadialGauge(self.ctx, 'radialGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueRadialGauge.settingsSchema;\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < -100) {\\n\\tvalue = -100;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":10,\"highlights\":[],\"showUnitTitle\":true,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":10,\"valueInt\":3,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"numbersFont\":{\"family\":\"Roboto\",\"size\":18,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":36,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"minValue\":-100,\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Radial gauge - Canvas Gauges\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "speed_gauge_canvas_gauges",
"name": "Speed gauge - Canvas Gauges",
"descriptor": {
"type": "latest",
"sizeX": 7,
"sizeY": 5,
"resources": [],
"templateHtml": "<canvas id=\"radialGauge\"></canvas>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueRadialGauge(self.ctx, 'radialGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueRadialGauge.settingsSchema;\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 220) {\\n\\tvalue = 220;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":180,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":false,\"defaultColor\":\"#e65100\",\"needleCircleSize\":7,\"highlights\":[{\"from\":80,\"to\":120,\"color\":\"#fdd835\"},{\"color\":\"#e57373\",\"from\":120,\"to\":180}],\"showUnitTitle\":false,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":2,\"valueInt\":3,\"minValue\":0,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":1500,\"animationRule\":\"linear\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"colorNeedleShadowDown\":\"rgba(188, 143, 143, 0.78)\",\"units\":\"MPH\",\"majorTicksCount\":9,\"numbersFont\":{\"family\":\"Roboto\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":28,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"size\":32,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\",\"family\":\"Segment7Standard\"},\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Speed gauge - Canvas Gauges\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "temperature_gauge_canvas_gauges",
"name": "Temperature gauge - Canvas Gauges",
"descriptor": {
"type": "latest",
"sizeX": 7,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"linearGauge\"></canvas>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueLinearGauge(self.ctx, 'linearGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueLinearGauge.settingsSchema;\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 30 - 15;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"defaultColor\":\"#e64a19\",\"barStrokeWidth\":2.5,\"colorBar\":\"rgba(255, 255, 255, 0.4)\",\"colorBarEnd\":\"rgba(221, 221, 221, 0.38)\",\"showUnitTitle\":true,\"minorTicks\":2,\"valueBox\":true,\"valueInt\":3,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"colorNeedleShadowUp\":\"rgba(2,255,255,0.2)\",\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\",\"highlightsWidth\":10,\"animation\":true,\"animationDuration\":1500,\"animationRule\":\"linear\",\"showBorder\":false,\"majorTicksCount\":8,\"numbersFont\":{\"family\":\"Arial\",\"size\":18,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#78909c\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":26,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#37474f\"},\"valueFont\":{\"family\":\"Roboto\",\"size\":40,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#444\",\"shadowColor\":\"rgba(0,0,0,0.3)\"},\"minValue\":-60,\"highlights\":[{\"from\":-60,\"to\":-40,\"color\":\"#90caf9\"},{\"from\":-40,\"to\":-20,\"color\":\"rgba(144, 202, 249, 0.66)\"},{\"from\":-20,\"to\":0,\"color\":\"rgba(144, 202, 249, 0.33)\"},{\"from\":0,\"to\":20,\"color\":\"rgba(244, 67, 54, 0.2)\"},{\"from\":20,\"to\":40,\"color\":\"rgba(244, 67, 54, 0.4)\"},{\"from\":40,\"to\":60,\"color\":\"rgba(244, 67, 54, 0.6)\"},{\"from\":60,\"to\":80,\"color\":\"rgba(244, 67, 54, 0.8)\"},{\"from\":80,\"to\":100,\"color\":\"#f44336\"}],\"unitTitle\":\"Temperature\",\"units\":\"°C\",\"colorBarProgress\":\"#90caf9\",\"colorBarProgressEnd\":\"#f44336\",\"colorBarStroke\":\"#b0bec5\",\"valueDec\":1},\"title\":\"Temperature gauge - Canvas Gauges\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "temperature_radial_gauge_canvas_gauges",
"name": "Temperature radial gauge - Canvas Gauges",
"descriptor": {
"type": "latest",
"sizeX": 6,
"sizeY": 5,
"resources": [],
"templateHtml": "<canvas id=\"radialGauge\"></canvas>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbAnalogueRadialGauge(self.ctx, 'radialGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.getSettingsSchema = function() {\n return TbAnalogueRadialGauge.settingsSchema;\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":60,\"startAngle\":67.5,\"ticksAngle\":225,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":7,\"highlights\":[{\"from\":-60,\"to\":-50,\"color\":\"#42a5f5\"},{\"from\":-50,\"to\":-40,\"color\":\"rgba(66, 165, 245, 0.83)\"},{\"from\":-40,\"to\":-30,\"color\":\"rgba(66, 165, 245, 0.66)\"},{\"from\":-30,\"to\":-20,\"color\":\"rgba(66, 165, 245, 0.5)\"},{\"from\":-20,\"to\":-10,\"color\":\"rgba(66, 165, 245, 0.33)\"},{\"from\":-10,\"to\":0,\"color\":\"rgba(66, 165, 245, 0.16)\"},{\"from\":0,\"to\":10,\"color\":\"rgba(229, 115, 115, 0.16)\"},{\"from\":10,\"to\":20,\"color\":\"rgba(229, 115, 115, 0.33)\"},{\"from\":20,\"to\":30,\"color\":\"rgba(229, 115, 115, 0.5)\"},{\"from\":30,\"to\":40,\"color\":\"rgba(229, 115, 115, 0.66)\"},{\"from\":40,\"to\":50,\"color\":\"rgba(229, 115, 115, 0.83)\"},{\"from\":50,\"to\":60,\"color\":\"#e57373\"}],\"showUnitTitle\":true,\"colorPlate\":\"#cfd8dc\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":2,\"valueInt\":3,\"valueDec\":1,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":1000,\"animationRule\":\"bounce\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"colorNeedleShadowDown\":\"rgba(188, 143, 143, 0.78)\",\"units\":\"°C\",\"majorTicksCount\":12,\"numbersFont\":{\"family\":\"Roboto\",\"size\":20,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":28,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":30,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\",\"unitTitle\":\"Temperature\",\"minValue\":-60},\"title\":\"Temperature radial gauge - Canvas Gauges\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
}
]
}

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

217
application/src/main/data/json/system/widget_bundles/digital_gauges.json

@ -0,0 +1,217 @@
{
"widgetsBundle": {
"alias": "digital_gauges",
"title": "Digital gauges",
"image": null
},
"widgetTypes": [
{
"alias": "digital_bar",
"name": "Digital horizontal bar",
"descriptor": {
"type": "latest",
"sizeX": 6,
"sizeY": 2.5,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 80) {\\n\\tvalue = 80;\\n} else if (value > 160) {\\n\\tvalue = 160;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"horizontalBar\",\"showTitle\":false},\"title\":\"Digital horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "digital_speedometer",
"name": "Digital speedometer",
"descriptor": {
"type": "latest",
"sizeX": 5,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 45) {\\n\\tvalue = 45;\\n} else if (value > 130) {\\n\\tvalue = 130;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"arc\"},\"title\":\"Digital speedometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "digital_thermometer",
"name": "Digital thermometer",
"descriptor": {
"type": "latest",
"sizeX": 3,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < -60) {\\n\\tvalue = 60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[\"#304ffe\",\"#7e57c2\",\"#ff4081\",\"#d32f2f\"],\"refreshAnimationType\":\"<>\",\"refreshAnimationTime\":700,\"startAnimationType\":\"<>\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"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\":-60,\"units\":\"°C\",\"gaugeColor\":\"#333333\",\"neonGlowBrightness\":35,\"gaugeType\":\"donut\"},\"title\":\"Digital thermometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "digital_vertical_bar",
"name": "Digital vertical bar",
"descriptor": {
"type": "latest",
"sizeX": 2.5,
"sizeY": 4.5,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#3d5afe\",\"#f44336\"],\"refreshAnimationType\":\"<>\",\"refreshAnimationTime\":700,\"startAnimationType\":\"<>\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"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,\"units\":\"°C\",\"minValue\":-60,\"dashThickness\":1.2},\"title\":\"Digital vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "gauge_justgage",
"name": "Gauge - justGage",
"descriptor": {
"type": "latest",
"sizeX": 4,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":36,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"arc\"},\"title\":\"Gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "horizontal_bar_justgage",
"name": "Horizontal bar - justGage",
"descriptor": {
"type": "latest",
"sizeX": 7,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>\n",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"horizontalBar\"},\"title\":\"Horizontal bar - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "lcd_bar_gauge",
"name": "LCD bar gauge",
"descriptor": {
"type": "latest",
"sizeX": 2,
"sizeY": 3.5,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"400\",\"size\":16},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"verticalBar\",\"units\":\"%\"},\"title\":\"LCD bar gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "lcd_gauge",
"name": "LCD gauge",
"descriptor": {
"type": "latest",
"sizeX": 5,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 180) {\\n\\tvalue = 180;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"arc\"},\"title\":\"LCD gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "mini_gauge_justgage",
"name": "Mini gauge - justGage",
"descriptor": {
"type": "latest",
"sizeX": 2,
"sizeY": 2,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#7cb342\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"roundedLineCap\":true,\"gaugeType\":\"donut\"},\"title\":\"Mini gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "neon_gauge_justgage",
"name": "Neon gauge - justGage",
"descriptor": {
"type": "latest",
"sizeX": 5,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":70,\"dashThickness\":1,\"decimals\":1,\"gaugeType\":\"arc\"},\"title\":\"Neon gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "simple_gauge_justgage",
"name": "Simple gauge - justGage",
"descriptor": {
"type": "latest",
"sizeX": 2,
"sizeY": 2,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>\n",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "\nself.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#ef6c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"gaugeColor\":\"#eeeeee\",\"gaugeType\":\"donut\"},\"title\":\"Simple gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "simple_neon_gauge_justgage",
"name": "Simple neon gauge - justGage",
"descriptor": {
"type": "latest",
"sizeX": 3,
"sizeY": 3,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#388e3c\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"decimals\":0,\"gaugeType\":\"donut\"},\"title\":\"Simple neon gauge - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
},
{
"alias": "vertical_bar_justgage",
"name": "Vertical bar - justGage",
"descriptor": {
"type": "latest",
"sizeX": 2,
"sizeY": 3.5,
"resources": [],
"templateHtml": "<canvas id=\"digitalGauge\"></canvas>",
"templateCss": "#gauge {\n text-align: center;\n /* margin-left: -100px;\n margin-right: -100px;*/\n /*margin-top: -50px;*/\n \n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.gauge = new TbCanvasDigitalGauge(self.ctx, 'digitalGauge'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.gauge.update();\n}\n\nself.onResize = function() {\n self.ctx.gauge.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbCanvasDigitalGauge.settingsSchema;\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.gauge.mobileModeChanged();\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":12,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":1.5,\"gaugeColor\":\"#eeeeee\",\"showTitle\":false,\"gaugeType\":\"verticalBar\"},\"title\":\"Vertical bar - justGage\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
}
}
]
}

73
application/src/main/data/json/system/widget_bundles/gpio_widgets.json

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

55
application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java

@ -0,0 +1,55 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.thingsboard.server.install.ThingsboardInstallService;
import java.util.Arrays;
@EnableAutoConfiguration
@SpringBootApplication
@ComponentScan({"org.thingsboard.server.install",
"org.thingsboard.server.service.component",
"org.thingsboard.server.service.install",
"org.thingsboard.server.dao"})
public class ThingsboardInstallApplication {
private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "thingsboard";
public static void main(String[] args) {
SpringApplication application = new SpringApplication(ThingsboardInstallApplication.class);
application.setAdditionalProfiles("install");
ConfigurableApplicationContext context = application.run(updateArguments(args));
context.getBean(ThingsboardInstallService.class).performInstall();
}
private static String[] updateArguments(String[] args) {
if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) {
String[] modifiedArgs = new String[args.length + 1];
System.arraycopy(args, 0, modifiedArgs, 0, args.length);
modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM;
return modifiedArgs;
}
return args;
}
}

31
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallException.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.install;
import org.springframework.boot.ExitCodeGenerator;
public class ThingsboardInstallException extends RuntimeException implements ExitCodeGenerator {
public ThingsboardInstallException(String message, Throwable cause) {
super(message, cause);
}
public int getExitCode() {
return 1;
}
}

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

@ -0,0 +1,95 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.install.DatabaseSchemaService;
import org.thingsboard.server.service.install.SystemDataLoaderService;
import java.nio.file.Files;
import java.nio.file.Paths;
@Service
@Profile("install")
@Slf4j
public class ThingsboardInstallService {
@Value("${install.data_dir}")
private String dataDir;
@Value("${install.load_demo:false}")
private Boolean loadDemo;
@Autowired
private DatabaseSchemaService databaseSchemaService;
@Autowired
private ComponentDiscoveryService componentDiscoveryService;
@Autowired
private ApplicationContext context;
@Autowired
private SystemDataLoaderService systemDataLoaderService;
public void performInstall() {
try {
log.info("Starting ThingsBoard Installation...");
if (this.dataDir == null) {
throw new RuntimeException("'install.data_dir' property should specified!");
}
if (!Files.isDirectory(Paths.get(this.dataDir))) {
throw new RuntimeException("'install.data_dir' property value is not a valid directory!");
}
log.info("Installing DataBase schema...");
databaseSchemaService.createDatabaseSchema();
log.info("Loading system data...");
componentDiscoveryService.discoverComponents();
systemDataLoaderService.createSysAdmin();
systemDataLoaderService.createAdminSettings();
systemDataLoaderService.loadSystemWidgets();
systemDataLoaderService.loadSystemPlugins();
systemDataLoaderService.loadSystemRules();
if (loadDemo) {
log.info("Loading demo data...");
systemDataLoaderService.loadDemoData();
}
log.info("Finished!");
} catch (Exception e) {
log.error("Unexpected error during ThingsBoard installation!", e);
throw new ThingsboardInstallException("Unexpected error during ThingsBoard installation!", e);
} finally {
SpringApplication.exit(context);
}
}
}

162
application/src/main/java/org/thingsboard/server/install/cql/CQLStatementsParser.java

@ -0,0 +1,162 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.install.cql;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class CQLStatementsParser {
enum State {
DEFAULT,
INSINGLELINECOMMENT,
INMULTILINECOMMENT,
INQUOTESTRING,
INSQUOTESTRING,
}
private String text;
private State state;
private int pos;
private List<String> statements;
public CQLStatementsParser(Path cql) throws IOException {
try {
List<String> lines = Files.readAllLines(cql);
StringBuffer t = new StringBuffer();
for (String l : lines) {
t.append(l.trim());
t.append('\n');
}
text = t.toString();
pos = 0;
state = State.DEFAULT;
parseStatements();
}
catch (IOException e) {
log.error("Unable to parse CQL file [{}]!", cql);
log.error("Exception", e);
throw e;
}
}
public List<String> getStatements() {
return this.statements;
}
private void parseStatements() {
this.statements = new ArrayList<>();
StringBuffer statementUnderConstruction = new StringBuffer();
char c;
while ((c = getChar()) != 0) {
switch (state) {
case DEFAULT:
if (c == '/' && peekAhead() == '/') {
state = State.INSINGLELINECOMMENT;
advance();
} else if (c == '-' && peekAhead() == '-') {
state = State.INSINGLELINECOMMENT;
advance();
} else if (c == '/' && peekAhead() == '*') {
state = State.INMULTILINECOMMENT;
advance();
} else if (c == '\n') {
statementUnderConstruction.append(' ');
} else {
statementUnderConstruction.append(c);
if (c == '\"') {
state = State.INQUOTESTRING;
} else if (c == '\'') {
state = State.INSQUOTESTRING;
} else if (c == ';') {
statements.add(statementUnderConstruction.toString().trim());
statementUnderConstruction.setLength(0);
}
}
break;
case INSINGLELINECOMMENT:
if (c == '\n') {
state = State.DEFAULT;
}
break;
case INMULTILINECOMMENT:
if (c == '*' && peekAhead() == '/') {
state = State.DEFAULT;
advance();
}
break;
case INQUOTESTRING:
statementUnderConstruction.append(c);
if (c == '"') {
if (peekAhead() == '"') {
statementUnderConstruction.append(getChar());
} else {
state = State.DEFAULT;
}
}
break;
case INSQUOTESTRING:
statementUnderConstruction.append(c);
if (c == '\'') {
if (peekAhead() == '\'') {
statementUnderConstruction.append(getChar());
} else {
state = State.DEFAULT;
}
}
break;
}
}
String tmp = statementUnderConstruction.toString().trim();
if (tmp.length() > 0) {
this.statements.add(tmp);
}
}
private char getChar() {
if (pos < text.length())
return text.charAt(pos++);
else
return 0;
}
private char peekAhead() {
if (pos < text.length())
return text.charAt(pos); // don't advance
else
return 0;
}
private void advance() {
pos++;
}
}

33
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java

@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.env.Environment;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
@ -42,6 +43,9 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
@Value("${plugins.scan_packages}")
private String[] scanPackages;
@Autowired
private Environment environment;
@Autowired
private ComponentDescriptorService componentDescriptorService;
@ -51,17 +55,15 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
private ObjectMapper mapper = new ObjectMapper();
private boolean isInstall() {
return environment.acceptsProfiles("install");
}
@PostConstruct
public void init() {
registerComponents(ComponentType.FILTER, Filter.class);
registerComponents(ComponentType.PROCESSOR, Processor.class);
registerComponents(ComponentType.ACTION, Action.class);
registerComponents(ComponentType.PLUGIN, Plugin.class);
log.info("Found following definitions: {}", components.values());
if (!isInstall()) {
discoverComponents();
}
}
private void registerComponents(ComponentType type, Class<? extends Annotation> annotation) {
@ -159,6 +161,19 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
return defs;
}
@Override
public void discoverComponents() {
registerComponents(ComponentType.FILTER, Filter.class);
registerComponents(ComponentType.PROCESSOR, Processor.class);
registerComponents(ComponentType.ACTION, Action.class);
registerComponents(ComponentType.PLUGIN, Plugin.class);
log.info("Found following definitions: {}", components.values());
}
@Override
public List<ComponentDescriptor> getComponents(ComponentType type) {
return Collections.unmodifiableList(componentsMap.get(type));

2
application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java

@ -26,6 +26,8 @@ import java.util.Optional;
*/
public interface ComponentDiscoveryService {
void discoverComponents();
List<ComponentDescriptor> getComponents(ComponentType type);
Optional<ComponentDescriptor> getComponent(String clazz);

59
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java

@ -0,0 +1,59 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.install.cql.CQLStatementsParser;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Service
@NoSqlDao
@Profile("install")
@Slf4j
public class CassandraDatabaseSchemaService implements DatabaseSchemaService {
private static final String SCHEMA_CQL = "schema.cql";
@Value("${install.data_dir}")
private String dataDir;
@Autowired
private CassandraInstallCluster cluster;
@Override
public void createDatabaseSchema() throws Exception {
log.info("Installing Cassandra DataBase schema...");
Path schemaFile = Paths.get(this.dataDir, SCHEMA_CQL);
loadCql(schemaFile);
}
private void loadCql(Path cql) throws Exception {
List<String> statements = new CQLStatementsParser(cql).getStatements();
statements.forEach(statement -> cluster.getSession().execute(statement));
}
}

22
application/src/main/java/org/thingsboard/server/service/install/DatabaseSchemaService.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
public interface DatabaseSchemaService {
void createDatabaseSchema() throws Exception;
}

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

@ -0,0 +1,336 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Service
@Profile("install")
@Slf4j
public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
private static final String JSON_DIR = "json";
private static final String SYSTEM_DIR = "system";
private static final String DEMO_DIR = "demo";
private static final String WIDGET_BUNDLES_DIR = "widget_bundles";
private static final String PLUGINS_DIR = "plugins";
private static final String RULES_DIR = "rules";
private static final String DASHBOARDS_DIR = "dashboards";
private static final ObjectMapper objectMapper = new ObjectMapper();
@Value("${install.data_dir}")
private String dataDir;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Autowired
private UserService userService;
@Autowired
private AdminSettingsService adminSettingsService;
@Autowired
private WidgetsBundleService widgetsBundleService;
@Autowired
private WidgetTypeService widgetTypeService;
@Autowired
private PluginService pluginService;
@Autowired
private RuleService ruleService;
@Autowired
private TenantService tenantService;
@Autowired
private CustomerService customerService;
@Autowired
private DeviceService deviceService;
@Autowired
private DeviceCredentialsService deviceCredentialsService;
@Autowired
private DashboardService dashboardService;
@Bean
protected BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void createSysAdmin() {
createUser(Authority.SYS_ADMIN, null, null, "sysadmin@thingsboard.org", "sysadmin");
}
@Override
public void createAdminSettings() throws Exception {
AdminSettings generalSettings = new AdminSettings();
generalSettings.setKey("general");
ObjectNode node = objectMapper.createObjectNode();
node.put("baseUrl", "http://localhost:8080");
generalSettings.setJsonValue(node);
adminSettingsService.saveAdminSettings(generalSettings);
AdminSettings mailSettings = new AdminSettings();
mailSettings.setKey("mail");
node = objectMapper.createObjectNode();
node.put("mailFrom", "ThingsBoard <sysadmin@localhost.localdomain>");
node.put("smtpProtocol", "smtp");
node.put("smtpHost", "localhost");
node.put("smtpPort", "25");
node.put("timeout", "10000");
node.put("enableTls", "false");
node.put("username", "");
node.put("password", "");
mailSettings.setJsonValue(node);
adminSettingsService.saveAdminSettings(mailSettings);
}
@Override
public void loadSystemWidgets() throws Exception {
Path widgetBundlesDir = Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(".json"))
.forEach(
path -> {
try {
JsonNode widgetsBundleDescriptorJson = objectMapper.readTree(path.toFile());
JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle");
WidgetsBundle widgetsBundle = objectMapper.treeToValue(widgetsBundleJson, WidgetsBundle.class);
WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes");
widgetTypesArrayJson.forEach(
widgetTypeJson -> {
try {
WidgetType widgetType = objectMapper.treeToValue(widgetTypeJson, WidgetType.class);
widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
widgetTypeService.saveWidgetType(widgetType);
} catch (Exception e) {
log.error("Unable to load widget type from json: [{}]", path.toString());
throw new RuntimeException("Unable to load widget type from json", e);
}
}
);
} catch (Exception e) {
log.error("Unable to load widgets bundle from json: [{}]", path.toString());
throw new RuntimeException("Unable to load widgets bundle from json", e);
}
}
);
}
@Override
public void loadSystemPlugins() throws Exception {
loadPlugins(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, PLUGINS_DIR), null);
}
@Override
public void loadSystemRules() throws Exception {
loadRules(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, RULES_DIR), null);
}
@Override
public void loadDemoData() throws Exception {
Tenant demoTenant = new Tenant();
demoTenant.setRegion("Global");
demoTenant.setTitle("Tenant");
demoTenant = tenantService.saveTenant(demoTenant);
createUser(Authority.TENANT_ADMIN, demoTenant.getId(), null, "tenant@thingsboard.org", "tenant");
Customer customerA = new Customer();
customerA.setTenantId(demoTenant.getId());
customerA.setTitle("Customer A");
customerA = customerService.saveCustomer(customerA);
Customer customerB = new Customer();
customerB.setTenantId(demoTenant.getId());
customerB.setTitle("Customer B");
customerB = customerService.saveCustomer(customerB);
Customer customerC = new Customer();
customerC.setTenantId(demoTenant.getId());
customerC.setTitle("Customer C");
customerC = customerService.saveCustomer(customerC);
createUser(Authority.CUSTOMER_USER, demoTenant.getId(), customerA.getId(), "customer@thingsboard.org", "customer");
createUser(Authority.CUSTOMER_USER, demoTenant.getId(), customerA.getId(), "customerA@thingsboard.org", "customer");
createUser(Authority.CUSTOMER_USER, demoTenant.getId(), customerB.getId(), "customerB@thingsboard.org", "customer");
createUser(Authority.CUSTOMER_USER, demoTenant.getId(), customerC.getId(), "customerC@thingsboard.org", "customer");
createDevice(demoTenant.getId(), customerA.getId(), "default", "Test Device A1", "A1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerA.getId(), "default", "Test Device A2", "A2_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerA.getId(), "default", "Test Device A3", "A3_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerB.getId(), "default", "Test Device B1", "B1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), customerC.getId(), "default", "Test Device C1", "C1_TEST_TOKEN", null);
createDevice(demoTenant.getId(), null, "default", "DHT11 Demo Device", "DHT11_DEMO_TOKEN", "Demo device that is used in sample " +
"applications that upload data from DHT11 temperature and humidity sensor");
createDevice(demoTenant.getId(), null, "default", "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
"Raspberry Pi GPIO control sample application");
loadPlugins(Paths.get(dataDir, JSON_DIR, DEMO_DIR, PLUGINS_DIR), demoTenant.getId());
loadRules(Paths.get(dataDir, JSON_DIR, DEMO_DIR, RULES_DIR), demoTenant.getId());
loadDashboards(Paths.get(dataDir, JSON_DIR, DEMO_DIR, DASHBOARDS_DIR), demoTenant.getId(), null);
}
private User createUser(Authority authority,
TenantId tenantId,
CustomerId customerId,
String email,
String password) {
User user = new User();
user.setAuthority(authority);
user.setEmail(email);
user.setTenantId(tenantId);
user.setCustomerId(customerId);
user = userService.saveUser(user);
UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getId());
userCredentials.setPassword(passwordEncoder.encode(password));
userCredentials.setEnabled(true);
userCredentials.setActivateToken(null);
userService.saveUserCredentials(userCredentials);
return user;
}
private Device createDevice(TenantId tenantId,
CustomerId customerId,
String type,
String name,
String accessToken,
String description) {
Device device = new Device();
device.setTenantId(tenantId);
device.setCustomerId(customerId);
device.setType(type);
device.setName(name);
if (description != null) {
ObjectNode additionalInfo = objectMapper.createObjectNode();
additionalInfo.put("description", description);
device.setAdditionalInfo(additionalInfo);
}
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
deviceCredentials.setCredentialsId(accessToken);
deviceCredentialsService.updateDeviceCredentials(deviceCredentials);
return device;
}
private void loadPlugins(Path pluginsDir, TenantId tenantId) throws Exception{
Files.newDirectoryStream(pluginsDir, path -> path.toString().endsWith(".json"))
.forEach(
path -> {
try {
JsonNode pluginJson = objectMapper.readTree(path.toFile());
PluginMetaData plugin = objectMapper.treeToValue(pluginJson, PluginMetaData.class);
plugin.setTenantId(tenantId);
if (plugin.getState() == ComponentLifecycleState.ACTIVE) {
plugin.setState(ComponentLifecycleState.SUSPENDED);
PluginMetaData savedPlugin = pluginService.savePlugin(plugin);
pluginService.activatePluginById(savedPlugin.getId());
} else {
pluginService.savePlugin(plugin);
}
} catch (Exception e) {
log.error("Unable to load plugin from json: [{}]", path.toString());
throw new RuntimeException("Unable to load plugin from json", e);
}
}
);
}
private void loadRules(Path rulesDir, TenantId tenantId) throws Exception {
Files.newDirectoryStream(rulesDir, path -> path.toString().endsWith(".json"))
.forEach(
path -> {
try {
JsonNode ruleJson = objectMapper.readTree(path.toFile());
RuleMetaData rule = objectMapper.treeToValue(ruleJson, RuleMetaData.class);
rule.setTenantId(tenantId);
if (rule.getState() == ComponentLifecycleState.ACTIVE) {
rule.setState(ComponentLifecycleState.SUSPENDED);
RuleMetaData savedRule = ruleService.saveRule(rule);
ruleService.activateRuleById(savedRule.getId());
} else {
ruleService.saveRule(rule);
}
} catch (Exception e) {
log.error("Unable to load rule from json: [{}]", path.toString());
throw new RuntimeException("Unable to load rule from json", e);
}
}
);
}
private void loadDashboards(Path dashboardsDir, TenantId tenantId, CustomerId customerId) throws Exception {
Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(".json"))
.forEach(
path -> {
try {
JsonNode dashboardJson = objectMapper.readTree(path.toFile());
Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
dashboard.setTenantId(tenantId);
dashboard.setCustomerId(customerId);
dashboardService.saveDashboard(dashboard);
} catch (Exception e) {
log.error("Unable to load dashboard from json: [{}]", path.toString());
throw new RuntimeException("Unable to load dashboard from json", e);
}
}
);
}
}

40
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
@Service
@Profile("install")
@Slf4j
public class SqlDatabaseSchemaService implements DatabaseSchemaService {
@Value("${install.data_dir}")
private String dataDir;
@Override
public void createDatabaseSchema() throws Exception {
log.info("Installing SQL DataBase schema...");
//TODO:
}
}

32
application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
public interface SystemDataLoaderService {
void createSysAdmin() throws Exception;
void createAdminSettings() throws Exception;
void loadSystemWidgets() throws Exception;
void loadSystemPlugins() throws Exception;
void loadSystemRules() throws Exception;
void loadDemoData() throws Exception;
}

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

@ -154,8 +154,6 @@ cassandra:
default_fetch_size: "${CASSANDRA_DEFAULT_FETCH_SIZE:2000}"
# Specify partitioning size for timestamp key-value storage. Example MINUTES, HOURS, DAYS, MONTHS
ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}"
# Specify max data points per request
min_aggregation_step_ms: "${TS_KV_MIN_AGGREGATION_STEP_MS:1000}"
# Actor system parameters
actors:

13
application/src/test/java/org/thingsboard/server/controller/AdminControllerTest.java

@ -78,19 +78,6 @@ public class AdminControllerTest extends AbstractControllerTest {
.andExpect(status().isOk());
}
@Test
public void testCreateAdminSettings() throws Exception {
loginSysAdmin();
AdminSettings adminSettings = new AdminSettings();
adminSettings.setKey("someKey");
adminSettings.setJsonValue(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
doPost("/api/admin/settings", adminSettings)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("is prohibited")));
}
@Test
public void testSaveAdminSettingsWithEmptyKey() throws Exception {
loginSysAdmin();

14
application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java

@ -355,20 +355,6 @@ public class DeviceControllerTest extends AbstractControllerTest {
doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isNotFound());
}
@Test
public void testSaveDeviceCredentialsWithInvalidCredentialsIdLength() throws Exception {
Device device = new Device();
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
deviceCredentials.setCredentialsId(RandomStringUtils.randomAlphanumeric(21));
doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Incorrect access token length")));
}
@Test
public void testFindTenantDevices() throws Exception {

187
dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java

@ -0,0 +1,187 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.cassandra;
import com.datastax.driver.core.*;
import com.datastax.driver.core.ProtocolOptions.Compression;
import com.datastax.driver.mapping.Mapper;
import com.datastax.driver.mapping.MappingManager;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Slf4j
@Data
public abstract class AbstractCassandraCluster {
private static final String COMMA = ",";
private static final String COLON = ":";
@Value("${cassandra.cluster_name}")
private String clusterName;
@Value("${cassandra.url}")
private String url;
@Value("${cassandra.compression}")
private String compression;
@Value("${cassandra.ssl}")
private Boolean ssl;
@Value("${cassandra.jmx}")
private Boolean jmx;
@Value("${cassandra.metrics}")
private Boolean metrics;
@Value("${cassandra.credentials}")
private Boolean credentials;
@Value("${cassandra.username}")
private String username;
@Value("${cassandra.password}")
private String password;
@Value("${cassandra.init_timeout_ms}")
private long initTimeout;
@Value("${cassandra.init_retry_interval_ms}")
private long initRetryInterval;
@Autowired
private CassandraSocketOptions socketOpts;
@Autowired
private CassandraQueryOptions queryOpts;
@Autowired
private Environment environment;
private Cluster cluster;
@Getter(AccessLevel.NONE) private Session session;
private MappingManager mappingManager;
public <T> Mapper<T> getMapper(Class<T> clazz) {
return mappingManager.mapper(clazz);
}
private String keyspaceName;
protected void init(String keyspaceName) {
this.keyspaceName = keyspaceName;
Cluster.Builder builder = Cluster.builder()
.addContactPointsWithPorts(getContactPoints(url))
.withClusterName(clusterName)
.withSocketOptions(socketOpts.getOpts())
.withPoolingOptions(new PoolingOptions()
.setMaxRequestsPerConnection(HostDistance.LOCAL, 32768)
.setMaxRequestsPerConnection(HostDistance.REMOTE, 32768));
builder.withQueryOptions(queryOpts.getOpts());
builder.withCompression(StringUtils.isEmpty(compression) ? Compression.NONE : Compression.valueOf(compression.toUpperCase()));
if (ssl) {
builder.withSSL();
}
if (!jmx) {
builder.withoutJMXReporting();
}
if (!metrics) {
builder.withoutMetrics();
}
if (credentials) {
builder.withCredentials(username, password);
}
cluster = builder.build();
cluster.init();
if (!isInstall()) {
initSession();
}
}
public Session getSession() {
if (!isInstall()) {
return session;
} else {
if (session == null) {
initSession();
}
return session;
}
}
private boolean isInstall() {
return environment.acceptsProfiles("install");
}
private void initSession() {
long endTime = System.currentTimeMillis() + initTimeout;
while (System.currentTimeMillis() < endTime) {
try {
if (this.keyspaceName != null) {
session = cluster.connect(keyspaceName);
} else {
session = cluster.connect();
}
mappingManager = new MappingManager(session);
break;
} catch (Exception e) {
log.warn("Failed to initialize cassandra cluster due to {}. Will retry in {} ms", e.getMessage(), initRetryInterval);
try {
Thread.sleep(initRetryInterval);
} catch (InterruptedException ie) {
log.warn("Failed to wait until retry", ie);
}
}
}
}
@PreDestroy
public void close() {
if (cluster != null) {
cluster.close();
}
}
private List<InetSocketAddress> getContactPoints(String url) {
List<InetSocketAddress> result;
if (StringUtils.isBlank(url)) {
result = Collections.emptyList();
} else {
result = new ArrayList<>();
for (String hostPort : url.split(COMMA)) {
String host = hostPort.split(COLON)[0];
Integer port = Integer.valueOf(hostPort.split(COLON)[1]);
result.add(new InetSocketAddress(host, port));
}
}
return result;
}
public ConsistencyLevel getDefaultReadConsistencyLevel() {
return queryOpts.getDefaultReadConsistencyLevel();
}
public ConsistencyLevel getDefaultWriteConsistencyLevel() {
return queryOpts.getDefaultWriteConsistencyLevel();
}
}

129
dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java

@ -15,144 +15,21 @@
*/
package org.thingsboard.server.dao.cassandra;
import com.datastax.driver.core.*;
import com.datastax.driver.core.ProtocolOptions.Compression;
import com.datastax.driver.mapping.Mapper;
import com.datastax.driver.mapping.MappingManager;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.dao.util.NoSqlDao;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.thingsboard.server.dao.util.NoSqlDao;
@Data
@Component
@Slf4j
@NoSqlDao
public class CassandraCluster {
private static final String COMMA = ",";
private static final String COLON = ":";
public class CassandraCluster extends AbstractCassandraCluster {
@Value("${cassandra.cluster_name}")
private String clusterName;
@Value("${cassandra.keyspace_name}")
private String keyspaceName;
@Value("${cassandra.url}")
private String url;
@Value("${cassandra.compression}")
private String compression;
@Value("${cassandra.ssl}")
private Boolean ssl;
@Value("${cassandra.jmx}")
private Boolean jmx;
@Value("${cassandra.metrics}")
private Boolean metrics;
@Value("${cassandra.credentials}")
private Boolean credentials;
@Value("${cassandra.username}")
private String username;
@Value("${cassandra.password}")
private String password;
@Value("${cassandra.init_timeout_ms}")
private long initTimeout;
@Value("${cassandra.init_retry_interval_ms}")
private long initRetryInterval;
@Autowired
private CassandraSocketOptions socketOpts;
@Autowired
private CassandraQueryOptions queryOpts;
private Cluster cluster;
private Session session;
private MappingManager mappingManager;
public <T> Mapper<T> getMapper(Class<T> clazz) {
return mappingManager.mapper(clazz);
}
@PostConstruct
public void init() {
long endTime = System.currentTimeMillis() + initTimeout;
while (System.currentTimeMillis() < endTime) {
try {
Cluster.Builder builder = Cluster.builder()
.addContactPointsWithPorts(getContactPoints(url))
.withClusterName(clusterName)
.withSocketOptions(socketOpts.getOpts())
.withPoolingOptions(new PoolingOptions()
.setMaxRequestsPerConnection(HostDistance.LOCAL, 32768)
.setMaxRequestsPerConnection(HostDistance.REMOTE, 32768));
builder.withQueryOptions(queryOpts.getOpts());
builder.withCompression(StringUtils.isEmpty(compression) ? Compression.NONE : Compression.valueOf(compression.toUpperCase()));
if (ssl) {
builder.withSSL();
}
if (!jmx) {
builder.withoutJMXReporting();
}
if (!metrics) {
builder.withoutMetrics();
}
if (credentials) {
builder.withCredentials(username, password);
}
cluster = builder.build();
cluster.init();
session = cluster.connect(keyspaceName);
mappingManager = new MappingManager(session);
break;
} catch (Exception e) {
log.warn("Failed to initialize cassandra cluster due to {}. Will retry in {} ms", e.getMessage(), initRetryInterval);
try {
Thread.sleep(initRetryInterval);
} catch (InterruptedException ie) {
log.warn("Failed to wait until retry", ie);
}
}
}
}
@PreDestroy
public void close() {
if (cluster != null) {
cluster.close();
}
}
private List<InetSocketAddress> getContactPoints(String url) {
List<InetSocketAddress> result;
if (StringUtils.isBlank(url)) {
result = Collections.emptyList();
} else {
result = new ArrayList<>();
for (String hostPort : url.split(COMMA)) {
String host = hostPort.split(COLON)[0];
Integer port = Integer.valueOf(hostPort.split(COLON)[1]);
result.add(new InetSocketAddress(host, port));
}
}
return result;
}
public ConsistencyLevel getDefaultReadConsistencyLevel() {
return queryOpts.getDefaultReadConsistencyLevel();
}
public ConsistencyLevel getDefaultWriteConsistencyLevel() {
return queryOpts.getDefaultWriteConsistencyLevel();
super.init(keyspaceName);
}
}

35
dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraInstallCluster.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.cassandra;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.thingsboard.server.dao.util.NoSqlDao;
import javax.annotation.PostConstruct;
@Component
@NoSqlDao
@Profile("install")
public class CassandraInstallCluster extends AbstractCassandraCluster {
@PostConstruct
public void init() {
super.init(null);
}
}

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

@ -127,19 +127,6 @@ public class DeviceCredentialsServiceImpl implements DeviceCredentialsService {
if (StringUtils.isEmpty(deviceCredentials.getCredentialsId())) {
throw new DataValidationException("Device credentials id should be specified!");
}
switch (deviceCredentials.getCredentialsType()) {
case ACCESS_TOKEN:
if (deviceCredentials.getCredentialsId().length() < 1 || deviceCredentials.getCredentialsId().length() > 20) {
throw new DataValidationException("Incorrect access token length [" + deviceCredentials.getCredentialsId().length() + "]!");
}
break;
case X509_CERTIFICATE:
if (deviceCredentials.getCredentialsId().length() == 0) {
throw new DataValidationException("X509 Certificate Cannot be empty!");
}
default:
break;
}
Device device = deviceService.findDeviceById(deviceCredentials.getDeviceId());
if (device == null) {
throw new DataValidationException("Can't assign device credentials to non-existent device!");

24
dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java

@ -55,11 +55,26 @@ public class AdminSettingsServiceImpl implements AdminSettingsService {
private DataValidator<AdminSettings> adminSettingsValidator =
new DataValidator<AdminSettings>() {
@Override
protected void validateCreate(AdminSettings adminSettings) {
throw new DataValidationException("Creation of new admin settings entry is prohibited!");
AdminSettings existentAdminSettingsWithKey = findAdminSettingsByKey(adminSettings.getKey());
if (existentAdminSettingsWithKey != null) {
throw new DataValidationException("Admin settings with such name already exists!");
}
}
@Override
protected void validateUpdate(AdminSettings adminSettings) {
AdminSettings existentAdminSettings = findAdminSettingsById(adminSettings.getId());
if (existentAdminSettings != null) {
if (!existentAdminSettings.getKey().equals(adminSettings.getKey())) {
throw new DataValidationException("Changing key of admin settings entry is prohibited!");
}
validateJsonStructure(existentAdminSettings.getJsonValue(), adminSettings.getJsonValue());
}
}
@Override
protected void validateDataImpl(AdminSettings adminSettings) {
@ -69,11 +84,6 @@ public class AdminSettingsServiceImpl implements AdminSettingsService {
if (adminSettings.getJsonValue() == null) {
throw new DataValidationException("Json value should be specified!");
}
AdminSettings existentAdminSettingsWithKey = findAdminSettingsByKey(adminSettings.getKey());
if (existentAdminSettingsWithKey == null || !isSameData(existentAdminSettingsWithKey, adminSettings)) {
throw new DataValidationException("Changing key of admin settings entry is prohibited!");
}
validateJsonStructure(existentAdminSettingsWithKey.getJsonValue(), adminSettings.getJsonValue());
}
};

30
dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java

@ -24,7 +24,9 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.*;
@ -54,8 +56,10 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
@NoSqlDao
public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implements TimeseriesDao {
@Value("${cassandra.query.min_aggregation_step_ms}")
private int minAggregationStepMs;
private static final int MIN_AGGREGATION_STEP_MS = 1000;
@Autowired
private Environment environment;
@Value("${cassandra.query.ts_key_value_partitioning}")
private String partitioning;
@ -71,16 +75,22 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
private PreparedStatement findLatestStmt;
private PreparedStatement findAllLatestStmt;
private boolean isInstall() {
return environment.acceptsProfiles("install");
}
@PostConstruct
public void init() {
super.startExecutor();
getFetchStmt(Aggregation.NONE);
Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning);
if (partition.isPresent()) {
tsFormat = partition.get();
} else {
log.warn("Incorrect configuration of partitioning {}", partitioning);
throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!");
if (!isInstall()) {
getFetchStmt(Aggregation.NONE);
Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning);
if (partition.isPresent()) {
tsFormat = partition.get();
} else {
log.warn("Incorrect configuration of partitioning {}", partitioning);
throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!");
}
}
}
@ -111,7 +121,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
if (query.getAggregation() == Aggregation.NONE) {
return findAllAsyncWithLimit(entityId, query);
} else {
long step = Math.max(query.getInterval(), minAggregationStepMs);
long step = Math.max(query.getInterval(), MIN_AGGREGATION_STEP_MS);
long stepTs = query.getStartTs();
List<ListenableFuture<Optional<TsKvEntry>>> futures = new ArrayList<>();
while (stepTs < query.getEndTs()) {

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

@ -235,9 +235,6 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
switch (authority) {
case SYS_ADMIN:
if (user.getId() == null) {
throw new DataValidationException("Creation of system administrator is prohibited!");
}
if (!tenantId.getId().equals(ModelConstants.NULL_UUID)
|| !customerId.getId().equals(ModelConstants.NULL_UUID)) {
throw new DataValidationException("System administrator can't be assigned neither to tenant nor to customer!");

8
dao/src/test/java/org/thingsboard/server/dao/service/BaseAdminSettingsServiceTest.java

@ -55,14 +55,6 @@ public abstract class BaseAdminSettingsServiceTest extends AbstractServiceTest {
Assert.assertEquals(adminSettings.getJsonValue(), savedAdminSettings.getJsonValue());
}
@Test(expected = DataValidationException.class)
public void testCreateAdminSettings() throws Exception {
AdminSettings adminSettings = new AdminSettings();
adminSettings.setKey("someKey");
adminSettings.setJsonValue(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
adminSettingsService.saveAdminSettings(adminSettings);
}
@Test(expected = DataValidationException.class)
public void testSaveAdminSettingsWithEmptyKey() {
AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey("mail");

16
dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsServiceTest.java

@ -138,22 +138,6 @@ public abstract class BaseDeviceCredentialsServiceTest extends AbstractServiceTe
}
}
@Test(expected = DataValidationException.class)
public void testSaveDeviceCredentialsWithInvalidCredemtialsIdLength() {
Device device = new Device();
device.setName("My device");
device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
deviceCredentials.setCredentialsId(RandomStringUtils.randomAlphanumeric(21));
try {
deviceCredentialsService.updateDeviceCredentials(deviceCredentials);
} finally {
deviceService.deleteDevice(device.getId());
}
}
@Test
public void testFindDeviceCredentialsByDeviceId() {
Device device = new Device();

2
dao/src/test/resources/cassandra-test.properties

@ -47,5 +47,3 @@ cassandra.query.default_fetch_size=2000
cassandra.query.ts_key_value_partitioning=HOURS
cassandra.query.max_limit_per_request=1000
cassandra.query.min_aggregation_step_ms=1000
Loading…
Cancel
Save