diff --git a/application/pom.xml b/application/pom.xml
index c920709ae4..98b73133b6 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.5.1-SNAPSHOT
+ 3.5.2-SNAPSHOT
thingsboard
application
@@ -354,6 +354,10 @@
com.slack.api
slack-api-client
+
+ com.google.oauth-client
+ google-oauth-client
+
diff --git a/application/src/main/data/certs/azure/BaltimoreCyberTrustRoot.crt.pem b/application/src/main/data/certs/azure/BaltimoreCyberTrustRoot.crt.pem
deleted file mode 100644
index 2bd16ebd47..0000000000
--- a/application/src/main/data/certs/azure/BaltimoreCyberTrustRoot.crt.pem
+++ /dev/null
@@ -1,22 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
-RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
-VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
-DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
-ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
-VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
-mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
-IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
-mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
-XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
-dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
-jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
-BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
-DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
-9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
-jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
-Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
-ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
-R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
------END CERTIFICATE-----
-
diff --git a/application/src/main/data/certs/azure/DigiCertGlobalRootG2.crt.pem b/application/src/main/data/certs/azure/DigiCertGlobalRootG2.crt.pem
new file mode 100644
index 0000000000..798e002751
--- /dev/null
+++ b/application/src/main/data/certs/azure/DigiCertGlobalRootG2.crt.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
+MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
+2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
+1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
+q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
+tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
+vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
+5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
+1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
+NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
+Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
+8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
+pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
+MrY=
+-----END CERTIFICATE-----
diff --git a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json
index 16a155cebb..04f4042fae 100644
--- a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json
+++ b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json
@@ -3,7 +3,9 @@
"alias": "alarm_widgets",
"title": "Alarm widgets",
"image": "data:image/png;base64,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",
- "description": "Visualization of alarms for devices, assets and other entities."
+ "description": "Visualization of alarms for devices, assets and other entities.",
+ "externalId": null,
+ "name": "Alarm widgets"
},
"widgetTypes": [
{
@@ -23,7 +25,9 @@
"dataKeySettingsSchema": "",
"settingsDirective": "tb-alarms-table-widget-settings",
"dataKeySettingsDirective": "tb-alarms-table-key-settings",
- "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,\"allowAssign\":true,\"displayActivity\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStickyAction\":false,\"enableFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"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},{\"name\":\"assignee\",\"type\":\"alarm\",\"label\":\"Assignee\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.5008441077416634}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{},\"alarmStatusList\":[],\"alarmSeverityList\":[],\"alarmTypeList\":[],\"searchPropagatedAlarms\":false}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-alarms-table-basic-config",
+ "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,\"allowAssign\":true,\"displayActivity\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStickyAction\":false,\"enableFilter\":true,\"entitiesTitle\":null,\"alarmsTitle\":\"Alarms\"},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"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},{\"name\":\"assignee\",\"type\":\"alarm\",\"label\":\"Assignee\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.5008441077416634}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"warning\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{},\"alarmStatusList\":[],\"alarmSeverityList\":[],\"alarmTypeList\":[],\"searchPropagatedAlarms\":false,\"configMode\":\"basic\",\"alarmFilterConfig\":null}"
}
}
]
diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json
index 4930219741..cc2c74c359 100644
--- a/application/src/main/data/json/system/widget_bundles/cards.json
+++ b/application/src/main/data/json/system/widget_bundles/cards.json
@@ -3,7 +3,9 @@
"alias": "cards",
"title": "Cards",
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAT5UlEQVR42u3d95MUVffHcf86/UEtqwxVapmzaH0VE2YR82PAgBkTKCYUAyrGR0mSxERwBXRV0EdFRcAc5vvaPnptZ5bdZWbDwJ5TW1s93T23b5/7vufe7t3PuXv8+OOP36YNZrzUqCzdNUR37fHdd9810gaz4qV01xDdlWAlWAlWgpVgJVgJVnoqwUqwEqwEK8FKsNISrAQrwdqlwfrtt9/+V7Off/55gIJ++OGH8m46bOvWrb/88sv4Aeubb76pu8vb5z/++MPvEaqPwocL8RUrVqhwfc8rr7zSYdsNBFZvb+/hhx/+n79t/fr1ree8+eabn376qY03KrPx2muvff311zaeffbZd955Z/yANWvWLF76v8psPPLII19++eVdd901QvX57LPP7rvvvqGfv3nz5ldffbXfQzfffPPy5cvre9zC999/P4JgnXfeefU9n3/+uX6pEkHMxo0bL7vsMh5UCU786quvfGXSpElz584VwByNkvUtX3nrrbdsRDmrVq1auHDhtm3bdr+h8LHHHpszZ05s//TTT9EbN2zYwG+LFy/mH+OACMGBf/75Z5zW09Pj0Pbt2wsxuuimTZvi46+//qr3vvvuu3H+hx9++MUXX/g6D3/88cdxzkcffbRo0aItW7bER+FHCa5VamW0ef75588555z4iootW7Zs5cqV0SLA0hyuUmJHAUutHOo3pnQElqpsq8xt2DNjxowzzjjjySefPP/8819++eUPPvjAxh133AGpJ5544plnnnHDp512mtNUa/r06QsWLOCOKVOmPPTQQ7rXDTfcoJCHH374+uuvd/KECROaRs/dDCwOvPjii21Mnjz58ssvF8JPPvnkK664wsYll1zCAw5xpvD21FNPcazRR5fj8xdeeIEbuff333/nYWXedttt/On8s88++8ILL5w3b56jV155ZQxbPKxMNJh+QEdRgVGJQ1pw5syZTnj77beRfdZZZ6nk7bffrtgAK/b4CowKWO5UUUoWPlxlOME69NBDL67MtQOsl156yYYa33rrrTbUbMmSJTYCLBvuVhe0EWDpYVwT0w5u1VduvPFG96AP6cfjByxBJToVkmysXr0aTwLGUUcdJbRzztSpU5cuXWq0uvba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",
- "description": "Tables and cards to display latest and historical values for multiple entities simultaneously."
+ "description": "Tables and cards to display latest and historical values for multiple entities simultaneously.",
+ "externalId": null,
+ "name": "Cards"
},
"widgetTypes": [
{
@@ -62,7 +64,9 @@
"settingsDirective": "tb-timeseries-table-widget-settings",
"dataKeySettingsDirective": "tb-timeseries-table-key-settings",
"latestDataKeySettingsDirective": "tb-timeseries-table-latest-key-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}],\"latestDataKeys\":null}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"displayTimewindow\":true}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-timeseries-table-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}],\"latestDataKeys\":null}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"displayTimewindow\":true,\"configMode\":\"basic\"}"
}
},
{
@@ -100,7 +104,9 @@
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-simple-card-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"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\":\"#ff5722\",\"color\":\"rgba(255, 255, 255, 0.87)\",\"padding\":\"16px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Simple card\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-simple-card-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"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;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ff5722\",\"color\":\"rgba(255, 255, 255, 0.87)\",\"padding\":\"16px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Simple card\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true}"
}
},
{
@@ -139,7 +145,9 @@
"dataKeySettingsSchema": "",
"settingsDirective": "tb-entities-table-widget-settings",
"dataKeySettingsDirective": "tb-entities-table-key-settings",
- "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\":{\"entitiesTitle\":\"\",\"enableSearch\":true,\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"reserveSpaceForHiddenAction\":\"true\",\"displayEntityName\":true,\"entityNameColumnTitle\":\"\",\"displayEntityLabel\":false,\"displayEntityType\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"useRowStyleFunction\":false},\"title\":\"Entities table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"useCellContentFunction\":false,\"defaultColumnVisibility\":\"visible\",\"columnSelectionToDisplay\":\"enabled\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}]}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-entities-table-basic-config",
+ "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\":{\"enableSearch\":true,\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"reserveSpaceForHiddenAction\":\"true\",\"displayEntityName\":false,\"displayEntityLabel\":false,\"displayEntityType\":false,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"name\",\"useRowStyleFunction\":false,\"entitiesTitle\":\"Entities\"},\"title\":\"Entities table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity name\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return 'Simulated';\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity type\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.782057645776538,\"funcBody\":\"return 'Device';\",\"decimals\":null,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.904797781901171,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\",\"decimals\":0},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.1961430898042078,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\",\"decimals\":0},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.7678057538205878,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"decimals\":2}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"displayTimewindow\":false,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"list\",\"iconColor\":null}"
}
},
{
@@ -217,6 +225,48 @@
"settingsDirective": "tb-dashboard-state-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"syncParentStateParams\":true,\"defaultAutofillLayout\":true,\"defaultMargin\":0,\"defaultBackgroundColor\":\"#fff\"},\"title\":\"Dashboard state widget\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"showLegend\":false}"
}
+ },
+ {
+ "alias": "value_card",
+ "name": "Value card",
+ "image": null,
+ "description": "Designed to display single value of the selected attribute or timeseries data. Widget styles are customizable.",
+ "descriptor": {
+ "type": "latest",
+ "sizeX": 2.5,
+ "sizeY": 2.5,
+ "resources": [],
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '250px',\n previewHeight: '250px'\n };\n};\n\nself.onDestroy = function() {\n};\n",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "",
+ "settingsDirective": "",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-value-card-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"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;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}"
+ }
+ },
+ {
+ "alias": "horizontal_value_card",
+ "name": "Horizontal value card",
+ "image": null,
+ "description": "Designed to display single value of the selected attribute or timeseries data. Widget styles are customizable.",
+ "descriptor": {
+ "type": "latest",
+ "sizeX": 5,
+ "sizeY": 1.5,
+ "resources": [],
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n horizontal: true,\n previewWidth: '420px',\n previewHeight: '130px'\n };\n};\n\nself.onDestroy = function() {\n};\n",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "",
+ "settingsDirective": "",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-value-card-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"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;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Horizontal value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}"
+ }
}
]
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json
index de32604dbb..8dac2b5f5f 100644
--- a/application/src/main/data/json/system/widget_bundles/charts.json
+++ b/application/src/main/data/json/system/widget_bundles/charts.json
@@ -3,7 +3,9 @@
"alias": "charts",
"title": "Charts",
"image": "data:image/png;base64,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",
- "description": "Display timeseries data using customizable line and bar charts. Use various pie charts to display latest values."
+ "description": "Display timeseries data using customizable line and bar charts. Use various pie charts to display latest values.",
+ "externalId": null,
+ "name": "Charts"
},
"widgetTypes": [
{
@@ -151,15 +153,17 @@
"sizeX": 8,
"sizeY": 5,
"resources": [],
- "templateHtml": "",
+ "templateHtml": "\n",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
- "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true,\n hasAdditionalLatestDataKeys: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
+ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.flotWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.flotWidget.onLatestDataUpdated();\n}\n\nself.onResize = function() {\n self.ctx.$scope.flotWidget.onResize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.flotWidget.onEditModeChanged();\n}\n\nself.onDestroy = function() {\n self.ctx.$scope.flotWidget.onDestroy();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true,\n hasAdditionalLatestDataKeys: true\n };\n}\n\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
"settingsDirective": "tb-flot-line-widget-settings",
"dataKeySettingsDirective": "tb-flot-line-key-settings",
"latestDataKeySettingsDirective": "tb-flot-latest-key-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-flot-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"stack\":false,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":0,\"max\":1.2,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"shadowSize\":4,\"smoothLines\":false,\"comparisonEnabled\":false,\"timeForComparison\":\"previousInterval\",\"comparisonCustomIntervalValue\":7200000,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"right\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false,\"dataKeysListForLabels\":[]},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false},\"configMode\":\"basic\",\"showTitleIcon\":false,\"titleIcon\":\"waterfall_chart\",\"iconColor\":\"#1F6BDD\"}"
}
},
{
@@ -172,16 +176,18 @@
"sizeX": 8,
"sizeY": 5,
"resources": [],
- "templateHtml": "",
+ "templateHtml": "\n",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
- "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasAdditionalLatestDataKeys: true\n };\n}\n",
+ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.flotWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.flotWidget.onLatestDataUpdated();\n}\n\nself.onResize = function() {\n self.ctx.$scope.flotWidget.onResize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.flotWidget.onEditModeChanged();\n}\n\nself.onDestroy = function() {\n self.ctx.$scope.flotWidget.onDestroy();\n}\n\nself.typeParameters = function() {\n return {\n hasAdditionalLatestDataKeys: true\n };\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}",
"settingsDirective": "tb-flot-line-widget-settings",
"dataKeySettingsDirective": "tb-flot-line-key-settings",
"latestDataKeySettingsDirective": "tb-flot-latest-key-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries Line Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-flot-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"stack\":false,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":null,\"max\":null,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"\"},\"shadowSize\":4,\"smoothLines\":false,\"comparisonEnabled\":false,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"bottom\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false},\"title\":\"Timeseries Line Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\"}"
}
},
{
@@ -194,15 +200,17 @@
"sizeX": 8,
"sizeY": 5,
"resources": [],
- "templateHtml": "",
+ "templateHtml": "\n",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
- "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasAdditionalLatestDataKeys: true\n };\n}\n",
+ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.flotWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.flotWidget.onLatestDataUpdated();\n}\n\nself.onResize = function() {\n self.ctx.$scope.flotWidget.onResize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.flotWidget.onEditModeChanged();\n}\n\nself.onDestroy = function() {\n self.ctx.$scope.flotWidget.onDestroy();\n}\n\nself.typeParameters = function() {\n return {\n hasAdditionalLatestDataKeys: true\n };\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
"settingsDirective": "tb-flot-bar-widget-settings",
"dataKeySettingsDirective": "tb-flot-bar-key-settings",
"latestDataKeySettingsDirective": "tb-flot-latest-key-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bar Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-flot-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"stack\":true,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":null,\"max\":null,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"\"},\"defaultBarWidth\":600,\"barAlignment\":\"left\",\"comparisonEnabled\":false,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"bottom\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false},\"title\":\"Timeseries Bar Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"configMode\":\"basic\",\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\"}"
}
}
]
diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json
index ca2e16e4dd..e8856b5d97 100644
--- a/application/src/main/data/json/system/widget_bundles/input_widgets.json
+++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json
@@ -498,7 +498,7 @@
"settingsSchema": "",
"dataKeySettingsSchema": "{}",
"settingsDirective": "tb-update-json-attribute-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"attributeScope\":\"SERVER_SCOPE\",\"showLabel\":true,\"attributeRequired\":true,\"showResultMessage\":true},\"title\":\"Update JSON attribute\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}"
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"widgetMode\":\"ATTRIBUTE\",\"attributeScope\":\"SERVER_SCOPE\",\"showLabel\":true,\"attributeRequired\":true,\"showResultMessage\":true},\"title\":\"Update JSON attribute\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}"
}
}
]
diff --git a/application/src/main/data/upgrade/3.5.0/schema_update.sql b/application/src/main/data/upgrade/3.5.0/schema_update.sql
new file mode 100644
index 0000000000..321112c4eb
--- /dev/null
+++ b/application/src/main/data/upgrade/3.5.0/schema_update.sql
@@ -0,0 +1,23 @@
+--
+-- Copyright © 2016-2023 The Thingsboard Authors
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+-- FIX DASHBOARD TEMPLATES AFTER ANGULAR MIGRATION TO VER.15
+
+UPDATE dashboard SET configuration = REPLACE(configuration, 'mat-button mat-icon-button', 'mat-icon-button')
+ WHERE configuration like '%mat-button mat-icon-button%';
+
+UPDATE widget_type SET descriptor = REPLACE(descriptor, 'mat-button mat-icon-button', 'mat-icon-button')
+ WHERE descriptor like '%mat-button mat-icon-button%';
diff --git a/application/src/main/data/upgrade/3.5.1/schema_update.sql b/application/src/main/data/upgrade/3.5.1/schema_update.sql
new file mode 100644
index 0000000000..1655ecb978
--- /dev/null
+++ b/application/src/main/data/upgrade/3.5.1/schema_update.sql
@@ -0,0 +1,124 @@
+--
+-- Copyright © 2016-2023 The Thingsboard Authors
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+ALTER TABLE notification_rule ADD COLUMN IF NOT EXISTS enabled BOOLEAN NOT NULL DEFAULT true;
+
+-- NOTIFICATION CONFIGS VERSION CONTROL START
+
+ALTER TABLE notification_template
+ ADD COLUMN IF NOT EXISTS external_id UUID;
+DO
+$$
+ BEGIN
+ IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_notification_template_external_id') THEN
+ ALTER TABLE notification_template ADD CONSTRAINT uq_notification_template_external_id UNIQUE (tenant_id, external_id);
+ END IF;
+ END;
+$$;
+
+ALTER TABLE notification_target
+ ADD COLUMN IF NOT EXISTS external_id UUID;
+DO
+$$
+ BEGIN
+ IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_notification_target_external_id') THEN
+ ALTER TABLE notification_target ADD CONSTRAINT uq_notification_target_external_id UNIQUE (tenant_id, external_id);
+ END IF;
+ END;
+$$;
+
+ALTER TABLE notification_rule
+ ADD COLUMN IF NOT EXISTS external_id UUID;
+DO
+$$
+ BEGIN
+ IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_notification_rule_external_id') THEN
+ ALTER TABLE notification_rule ADD CONSTRAINT uq_notification_rule_external_id UNIQUE (tenant_id, external_id);
+ END IF;
+ END;
+$$;
+
+-- NOTIFICATION CONFIGS VERSION CONTROL END
+
+-- EDGE EVENTS MIGRATION START
+DO
+$$
+ DECLARE table_partition RECORD;
+ BEGIN
+ -- in case of running the upgrade script a second time:
+ IF NOT (SELECT exists(SELECT FROM pg_tables WHERE tablename = 'old_edge_event')) THEN
+ ALTER TABLE edge_event RENAME TO old_edge_event;
+ CREATE INDEX IF NOT EXISTS idx_old_edge_event_created_time_tmp ON old_edge_event(created_time);
+ ALTER INDEX IF EXISTS idx_edge_event_tenant_id_and_created_time RENAME TO idx_old_edge_event_tenant_id_and_created_time;
+
+ FOR table_partition IN SELECT tablename AS name, split_part(tablename, '_', 3) AS partition_ts
+ FROM pg_tables WHERE tablename LIKE 'edge_event_%'
+ LOOP
+ EXECUTE format('ALTER TABLE %s RENAME TO old_edge_event_%s', table_partition.name, table_partition.partition_ts);
+ END LOOP;
+ ELSE
+ RAISE NOTICE 'Table old_edge_event already exists, leaving as is';
+ END IF;
+ END;
+$$;
+
+CREATE TABLE IF NOT EXISTS edge_event (
+ seq_id INT GENERATED ALWAYS AS IDENTITY,
+ id uuid NOT NULL,
+ created_time bigint NOT NULL,
+ edge_id uuid,
+ edge_event_type varchar(255),
+ edge_event_uid varchar(255),
+ entity_id uuid,
+ edge_event_action varchar(255),
+ body varchar(10000000),
+ tenant_id uuid,
+ ts bigint NOT NULL
+) PARTITION BY RANGE (created_time);
+CREATE INDEX IF NOT EXISTS idx_edge_event_tenant_id_and_created_time ON edge_event(tenant_id, created_time DESC);
+CREATE INDEX IF NOT EXISTS idx_edge_event_id ON edge_event(id);
+ALTER TABLE IF EXISTS edge_event ALTER COLUMN seq_id SET CYCLE;
+
+CREATE OR REPLACE PROCEDURE migrate_edge_event(IN start_time_ms BIGINT, IN end_time_ms BIGINT, IN partition_size_ms BIGINT)
+ LANGUAGE plpgsql AS
+$$
+DECLARE
+ p RECORD;
+ partition_end_ts BIGINT;
+BEGIN
+ FOR p IN SELECT DISTINCT (created_time - created_time % partition_size_ms) AS partition_ts FROM old_edge_event
+ WHERE created_time >= start_time_ms AND created_time < end_time_ms
+ LOOP
+ partition_end_ts = p.partition_ts + partition_size_ms;
+ RAISE NOTICE '[edge_event] Partition to create : [%-%]', p.partition_ts, partition_end_ts;
+ EXECUTE format('CREATE TABLE IF NOT EXISTS edge_event_%s PARTITION OF edge_event ' ||
+ 'FOR VALUES FROM ( %s ) TO ( %s )', p.partition_ts, p.partition_ts, partition_end_ts);
+ END LOOP;
+
+ INSERT INTO edge_event (id, created_time, edge_id, edge_event_type, edge_event_uid, entity_id, edge_event_action, body, tenant_id, ts)
+ SELECT id, created_time, edge_id, edge_event_type, edge_event_uid, entity_id, edge_event_action, body, tenant_id, ts
+ FROM old_edge_event
+ WHERE created_time >= start_time_ms AND created_time < end_time_ms;
+END;
+$$;
+-- EDGE EVENTS MIGRATION END
+
+ALTER TABLE resource
+ ADD COLUMN IF NOT EXISTS etag varchar;
+
+UPDATE resource
+ SET etag = encode(sha256(decode(resource.data, 'base64')),'hex') WHERE resource.data is not null;
+
diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
index 515c635e68..fc822ce226 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
@@ -15,7 +15,6 @@
*/
package org.thingsboard.server.actors;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -29,6 +28,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.rule.engine.api.SmsService;
@@ -90,7 +90,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.discovery.DiscoveryService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
-import org.thingsboard.server.queue.notification.NotificationRuleProcessor;
+import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
@@ -150,8 +150,6 @@ public class ActorSystemContext {
}
};
- protected final ObjectMapper mapper = new ObjectMapper();
-
private final ConcurrentMap debugPerTenantLimits = new ConcurrentHashMap<>();
public ConcurrentMap getDebugPerTenantLimits() {
@@ -538,6 +536,10 @@ public class ActorSystemContext {
@Getter
private int maxRpcRetries;
+ @Value("${actors.rule.external.force_ack:false}")
+ @Getter
+ private boolean externalNodeForceAck;
+
@Getter
@Setter
private TbActorSystem actorSystem;
@@ -646,7 +648,7 @@ public class ActorSystemContext {
.dataType(tbMsg.getDataType().name())
.relationType(relationType)
.data(tbMsg.getData())
- .metadata(mapper.writeValueAsString(tbMsg.getMetaData().getData()));
+ .metadata(JacksonUtil.toString(tbMsg.getMetaData().getData()));
if (error != null) {
event.error(toString(error));
@@ -656,7 +658,7 @@ public class ActorSystemContext {
ListenableFuture future = eventService.saveAsync(event.build());
Futures.addCallback(future, RULE_NODE_DEBUG_EVENT_ERROR_CALLBACK, MoreExecutors.directExecutor());
- } catch (IOException ex) {
+ } catch (IllegalArgumentException ex) {
log.warn("Failed to persist rule node debug message", ex);
}
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
index fb6fbbdff2..1461654216 100644
--- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
@@ -73,10 +73,14 @@ public class AppActor extends ContextAwareActor {
@Override
protected boolean doProcess(TbActorMsg msg) {
if (!ruleChainsInitialized) {
- initTenantActors();
- ruleChainsInitialized = true;
- if (msg.getMsgType() != MsgType.APP_INIT_MSG && msg.getMsgType() != MsgType.PARTITION_CHANGE_MSG) {
- log.warn("Rule Chains initialized by unexpected message: {}", msg);
+ if (MsgType.APP_INIT_MSG.equals(msg.getMsgType())) {
+ initTenantActors();
+ ruleChainsInitialized = true;
+ } else {
+ if (!msg.getMsgType().isIgnoreOnStart()) {
+ log.warn("Attempt to initialize Rule Chains by unexpected message: {}", msg);
+ }
+ return true;
}
}
switch (msg.getMsgType()) {
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
index d5833e0c07..2779a83979 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
@@ -59,10 +59,10 @@ public class DeviceActor extends ContextAwareActor {
protected boolean doProcess(TbActorMsg msg) {
switch (msg.getMsgType()) {
case TRANSPORT_TO_DEVICE_ACTOR_MSG:
- processor.process(ctx, (TransportToDeviceActorMsgWrapper) msg);
+ processor.process((TransportToDeviceActorMsgWrapper) msg);
break;
case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
- processor.processAttributesUpdate(ctx, (DeviceAttributesEventNotificationMsg) msg);
+ processor.processAttributesUpdate((DeviceAttributesEventNotificationMsg) msg);
break;
case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
processor.processCredentialsUpdate(msg);
@@ -74,10 +74,10 @@ public class DeviceActor extends ContextAwareActor {
processor.processRpcRequest(ctx, (ToDeviceRpcRequestActorMsg) msg);
break;
case DEVICE_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
- processor.processRpcResponsesFromEdge(ctx, (FromDeviceRpcResponseActorMsg) msg);
+ processor.processRpcResponsesFromEdge((FromDeviceRpcResponseActorMsg) msg);
break;
case DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG:
- processor.processServerSideRpcTimeout(ctx, (DeviceActorServerSideRpcTimeoutMsg) msg);
+ processor.processServerSideRpcTimeout((DeviceActorServerSideRpcTimeoutMsg) msg);
break;
case SESSION_TIMEOUT_MSG:
processor.checkSessionsTimeout();
@@ -86,7 +86,7 @@ public class DeviceActor extends ContextAwareActor {
processor.processEdgeUpdate((DeviceEdgeUpdateMsg) msg);
break;
case REMOVE_RPC_TO_DEVICE_ACTOR_MSG:
- processor.processRemoveRpc(ctx, (RemoveRpcActorMsg) msg);
+ processor.processRemoveRpc((RemoveRpcActorMsg) msg);
break;
default:
return false;
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
index b8c890b75e..0ce0ae7884 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
@@ -83,7 +83,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProt
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseStatusMsg;
-import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
@@ -182,13 +181,15 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
void processRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg) {
ToDeviceRpcRequest request = msg.getMsg();
+ UUID rpcId = request.getId();
+ log.debug("[{}][{}] Received RPC request to process ...", deviceId, rpcId);
ToDeviceRpcRequestMsg rpcRequest = creteToDeviceRpcRequestMsg(request);
long timeout = request.getExpirationTime() - System.currentTimeMillis();
boolean persisted = request.isPersisted();
if (timeout <= 0) {
- log.debug("[{}][{}] Ignoring message due to exp time reached, {}", deviceId, request.getId(), request.getExpirationTime());
+ log.debug("[{}][{}] Ignoring message due to exp time reached, {}", deviceId, rpcId, request.getExpirationTime());
if (persisted) {
createRpc(request, RpcStatus.EXPIRED);
}
@@ -198,21 +199,23 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
boolean sent = false;
+ int requestId = rpcRequest.getRequestId();
if (systemContext.isEdgesEnabled() && edgeId != null) {
- log.debug("[{}][{}] device is related to edge [{}]. Saving RPC request to edge queue", tenantId, deviceId, edgeId.getId());
+ log.debug("[{}][{}] device is related to edge: [{}]. Saving RPC request: [{}][{}] to edge queue", tenantId, deviceId, edgeId.getId(), rpcId, requestId);
try {
- saveRpcRequestToEdgeQueue(request, rpcRequest.getRequestId()).get();
+ saveRpcRequestToEdgeQueue(request, requestId).get();
sent = true;
} catch (InterruptedException | ExecutionException e) {
- log.error("[{}][{}][{}] Failed to save rpc request to edge queue {}", tenantId, deviceId, edgeId.getId(), request, e);
+ log.error("[{}][{}][{}] Failed to save RPC request to edge queue {}", tenantId, deviceId, edgeId.getId(), request, e);
}
} else if (isSendNewRpcAvailable()) {
sent = rpcSubscriptions.size() > 0;
Set syncSessionSet = new HashSet<>();
- rpcSubscriptions.forEach((key, value) -> {
- sendToTransport(rpcRequest, key, value.getNodeId());
- if (SessionType.SYNC == value.getType()) {
- syncSessionSet.add(key);
+ rpcSubscriptions.forEach((sessionId, sessionInfo) -> {
+ log.debug("[{}][{}][{}][{}] send RPC request to transport ...", deviceId, sessionId, rpcId, requestId);
+ sendToTransport(rpcRequest, sessionId, sessionInfo.getNodeId());
+ if (SessionType.SYNC == sessionInfo.getType()) {
+ syncSessionSet.add(sessionId);
}
});
log.trace("Rpc syncSessionSet [{}] subscription after sent [{}]", syncSessionSet, rpcSubscriptions);
@@ -221,20 +224,20 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
if (persisted) {
ObjectNode response = JacksonUtil.newObjectNode();
- response.put("rpcId", request.getId().toString());
+ response.put("rpcId", rpcId.toString());
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), JacksonUtil.toString(response), null));
}
if (!persisted && request.isOneway() && sent) {
- log.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId());
+ log.debug("[{}] RPC command response sent [{}][{}]!", deviceId, rpcId, requestId);
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null));
} else {
registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout);
}
if (sent) {
- log.debug("[{}] RPC request {} is sent!", deviceId, request.getId());
+ log.debug("[{}][{}][{}] RPC request is sent!", deviceId, rpcId, requestId);
} else {
- log.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId());
+ log.debug("[{}][{}][{}] RPC request is NOT sent!", deviceId, rpcId, requestId);
}
}
@@ -242,7 +245,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
return !rpcSequential || toDeviceRpcPendingMap.values().stream().filter(md -> !md.isDelivered()).findAny().isEmpty();
}
- private Rpc createRpc(ToDeviceRpcRequest request, RpcStatus status) {
+ private void createRpc(ToDeviceRpcRequest request, RpcStatus status) {
Rpc rpc = new Rpc(new RpcId(request.getId()));
rpc.setCreatedTime(System.currentTimeMillis());
rpc.setTenantId(tenantId);
@@ -251,7 +254,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
rpc.setRequest(JacksonUtil.valueToTree(request));
rpc.setStatus(status);
rpc.setAdditionalInfo(JacksonUtil.toJsonNode(request.getAdditionalInfo()));
- return systemContext.getTbRpcService().save(tenantId, rpc);
+ systemContext.getTbRpcService().save(tenantId, rpc);
}
private ToDeviceRpcRequestMsg creteToDeviceRpcRequestMsg(ToDeviceRpcRequest request) {
@@ -268,82 +271,92 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
.build();
}
- void processRpcResponsesFromEdge(TbActorCtx context, FromDeviceRpcResponseActorMsg responseMsg) {
- log.debug("[{}] Processing rpc command response from edge session", deviceId);
+ void processRpcResponsesFromEdge(FromDeviceRpcResponseActorMsg responseMsg) {
+ log.debug("[{}] Processing RPC command response from edge session", deviceId);
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
boolean success = requestMd != null;
if (success) {
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(responseMsg.getMsg());
} else {
- log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
+ log.debug("[{}] RPC command response [{}] is stale!", deviceId, responseMsg.getRequestId());
}
}
- void processRemoveRpc(TbActorCtx context, RemoveRpcActorMsg msg) {
- log.debug("[{}] Processing remove rpc command", msg.getRequestId());
+ void processRemoveRpc(RemoveRpcActorMsg msg) {
+ UUID requestId = msg.getRequestId();
+ log.debug("[{}][{}] Received remove RPC request ...", deviceId, requestId);
Map.Entry entry = null;
for (Map.Entry e : toDeviceRpcPendingMap.entrySet()) {
- if (e.getValue().getMsg().getMsg().getId().equals(msg.getRequestId())) {
+ if (e.getValue().getMsg().getMsg().getId().equals(requestId)) {
entry = e;
break;
}
}
if (entry != null) {
+ Integer key = entry.getKey();
if (entry.getValue().isDelivered()) {
- toDeviceRpcPendingMap.remove(entry.getKey());
+ toDeviceRpcPendingMap.remove(key);
} else {
Optional> firstRpc = getFirstRpc();
- if (firstRpc.isPresent() && entry.getKey().equals(firstRpc.get().getKey())) {
- toDeviceRpcPendingMap.remove(entry.getKey());
- sendNextPendingRequest(context);
+ if (firstRpc.isPresent() && key.equals(firstRpc.get().getKey())) {
+ toDeviceRpcPendingMap.remove(key);
+ log.debug("[{}][{}][{}] Removed pending RPC! Going to send next pending request ...", deviceId, requestId, key);
+ sendNextPendingRequest();
} else {
- toDeviceRpcPendingMap.remove(entry.getKey());
+ toDeviceRpcPendingMap.remove(key);
}
}
}
}
private void registerPendingRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
- toDeviceRpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent));
- DeviceActorServerSideRpcTimeoutMsg timeoutMsg = new DeviceActorServerSideRpcTimeoutMsg(rpcRequest.getRequestId(), timeout);
+ int requestId = rpcRequest.getRequestId();
+ UUID rpcId = new UUID(rpcRequest.getRequestIdMSB(), rpcRequest.getRequestIdLSB());
+ log.debug("[{}][{}][{}] Registering pending RPC request...", deviceId, rpcId, requestId);
+ toDeviceRpcPendingMap.put(requestId, new ToDeviceRpcRequestMetadata(msg, sent));
+ DeviceActorServerSideRpcTimeoutMsg timeoutMsg = new DeviceActorServerSideRpcTimeoutMsg(requestId, timeout);
scheduleMsgWithDelay(context, timeoutMsg, timeoutMsg.getTimeout());
}
- void processServerSideRpcTimeout(TbActorCtx context, DeviceActorServerSideRpcTimeoutMsg msg) {
- ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId());
+ void processServerSideRpcTimeout(DeviceActorServerSideRpcTimeoutMsg msg) {
+ Integer requestId = msg.getId();
+ ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(requestId);
if (requestMd != null) {
- log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
- if (requestMd.getMsg().getMsg().isPersisted()) {
- systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.EXPIRED, null);
+ ToDeviceRpcRequest toDeviceRpcRequest = requestMd.getMsg().getMsg();
+ UUID rpcId = toDeviceRpcRequest.getId();
+ log.debug("[{}][{}][{}] RPC request timeout detected!", deviceId, rpcId, requestId);
+ if (toDeviceRpcRequest.isPersisted()) {
+ systemContext.getTbRpcService().save(tenantId, new RpcId(rpcId), RpcStatus.EXPIRED, null);
}
- systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
+ systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(rpcId,
null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION));
if (!requestMd.isDelivered()) {
- sendNextPendingRequest(context);
+ log.debug("[{}][{}][{}] Pending RPC timeout detected! Going to send next pending request ...", deviceId, rpcId, requestId);
+ sendNextPendingRequest();
}
}
}
- private void sendPendingRequests(TbActorCtx context, UUID sessionId, String nodeId) {
+ private void sendPendingRequests(UUID sessionId, String nodeId) {
SessionType sessionType = getSessionType(sessionId);
if (!toDeviceRpcPendingMap.isEmpty()) {
- log.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId);
+ log.debug("[{}] Pushing {} pending RPC messages to session: [{}]", deviceId, sessionId, toDeviceRpcPendingMap.size());
if (sessionType == SessionType.SYNC) {
- log.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId);
+ log.debug("[{}] Cleanup sync RPC session [{}]", deviceId, sessionId);
rpcSubscriptions.remove(sessionId);
}
} else {
- log.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId);
+ log.debug("[{}] No pending RPC messages for session: [{}]", deviceId, sessionId);
}
Set sentOneWayIds = new HashSet<>();
if (rpcSequential) {
- getFirstRpc().ifPresent(processPendingRpc(context, sessionId, nodeId, sentOneWayIds));
+ getFirstRpc().ifPresent(processPendingRpc(sessionId, nodeId, sentOneWayIds));
} else if (sessionType == SessionType.ASYNC) {
- toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, nodeId, sentOneWayIds));
+ toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(sessionId, nodeId, sentOneWayIds));
} else {
- toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, nodeId, sentOneWayIds));
+ toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(sessionId, nodeId, sentOneWayIds));
}
sentOneWayIds.stream().filter(id -> !toDeviceRpcPendingMap.get(id).getMsg().getMsg().isPersisted()).forEach(toDeviceRpcPendingMap::remove);
@@ -353,35 +366,38 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
return toDeviceRpcPendingMap.entrySet().stream().filter(e -> !e.getValue().isDelivered()).findFirst();
}
- private void sendNextPendingRequest(TbActorCtx context) {
+ private void sendNextPendingRequest() {
if (rpcSequential) {
- rpcSubscriptions.forEach((id, s) -> sendPendingRequests(context, id, s.getNodeId()));
+ rpcSubscriptions.forEach((id, s) -> sendPendingRequests(id, s.getNodeId()));
}
}
- private Consumer> processPendingRpc(TbActorCtx context, UUID sessionId, String nodeId, Set sentOneWayIds) {
+ private Consumer> processPendingRpc(UUID sessionId, String nodeId, Set sentOneWayIds) {
return entry -> {
ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg();
ToDeviceRpcRequestBody body = request.getBody();
+ Integer requestId = entry.getKey();
+ UUID rpcId = request.getId();
if (request.isOneway() && !rpcSequential) {
- sentOneWayIds.add(entry.getKey());
- systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(request.getId(), null, null));
+ sentOneWayIds.add(requestId);
+ systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(rpcId, null, null));
}
ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder()
- .setRequestId(entry.getKey())
+ .setRequestId(requestId)
.setMethodName(body.getMethod())
.setParams(body.getParams())
.setExpirationTime(request.getExpirationTime())
- .setRequestIdMSB(request.getId().getMostSignificantBits())
- .setRequestIdLSB(request.getId().getLeastSignificantBits())
+ .setRequestIdMSB(rpcId.getMostSignificantBits())
+ .setRequestIdLSB(rpcId.getLeastSignificantBits())
.setOneway(request.isOneway())
.setPersisted(request.isPersisted())
.build();
+ log.debug("[{}][{}][{}][{}] Send pending RPC request to transport ...", deviceId, sessionId, rpcId, requestId);
sendToTransport(rpcRequest, sessionId, nodeId);
};
}
- void process(TbActorCtx context, TransportToDeviceActorMsgWrapper wrapper) {
+ void process(TransportToDeviceActorMsgWrapper wrapper) {
TransportToDeviceActorMsg msg = wrapper.getMsg();
TbCallback callback = wrapper.getCallback();
var sessionInfo = msg.getSessionInfo();
@@ -390,36 +406,36 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
processSessionStateMsgs(sessionInfo, msg.getSessionEvent());
}
if (msg.hasSubscribeToAttributes()) {
- processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToAttributes());
+ processSubscriptionCommands(sessionInfo, msg.getSubscribeToAttributes());
}
if (msg.hasSubscribeToRPC()) {
- processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToRPC());
+ processSubscriptionCommands(sessionInfo, msg.getSubscribeToRPC());
}
if (msg.hasSendPendingRPC()) {
- sendPendingRequests(context, getSessionId(sessionInfo), sessionInfo.getNodeId());
+ sendPendingRequests(getSessionId(sessionInfo), sessionInfo.getNodeId());
}
if (msg.hasGetAttributes()) {
- handleGetAttributesRequest(context, sessionInfo, msg.getGetAttributes());
+ handleGetAttributesRequest(sessionInfo, msg.getGetAttributes());
}
if (msg.hasToDeviceRPCCallResponse()) {
- processRpcResponses(context, sessionInfo, msg.getToDeviceRPCCallResponse());
+ processRpcResponses(sessionInfo, msg.getToDeviceRPCCallResponse());
}
if (msg.hasSubscriptionInfo()) {
- handleSessionActivity(context, sessionInfo, msg.getSubscriptionInfo());
+ handleSessionActivity(sessionInfo, msg.getSubscriptionInfo());
}
if (msg.hasClaimDevice()) {
- handleClaimDeviceMsg(context, sessionInfo, msg.getClaimDevice());
+ handleClaimDeviceMsg(msg.getClaimDevice());
}
if (msg.hasRpcResponseStatusMsg()) {
- processRpcResponseStatus(context, sessionInfo, msg.getRpcResponseStatusMsg());
+ processRpcResponseStatus(sessionInfo, msg.getRpcResponseStatusMsg());
}
if (msg.hasUplinkNotificationMsg()) {
- processUplinkNotificationMsg(context, sessionInfo, msg.getUplinkNotificationMsg());
+ processUplinkNotificationMsg(sessionInfo, msg.getUplinkNotificationMsg());
}
callback.onSuccess();
}
- private void processUplinkNotificationMsg(TbActorCtx context, SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg uplinkNotificationMsg) {
+ private void processUplinkNotificationMsg(SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg uplinkNotificationMsg) {
String nodeId = sessionInfo.getNodeId();
sessions.entrySet().stream()
.filter(kv -> kv.getValue().getSessionInfo().getNodeId().equals(nodeId) && (kv.getValue().isSubscribedToAttributes() || kv.getValue().isSubscribedToRPC()))
@@ -433,7 +449,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
});
}
- private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, ClaimDeviceMsg msg) {
+ private void handleClaimDeviceMsg(ClaimDeviceMsg msg) {
DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs());
}
@@ -446,7 +462,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
systemContext.getDeviceStateService().onDeviceDisconnect(tenantId, deviceId);
}
- private void handleGetAttributesRequest(TbActorCtx context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
+ private void handleGetAttributesRequest(SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
int requestId = request.getRequestId();
if (request.getOnlyShared()) {
Futures.addCallback(findAllAttributesByScope(DataConstants.SHARED_SCOPE), new FutureCallback<>() {
@@ -530,7 +546,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
return sessions.containsKey(sessionId) ? SessionType.ASYNC : SessionType.SYNC;
}
- void processAttributesUpdate(TbActorCtx context, DeviceAttributesEventNotificationMsg msg) {
+ void processAttributesUpdate(DeviceAttributesEventNotificationMsg msg) {
if (attributeSubscriptions.size() > 0) {
boolean hasNotificationData = false;
AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder();
@@ -567,19 +583,21 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
}
- private void processRpcResponses(TbActorCtx context, SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg responseMsg) {
+ private void processRpcResponses(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg responseMsg) {
UUID sessionId = getSessionId(sessionInfo);
- log.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId);
- ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
+ log.debug("[{}][{}] Processing RPC command response: {}", deviceId, sessionId, responseMsg);
+ int requestId = responseMsg.getRequestId();
+ ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(requestId);
boolean success = requestMd != null;
if (success) {
+ ToDeviceRpcRequest toDeviceRequestMsg = requestMd.getMsg().getMsg();
+ boolean delivered = requestMd.isDelivered();
boolean hasError = StringUtils.isNotEmpty(responseMsg.getError());
try {
String payload = hasError ? responseMsg.getError() : responseMsg.getPayload();
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(
- new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
- payload, null));
- if (requestMd.getMsg().getMsg().isPersisted()) {
+ new FromDeviceRpcResponse(toDeviceRequestMsg.getId(), payload, null));
+ if (toDeviceRequestMsg.isPersisted()) {
RpcStatus status = hasError ? RpcStatus.FAILED : RpcStatus.SUCCESSFUL;
JsonNode response;
try {
@@ -587,28 +605,33 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
} catch (IllegalArgumentException e) {
response = JacksonUtil.newObjectNode().put("error", payload);
}
- systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), status, response);
+ systemContext.getTbRpcService().save(tenantId, new RpcId(toDeviceRequestMsg.getId()), status, response);
}
} finally {
- if (hasError && !requestMd.isDelivered()) {
- sendNextPendingRequest(context);
+ if (!delivered) {
+ String errorResponse = hasError ? "error" : "";
+ log.debug("[{}][{}][{}] Received {} response for undelivered RPC! Going to send next pending request ...", deviceId, sessionId, requestId, errorResponse);
+ sendNextPendingRequest();
}
}
} else {
- log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
+ log.debug("[{}][{}][{}] RPC command response is stale!", deviceId, sessionId, requestId);
}
}
- private void processRpcResponseStatus(TbActorCtx context, SessionInfoProto sessionInfo, ToDeviceRpcResponseStatusMsg responseMsg) {
+ private void processRpcResponseStatus(SessionInfoProto sessionInfo, ToDeviceRpcResponseStatusMsg responseMsg) {
UUID rpcId = new UUID(responseMsg.getRequestIdMSB(), responseMsg.getRequestIdLSB());
RpcStatus status = RpcStatus.valueOf(responseMsg.getStatus());
- ToDeviceRpcRequestMetadata md = toDeviceRpcPendingMap.get(responseMsg.getRequestId());
+ UUID sessionId = getSessionId(sessionInfo);
+ int requestId = responseMsg.getRequestId();
+ log.debug("[{}][{}][{}][{}] Processing RPC command response status: [{}]", deviceId, sessionId, rpcId, requestId, status);
+ ToDeviceRpcRequestMetadata md = toDeviceRpcPendingMap.get(requestId);
if (md != null) {
JsonNode response = null;
if (status.equals(RpcStatus.DELIVERED)) {
if (md.getMsg().getMsg().isOneway()) {
- toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
+ toDeviceRpcPendingMap.remove(requestId);
if (rpcSequential) {
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(rpcId, null, null));
}
@@ -619,7 +642,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
Integer maxRpcRetries = md.getMsg().getMsg().getRetries();
maxRpcRetries = maxRpcRetries == null ? systemContext.getMaxRpcRetries() : Math.min(maxRpcRetries, systemContext.getMaxRpcRetries());
if (maxRpcRetries <= md.getRetries()) {
- toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
+ toDeviceRpcPendingMap.remove(requestId);
status = RpcStatus.FAILED;
response = JacksonUtil.newObjectNode().put("error", "There was a Timeout and all retry attempts have been exhausted. Retry attempts set: " + maxRpcRetries);
} else {
@@ -631,17 +654,18 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
systemContext.getTbRpcService().save(tenantId, new RpcId(rpcId), status, response);
}
if (status != RpcStatus.SENT) {
- sendNextPendingRequest(context);
+ log.debug("[{}][{}][{}][{}] RPC was {}! Going to send next pending request ...", deviceId, sessionId, rpcId, requestId, status.name().toLowerCase());
+ sendNextPendingRequest();
}
} else {
- log.info("[{}][{}] Rpc has already removed from pending map.", deviceId, rpcId);
+ log.warn("[{}][{}][{}][{}] RPC has already been removed from pending map.", deviceId, sessionId, rpcId, requestId);
}
}
- private void processSubscriptionCommands(TbActorCtx context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) {
+ private void processSubscriptionCommands(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) {
UUID sessionId = getSessionId(sessionInfo);
if (subscribeCmd.getUnsubscribe()) {
- log.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId);
+ log.debug("[{}] Canceling attributes subscription for session: [{}]", deviceId, sessionId);
attributeSubscriptions.remove(sessionId);
} else {
SessionInfoMetaData sessionMD = sessions.get(sessionId);
@@ -649,7 +673,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId()));
}
sessionMD.setSubscribedToAttributes(true);
- log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId);
+ log.debug("[{}] Registering attributes subscription for session: [{}]", deviceId, sessionId);
attributeSubscriptions.put(sessionId, sessionMD.getSessionInfo());
dumpSessions();
}
@@ -659,10 +683,10 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
}
- private void processSubscriptionCommands(TbActorCtx context, SessionInfoProto sessionInfo, SubscribeToRPCMsg subscribeCmd) {
+ private void processSubscriptionCommands(SessionInfoProto sessionInfo, SubscribeToRPCMsg subscribeCmd) {
UUID sessionId = getSessionId(sessionInfo);
if (subscribeCmd.getUnsubscribe()) {
- log.debug("[{}] Canceling rpc subscription for session [{}]", deviceId, sessionId);
+ log.debug("[{}] Canceling RPC subscription for session: [{}]", deviceId, sessionId);
rpcSubscriptions.remove(sessionId);
} else {
SessionInfoMetaData sessionMD = sessions.get(sessionId);
@@ -670,9 +694,9 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId()));
}
sessionMD.setSubscribedToRPC(true);
- log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId);
rpcSubscriptions.put(sessionId, sessionMD.getSessionInfo());
- sendPendingRequests(context, sessionId, sessionInfo.getNodeId());
+ log.debug("[{}] Registered RPC subscription for session: [{}] Going to check for pending requests ...", deviceId, sessionId);
+ sendPendingRequests(sessionId, sessionInfo.getNodeId());
dumpSessions();
}
}
@@ -682,10 +706,10 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
Objects.requireNonNull(sessionId);
if (msg.getEvent() == SessionEvent.OPEN) {
if (sessions.containsKey(sessionId)) {
- log.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId);
+ log.debug("[{}][{}] Received duplicate session open event.", deviceId, sessionId);
return;
}
- log.debug("[{}] Processing new session [{}]. Current sessions size {}", deviceId, sessionId, sessions.size());
+ log.debug("[{}] Processing new session: [{}] Current sessions size: {}", deviceId, sessionId, sessions.size());
sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfo.getNodeId())));
if (sessions.size() == 1) {
@@ -694,7 +718,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
systemContext.getDeviceStateService().onDeviceActivity(tenantId, deviceId, System.currentTimeMillis());
dumpSessions();
} else if (msg.getEvent() == SessionEvent.CLOSED) {
- log.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId);
+ log.debug("[{}][{}] Canceling subscriptions for closed session.", deviceId, sessionId);
sessions.remove(sessionId);
attributeSubscriptions.remove(sessionId);
rpcSubscriptions.remove(sessionId);
@@ -705,7 +729,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
}
- private void handleSessionActivity(TbActorCtx context, SessionInfoProto sessionInfoProto, SubscriptionInfoProto subscriptionInfo) {
+ private void handleSessionActivity(SessionInfoProto sessionInfoProto, SubscriptionInfoProto subscriptionInfo) {
UUID sessionId = getSessionId(sessionInfoProto);
Objects.requireNonNull(sessionId);
@@ -742,7 +766,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
private void notifyTransportAboutClosedSessionMaxSessionsLimit(UUID sessionId, SessionInfoMetaData sessionMd) {
- log.debug("remove eldest session (max concurrent sessions limit reached per device) sessionId [{}] sessionMd [{}]", sessionId, sessionMd);
+ log.debug("remove eldest session (max concurrent sessions limit reached per device) sessionId: [{}] sessionMd: [{}]", sessionId, sessionMd);
notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!");
}
@@ -806,16 +830,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
systemContext.getTbCoreToTransportService().process(nodeId, msg);
}
- private void sendToTransport(ToServerRpcResponseMsg rpcMsg, UUID sessionId, String nodeId) {
- ToTransportMsg msg = ToTransportMsg.newBuilder()
- .setSessionIdMSB(sessionId.getMostSignificantBits())
- .setSessionIdLSB(sessionId.getLeastSignificantBits())
- .setToServerResponse(rpcMsg).build();
- systemContext.getTbCoreToTransportService().process(nodeId, msg);
- }
-
private ListenableFuture saveRpcRequestToEdgeQueue(ToDeviceRpcRequest msg, Integer requestId) {
- ObjectNode body = mapper.createObjectNode();
+ ObjectNode body = JacksonUtil.newObjectNode();
body.put("requestId", requestId);
body.put("requestUUID", msg.getId().toString());
body.put("oneway", msg.isOneway());
@@ -914,14 +930,14 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
log.debug("[{}] Restored session: {}", deviceId, sessionMD);
}
- log.debug("[{}] Restored sessions: {}, rpc subscriptions: {}, attribute subscriptions: {}", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size());
+ log.debug("[{}] Restored sessions: {}, RPC subscriptions: {}, attribute subscriptions: {}", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size());
}
private void dumpSessions() {
if (systemContext.isLocalCacheType()) {
return;
}
- log.debug("[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size());
+ log.debug("[{}] Dumping sessions: {}, RPC subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size());
List sessionsList = new ArrayList<>(sessions.size());
sessions.forEach((uuid, sessionMD) -> {
if (sessionMD.getSessionInfo().getType() == SessionType.SYNC) {
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index 59d633540e..30d909cd5a 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
@@ -16,7 +16,6 @@
package org.thingsboard.server.actors.ruleChain;
import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.netty.channel.EventLoopGroup;
@@ -35,6 +34,7 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.TbContext;
+import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
@@ -121,8 +121,6 @@ import java.util.function.Consumer;
@Slf4j
class DefaultTbContext implements TbContext {
- public final static ObjectMapper mapper = new ObjectMapper();
-
private final ActorSystemContext mainCtx;
private final String ruleChainName;
private final RuleNodeCtx nodeCtx;
@@ -217,6 +215,12 @@ class DefaultTbContext implements TbContext {
enqueueForTellNext(tpi, tbMsg, Collections.singleton(TbRelationTypes.FAILURE), failureMessage, null, null);
}
+ @Override
+ public void enqueueForTellFailure(TbMsg tbMsg, Throwable th) {
+ TopicPartitionInfo tpi = resolvePartition(tbMsg);
+ enqueueForTellNext(tpi, tbMsg, Collections.singleton(TbRelationTypes.FAILURE), getFailureMessage(th), null, null);
+ }
+
@Override
public void enqueueForTellNext(TbMsg tbMsg, String relationType) {
TopicPartitionInfo tpi = resolvePartition(tbMsg);
@@ -314,16 +318,7 @@ class DefaultTbContext implements TbContext {
if (nodeCtx.getSelf().isDebugMode()) {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, TbRelationTypes.FAILURE, th);
}
- String failureMessage;
- if (th != null) {
- if (!StringUtils.isEmpty(th.getMessage())) {
- failureMessage = th.getMessage();
- } else {
- failureMessage = th.getClass().getSimpleName();
- }
- } else {
- failureMessage = null;
- }
+ String failureMessage = getFailureMessage(th);
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getRuleChainId(),
nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE),
msg, failureMessage));
@@ -422,8 +417,8 @@ class DefaultTbContext implements TbContext {
public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action, K profile) {
try {
- return entityActionMsg(id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity)), action, profile);
- } catch (JsonProcessingException | IllegalArgumentException e) {
+ return entityActionMsg(id, getActionMetaData(ruleNodeId), JacksonUtil.toString(JacksonUtil.valueToTree(entity)), action, profile);
+ } catch (IllegalArgumentException e) {
throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e);
}
}
@@ -727,6 +722,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getSlackService();
}
+ @Override
+ public boolean isExternalNodeForceAck() {
+ return mainCtx.isExternalNodeForceAck();
+ }
+
@Override
public RuleEngineRpcService getRpcService() {
return mainCtx.getTbRuleEngineDeviceRpcService();
@@ -843,10 +843,24 @@ class DefaultTbContext implements TbContext {
}
@Override
- public void checkTenantEntity(EntityId entityId) {
+ public void checkTenantEntity(EntityId entityId) throws TbNodeException {
if (!this.getTenantId().equals(TenantIdLoader.findTenantId(this, entityId))) {
- throw new RuntimeException("Entity with id: '" + entityId + "' specified in the configuration doesn't belong to the current tenant.");
+ throw new TbNodeException("Entity with id: '" + entityId + "' specified in the configuration doesn't belong to the current tenant.", true);
+ }
+ }
+
+ private static String getFailureMessage(Throwable th) {
+ String failureMessage;
+ if (th != null) {
+ if (!StringUtils.isEmpty(th.getMessage())) {
+ failureMessage = th.getMessage();
+ } else {
+ failureMessage = th.getClass().getSimpleName();
+ }
+ } else {
+ failureMessage = null;
}
+ return failureMessage;
}
private class SimpleTbQueueCallback implements TbQueueCallback {
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java
index 9534104ec1..7f919754fc 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java
@@ -23,6 +23,7 @@ import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.DefaultActorService;
+import org.thingsboard.server.actors.shared.RuleChainErrorActor;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
@@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.msg.TbActorMsg;
+import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.dao.rule.RuleChainService;
import java.util.function.Function;
@@ -86,7 +88,12 @@ public abstract class RuleChainManagerActor extends ContextAwareActor {
() -> DefaultActorService.RULE_DISPATCHER_NAME,
() -> {
RuleChain ruleChain = provider.apply(ruleChainId);
- return new RuleChainActor.ActorCreator(systemContext, tenantId, ruleChain);
+ if (ruleChain == null) {
+ return new RuleChainErrorActor.ActorCreator(systemContext, tenantId,
+ new RuleEngineException("Rule Chain with id: " + ruleChainId + " not found!"));
+ } else {
+ return new RuleChainActor.ActorCreator(systemContext, tenantId, ruleChain);
+ }
});
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java
index e1918466a8..42a78b7965 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java
@@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbActorStopReason;
-import org.thingsboard.server.common.msg.notification.trigger.RuleEngineComponentLifecycleEventTrigger;
+import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventTrigger;
public abstract class RuleEngineComponentActor> extends ComponentActor {
diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
index e0c11102bf..395b8a24d0 100644
--- a/application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
@@ -15,7 +15,6 @@
*/
package org.thingsboard.server.actors.shared;
-import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx;
@@ -26,8 +25,6 @@ import java.util.concurrent.ScheduledExecutorService;
@Slf4j
public abstract class AbstractContextAwareMsgProcessor {
- protected final static ObjectMapper mapper = new ObjectMapper();
-
protected final ActorSystemContext systemContext;
protected AbstractContextAwareMsgProcessor(ActorSystemContext systemContext) {
diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/RuleChainErrorActor.java b/application/src/main/java/org/thingsboard/server/actors/shared/RuleChainErrorActor.java
new file mode 100644
index 0000000000..a204c7b286
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/shared/RuleChainErrorActor.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.actors.shared;
+
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.actors.ActorSystemContext;
+import org.thingsboard.server.actors.TbActor;
+import org.thingsboard.server.actors.TbActorId;
+import org.thingsboard.server.actors.TbStringActorId;
+import org.thingsboard.server.actors.service.ContextAwareActor;
+import org.thingsboard.server.actors.service.ContextBasedCreator;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.TbActorMsg;
+import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
+import org.thingsboard.server.common.msg.queue.RuleEngineException;
+
+import java.util.UUID;
+
+@Slf4j
+public class RuleChainErrorActor extends ContextAwareActor {
+
+ private final TenantId tenantId;
+ private final RuleEngineException error;
+
+ private RuleChainErrorActor(ActorSystemContext systemContext, TenantId tenantId, RuleEngineException error) {
+ super(systemContext);
+ this.tenantId = tenantId;
+ this.error = error;
+ }
+
+ @Override
+ protected boolean doProcess(TbActorMsg msg) {
+ if (msg instanceof RuleChainAwareMsg) {
+ log.debug("[{}] Reply with {} for message {}", tenantId, error.getMessage(), msg);
+ var rcMsg = (RuleChainAwareMsg) msg;
+ rcMsg.getMsg().getCallback().onFailure(error);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public static class ActorCreator extends ContextBasedCreator {
+
+ private final TenantId tenantId;
+ private final RuleEngineException error;
+
+ public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleEngineException error) {
+ super(context);
+ this.tenantId = tenantId;
+ this.error = error;
+ }
+
+ @Override
+ public TbActorId createActorId() {
+ return new TbStringActorId(UUID.randomUUID().toString());
+ }
+
+ @Override
+ public TbActor createActor() {
+ return new RuleChainErrorActor(context, tenantId, error);
+ }
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java b/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
index 34fbecb4b5..ed0892f772 100644
--- a/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
@@ -16,8 +16,8 @@
package org.thingsboard.server.actors.stats;
import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorId;
@@ -31,8 +31,6 @@ import org.thingsboard.server.common.msg.TbActorMsg;
@Slf4j
public class StatsActor extends ContextAwareActor {
- private final ObjectMapper mapper = new ObjectMapper();
-
public StatsActor(ActorSystemContext context) {
super(context);
}
@@ -63,7 +61,7 @@ public class StatsActor extends ContextAwareActor {
}
private JsonNode toBodyJson(String serviceId, long messagesProcessed, long errorsOccurred) {
- return mapper.createObjectNode().put("server", serviceId).put("messagesProcessed", messagesProcessed).put("errorsOccurred", errorsOccurred);
+ return JacksonUtil.newObjectNode().put("server", serviceId).put("messagesProcessed", messagesProcessed).put("errorsOccurred", errorsOccurred);
}
public static class ActorCreator extends ContextBasedCreator {
diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
index 84b7757846..11a5895fef 100644
--- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
@@ -49,7 +49,6 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
import org.thingsboard.server.common.msg.edge.EdgeSessionMsg;
-import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
@@ -211,10 +210,6 @@ public class TenantActor extends RuleChainManagerActor {
log.trace("[{}] Ack message because Rule Engine is disabled", tenantId);
tbMsg.getCallback().onSuccess();
}
- systemContext.getNotificationRuleProcessor().process(RuleEngineMsgTrigger.builder()
- .tenantId(tenantId)
- .msg(tbMsg)
- .build());
}
private void onRuleChainMsg(RuleChainAwareMsg msg) {
diff --git a/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java b/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java
index 0593b9765a..2ecc8590b8 100644
--- a/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java
+++ b/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java
@@ -15,64 +15,53 @@
*/
package org.thingsboard.server.config;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
-import org.thingsboard.server.common.data.StringUtils;
-import org.thingsboard.server.common.data.id.CustomerId;
-import org.thingsboard.server.common.data.id.EntityId;
-import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.msg.tools.TbRateLimits;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.exception.TenantProfileNotFoundException;
import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
-import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
+import org.thingsboard.server.common.data.limit.LimitedApi;
+import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
-import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
@Slf4j
@Component
+@RequiredArgsConstructor
public class RateLimitProcessingFilter extends OncePerRequestFilter {
- @Autowired
- private ThingsboardErrorResponseHandler errorResponseHandler;
-
- @Autowired
- @Lazy
- private TbTenantProfileCache tenantProfileCache;
-
- private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>();
- private final ConcurrentMap perCustomerLimits = new ConcurrentHashMap<>();
+ private final ThingsboardErrorResponseHandler errorResponseHandler;
+ private final RateLimitService rateLimitService;
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
SecurityUser user = getCurrentUser();
if (user != null && !user.isSystemAdmin()) {
- var profile = tenantProfileCache.get(user.getTenantId());
- if (profile == null) {
+ try {
+ if (!rateLimitService.checkRateLimit(LimitedApi.REST_REQUESTS_PER_TENANT, user.getTenantId())) {
+ rateLimitExceeded(EntityType.TENANT, response);
+ return;
+ }
+ } catch (TenantProfileNotFoundException e) {
log.debug("[{}] Failed to lookup tenant profile", user.getTenantId());
errorResponseHandler.handle(new BadCredentialsException("Failed to lookup tenant profile"), response);
return;
}
- var profileConfiguration = profile.getDefaultProfileConfiguration();
- if (!checkRateLimits(user.getTenantId(), profileConfiguration.getTenantServerRestLimitsConfiguration(), perTenantLimits, response)) {
- return;
- }
+
if (user.isCustomerUser()) {
- if (!checkRateLimits(user.getCustomerId(), profileConfiguration.getCustomerServerRestLimitsConfiguration(), perCustomerLimits, response)) {
+ if (!rateLimitService.checkRateLimit(LimitedApi.REST_REQUESTS_PER_CUSTOMER, user.getTenantId(), user.getCustomerId())) {
+ rateLimitExceeded(EntityType.CUSTOMER, response);
return;
}
}
@@ -90,23 +79,8 @@ public class RateLimitProcessingFilter extends OncePerRequestFilter {
return false;
}
- private boolean checkRateLimits(I ownerId, String rateLimitConfig, Map rateLimitsMap, ServletResponse response) {
- if (StringUtils.isNotEmpty(rateLimitConfig)) {
- TbRateLimits rateLimits = rateLimitsMap.get(ownerId);
- if (rateLimits == null || !rateLimits.getConfiguration().equals(rateLimitConfig)) {
- rateLimits = new TbRateLimits(rateLimitConfig);
- rateLimitsMap.put(ownerId, rateLimits);
- }
-
- if (!rateLimits.tryConsume()) {
- errorResponseHandler.handle(new TbRateLimitsException(ownerId.getEntityType()), (HttpServletResponse) response);
- return false;
- }
- } else {
- rateLimitsMap.remove(ownerId);
- }
-
- return true;
+ private void rateLimitExceeded(EntityType type, HttpServletResponse response) {
+ errorResponseHandler.handle(new TbRateLimitsException(type), response);
}
protected SecurityUser getCurrentUser() {
diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
index 568c08ec56..4415f1a0bf 100644
--- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
+++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
@@ -87,6 +87,8 @@ import static springfox.documentation.builders.PathSelectors.regex;
@Profile("!test")
public class SwaggerConfiguration {
+ @Value("${swagger.enabled:true}")
+ private boolean enabled;
@Value("${swagger.api_path_regex}")
private String apiPathRegex;
@Value("${swagger.security_path_regex}")
@@ -116,6 +118,7 @@ public class SwaggerConfiguration {
public Docket thingsboardApi() {
TypeResolver typeResolver = new TypeResolver();
return new Docket(DocumentationType.OAS_30)
+ .enable(enabled)
.groupName("thingsboard")
.apiInfo(apiInfo())
.additionalModels(
diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
index 9405d2da84..0cf820c6f3 100644
--- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
+++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
@@ -15,14 +15,15 @@
*/
package org.thingsboard.server.config;
-import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
@@ -30,7 +31,6 @@ import org.springframework.security.config.annotation.authentication.builders.Au
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
@@ -38,9 +38,11 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.header.writers.StaticHeadersWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.filter.ShallowEtagHeaderFilter;
import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import org.thingsboard.server.queue.util.TbCoreComponent;
@@ -78,6 +80,8 @@ public class ThingsboardSecurityConfiguration {
protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**"};
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**";
+ public static final String MAIL_OAUTH2_PROCESSING_ENTRY_POINT = "/api/admin/mail/oauth2/code";
+
@Autowired private ThingsboardErrorResponseHandler restAccessDeniedHandler;
@@ -116,20 +120,29 @@ public class ThingsboardSecurityConfiguration {
@Autowired private AuthenticationManager authenticationManager;
- @Autowired private ObjectMapper objectMapper;
-
@Autowired private RateLimitProcessingFilter rateLimitProcessingFilter;
+ @Bean
+ protected FilterRegistrationBean buildEtagFilter() throws Exception {
+ ShallowEtagHeaderFilter etagFilter = new ShallowEtagHeaderFilter();
+ etagFilter.setWriteWeakETag(true);
+ FilterRegistrationBean filterRegistrationBean
+ = new FilterRegistrationBean<>( etagFilter);
+ filterRegistrationBean.addUrlPatterns("*.js","*.css","*.ico","/assets/*","/static/*");
+ filterRegistrationBean.setName("etagFilter");
+ return filterRegistrationBean;
+ }
+
@Bean
protected RestLoginProcessingFilter buildRestLoginProcessingFilter() throws Exception {
- RestLoginProcessingFilter filter = new RestLoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper);
+ RestLoginProcessingFilter filter = new RestLoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler);
filter.setAuthenticationManager(this.authenticationManager);
return filter;
}
@Bean
protected RestPublicLoginProcessingFilter buildRestPublicLoginProcessingFilter() throws Exception {
- RestPublicLoginProcessingFilter filter = new RestPublicLoginProcessingFilter(PUBLIC_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper);
+ RestPublicLoginProcessingFilter filter = new RestPublicLoginProcessingFilter(PUBLIC_LOGIN_ENTRY_POINT, successHandler, failureHandler);
filter.setAuthenticationManager(this.authenticationManager);
return filter;
}
@@ -137,7 +150,7 @@ public class ThingsboardSecurityConfiguration {
protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
List pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS));
pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT,
- PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT));
+ PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT));
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
JwtTokenAuthenticationProcessingFilter filter
= new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtHeaderTokenExtractor, matcher);
@@ -147,7 +160,7 @@ public class ThingsboardSecurityConfiguration {
@Bean
protected RefreshTokenProcessingFilter buildRefreshTokenProcessingFilter() throws Exception {
- RefreshTokenProcessingFilter filter = new RefreshTokenProcessingFilter(TOKEN_REFRESH_ENTRY_POINT, successHandler, failureHandler, objectMapper);
+ RefreshTokenProcessingFilter filter = new RefreshTokenProcessingFilter(TOKEN_REFRESH_ENTRY_POINT, successHandler, failureHandler);
filter.setAuthenticationManager(this.authenticationManager);
return filter;
}
@@ -182,8 +195,18 @@ public class ThingsboardSecurityConfiguration {
private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver;
@Bean
- public WebSecurityCustomizer webSecurityCustomizer() {
- return (web) -> web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**");
+ @Order(0)
+ SecurityFilterChain resources(HttpSecurity http) throws Exception {
+ http
+ .requestMatchers((matchers) -> matchers.antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**"))
+ .headers().defaultsDisabled()
+ .addHeaderWriter(new StaticHeadersWriter(HttpHeaders.CACHE_CONTROL, "max-age=0, public"))
+ .and()
+ .authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll())
+ .requestCache().disable()
+ .securityContext().disable()
+ .sessionManagement().disable();
+ return http.build();
}
@Bean
@@ -204,6 +227,7 @@ public class ThingsboardSecurityConfiguration {
.antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
.antMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll() // Public login end-point
.antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
+ .antMatchers(MAIL_OAUTH2_PROCESSING_ENTRY_POINT).permitAll() // Mail oauth2 code processing url
.antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points
.and()
.authorizeRequests()
diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java
index dfefa9c755..4019efe8ee 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java
@@ -15,13 +15,24 @@
*/
package org.thingsboard.server.controller;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
+import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest;
+import com.google.api.client.auth.oauth2.ClientParametersAuthentication;
+import com.google.api.client.auth.oauth2.TokenResponse;
+import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.client.json.gson.GsonFactory;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -32,17 +43,25 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.server.common.data.AdminSettings;
+import org.thingsboard.server.common.data.StringUtils;
+import org.thingsboard.server.common.data.FeaturesInfo;
import org.thingsboard.server.common.data.FeaturesInfo;
import org.thingsboard.server.common.data.SystemInfo;
import org.thingsboard.server.common.data.UpdateMessage;
+import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
+import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.security.model.JwtSettings;
@@ -52,8 +71,10 @@ import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings;
import org.thingsboard.server.common.data.sync.vc.RepositorySettings;
import org.thingsboard.server.common.data.sync.vc.RepositorySettingsInfo;
import org.thingsboard.server.common.data.sync.vc.VcUtils;
+import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.service.security.auth.oauth2.CookieUtils;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
@@ -65,15 +86,29 @@ import org.thingsboard.server.service.sync.vc.autocommit.TbAutoCommitSettingsSer
import org.thingsboard.server.service.system.SystemInfoService;
import org.thingsboard.server.service.update.UpdateService;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+import static org.thingsboard.server.controller.ControllerConstants.*;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH;
@RestController
@TbCoreComponent
+@Slf4j
@RequestMapping("/api/admin")
@RequiredArgsConstructor
public class AdminController extends BaseController {
+ private static final String PREV_URI_PATH_PARAMETER = "prevUri";
+ private static final String PREV_URI_COOKIE_NAME = "prev_uri";
+ private static final String STATE_COOKIE_NAME = "state";
+ private static final String MAIL_SETTINGS_KEY = "mail";
+
private final MailService mailService;
private final SmsService smsService;
private final AdminSettingsService adminSettingsService;
@@ -86,6 +121,7 @@ public class AdminController extends BaseController {
private final TbAutoCommitSettingsService autoCommitSettingsService;
private final UpdateService updateService;
private final SystemInfoService systemInfoService;
+ private final AuditLogService auditLogService;
@ApiOperation(value = "Get the Administration Settings object using key (getAdminSettings)",
notes = "Get the Administration Settings object using specified string key. Referencing non-existing key will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH)
@@ -99,6 +135,7 @@ public class AdminController extends BaseController {
AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key), "No Administration settings found for key: " + key);
if (adminSettings.getKey().equals("mail")) {
((ObjectNode) adminSettings.getJsonValue()).remove("password");
+ ((ObjectNode) adminSettings.getJsonValue()).remove("refreshToken");
}
return adminSettings;
}
@@ -119,6 +156,7 @@ public class AdminController extends BaseController {
if (adminSettings.getKey().equals("mail")) {
mailService.updateMailConfiguration();
((ObjectNode) adminSettings.getJsonValue()).remove("password");
+ ((ObjectNode) adminSettings.getJsonValue()).remove("refreshToken");
} else if (adminSettings.getKey().equals("sms")) {
smsService.updateSmsConfiguration();
}
@@ -185,9 +223,20 @@ public class AdminController extends BaseController {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
adminSettings = checkNotNull(adminSettings);
if (adminSettings.getKey().equals("mail")) {
- if (!adminSettings.getJsonValue().has("password")) {
+ if (adminSettings.getJsonValue().has("enableOauth2") && adminSettings.getJsonValue().get("enableOauth2").asBoolean()){
AdminSettings mailSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"));
- ((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText());
+ JsonNode refreshToken = mailSettings.getJsonValue().get("refreshToken");
+ if (refreshToken == null) {
+ throw new ThingsboardException("Refresh token was not generated. Please, generate refresh token.", ThingsboardErrorCode.GENERAL);
+ }
+ ObjectNode settings = (ObjectNode) adminSettings.getJsonValue();
+ settings.put("refreshToken", refreshToken.asText());
+ }
+ else {
+ if (!adminSettings.getJsonValue().has("password")) {
+ AdminSettings mailSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"));
+ ((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText());
+ }
}
String email = getCurrentUser().getEmail();
mailService.sendTestMail(adminSettings.getJsonValue(), email);
@@ -202,8 +251,15 @@ public class AdminController extends BaseController {
public void sendTestSms(
@ApiParam(value = "A JSON value representing the Test SMS request.")
@RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException {
- accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
- smsService.sendTestSms(testSmsRequest);
+ SecurityUser user = getCurrentUser();
+ accessControlService.checkPermission(user, Resource.ADMIN_SETTINGS, Operation.READ);
+ try {
+ smsService.sendTestSms(testSmsRequest);
+ auditLogService.logEntityAction(user.getTenantId(), user.getCustomerId(), user.getId(), user.getName(), user.getId(), user, ActionType.SMS_SENT, null, testSmsRequest.getNumberTo());
+ } catch (ThingsboardException e) {
+ auditLogService.logEntityAction(user.getTenantId(), user.getCustomerId(), user.getId(), user.getName(), user.getId(), user, ActionType.SMS_SENT, e, testSmsRequest.getNumberTo());
+ throw e;
+ }
}
@ApiOperation(value = "Get repository settings (getRepositorySettings)",
@@ -352,4 +408,84 @@ public class AdminController extends BaseController {
return systemInfoService.getFeaturesInfo();
}
+ @ApiOperation(value = "Get OAuth2 log in processing URL (getMailProcessingUrl)", notes = "Returns the URL enclosed in " +
+ "double quotes. After successful authentication with OAuth2 provider and user consent for requested scope, it makes a redirect to this path so that the platform can do " +
+ "further log in processing and generating access tokens. " + SYSTEM_AUTHORITY_PARAGRAPH)
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
+ @RequestMapping(value = "/mail/oauth2/loginProcessingUrl", method = RequestMethod.GET)
+ @ResponseBody
+ public String getMailProcessingUrl() throws ThingsboardException {
+ accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
+ return "\"/api/admin/mail/oauth2/code\"";
+ }
+
+ @ApiOperation(value = "Redirect user to mail provider login page. ", notes = "After user logged in and provided access" +
+ "provider sends authorization code to specified redirect uri.)" )
+ @PreAuthorize("hasAuthority('SYS_ADMIN')")
+ @RequestMapping(value = "/mail/oauth2/authorize", method = RequestMethod.GET, produces = "application/text")
+ public String getAuthorizationUrl(HttpServletRequest request, HttpServletResponse response) throws ThingsboardException {
+ String state = StringUtils.generateSafeToken();
+ if (request.getParameter(PREV_URI_PATH_PARAMETER) != null) {
+ CookieUtils.addCookie(response, PREV_URI_COOKIE_NAME, request.getParameter(PREV_URI_PATH_PARAMETER), 180);
+ }
+ CookieUtils.addCookie(response, STATE_COOKIE_NAME, state, 180);
+
+ accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
+ AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, MAIL_SETTINGS_KEY), "No Administration mail settings found");
+ JsonNode jsonValue = adminSettings.getJsonValue();
+
+ String clientId = checkNotNull(jsonValue.get("clientId"), "No clientId was configured").asText();
+ String authUri = checkNotNull(jsonValue.get("authUri"), "No authorization uri was configured").asText();
+ String redirectUri = checkNotNull(jsonValue.get("redirectUri"), "No Redirect uri was configured").asText();
+ List scope = JacksonUtil.convertValue(checkNotNull(jsonValue.get("scope"), "No scope was configured"), new TypeReference<>() {
+ });
+
+ return "\"" + new AuthorizationCodeRequestUrl(authUri, clientId)
+ .setScopes(scope)
+ .setState(state)
+ .setRedirectUri(redirectUri)
+ .build() + "\"";
+ }
+
+ @RequestMapping(value = "/mail/oauth2/code", params = {"code", "state"}, method = RequestMethod.GET)
+ public void codeProcessingUrl(
+ @RequestParam(value = "code") String code, @RequestParam(value = "state") String state,
+ HttpServletRequest request, HttpServletResponse response) throws ThingsboardException, IOException {
+ Optional prevUrlOpt = CookieUtils.getCookie(request, PREV_URI_COOKIE_NAME);
+ Optional cookieState = CookieUtils.getCookie(request, STATE_COOKIE_NAME);
+
+ String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
+ String prevUri = baseUrl + (prevUrlOpt.isPresent() ? prevUrlOpt.get().getValue(): "/settings/outgoing-mail");
+
+ if (cookieState.isEmpty() || !cookieState.get().getValue().equals(state)) {
+ CookieUtils.deleteCookie(request, response, STATE_COOKIE_NAME);
+ throw new ThingsboardException("Refresh token was not generated, invalid state param", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+ }
+ CookieUtils.deleteCookie(request, response, STATE_COOKIE_NAME);
+ CookieUtils.deleteCookie(request, response, PREV_URI_COOKIE_NAME);
+
+ AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, MAIL_SETTINGS_KEY), "No Administration mail settings found");
+ JsonNode jsonValue = adminSettings.getJsonValue();
+
+ String clientId = checkNotNull(jsonValue.get("clientId"), "No clientId was configured").asText();
+ String clientSecret = checkNotNull(jsonValue.get("clientSecret"), "No client secret was configured").asText();
+ String clientRedirectUri = checkNotNull(jsonValue.get("redirectUri"), "No Redirect uri was configured").asText();
+ String tokenUri = checkNotNull(jsonValue.get("tokenUri"), "No authorization uri was configured").asText();
+
+ TokenResponse tokenResponse;
+ try {
+ tokenResponse = new AuthorizationCodeTokenRequest(new NetHttpTransport(), new GsonFactory(), new GenericUrl(tokenUri), code)
+ .setRedirectUri(clientRedirectUri)
+ .setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret))
+ .execute();
+ } catch (IOException e) {
+ log.warn("Unable to retrieve refresh token: {}", e.getMessage());
+ throw new ThingsboardException("Error while requesting access token: " + e.getMessage(), ThingsboardErrorCode.GENERAL);
+ }
+ ((ObjectNode)jsonValue).put("refreshToken", tokenResponse.getRefreshToken());
+ ((ObjectNode)jsonValue).put("tokenGenerated", true);
+
+ adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings);
+ response.sendRedirect(prevUri);
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java
index 92b2cb923f..12195e83bc 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java
@@ -77,7 +77,7 @@ public class AlarmCommentController extends BaseController {
@PathVariable(ALARM_ID) String strAlarmId, @ApiParam(value = "A JSON value representing the comment.") @RequestBody AlarmComment alarmComment) throws ThingsboardException {
checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
- Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
+ Alarm alarm = checkAlarmInfoId(alarmId, Operation.WRITE);
alarmComment.setAlarmId(alarmId);
return tbAlarmCommentService.saveAlarmComment(alarm, alarmComment, getCurrentUser());
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
index f3a3a2397c..4512334d61 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
@@ -42,16 +42,15 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent;
import org.thingsboard.server.common.data.security.event.UserSessionInvalidationEvent;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
-import org.thingsboard.server.common.msg.tools.TbRateLimits;
-import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.common.data.limit.LimitedApi;
+import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
import org.thingsboard.server.service.security.model.ActivateUserRequest;
import org.thingsboard.server.service.security.model.ChangePasswordRequest;
@@ -65,8 +64,6 @@ import org.thingsboard.server.service.security.system.SystemSecurityService;
import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.net.URISyntaxException;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
@RestController
@TbCoreComponent
@@ -77,12 +74,11 @@ public class AuthController extends BaseController {
@Value("${server.rest.rate_limits.reset_password_per_user:5:3600}")
private String defaultLimitsConfiguration;
- private final ConcurrentMap resetPasswordRateLimits = new ConcurrentHashMap<>();
private final BCryptPasswordEncoder passwordEncoder;
private final JwtTokenFactory tokenFactory;
private final MailService mailService;
private final SystemSecurityService systemSecurityService;
- private final AuditLogService auditLogService;
+ private final RateLimitService rateLimitService;
private final ApplicationEventPublisher eventPublisher;
@@ -210,8 +206,7 @@ public class AuthController extends BaseController {
UserCredentials userCredentials = userService.findUserCredentialsByResetToken(TenantId.SYS_TENANT_ID, resetToken);
if (userCredentials != null) {
- TbRateLimits tbRateLimits = getTbRateLimits(userCredentials.getUserId());
- if (!tbRateLimits.tryConsume()) {
+ if (!rateLimitService.checkRateLimit(LimitedApi.PASSWORD_RESET, userCredentials.getUserId(), defaultLimitsConfiguration)) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build();
}
try {
@@ -314,8 +309,4 @@ public class AuthController extends BaseController {
eventPublisher.publishEvent(new UserSessionInvalidationEvent(user.getSessionId()));
}
- private TbRateLimits getTbRateLimits(UserId userId) {
- return resetPasswordRateLimits.computeIfAbsent(userId,
- key -> new TbRateLimits(defaultLimitsConfiguration, true));
- }
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 5104fe9d32..68a987a0bc 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -15,7 +15,6 @@
*/
package org.thingsboard.server.controller;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
@@ -185,8 +184,6 @@ public abstract class BaseController {
/*Swagger UI description*/
- private static final ObjectMapper json = new ObjectMapper();
-
@Autowired
private ThingsboardErrorResponseHandler errorResponseHandler;
diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
index f425bde86c..a6a49f6b3c 100644
--- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
+++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
@@ -140,6 +140,9 @@ public class ControllerConstants {
protected static final String RESOURCE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the resource title.";
protected static final String RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, resourceType, tenantId";
+ protected static final String RESOURCE_TYPE_PROPERTY_ALLOWABLE_VALUES = "LWM2M_MODEL, JKS, PKCS_12, JS_MODULE";
+ protected static final String RESOURCE_TYPE = "A string value representing the resource type.";
+
protected static final String LWM2M_OBJECT_DESCRIPTION = "LwM2M Object is a object that includes information about the LwM2M model which can be used in transport configuration for the LwM2M device profile. ";
protected static final String LWM2M_OBJECT_SORT_PROPERTY_ALLOWABLE_VALUES = "id, name";
@@ -204,42 +207,222 @@ public class ControllerConstants {
protected static final String IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION = "A Boolean value representing the Server SecurityInfo for future Bootstrap client mode settings. Values: 'true' for Bootstrap Server; 'false' for Lwm2m Server. ";
- protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_DESCRIPTION =
+ protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_ACCESS_TOKEN_PARAM_DESCRIPTION =
"{\n" +
" \"device\": {\n" +
- " \"name\": \"LwRpk00000000\",\n" +
- " \"type\": \"lwm2mProfileRpk\"\n" +
- " },\n" +
+ " \"name\":\"Name_DeviceWithCredantial_AccessToken\",\n" +
+ " \"label\":\"Label_DeviceWithCredantial_AccessToken\",\n" +
+ " \"deviceProfileId\":{\n" +
+ " \"id\":\"9d9588c0-06c9-11ee-b618-19be30fdeb60\",\n" +
+ " \"entityType\":\"DEVICE_PROFILE\"\n" +
+ " }\n" +
+ " },\n" +
" \"credentials\": {\n" +
- " \"id\": \"null\",\n" +
- " \"createdTime\": 0,\n" +
- " \"deviceId\": \"null\",\n" +
- " \"credentialsType\": \"LWM2M_CREDENTIALS\",\n" +
- " \"credentialsId\": \"LwRpk00000000\",\n" +
- " \"credentialsValue\": {\n" +
- " \"client\": {\n" +
- " \"endpoint\": \"LwRpk00000000\",\n" +
- " \"securityConfigClientMode\": \"RPK\",\n" +
- " \"key\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUEBxNl/RcYJNm8mk91CyVXoIJiROYDlXcSSqK6e5bDHwOW4ZiN2lNnXalyF0Jxw8MbAytnDMERXyAja5VEMeVQ==\"\n" +
- " },\n" +
- " \"bootstrap\": {\n" +
- " \"bootstrapServer\": {\n" +
- " \"securityMode\": \"RPK\",\n" +
- " \"clientPublicKeyOrId\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUEBxNl/RcYJNm8mk91CyVXoIJiROYDlXcSSqK6e5bDHwOW4ZiN2lNnXalyF0Jxw8MbAytnDMERXyAja5VEMeVQ==\",\n" +
- " \"clientSecretKey\": \"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgd9GAx7yZW37autew5KZykn4IgRpge/tZSjnudnZJnMahRANCAARQQHE2X9Fxgk2byaT3ULJVeggmJE5gOVdxJKorp7lsMfA5bhmI3aU2ddqXIXQnHDwxsDK2cMwRFfICNrlUQx5V\"\n" +
- " },\n" +
- " \"lwm2mServer\": {\n" +
- " \"securityMode\": \"RPK\",\n" +
- " \"clientPublicKeyOrId\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUEBxNl/RcYJNm8mk91CyVXoIJiROYDlXcSSqK6e5bDHwOW4ZiN2lNnXalyF0Jxw8MbAytnDMERXyAja5VEMeVQ==\",\n" +
- " \"clientSecretKey\": \"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgd9GAx7yZW37autew5KZykn4IgRpge/tZSjnudnZJnMahRANCAARQQHE2X9Fxgk2byaT3ULJVeggmJE5gOVdxJKorp7lsMfA5bhmI3aU2ddqXIXQnHDwxsDK2cMwRFfICNrlUQx5V\"\n" +
- " }\n" +
- " }\n" +
- " }\n" +
- " }\n" +
+ " \"credentialsType\": \"ACCESS_TOKEN\",\n" +
+ " \"credentialsId\": \"6hmxew8pmmzng4e3une2\"\n" +
+ " }\n" +
+ "}";
+
+ protected static final String DEVICE_UPDATE_CREDENTIALS_ACCESS_TOKEN_PARAM_DESCRIPTION =
+ "{\n" +
+ " \"id\": {\n" +
+ " \"id\":\"c886a090-168d-11ee-87c9-6f157dbc816a\"\n" +
+ " },\n" +
+ " \"deviceId\": {\n" +
+ " \"id\":\"c5fb3ac0-168d-11ee-87c9-6f157dbc816a\",\n" +
+ " \"entityType\":\"DEVICE\"\n" +
+ " },\n" +
+ " \"credentialsType\": \"ACCESS_TOKEN\",\n" +
+ " \"credentialsId\": \"6hmxew8pmmzng4e3une4\"\n" +
+ "}";
+
+ protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_ACCESS_TOKEN_DEFAULT_PARAM_DESCRIPTION =
+ "{\n" +
+ " \"device\": {\n" +
+ " \"name\":\"Name_DeviceWithCredantial_AccessToken_Default\",\n" +
+ " \"label\":\"Label_DeviceWithCredantial_AccessToken_Default\",\n" +
+ " \"type\": \"default\"\n" +
+ " },\n" +
+ " \"credentials\": {\n" +
+ " \"credentialsType\": \"ACCESS_TOKEN\",\n" +
+ " \"credentialsId\": \"6hmxew8pmmzng4e3une3\"\n" +
+ " }\n" +
"}";
- protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_DESCRIPTION_MARKDOWN =
- MARKDOWN_CODE_BLOCK_START + DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_DESCRIPTION + MARKDOWN_CODE_BLOCK_END;
+ protected static final String certificateValue = "\"-----BEGIN CERTIFICATE----- " +
+ "MIICMTCCAdegAwIBAgIUI9dBuwN6pTtK6uZ03rkiCwV4wEYwCgYIKoZIzj0EAwIwbjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRowGAYDVQQKDBFUaGluZ3NCb2FyZCwgSW5jLjEwMC4GA1UEAwwnZGV2aWNlQ2VydGlmaWNhdGVAWDUwOVByb3Zpc2lvblN0cmF0ZWd5MB4XDTIzMDMyOTE0NTYxN1oXDTI0MDMyODE0NTYxN1owbjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRowGAYDVQQKDBFUaGluZ3NCb2FyZCwgSW5jLjEwMC4GA1UEAwwnZGV2aWNlQ2VydGlmaWNhdGVAWDUwOVByb3Zpc2lvblN0cmF0ZWd5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9Zo791qKQiGNBm11r4ZGxh+w+ossZL3xc46ufq5QckQHP7zkD2XDAcmP5GvdkM1sBFN9AWaCkQfNnWmfERsOOKNTMFEwHQYDVR0OBBYEFFFc5uyCyglQoZiKhzXzMcQ3BKORMB8GA1UdIwQYMBaAFFFc5uyCyglQoZiKhzXzMcQ3BKORMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhANbA9CuhoOifZMMmqkpuld+65CR+ItKdXeRAhLMZuccuAiB0FSQB34zMutXrZj1g8Gl5OkE7YryFHbei1z0SveHR8g== " +
+ "-----END CERTIFICATE-----\"";
+
+ protected static final String certificateId = "\"84f5911765abba1f96bf4165604e9e90338fc6214081a8e623b6ff9669aedb27\"";
+
+ protected static final String certificateValueUpdate = "\"-----BEGIN CERTIFICATE----- " +
+ "MIICMTCCAdegAwIBAgIUUEKxS9hTz4l+oLUMF0LV6TC/gCIwCgYIKoZIzj0EAwIwbjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRowGAYDVQQKDBFUaGluZ3NCb2FyZCwgSW5jLjEwMC4GA1UEAwwnZGV2aWNlUHJvZmlsZUNlcnRAWDUwOVByb3Zpc2lvblN0cmF0ZWd5MB4XDTIzMDMyOTE0NTczNloXDTI0MDMyODE0NTczNlowbjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRowGAYDVQQKDBFUaGluZ3NCb2FyZCwgSW5jLjEwMC4GA1UEAwwnZGV2aWNlUHJvZmlsZUNlcnRAWDUwOVByb3Zpc2lvblN0cmF0ZWd5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECMlWO72krDoUL9FQjUmSCetkhaEGJUfQkdSfkLSNa0GyAEIMbfmzI4zITeapunu4rGet3EMyLydQzuQanBicp6NTMFEwHQYDVR0OBBYEFHpZ78tPnztNii4Da/yCw6mhEIL3MB8GA1UdIwQYMBaAFHpZ78tPnztNii4Da/yCw6mhEIL3MA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIgJ7qyMFqNcwSYkH6o+UlQXzLWfwZbNjVk+aR7foAZNGsCIQDsd7v3WQIGHiArfZeDs1DLEDuV/2h6L+ZNoGNhEKL+1A== " +
+ "-----END CERTIFICATE-----\"";
+
+ protected static final String certificateIdUpdate = "\"6b8adb49015500e51a527acd332b51684ab9b49b4ade03a9582a44c455e2e9b6\"";
+
+ protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_X509_CERTIFICATE_PARAM_DESCRIPTION =
+ "{\n" +
+ " \"device\": {\n" +
+ " \"name\":\"Name_DeviceWithCredantial_X509_Certificate\",\n" +
+ " \"label\":\"Label_DeviceWithCredantial_X509_Certificate\",\n" +
+ " \"deviceProfileId\":{\n" +
+ " \"id\":\"9d9588c0-06c9-11ee-b618-19be30fdeb60\",\n" +
+ " \"entityType\":\"DEVICE_PROFILE\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"credentials\": {\n" +
+ " \"credentialsType\": \"X509_CERTIFICATE\",\n" +
+ " \"credentialsId\": " + certificateId + ",\n" +
+ " \"credentialsValue\": " + certificateValue + "\n" +
+ " }\n" +
+ "}";
+
+ protected static final String DEVICE_UPDATE_CREDENTIALS_X509_CERTIFICATE_PARAM_DESCRIPTION =
+ "{\n" +
+ " \"id\": {\n" +
+ " \"id\":\"309bd9c0-14f4-11ee-9fc9-d9b7463abb63\"\n" +
+ " },\n" +
+ " \"deviceId\": {\n" +
+ " \"id\":\"3092b200-14f4-11ee-9fc9-d9b7463abb63\",\n" +
+ " \"entityType\":\"DEVICE\"\n" +
+ " },\n" +
+ " \"credentialsType\": \"X509_CERTIFICATE\",\n" +
+ " \"credentialsId\": " + certificateIdUpdate + ",\n" +
+ " \"credentialsValue\": " + certificateValueUpdate + "\n" +
+ "}";
+
+ protected static final String MQTT_BASIC_VALUE = "\"{\\\"clientId\\\":\\\"5euh5nzm34bjjh1efmlt\\\",\\\"userName\\\":\\\"onasd1lgwasmjl7v2v7h\\\",\\\"password\\\":\\\"b9xtm4ny8kt9zewaga5o\\\"}\"";
+
+ protected static final String MQTT_BASIC_VALUE_UPDATE = "\"{\\\"clientId\\\":\\\"juy03yv4owqxcmqhqtvk\\\",\\\"userName\\\":\\\"ov19fxca0cyjn7lm7w7u\\\",\\\"password\\\":\\\"twy94he114dfi9usyk1o\\\"}\"";
+
+ protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_MQTT_BASIC_PARAM_DESCRIPTION =
+ "{\n" +
+ " \"device\": {\n" +
+ " \"name\":\"Name_DeviceWithCredantial_MQTT_Basic\",\n" +
+ " \"label\":\"Label_DeviceWithCredantial_MQTT_Basic\",\n" +
+ " \"deviceProfileId\":{\n" +
+ " \"id\":\"9d9588c0-06c9-11ee-b618-19be30fdeb60\",\n" +
+ " \"entityType\":\"DEVICE_PROFILE\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"credentials\": {\n" +
+ " \"credentialsType\": \"MQTT_BASIC\",\n" +
+ " \"credentialsValue\": " + MQTT_BASIC_VALUE + "\n" +
+ " }\n" +
+ "}";
+
+ protected static final String DEVICE_UPDATE_CREDENTIALS_MQTT_BASIC_PARAM_DESCRIPTION =
+ "{\n" +
+ " \"id\": {\n" +
+ " \"id\":\"d877ffb0-14f5-11ee-9fc9-d9b7463abb63\"\n" +
+ " },\n" +
+ " \"deviceId\": {\n" +
+ " \"id\":\"d875dcd0-14f5-11ee-9fc9-d9b7463abb63\",\n" +
+ " \"entityType\":\"DEVICE\"\n" +
+ " },\n" +
+ " \"credentialsType\": \"MQTT_BASIC\",\n" +
+ " \"credentialsValue\": " + MQTT_BASIC_VALUE_UPDATE + "\n" +
+ "}";
+
+ protected static final String CREDENTIALS_VALUE_LVM2M_RPK_DESCRIPTION =
+ " \"{" +
+ "\\\"client\\\":{ " +
+ "\\\"endpoint\\\":\\\"LwRpk00000000\\\", " +
+ "\\\"securityConfigClientMode\\\":\\\"RPK\\\", " +
+ "\\\"key\\\":\\\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUEBxNl/RcYJNm8mk91CyVXoIJiROYDlXcSSqK6e5bDHwOW4ZiN2lNnXalyF0Jxw8MbAytnDMERXyAja5VEMeVQ==\\\"" +
+ " }, " +
+ "\\\"bootstrap\\\":{ " +
+ "\\\"bootstrapServer\\\":{ " +
+ "\\\"securityMode\\\":\\\"RPK\\\", " +
+ "\\\"clientPublicKeyOrId\\\":\\\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUEBxNl/RcYJNm8mk91CyVXoIJiROYDlXcSSqK6e5bDHwOW4ZiN2lNnXalyF0Jxw8MbAytnDMERXyAja5VEMeVQ==\\\", " +
+ "\\\"clientSecretKey\\\":\\\"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgd9GAx7yZW37autew5KZykn4IgRpge/tZSjnudnZJnMahRANCAARQQHE2X9Fxgk2byaT3ULJVeggmJE5gOVdxJKorp7lsMfA5bhmI3aU2ddqXIXQnHDwxsDK2cMwRFfICNrlUQx5V\\\"" +
+ "}, " +
+ "\\\"lwm2mServer\\\":{ \\\"securityMode\\\":\\\"RPK\\\", " +
+ "\\\"clientPublicKeyOrId\\\":\\\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUEBxNl/RcYJNm8mk91CyVXoIJiROYDlXcSSqK6e5bDHwOW4ZiN2lNnXalyF0Jxw8MbAytnDMERXyAja5VEMeVQ==\\\", " +
+ "\\\"clientSecretKey\\\":\\\"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgd9GAx7yZW37autew5KZykn4IgRpge/tZSjnudnZJnMahRANCAARQQHE2X9Fxgk2byaT3ULJVeggmJE5gOVdxJKorp7lsMfA5bhmI3aU2ddqXIXQnHDwxsDK2cMwRFfICNrlUQx5V\\\"" +
+ "}" +
+ "} " +
+ "}\"";
+
+ protected static final String CREDENTIALS_VALUE_UPDATE_LVM2M_RPK_DESCRIPTION =
+ " \"{" +
+ "\\\"client\\\":{ " +
+ "\\\"endpoint\\\":\\\"LwRpk00000000\\\", " +
+ "\\\"securityConfigClientMode\\\":\\\"RPK\\\", " +
+ "\\\"key\\\":\\\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdvBZZ2vQRK9wgDhctj6B1c7bxR3Z0wYg1+YdoYFnVUKWb+rIfTTyYK9tmQJx5Vlb5fxdLnVv1RJOPiwsLIQbAA==\\\"" +
+ " }, " +
+ "\\\"bootstrap\\\":{ " +
+ "\\\"bootstrapServer\\\":{ " +
+ "\\\"securityMode\\\":\\\"RPK\\\", " +
+ "\\\"clientPublicKeyOrId\\\":\\\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUEBxNl/RcYJNm8mk91CyVXoIJiROYDlXcSSqK6e5bDHwOW4ZiN2lNnXalyF0Jxw8MbAytnDMERXyAja5VEMeVQ==\\\", " +
+ "\\\"clientSecretKey\\\":\\\"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgd9GAx7yZW37autew5KZykn4IgRpge/tZSjnudnZJnMahRANCAARQQHE2X9Fxgk2byaT3ULJVeggmJE5gOVdxJKorp7lsMfA5bhmI3aU2ddqXIXQnHDwxsDK2cMwRFfICNrlUQx5V\\\"" +
+ "}, " +
+ "\\\"lwm2mServer\\\":{ \\\"securityMode\\\":\\\"RPK\\\", " +
+ "\\\"clientPublicKeyOrId\\\":\\\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUEBxNl/RcYJNm8mk91CyVXoIJiROYDlXcSSqK6e5bDHwOW4ZiN2lNnXalyF0Jxw8MbAytnDMERXyAja5VEMeVQ==\\\", " +
+ "\\\"clientSecretKey\\\":\\\"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgd9GAx7yZW37autew5KZykn4IgRpge/tZSjnudnZJnMahRANCAARQQHE2X9Fxgk2byaT3ULJVeggmJE5gOVdxJKorp7lsMfA5bhmI3aU2ddqXIXQnHDwxsDK2cMwRFfICNrlUQx5V\\\"" +
+ "}" +
+ "} " +
+ "}\"";
+
+ protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION =
+ "{\n" +
+ " \"device\": {\n" +
+ " \"name\":\"Name_LwRpk00000000\",\n" +
+ " \"label\":\"Label_LwRpk00000000\",\n" +
+ " \"deviceProfileId\":{\n" +
+ " \"id\":\"a660bd50-10ef-11ee-8737-b5634e73c779\",\n" +
+ " \"entityType\":\"DEVICE_PROFILE\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"credentials\": {\n" +
+ " \"credentialsType\": \"LWM2M_CREDENTIALS\",\n" +
+ " \"credentialsId\": \"LwRpk00000000\",\n" +
+ " \"credentialsValue\":\n" + CREDENTIALS_VALUE_LVM2M_RPK_DESCRIPTION + "\n" +
+ " }\n" +
+ "}";
+
+ protected static final String DEVICE_UPDATE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION =
+ "{\n" +
+ " \"id\": {\n" +
+ " \"id\":\"e238d4d0-1689-11ee-98c6-1713c1be5a8e\"\n" +
+ " },\n" +
+ " \"deviceId\": {\n" +
+ " \"id\":\"e232e160-1689-11ee-98c6-1713c1be5a8e\",\n" +
+ " \"entityType\":\"DEVICE\"\n" +
+ " },\n" +
+ " \"credentialsType\": \"LWM2M_CREDENTIALS\",\n" +
+ " \"credentialsId\": \"LwRpk00000000\",\n" +
+ " \"credentialsValue\":\n" + CREDENTIALS_VALUE_UPDATE_LVM2M_RPK_DESCRIPTION + "\n" +
+ "}";
+
+ protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN =
+ MARKDOWN_CODE_BLOCK_START + DEVICE_WITH_DEVICE_CREDENTIALS_ACCESS_TOKEN_PARAM_DESCRIPTION + MARKDOWN_CODE_BLOCK_END;
+
+ protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DEFAULT_DESCRIPTION_MARKDOWN =
+ MARKDOWN_CODE_BLOCK_START + DEVICE_WITH_DEVICE_CREDENTIALS_ACCESS_TOKEN_DEFAULT_PARAM_DESCRIPTION + MARKDOWN_CODE_BLOCK_END;
+
+ protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN =
+ MARKDOWN_CODE_BLOCK_START + DEVICE_WITH_DEVICE_CREDENTIALS_X509_CERTIFICATE_PARAM_DESCRIPTION + MARKDOWN_CODE_BLOCK_END;
+
+ protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN =
+ MARKDOWN_CODE_BLOCK_START + DEVICE_WITH_DEVICE_CREDENTIALS_MQTT_BASIC_PARAM_DESCRIPTION + MARKDOWN_CODE_BLOCK_END;
+
+ protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION_MARKDOWN =
+ MARKDOWN_CODE_BLOCK_START + DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION + MARKDOWN_CODE_BLOCK_END;
+
+ protected static final String DEVICE_UPDATE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN =
+ MARKDOWN_CODE_BLOCK_START + DEVICE_UPDATE_CREDENTIALS_ACCESS_TOKEN_PARAM_DESCRIPTION + MARKDOWN_CODE_BLOCK_END;
+
+ protected static final String DEVICE_UPDATE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN =
+ MARKDOWN_CODE_BLOCK_START + DEVICE_UPDATE_CREDENTIALS_X509_CERTIFICATE_PARAM_DESCRIPTION + MARKDOWN_CODE_BLOCK_END;
+
+ protected static final String DEVICE_UPDATE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN =
+ MARKDOWN_CODE_BLOCK_START + DEVICE_UPDATE_CREDENTIALS_MQTT_BASIC_PARAM_DESCRIPTION + MARKDOWN_CODE_BLOCK_END;
+
+ protected static final String DEVICE_UPDATE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION_MARKDOWN =
+ MARKDOWN_CODE_BLOCK_START + DEVICE_UPDATE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION + MARKDOWN_CODE_BLOCK_END;
+
protected static final String FILTER_VALUE_TYPE = NEW_LINE + "## Value Type and Operations" + NEW_LINE +
@@ -251,7 +434,7 @@ public class ControllerConstants {
" * 'BOOLEAN' - used for boolean values. Operations: EQUAL, NOT_EQUAL;\n" +
" * 'DATE_TIME' - similar to numeric, transforms value to milliseconds since epoch. Operations: EQUAL, NOT_EQUAL, GREATER, LESS, GREATER_OR_EQUAL, LESS_OR_EQUAL; \n";
- protected static final String DEVICE_PROFILE_ALARM_SCHEDULE_SPECIFIC_TIME_EXAMPLE = MARKDOWN_CODE_BLOCK_START +
+ protected static final String DEVICE_PROFILE_ALARM_SCHEDULE_SPECIFIC_TIME_EXAMPLE = MARKDOWN_CODE_BLOCK_START +
"{\n" +
" \"schedule\":{\n" +
" \"type\":\"SPECIFIC_TIME\",\n" +
@@ -266,7 +449,7 @@ public class ControllerConstants {
" }\n" +
"}" +
MARKDOWN_CODE_BLOCK_END;
- protected static final String DEVICE_PROFILE_ALARM_SCHEDULE_CUSTOM_EXAMPLE = MARKDOWN_CODE_BLOCK_START +
+ protected static final String DEVICE_PROFILE_ALARM_SCHEDULE_CUSTOM_EXAMPLE = MARKDOWN_CODE_BLOCK_START +
"{\n" +
" \"schedule\":{\n" +
" \"type\":\"CUSTOM\",\n" +
@@ -318,9 +501,9 @@ public class ControllerConstants {
" }\n" +
"}" +
MARKDOWN_CODE_BLOCK_END;
- protected static final String DEVICE_PROFILE_ALARM_SCHEDULE_ALWAYS_EXAMPLE = MARKDOWN_CODE_BLOCK_START + "\"schedule\": null" + MARKDOWN_CODE_BLOCK_END;
+ protected static final String DEVICE_PROFILE_ALARM_SCHEDULE_ALWAYS_EXAMPLE = MARKDOWN_CODE_BLOCK_START + "\"schedule\": null" + MARKDOWN_CODE_BLOCK_END;
- protected static final String DEVICE_PROFILE_ALARM_CONDITION_REPEATING_EXAMPLE = MARKDOWN_CODE_BLOCK_START +
+ protected static final String DEVICE_PROFILE_ALARM_CONDITION_REPEATING_EXAMPLE = MARKDOWN_CODE_BLOCK_START +
"{\n" +
" \"spec\":{\n" +
" \"type\":\"REPEATING\",\n" +
@@ -336,7 +519,8 @@ public class ControllerConstants {
" }\n" +
"}" +
MARKDOWN_CODE_BLOCK_END;
- protected static final String DEVICE_PROFILE_ALARM_CONDITION_DURATION_EXAMPLE = MARKDOWN_CODE_BLOCK_START +
+
+ protected static final String DEVICE_PROFILE_ALARM_CONDITION_DURATION_EXAMPLE = MARKDOWN_CODE_BLOCK_START +
"{\n" +
" \"spec\":{\n" +
" \"type\":\"DURATION\",\n" +
diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
index 8156c72a41..dd904add2e 100644
--- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
@@ -16,7 +16,6 @@
package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
@@ -31,6 +30,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
@@ -100,8 +100,7 @@ public class CustomerController extends BaseController {
checkParameter(CUSTOMER_ID, strCustomerId);
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
Customer customer = checkCustomerId(customerId, Operation.READ);
- ObjectMapper objectMapper = new ObjectMapper();
- ObjectNode infoObject = objectMapper.createObjectNode();
+ ObjectNode infoObject = JacksonUtil.newObjectNode();
infoObject.put("title", customer.getTitle());
infoObject.put(IS_PUBLIC, customer.isPublic());
return infoObject;
diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
index d4e5169e7e..afc737ab88 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
@@ -528,7 +528,7 @@ public class DashboardController extends BaseController {
Tenant tenant = tenantService.findTenantById(getTenantId());
JsonNode additionalInfo = tenant.getAdditionalInfo();
if (additionalInfo == null || !(additionalInfo instanceof ObjectNode)) {
- additionalInfo = JacksonUtil.OBJECT_MAPPER.createObjectNode();
+ additionalInfo = JacksonUtil.newObjectNode();
}
if (homeDashboardInfo.getDashboardId() != null) {
((ObjectNode) additionalInfo).put(HOME_DASHBOARD_ID, homeDashboardInfo.getDashboardId().getId().toString());
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
index bb34f6d5b2..d73915b617 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
@@ -75,6 +75,7 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import javax.annotation.Nullable;
+import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -93,7 +94,15 @@ import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFI
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_TYPE_DESCRIPTION;
-import static org.thingsboard.server.controller.ControllerConstants.DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_DESCRIPTION_MARKDOWN;
+import static org.thingsboard.server.controller.ControllerConstants.DEVICE_UPDATE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN;
+import static org.thingsboard.server.controller.ControllerConstants.DEVICE_UPDATE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION_MARKDOWN;
+import static org.thingsboard.server.controller.ControllerConstants.DEVICE_UPDATE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN;
+import static org.thingsboard.server.controller.ControllerConstants.DEVICE_UPDATE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN;
+import static org.thingsboard.server.controller.ControllerConstants.DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DEFAULT_DESCRIPTION_MARKDOWN;
+import static org.thingsboard.server.controller.ControllerConstants.DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN;
+import static org.thingsboard.server.controller.ControllerConstants.DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION_MARKDOWN;
+import static org.thingsboard.server.controller.ControllerConstants.DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN;
+import static org.thingsboard.server.controller.ControllerConstants.DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION;
@@ -182,18 +191,29 @@ public class DeviceController extends BaseController {
@ApiOperation(value = "Create Device (saveDevice) with credentials ",
notes = "Create or update the Device. When creating device, platform generates Device Id as " + UUID_WIKI_LINK +
- "Requires to provide the Device Credentials object as well. Useful to create device and credentials in one request. " +
- "You may find the example of LwM2M device and RPK credentials below: \n\n" +
- DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_DESCRIPTION_MARKDOWN +
+ "Requires to provide the Device Credentials object as well as an existing device profile ID or use \"default\".\n" +
+ "You may find the example of device with different type of credentials below: \n\n" +
+ "- Credentials type: \"Access token\" with device profile ID below: \n\n" +
+ DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN + "\n\n" +
+ "- Credentials type: \"Access token\" with device profile default below: \n\n" +
+ DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DEFAULT_DESCRIPTION_MARKDOWN + "\n\n" +
+ "- Credentials type: \"X509\" with device profile ID below: \n\n" +
+ "Note: credentialsId - format Sha3Hash, certificateValue - format PEM (with \"--BEGIN CERTIFICATE----\" and -\"----END CERTIFICATE-\").\n\n" +
+ DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN + "\n\n" +
+ "- Credentials type: \"MQTT_BASIC\" with device profile ID below: \n\n" +
+ DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN + "\n\n" +
+ "- You may find the example of LwM2M device and RPK credentials below: \n\n" +
+ "Note: LwM2M device - only existing device profile ID (Transport configuration -> Transport type: \"LWM2M\".\n\n" +
+ DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION_MARKDOWN + "\n\n" +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Device entity. " +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device-with-credentials", method = RequestMethod.POST)
@ResponseBody
public Device saveDeviceWithCredentials(@ApiParam(value = "The JSON object with device and credentials. See method description above for example.")
- @RequestBody SaveDeviceWithCredentialsRequest deviceAndCredentials) throws ThingsboardException {
- Device device = checkNotNull(deviceAndCredentials.getDevice());
- DeviceCredentials credentials = checkNotNull(deviceAndCredentials.getCredentials());
+ @Valid @RequestBody SaveDeviceWithCredentialsRequest deviceAndCredentials) throws ThingsboardException {
+ Device device = deviceAndCredentials.getDevice();
+ DeviceCredentials credentials = deviceAndCredentials.getCredentials();
device.setTenantId(getCurrentUser().getTenantId());
checkEntity(device.getId(), device, Resource.DEVICE);
return tbDeviceService.saveDeviceWithCredentials(device, credentials, getCurrentUser());
@@ -277,10 +297,27 @@ public class DeviceController extends BaseController {
return tbDeviceService.getDeviceCredentialsByDeviceId(device, getCurrentUser());
}
- @ApiOperation(value = "Update device credentials (updateDeviceCredentials)", notes = "During device creation, platform generates random 'ACCESS_TOKEN' credentials. " +
- "Use this method to update the device credentials. First use 'getDeviceCredentialsByDeviceId' to get the credentials id and value. " +
- "Then use current method to update the credentials type and value. It is not possible to create multiple device credentials for the same device. " +
- "The structure of device credentials id and value is simple for the 'ACCESS_TOKEN' but is much more complex for the 'MQTT_BASIC' or 'LWM2M_CREDENTIALS'." + TENANT_AUTHORITY_PARAGRAPH)
+ @ApiOperation(value = "Update device credentials (updateDeviceCredentials)",
+ notes = "During device creation, platform generates random 'ACCESS_TOKEN' credentials. \" +\n" +
+ "Use this method to update the device credentials. First use 'getDeviceCredentialsByDeviceId' to get the credentials id and value.\n" +
+ "Then use current method to update the credentials type and value. It is not possible to create multiple device credentials for the same device.\n" +
+ "The structure of device credentials id and value is simple for the 'ACCESS_TOKEN' but is much more complex for the 'MQTT_BASIC' or 'LWM2M_CREDENTIALS'.\n" +
+ "You may find the example of device with different type of credentials below: \n\n" +
+ "- Credentials type: \"Access token\" with device ID and with device ID below: \n\n" +
+ DEVICE_UPDATE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN + "\n\n" +
+ "- Credentials type: \"X509\" with device profile ID below: \n\n" +
+ "Note: credentialsId - format Sha3Hash, certificateValue - format PEM (with \"--BEGIN CERTIFICATE----\" and -\"----END CERTIFICATE-\").\n\n" +
+ DEVICE_UPDATE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN + "\n\n" +
+ "- Credentials type: \"MQTT_BASIC\" with device profile ID below: \n\n" +
+ DEVICE_UPDATE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN + "\n\n" +
+ "- You may find the example of LwM2M device and RPK credentials below: \n\n" +
+ "Note: LwM2M device - only existing device profile ID (Transport configuration -> Transport type: \"LWM2M\".\n\n" +
+ DEVICE_UPDATE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION_MARKDOWN + "\n\n" +
+ "Update to real value:\n" +
+ " - 'id' (this is id of Device Credentials -> \"Get Device Credentials (getDeviceCredentialsByDeviceId)\",\n" +
+ " - 'deviceId.id' (this is id of Device).\n" +
+ "Remove 'tenantId' and optionally 'customerId' from the request body example (below) to create new Device entity." +
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/device/credentials", method = RequestMethod.POST)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java
index fc85f439e0..386b1eab01 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java
@@ -85,6 +85,6 @@ public class EdgeEventController extends BaseController {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
- return checkNotNull(edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, false));
+ return checkNotNull(edgeEventService.findEdgeEvents(tenantId, edgeId, 0L, null, pageLink));
}
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
index ae1dec43fe..8949cd95dd 100644
--- a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
@@ -15,7 +15,6 @@
*/
package org.thingsboard.server.controller;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
@@ -28,6 +27,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault;
@@ -72,9 +72,8 @@ public class Lwm2mController extends BaseController {
@RequestMapping(value = "/lwm2m/device-credentials", method = RequestMethod.POST)
@ResponseBody
public Device saveDeviceWithCredentials(@RequestBody Map, Object> deviceWithDeviceCredentials) throws ThingsboardException {
- ObjectMapper mapper = new ObjectMapper();
- Device device = checkNotNull(mapper.convertValue(deviceWithDeviceCredentials.get(Device.class), Device.class));
- DeviceCredentials credentials = checkNotNull(mapper.convertValue(deviceWithDeviceCredentials.get(DeviceCredentials.class), DeviceCredentials.class));
+ Device device = checkNotNull(JacksonUtil.convertValue(deviceWithDeviceCredentials.get(Device.class), Device.class));
+ DeviceCredentials credentials = checkNotNull(JacksonUtil.convertValue(deviceWithDeviceCredentials.get(DeviceCredentials.class), DeviceCredentials.class));
return deviceController.saveDeviceWithCredentials(new SaveDeviceWithCredentialsRequest(device, credentials));
}
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/MailConfigTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/MailConfigTemplateController.java
new file mode 100644
index 0000000000..09aed4fee3
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/MailConfigTemplateController.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.controller;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+import org.thingsboard.server.common.data.exception.ThingsboardException;
+import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.service.mail.TbMailConfigTemplateService;
+import org.thingsboard.server.service.security.permission.Operation;
+import org.thingsboard.server.service.security.permission.Resource;
+
+import java.io.IOException;
+
+import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH;
+
+@RestController
+@TbCoreComponent
+@RequiredArgsConstructor
+@RequestMapping("/api/mail/config/template")
+@Slf4j
+public class MailConfigTemplateController extends BaseController {
+ private static final String MAIL_CONFIG_TEMPLATE_DEFINITION = "Mail configuration template is set of default smtp settings for mail server that specific provider supports";
+ private final TbMailConfigTemplateService mailConfigTemplateService;
+
+ @ApiOperation(value = "Get the list of all OAuth2 client registration templates (getClientRegistrationTemplates)" + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH,
+ notes = MAIL_CONFIG_TEMPLATE_DEFINITION)
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+ @RequestMapping(method = RequestMethod.GET, produces = "application/json")
+ @ResponseBody
+ public JsonNode getClientRegistrationTemplates() throws ThingsboardException, IOException {
+ accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
+ return mailConfigTemplateService.findAllMailConfigTemplates();
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java
index ccf2124113..2afb67a8bd 100644
--- a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java
@@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
-import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
+import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
diff --git a/application/src/main/java/org/thingsboard/server/controller/QueueController.java b/application/src/main/java/org/thingsboard/server/controller/QueueController.java
index 3ba1df8c2a..0e79ae7955 100644
--- a/application/src/main/java/org/thingsboard/server/controller/QueueController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/QueueController.java
@@ -83,7 +83,7 @@ public class QueueController extends BaseController {
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("serviceType", serviceType);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
- ServiceType type = ServiceType.valueOf(serviceType);
+ ServiceType type = ServiceType.of(serviceType);
switch (type) {
case TB_RULE_ENGINE:
return queueService.findQueuesByTenantId(getTenantId(), pageLink);
@@ -136,7 +136,7 @@ public class QueueController extends BaseController {
checkEntity(queue.getId(), queue, Resource.QUEUE);
- ServiceType type = ServiceType.valueOf(serviceType);
+ ServiceType type = ServiceType.of(serviceType);
switch (type) {
case TB_RULE_ENGINE:
queue.setTenantId(getTenantId());
diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
index 81cf8be9fa..e43a365cf8 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -18,7 +18,6 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.ApiOperation;
@@ -37,6 +36,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.script.api.js.JsInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
@@ -115,7 +115,6 @@ public class RuleChainController extends BaseController {
private static final int DEFAULT_PAGE_SIZE = 1000;
- private static final ObjectMapper objectMapper = new ObjectMapper();
public static final int TIMEOUT = 20;
private static final String RULE_CHAIN_DESCRIPTION = "The rule chain object is lightweight and contains general information about the rule chain. " +
@@ -377,11 +376,11 @@ public class RuleChainController extends BaseController {
String script = inputParams.get("script").asText();
String scriptType = inputParams.get("scriptType").asText();
JsonNode argNamesJson = inputParams.get("argNames");
- String[] argNames = objectMapper.treeToValue(argNamesJson, String[].class);
+ String[] argNames = JacksonUtil.treeToValue(argNamesJson, String[].class);
String data = inputParams.get("msg").asText();
JsonNode metadataJson = inputParams.get("metadata");
- Map metadata = objectMapper.convertValue(metadataJson, new TypeReference